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:
+498
-113
@@ -21,8 +21,7 @@
|
||||
<view class="quit-hero-top">
|
||||
<view class="quit-hero-copy">
|
||||
<text class="quit-hero-eyebrow">无烟旅程</text>
|
||||
<text class="quit-hero-title">今天也在慢慢变好</text>
|
||||
<text class="quit-hero-subtitle">{{ quitEncouragement }}</text>
|
||||
<text class="quit-hero-title">{{ quitEncouragement }}</text>
|
||||
</view>
|
||||
<text class="quit-hero-chip">{{ todayChecked ? '今日已完成' : '等待打卡' }}</text>
|
||||
</view>
|
||||
@@ -53,6 +52,25 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="achievementData" class="quit-ach-inline">
|
||||
<view class="quit-ach-left">
|
||||
<text class="quit-ach-icon">{{ achievementData.theme_icon }}</text>
|
||||
<view class="quit-ach-info">
|
||||
<text class="quit-ach-rank">{{ achievementData.current?.name || '--' }}</text>
|
||||
<text class="quit-ach-theme">{{ achievementData.theme_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="quit-ach-right">
|
||||
<view v-if="achievementData.next" class="quit-ach-progress-area">
|
||||
<view class="quit-ach-bar">
|
||||
<view class="quit-ach-fill" :style="{ width: (achievementData.progress * 100) + '%' }"></view>
|
||||
</view>
|
||||
<text class="quit-ach-hint">距「{{ achievementData.next.name }}」还需 {{ achievementData.next.required_days - achievementData.days }} 天</text>
|
||||
</view>
|
||||
<text v-else class="quit-ach-max">已达最高等级</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
@@ -78,30 +96,47 @@
|
||||
<view class="quit-health-card">
|
||||
<view class="quit-health-header">
|
||||
<view>
|
||||
<text class="quit-health-title">健康恢复</text>
|
||||
<text class="quit-health-subtitle">身体正在按自己的节奏修复</text>
|
||||
<text class="quit-health-title">健康恢复里程碑</text>
|
||||
</view>
|
||||
<view class="quit-health-badge" :class="{ 'quit-health-badge-done': healthProgress >= 100 }">
|
||||
{{ healthProgress >= 100 ? '已达成' : `${healthProgress}%` }}
|
||||
</view>
|
||||
<text class="quit-health-value">{{ healthProgress }}%</text>
|
||||
</view>
|
||||
<view class="quit-health-bar">
|
||||
<view class="quit-health-bar-fill" :style="{ width: healthProgress + '%' }"></view>
|
||||
</view>
|
||||
<view class="quit-health-milestones">
|
||||
<view class="quit-milestone-list">
|
||||
<view
|
||||
v-for="(milestone, index) in healthMilestones"
|
||||
v-for="(ms, index) in healthMilestoneItems"
|
||||
:key="index"
|
||||
class="quit-milestone-item"
|
||||
:class="{ 'quit-milestone-done': quitDays >= milestone.days }"
|
||||
class="quit-ms-item"
|
||||
>
|
||||
<view class="quit-milestone-dot"></view>
|
||||
<text class="quit-milestone-text">{{ milestone.label }}</text>
|
||||
<view class="quit-ms-top">
|
||||
<text class="quit-ms-name">{{ ms.name }}</text>
|
||||
<text class="quit-ms-pct" :class="{ 'quit-ms-pct-done': ms.percent >= 100 }">{{ ms.percent }}%</text>
|
||||
</view>
|
||||
<view class="quit-ms-bar">
|
||||
<view
|
||||
class="quit-ms-fill"
|
||||
:class="ms.percent >= 100 ? 'quit-ms-fill-done' : 'quit-ms-fill-pending'"
|
||||
:style="{ width: ms.percent + '%' }"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="quit-motivation-card">
|
||||
<text class="quit-motivation-label">今日提醒</text>
|
||||
<text class="quit-motivation-text">{{ healthTip }}</text>
|
||||
<view class="quit-dream-entry" @tap="gotoDreamGoals">
|
||||
<view class="quit-dream-left">
|
||||
<text class="quit-dream-icon">🎯</text>
|
||||
<view class="quit-dream-info">
|
||||
<text class="quit-dream-title">梦想清单</text>
|
||||
<text class="quit-dream-desc">{{ activeGoalText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="quit-dream-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="quit-tip-bar">
|
||||
<text class="quit-tip-icon">💡</text>
|
||||
<text class="quit-tip-text">{{ healthTip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -212,6 +247,28 @@
|
||||
<text class="health-tip-icon">💡</text>
|
||||
<text class="health-tip-text">{{ recordHealthTip }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="achievementData" class="record-achievement-card">
|
||||
<view class="ach-header">
|
||||
<text class="ach-title">{{ achievementData.theme_icon }} 成就称号</text>
|
||||
<text class="ach-theme-name">{{ achievementData.theme_name }}</text>
|
||||
</view>
|
||||
<view class="ach-body">
|
||||
<view class="ach-current">
|
||||
<text class="ach-rank">{{ achievementData.current?.name || '--' }}</text>
|
||||
<text class="ach-days">第 {{ achievementData.days }} 天</text>
|
||||
</view>
|
||||
<view v-if="achievementData.next" class="ach-progress-wrap">
|
||||
<view class="ach-progress-bar">
|
||||
<view class="ach-progress-fill" :style="{ width: (achievementData.progress * 100) + '%' }"></view>
|
||||
</view>
|
||||
<text class="ach-next-hint">距下一级「{{ achievementData.next.name }}」还需 {{ achievementData.next.required_days - achievementData.days }} 天</text>
|
||||
</view>
|
||||
<view v-else class="ach-max">
|
||||
<text class="ach-max-text">已达最高等级</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -244,6 +301,8 @@ const showDialog = ref(false)
|
||||
const dialogType = ref('smoke')
|
||||
const homeData = ref(null)
|
||||
const pageReady = ref(false)
|
||||
const achievementData = ref(null)
|
||||
const quitHomeData = ref(null)
|
||||
const quitState = ref(defaultQuitState())
|
||||
|
||||
let timerInterval = null
|
||||
@@ -288,35 +347,59 @@ const nextSmokeTimeText = computed(() => {
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
})
|
||||
|
||||
// 戒烟模式计算
|
||||
// 戒烟模式计算 — 优先使用 V2 API 数据
|
||||
const quitSummary = computed(() => quitHomeData.value?.summary || {})
|
||||
const quitDailyStatus = computed(() => quitHomeData.value?.daily_status || {})
|
||||
|
||||
const baselineCigsPerDay = computed(() => profileStore.profile?.baseline_cigs_per_day || 10)
|
||||
const packPriceYuan = computed(() => (profileStore.profile?.pack_price_cent || 2500) / 100)
|
||||
|
||||
const quitDays = computed(() => {
|
||||
if (quitSummary.value.current_streak_days !== undefined) {
|
||||
return quitSummary.value.current_streak_days
|
||||
}
|
||||
if (!quitState.value.lastCheckinDate) return 0
|
||||
const gap = diffDays(quitState.value.lastCheckinDate, formatDate(new Date()))
|
||||
if (gap > 1) return 0
|
||||
return Number(quitState.value.streakDays || 0)
|
||||
})
|
||||
|
||||
const todayChecked = computed(() => quitState.value.lastCheckinDate === formatDate(new Date()))
|
||||
const todayCheckinTime = computed(() => formatClock(quitState.value.lastCheckinAt))
|
||||
const todayChecked = computed(() => {
|
||||
if (quitDailyStatus.value.status) {
|
||||
return quitDailyStatus.value.status === 'checked_in'
|
||||
}
|
||||
return quitState.value.lastCheckinDate === formatDate(new Date())
|
||||
})
|
||||
|
||||
const todayCheckinTime = computed(() => {
|
||||
if (quitDailyStatus.value.checkin_at) {
|
||||
return formatClock(quitDailyStatus.value.checkin_at)
|
||||
}
|
||||
return formatClock(quitState.value.lastCheckinAt)
|
||||
})
|
||||
|
||||
const savedMoney = computed(() => {
|
||||
const total = (quitDays.value * baselineCigsPerDay.value / 20) * packPriceYuan.value
|
||||
return Math.round(total)
|
||||
if (quitSummary.value.saved_money_cent !== undefined) {
|
||||
return Math.round(quitSummary.value.saved_money_cent / 100)
|
||||
}
|
||||
return Math.round((quitDays.value * baselineCigsPerDay.value / 20) * packPriceYuan.value)
|
||||
})
|
||||
|
||||
const avoidedCigs = computed(() => {
|
||||
if (quitSummary.value.avoided_cigs !== undefined) {
|
||||
return quitSummary.value.avoided_cigs
|
||||
}
|
||||
return quitDays.value * baselineCigsPerDay.value
|
||||
})
|
||||
|
||||
const lifeSaved = computed(() => {
|
||||
// 每支烟减少约11分钟生命,换算成小时
|
||||
return Math.round(quitDays.value * baselineCigsPerDay.value * 11 / 60)
|
||||
return Math.round(avoidedCigs.value * 11 / 60)
|
||||
})
|
||||
|
||||
const healthProgress = computed(() => {
|
||||
if (quitSummary.value.health_recovery_percent !== undefined) {
|
||||
return quitSummary.value.health_recovery_percent
|
||||
}
|
||||
if (quitDays.value >= 365) return 100
|
||||
if (quitDays.value >= 180) return 85
|
||||
if (quitDays.value >= 90) return 70
|
||||
@@ -361,6 +444,30 @@ const healthMilestones = computed(() => [
|
||||
{ days: 365, label: '1年' }
|
||||
])
|
||||
|
||||
const healthMilestoneItems = computed(() => {
|
||||
const milestones = [
|
||||
{ name: '血压心率恢复', minutes: 20 },
|
||||
{ name: '一氧化碳排出', minutes: 480 },
|
||||
{ name: '尼古丁代谢完', minutes: 4320 },
|
||||
{ name: '味觉嗅觉恢复', minutes: 43200 },
|
||||
{ name: '血液循环改善', minutes: 129600 },
|
||||
{ name: '肺功能提升', minutes: 525600 },
|
||||
]
|
||||
const minutes = quitDays.value * 1440
|
||||
return milestones.map(m => ({
|
||||
name: m.name,
|
||||
percent: Math.min(Math.round((minutes / m.minutes) * 100), 100)
|
||||
}))
|
||||
})
|
||||
|
||||
const activeGoalText = computed(() => {
|
||||
const goal = quitHomeData.value?.goal
|
||||
if (!goal) return '设定一个小目标,攒钱实现它'
|
||||
const remaining = goal.target_amount_cent - (goal.current_amount_cent || 0)
|
||||
if (remaining <= 0) return `「${goal.title}」已攒够!`
|
||||
return `「${goal.title}」还差 ¥${Math.round(remaining / 100)}`
|
||||
})
|
||||
|
||||
const todayCountPercent = computed(() => {
|
||||
if (!dailyTarget.value || dailyTarget.value <= 0) return todayCount.value > 0 ? 100 : 0
|
||||
const percent = Math.round((todayCount.value / dailyTarget.value) * 100)
|
||||
@@ -518,21 +625,73 @@ async function handleSubmit(submitData) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuitCheckin() {
|
||||
async function handleQuitCheckin() {
|
||||
if (todayChecked.value) {
|
||||
uni.showToast({ title: '今天已经打过卡', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const today = formatDate(new Date())
|
||||
const previousDate = quitState.value.lastCheckinDate
|
||||
let streakDays = 1
|
||||
if (previousDate) {
|
||||
const gap = diffDays(previousDate, today)
|
||||
if (gap === 1) streakDays = Number(quitState.value.streakDays || 0) + 1
|
||||
else if (gap === 0) streakDays = Number(quitState.value.streakDays || 0)
|
||||
try {
|
||||
const today = formatDate(new Date())
|
||||
const res = await api.quitCheckin({ date: today })
|
||||
applyQuitHomeData(res.data)
|
||||
saveQuitState({
|
||||
lastCheckinDate: today,
|
||||
lastCheckinAt: new Date().toISOString(),
|
||||
streakDays: res.data?.summary?.current_streak_days || 0
|
||||
})
|
||||
uni.showToast({ title: '打卡成功', icon: 'success' })
|
||||
} catch (e) {
|
||||
console.error('handleQuitCheckin error:', e)
|
||||
uni.showToast({ title: '打卡失败,请重试', icon: 'none' })
|
||||
}
|
||||
saveQuitState({ lastCheckinDate: today, lastCheckinAt: new Date().toISOString(), streakDays })
|
||||
uni.showToast({ title: '打卡成功', icon: 'success' })
|
||||
}
|
||||
|
||||
function gotoDreamGoals() {
|
||||
uni.navigateTo({ url: '/pages/dream-goals/index' })
|
||||
}
|
||||
|
||||
function applyQuitHomeData(data) {
|
||||
if (!data) return
|
||||
quitHomeData.value = data
|
||||
if (data.daily_status?.status === 'checked_in' && data.daily_status?.checkin_at) {
|
||||
saveQuitState({
|
||||
lastCheckinDate: data.daily_status.date,
|
||||
lastCheckinAt: data.daily_status.checkin_at,
|
||||
streakDays: data.summary?.current_streak_days || 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchQuitHomeData() {
|
||||
try {
|
||||
const res = await api.getQuitCheckinHome()
|
||||
applyQuitHomeData(res.data)
|
||||
} catch (e) {
|
||||
const msg = e?.message || ''
|
||||
if (msg.includes('基础资料')) {
|
||||
await ensureQuitProfile()
|
||||
try {
|
||||
const res = await api.getQuitCheckinHome()
|
||||
applyQuitHomeData(res.data)
|
||||
} catch (retryErr) {
|
||||
console.error('fetchQuitHomeData retry error:', retryErr)
|
||||
loadQuitState()
|
||||
}
|
||||
} else {
|
||||
console.error('fetchQuitHomeData error:', e)
|
||||
loadQuitState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureQuitProfile() {
|
||||
const profile = profileStore.profile
|
||||
if (!profile) return
|
||||
await api.upsertQuitCheckinProfile({
|
||||
quit_start_date: formatDate(new Date()),
|
||||
pack_price_cent: profile.pack_price_cent || 2500,
|
||||
baseline_cigs_per_day: profile.baseline_cigs_per_day || 10
|
||||
})
|
||||
}
|
||||
|
||||
async function ensureProfileReady() {
|
||||
@@ -548,13 +707,23 @@ async function ensureProfileReady() {
|
||||
return true
|
||||
}
|
||||
|
||||
async function fetchAchievement() {
|
||||
try {
|
||||
const res = await api.getAchievement()
|
||||
achievementData.value = res.data?.achievement || null
|
||||
} catch (e) {
|
||||
console.error('fetchAchievement error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCurrentMode() {
|
||||
if (!userStore.mode) return
|
||||
const profileReady = await ensureProfileReady()
|
||||
if (!profileReady) return
|
||||
fetchAchievement()
|
||||
if (isQuitMode.value) {
|
||||
stopTimer()
|
||||
loadQuitState()
|
||||
await fetchQuitHomeData()
|
||||
return
|
||||
}
|
||||
await fetchRecordHomeData()
|
||||
@@ -698,8 +867,7 @@ onShareAppMessage(() => ({
|
||||
|
||||
.quit-hero-card,
|
||||
.quit-checkin-card,
|
||||
.quit-health-card,
|
||||
.quit-motivation-card {
|
||||
.quit-health-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(251, 253, 252, 0.94) 100%);
|
||||
@@ -714,8 +882,7 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
.quit-hero-card::before,
|
||||
.quit-health-card::before,
|
||||
.quit-motivation-card::before {
|
||||
.quit-health-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 240rpx;
|
||||
@@ -744,7 +911,7 @@ onShareAppMessage(() => ({
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 26rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.quit-hero-copy {
|
||||
@@ -765,20 +932,12 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
.quit-hero-title {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 42rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 800;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.quit-hero-subtitle {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #6b7280;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
font-weight: 700;
|
||||
color: #14936d;
|
||||
}
|
||||
|
||||
.quit-hero-chip {
|
||||
@@ -966,10 +1125,10 @@ onShareAppMessage(() => ({
|
||||
|
||||
.quit-health-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 18rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.quit-health-title {
|
||||
@@ -979,101 +1138,238 @@ onShareAppMessage(() => ({
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.quit-health-subtitle {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
.quit-health-badge {
|
||||
font-size: 21rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(240, 252, 248, 0.94);
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.05);
|
||||
color: #14936d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quit-health-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: 800;
|
||||
.quit-health-badge-done {
|
||||
background: linear-gradient(135deg, #10B981, #14936d);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.quit-milestone-list {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.quit-ms-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quit-ms-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.quit-ms-name {
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quit-ms-pct {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quit-ms-pct-done {
|
||||
color: #14936d;
|
||||
}
|
||||
|
||||
.quit-health-bar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 14rpx;
|
||||
.quit-ms-bar {
|
||||
height: 10rpx;
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
border-radius: 999rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quit-health-bar-fill {
|
||||
.quit-ms-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #31c18b 0%, #14936d 100%);
|
||||
border-radius: 999rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.quit-health-milestones {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8rpx;
|
||||
margin-top: 22rpx;
|
||||
.quit-ms-fill-done {
|
||||
background: linear-gradient(90deg, #31c18b, #14936d);
|
||||
}
|
||||
|
||||
.quit-milestone-item {
|
||||
.quit-ms-fill-pending {
|
||||
background: rgba(52, 200, 160, 0.3);
|
||||
}
|
||||
|
||||
.quit-ach-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 22rpx;
|
||||
background: linear-gradient(180deg, #f5fbf9 0%, #eef6f2 100%);
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.05);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.quit-ach-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quit-ach-icon {
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quit-ach-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.quit-milestone-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(15, 23, 42, 0.08);
|
||||
border: 2rpx solid rgba(15, 23, 42, 0.1);
|
||||
.quit-ach-rank {
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
color: #14936d;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.quit-milestone-done .quit-milestone-dot {
|
||||
background: #31c18b;
|
||||
border-color: #14936d;
|
||||
}
|
||||
|
||||
.quit-milestone-text {
|
||||
.quit-ach-theme {
|
||||
font-size: 20rpx;
|
||||
color: #9ca3af;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
.quit-milestone-done .quit-milestone-text {
|
||||
.quit-ach-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.quit-ach-progress-area {
|
||||
width: 100%;
|
||||
max-width: 280rpx;
|
||||
}
|
||||
|
||||
.quit-ach-bar {
|
||||
height: 8rpx;
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
border-radius: 999rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quit-ach-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #10B981, #34D399);
|
||||
border-radius: 999rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.quit-ach-hint {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 20rpx;
|
||||
color: #9ca3af;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quit-ach-max {
|
||||
font-size: 20rpx;
|
||||
color: #14936d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quit-motivation-card {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.quit-motivation-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
.quit-dream-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(240, 252, 248, 0.94);
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.05);
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
justify-content: space-between;
|
||||
padding: 22rpx 24rpx;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(251, 253, 252, 0.94) 100%);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.06);
|
||||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.quit-motivation-text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.quit-dream-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.quit-dream-icon {
|
||||
font-size: 36rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quit-dream-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.quit-dream-title {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.quit-dream-desc {
|
||||
display: block;
|
||||
margin-top: 4rpx;
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quit-dream-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #d1d5db;
|
||||
flex-shrink: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.quit-tip-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 14rpx 18rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(240, 252, 248, 0.6);
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.quit-tip-icon {
|
||||
font-size: 22rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quit-tip-text {
|
||||
font-size: 20rpx;
|
||||
line-height: 1.5;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* ===== 记录模式 ===== */
|
||||
@@ -1497,4 +1793,93 @@ onShareAppMessage(() => ({
|
||||
color: #4b5563;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.record-achievement-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 251, 249, 0.94) 100%);
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.06);
|
||||
box-shadow: 0 6rpx 14rpx rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.ach-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.ach-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.ach-theme-name {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
padding: 6rpx 14rpx;
|
||||
background: rgba(240, 252, 248, 0.94);
|
||||
border-radius: 999rpx;
|
||||
border: 1rpx solid rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.ach-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ach-current {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.ach-rank {
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
color: #14936d;
|
||||
font-family: 'DIN Alternate', -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.ach-days {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ach-progress-wrap {
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.ach-progress-bar {
|
||||
height: 12rpx;
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.ach-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #10B981, #34D399);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.ach-next-hint {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ach-max {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.ach-max-text {
|
||||
font-size: 22rpx;
|
||||
color: #14936d;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user