feat(nsti): add nicotine personality test flow (#36)

* fix: polish logs filter and stats money display

* fix: align floating tabs on logs and stats

* feat: enhance user profile and achievement features

- Add functionality for users to update their profile picture and nickname
- Implement achievement theme selection in onboarding
- Update API integration for profile updates and achievement themes
- Refine UI elements for better user interaction and experience

* feat: 梦想清单页与戒烟相关 API

- 梦想清单:系统导航栏、浮动添加、图标来自后台预设
- dream-presets API、pages.json 导航样式

Made-with: Cursor

* feat(nsti): add nicotine personality test flow
This commit is contained in:
hello-dd-code
2026-04-11 01:49:19 +08:00
committed by GitHub
parent ef36ca072b
commit ec87a9fc55
22 changed files with 4157 additions and 508 deletions
+43 -12
View File
@@ -283,6 +283,12 @@ const weeklyTrendItems = computed(() => {
})
})
function safeNumber(value) {
if (value === undefined || value === null || value === '') return 0
const num = Number(value)
return Number.isNaN(num) ? 0 : num
}
function calDotClass(count) {
if (count === 0) return 'cal-dot-zero'
if (count <= 3) return 'cal-dot-low'
@@ -290,10 +296,15 @@ function calDotClass(count) {
return 'cal-dot-high'
}
const savedMoneyText = computed(() => {
const savedMoneyCent = computed(() => {
const money = statsData.value?.money
if (!money || !money.available) return '--'
return `¥${(money.saved_cent / 100).toFixed(2)}`
if (!money || !money.available) return 0
return safeNumber(money.saved_cent)
})
const savedMoneyText = computed(() => {
if (!moneyAvailable.value) return '--'
return `¥${(savedMoneyCent.value / 100).toFixed(2)}`
})
const moneyAvailable = computed(() => !!statsData.value?.money?.available)
@@ -301,13 +312,13 @@ const moneyAvailable = computed(() => !!statsData.value?.money?.available)
const moneyExpectedTotal = computed(() => {
const money = statsData.value?.money
if (!money || !money.available) return 0
return Number(money.expected_total) || 0
return safeNumber(money.expected_total)
})
const moneyActualTotal = computed(() => {
const money = statsData.value?.money
if (!money || !money.available) return 0
return Number(money.actual_total) || 0
return safeNumber(money.actual_total)
})
const moneySubtitle = computed(() => {
@@ -318,16 +329,17 @@ const moneySubtitle = computed(() => {
const moneyTargetCent = computed(() => {
const money = statsData.value?.money
if (!money || !money.available) return 0
const { expected_total, pack_price_cent, cigs_per_pack } = money
if (!expected_total || !pack_price_cent || !cigs_per_pack) return 0
return Math.round((expected_total / cigs_per_pack) * pack_price_cent)
const expectedTotal = safeNumber(money.expected_total)
const packPriceCent = safeNumber(money.pack_price_cent)
const cigsPerPack = safeNumber(money.cigs_per_pack)
if (expectedTotal <= 0 || packPriceCent <= 0 || cigsPerPack <= 0) return 0
return Math.round((expectedTotal / cigsPerPack) * packPriceCent)
})
const moneyPercent = computed(() => {
const money = statsData.value?.money
const target = moneyTargetCent.value
if (!money || !money.available || target <= 0) return 0
const percent = Math.round((money.saved_cent / target) * 100)
if (!moneyAvailable.value || target <= 0) return 0
const percent = Math.round((savedMoneyCent.value / target) * 100)
return Math.min(Math.max(percent, 0), 100)
})
@@ -486,10 +498,17 @@ onShareAppMessage(() => {
/* ── Tab 切换 ── */
.segment-wrap {
padding: 8rpx 0 20rpx;
position: relative;
height: 148rpx;
flex-shrink: 0;
z-index: 20;
}
.segment {
position: fixed;
left: 28rpx;
right: 28rpx;
z-index: 50;
display: flex;
background: rgba(255, 255, 255, 0.82);
padding: 6rpx;
@@ -497,6 +516,18 @@ onShareAppMessage(() => {
gap: 6rpx;
border: 1.5rpx solid rgba(52, 200, 160, 0.14);
box-shadow: 0 4rpx 16rpx rgba(52, 200, 160, 0.07);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
.segment::before {
content: '';
position: absolute;
inset: -12rpx -8rpx -10rpx;
border-radius: 34rpx;
background: linear-gradient(180deg, rgba(230, 247, 242, 0.96) 0%, rgba(240, 251, 247, 0.88) 72%, rgba(240, 251, 247, 0) 100%);
z-index: -1;
pointer-events: none;
}
.segment-item {