From 031eef964343c37585bff308d83d241230122c3e Mon Sep 17 00:00:00 2001
From: nepiedg <806669289@qq.com>
Date: Mon, 23 Feb 2026 22:24:29 +0800
Subject: [PATCH] aaa
---
api/request.js | 22 +-
.../smoke-record-dialog.vue | 7 +-
config/index.js | 2 +-
pages/logs/index.vue | 15 +-
pages/stats/index.vue | 584 ++++++++++++++----
stores/logs.js | 7 +-
utils/time.js | 5 +-
7 files changed, 494 insertions(+), 148 deletions(-)
diff --git a/api/request.js b/api/request.js
index 757439d..4c0e420 100644
--- a/api/request.js
+++ b/api/request.js
@@ -1,9 +1,19 @@
import { BASE_URL } from '@/config'
import { storage, SESSION_KEY } from '@/utils/storage'
+import { login as authLogin } from './auth'
+
+// 是否为 token 失效(HTTP 401 或 body code 401,如 invalid token)
+function isInvalidToken(res) {
+ if (res.statusCode === 401) return true
+ const body = res.data
+ if (body && body.code === 401) return true
+ return false
+}
export const request = {
async request(options) {
const sessionKey = storage.get(SESSION_KEY)
+ const isRetryAfter401 = options._retryAfter401 === true
return new Promise((resolve, reject) => {
uni.request({
@@ -15,11 +25,15 @@ export const request = {
'Authorization': sessionKey ? `Bearer ${sessionKey}` : ''
},
success: async (res) => {
- if (res.statusCode === 401) {
- const { login } = await import('./auth')
+ if (isInvalidToken(res)) {
+ if (isRetryAfter401) {
+ reject(new Error(res.data?.message || 'invalid token'))
+ return
+ }
try {
- await login()
- resolve(this.request(options))
+ await authLogin()
+ const nextOpts = { ...options, _retryAfter401: true }
+ resolve(this.request(nextOpts))
} catch (e) {
reject(e)
}
diff --git a/components/smoke-record-dialog/smoke-record-dialog.vue b/components/smoke-record-dialog/smoke-record-dialog.vue
index fd3e4d2..bd30fa9 100644
--- a/components/smoke-record-dialog/smoke-record-dialog.vue
+++ b/components/smoke-record-dialog/smoke-record-dialog.vue
@@ -147,9 +147,12 @@ export default {
num: this.initialData.num ?? 1
}
} else {
- // 新建模式,使用当前时间
+ // 新建模式,使用当前本地时间(不用 toISOString,避免 UTC 导致日期差一天)
const now = new Date()
- const dateStr = now.toISOString().split('T')[0]
+ const y = now.getFullYear()
+ const m = String(now.getMonth() + 1).padStart(2, '0')
+ const d = String(now.getDate()).padStart(2, '0')
+ const dateStr = `${y}-${m}-${d}`
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
const datetimeStr = `${dateStr} ${timeStr}:00`
diff --git a/config/index.js b/config/index.js
index c625986..0488f6b 100644
--- a/config/index.js
+++ b/config/index.js
@@ -1,6 +1,6 @@
const ENV = {
development: {
- BASE_URL: ' http://192.168.31.132:8080/api/v1',
+ BASE_URL: 'http://192.168.31.46:8080/api/v1',
MINI_PROGRAM_ID: 2
},
production: {
diff --git a/pages/logs/index.vue b/pages/logs/index.vue
index 614e92d..382d59d 100644
--- a/pages/logs/index.vue
+++ b/pages/logs/index.vue
@@ -13,9 +13,6 @@
{{ tab.label }}
-
- 📅
-
@@ -165,6 +162,14 @@ const groupedLogs = computed(() => {
}, {})
})
+// 本地日期 YYYY-MM-DD(避免 toISOString 用 UTC 导致日期差一天)
+function localDateStr(d) {
+ const y = d.getFullYear()
+ const m = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ return `${y}-${m}-${day}`
+}
+
// 格式化分组标题
function formatGroupTitle(dateStr) {
if (!dateStr) return ''
@@ -174,8 +179,8 @@ function formatGroupTitle(dateStr) {
const yesterday = new Date(today)
yesterday.setDate(yesterday.getDate() - 1)
- const todayStr = today.toISOString().split('T')[0]
- const yesterdayStr = yesterday.toISOString().split('T')[0]
+ const todayStr = localDateStr(today)
+ const yesterdayStr = localDateStr(yesterday)
if (dateStr === todayStr) {
return '今天'
diff --git a/pages/stats/index.vue b/pages/stats/index.vue
index 4468111..345e942 100644
--- a/pages/stats/index.vue
+++ b/pages/stats/index.vue
@@ -1,4 +1,4 @@
-
+
@@ -16,7 +16,9 @@
- ✨
+
+ {{ insightEmoji }}
+
每周洞察
{{ insightText }}
@@ -30,7 +32,7 @@
{{ trendRangeText }}
-
+ {{ statusArrow }}
{{ statusText }}
@@ -43,12 +45,19 @@
支/天
-
-
- {{ item.count }}
-
- {{ item.label }}
+
+
+
+ {{ item.count }}
+
+
+
+ {{ item.label }}
+
+
+
+ 暂无趋势数据
@@ -58,37 +67,72 @@
{{ savedMoneyText }}
- 目标 ¥{{ moneyTargetYuan }} ({{ moneyPercent }}%)
+ 目标 ¥{{ moneyTargetYuan }} ({{ moneyPercent }}%)
+
+
+ 预计
+ {{ moneyExpectedTotal }}
+ 支
+
+
+ 实际
+ {{ moneyActualTotal }}
+ 支
+
+
- {{ moneyPercent }}%
+ {{ moneyRingText }}
+
+ 🔒
+ 完善基础信息后解锁节省金额
+
+
+
+ ⏱️
+ {{ smokeFreeText }}
+ 无烟时长
+
+
+
+ {{ lungRecoveryPercent }}%
+ 肺功能
+
+
+
+
+ 🫁
+ 暂无健康数据,记录一次后解锁
@@ -110,18 +154,18 @@
-
+ 🔥
连续记录
{{ streakDays }}
天
- 保持未吸烟
+ 连续记录天数
-
+ 🛡️
已拒绝
@@ -152,15 +196,32 @@ const tabs = [
const currentTab = ref('week')
const statsData = ref(null)
+const changePercent = computed(() => {
+ const value = statsData.value?.change_percent
+ if (value === undefined || value === null) return null
+ const num = Number(value)
+ return Number.isNaN(num) ? null : num
+})
+
+const insightEmoji = computed(() => {
+ if (changePercent.value === null) return '✨'
+ return changePercent.value <= 0 ? '🌿' : '⚠️'
+})
+
+const insightIconClass = computed(() => {
+ if (changePercent.value === null) return 'insight-neutral'
+ return changePercent.value <= 0 ? 'insight-good' : 'insight-warn'
+})
+
const insightText = computed(() => {
- const change = statsData.value?.change_percent
- if (change === undefined || change === null) {
- return '前5天保持完成记录!周日虽有小量吸烟,但整体趋势依然可控,下周继续保持。'
+ if (changePercent.value === null) {
+ return '本阶段数据逐步稳定,建议保持记录,下一周期可获得趋势对比。'
}
- if (change <= 0) {
- return '本阶段总体保持良好,趋势持续向好,继续保持节奏。'
+ const abs = Math.abs(changePercent.value)
+ if (changePercent.value <= 0) {
+ return `较上期下降 ${abs}%,趋势向好,继续保持节奏。`
}
- return '近期吸烟量略有上升,试着减少高峰时段的冲动。'
+ return `较上期上升 ${abs}%,留意高峰时段,尝试延迟第一支。`
})
const trendRangeText = computed(() => {
@@ -173,15 +234,24 @@ const trendRangeText = computed(() => {
})
const statusText = computed(() => {
- const change = statsData.value?.change_percent
- if (change === undefined || change === null) return '总体良好'
- return change <= 0 ? '总体良好' : '有所增加'
+ if (changePercent.value === null) return '暂无对比'
+ const sign = changePercent.value > 0 ? '+' : ''
+ return `较上期 ${sign}${changePercent.value}%`
})
const statusChipClass = computed(() => {
- const change = statsData.value?.change_percent
- if (change === undefined || change === null) return 'status-good'
- return change <= 0 ? 'status-good' : 'status-warn'
+ if (changePercent.value === null) return 'status-neutral'
+ return changePercent.value <= 0 ? 'status-good' : 'status-warn'
+})
+
+const statusArrow = computed(() => {
+ if (changePercent.value === null) return '→'
+ return changePercent.value <= 0 ? '↓' : '↑'
+})
+
+const statusIconClass = computed(() => {
+ if (changePercent.value === null) return 'status-icon-neutral'
+ return changePercent.value <= 0 ? 'status-icon-good' : 'status-icon-warn'
})
const averageCount = computed(() => {
@@ -190,40 +260,57 @@ const averageCount = computed(() => {
return avg
})
+// 图表仅使用接口返回的 trend 渲染,无数据时为空
const trendItems = computed(() => {
const trend = statsData.value?.trend
const trendUnit = statsData.value?.trend_unit
- if (!trend || trend.length === 0) {
- return [
- { label: '19日', count: 0, height: '6%', isHighlight: false },
- { label: '20日', count: 0, height: '6%', isHighlight: false },
- { label: '21日', count: 0, height: '6%', isHighlight: false },
- { label: '22日', count: 0, height: '6%', isHighlight: false },
- { label: '23日', count: 0, height: '6%', isHighlight: false },
- { label: '24日', count: 0, height: '6%', isHighlight: false },
- { label: '25日', count: 6, height: '85%', isHighlight: true }
- ]
+ if (!trend || !Array.isArray(trend) || trend.length === 0) {
+ return []
}
- const maxCount = Math.max(...trend.map(item => item.count), 1)
+ const maxCount = Math.max(...trend.map(item => Number(item.count) || 0), 1)
return trend.map((item, index) => {
- const height = `${Math.max((item.count / maxCount) * 100, 6)}%`
+ const count = Number(item.count) || 0
+ const height = `${Math.max((count / maxCount) * 100, 6)}%`
return {
label: formatTrendLabel(item.label, trendUnit),
- count: item.count,
+ count,
height,
isHighlight: index === trend.length - 1
}
})
})
+// 图表最小宽度:保证每根柱子有足够空间,年视图 12 个月时不被挤压或裁切
+const trendChartMinWidth = computed(() => {
+ const n = trendItems.value.length
+ const perItem = 80
+ return `${Math.max(n * perItem, 400)}rpx`
+})
+
const savedMoneyText = computed(() => {
const money = statsData.value?.money
- if (!money || !money.available) {
- return '¥0.00'
- }
+ if (!money || !money.available) return '--'
return `¥${(money.saved_cent / 100).toFixed(2)}`
})
+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
+})
+
+const moneyActualTotal = computed(() => {
+ const money = statsData.value?.money
+ if (!money || !money.available) return 0
+ return Number(money.actual_total) || 0
+})
+
+const moneySubtitle = computed(() => {
+ if (!moneyAvailable.value) return '尚未解锁'
+ return `预计 ${moneyExpectedTotal.value} 支`
+})
const moneyTargetCent = computed(() => {
const money = statsData.value?.money
@@ -250,6 +337,11 @@ const moneyTargetYuan = computed(() => {
})
const moneyRingStyle = computed(() => {
+ if (!moneyAvailable.value) {
+ return {
+ background: 'conic-gradient(#E2E8F0 0deg 360deg)'
+ }
+ }
const percent = moneyPercent.value
const angle = Math.round(percent * 3.6)
return {
@@ -257,24 +349,56 @@ const moneyRingStyle = computed(() => {
}
})
+const moneyRingText = computed(() => {
+ if (!moneyAvailable.value) return '--'
+ return `${moneyPercent.value}%`
+})
+
const healthStatusText = computed(() => {
const health = statsData.value?.health
- if (!health || !health.available) return '进行中'
+ if (!health || !health.available) return '暂无数据'
+ const percent = Math.round(Number(health.lung_recovery_percent) || 0)
+ if (percent >= 100) return '已达成'
+ if (percent > 0) return `已恢复 ${percent}%`
return '进行中'
})
+const healthAvailable = computed(() => !!statsData.value?.health?.available)
+
+const lungRecoveryPercent = computed(() => {
+ const health = statsData.value?.health
+ if (!health || !health.available) return 0
+ const value = Number(health.lung_recovery_percent) || 0
+ return Math.min(Math.max(Math.round(value), 0), 100)
+})
+
+const healthRingStyle = computed(() => {
+ if (!healthAvailable.value) {
+ return {
+ background: 'conic-gradient(#E2E8F0 0deg 360deg)'
+ }
+ }
+ const angle = Math.round(lungRecoveryPercent.value * 3.6)
+ return {
+ background: `conic-gradient(#10B981 0deg ${angle}deg, #E2E8F0 ${angle}deg 360deg)`
+ }
+})
+
+const smokeFreeText = computed(() => {
+ const health = statsData.value?.health
+ if (!health || !health.available) return '0分钟'
+ const total = Math.max(Number(health.smoke_free_minutes) || 0, 0)
+ const days = Math.floor(total / 1440)
+ const hours = Math.floor((total % 1440) / 60)
+ const minutes = total % 60
+ if (days > 0) return `${days}天${hours}小时`
+ if (hours > 0) return `${hours}小时${minutes}分钟`
+ return `${minutes}分钟`
+})
const healthItems = computed(() => {
const health = statsData.value?.health
- if (!health || !health.available || !health.milestones || health.milestones.length === 0) {
- return [
- { name: '心率血压恢复正常', percent: 100 },
- { name: '血氧水平恢复', percent: 100 },
- { name: '一氧化碳排出', percent: 95 },
- { name: '嗅觉味觉改善', percent: 40 },
- { name: '肺部功能恢复', percent: 10 }
- ]
- }
+ if (!health || !health.available || !health.milestones || health.milestones.length === 0) return []
const minutes = health.smoke_free_minutes || 0
return health.milestones.map(item => {
if (item.reached) {
@@ -286,8 +410,8 @@ const healthItems = computed(() => {
})
})
-const streakDays = computed(() => statsData.value?.streak_days ?? 12)
-const resistedTotal = computed(() => statsData.value?.resisted_total ?? 24)
+const streakDays = computed(() => statsData.value?.streak_days ?? 0)
+const resistedTotal = computed(() => statsData.value?.resisted_total ?? 0)
const moneyIconClass = computed(() => {
const money = statsData.value?.money
@@ -438,11 +562,30 @@ onMounted(() => {
.insight-icon {
width: 64rpx;
height: 64rpx;
- border-radius: 50%;
- background-color: #D9FBE7;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, #BBF7D0 0%, #A7F3D0 100%);
display: flex;
align-items: center;
justify-content: center;
+ box-shadow: 0 10rpx 18rpx rgba(16, 185, 129, 0.18);
+}
+
+.insight-icon.insight-good {
+ background: linear-gradient(135deg, #BBF7D0 0%, #A7F3D0 100%);
+ box-shadow: 0 10rpx 18rpx rgba(16, 185, 129, 0.18);
+}
+
+.insight-icon.insight-warn {
+ background: linear-gradient(135deg, #FED7AA 0%, #FDBA74 100%);
+ box-shadow: 0 10rpx 18rpx rgba(249, 115, 22, 0.18);
+}
+
+.insight-icon.insight-neutral {
+ background: linear-gradient(135deg, #E2E8F0 0%, #CBD5F5 100%);
+ box-shadow: 0 10rpx 18rpx rgba(100, 116, 139, 0.12);
+}
+
+.insight-emoji {
font-size: 30rpx;
}
@@ -507,11 +650,37 @@ onMounted(() => {
border-color: #FDE68A;
}
-.status-dot {
- width: 10rpx;
- height: 10rpx;
- border-radius: 50%;
- background-color: currentColor;
+.status-neutral {
+ background-color: #F1F5F9;
+ color: #64748B;
+ border-color: #E2E8F0;
+}
+
+.status-icon {
+ width: 22rpx;
+ height: 22rpx;
+ border-radius: 999rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18rpx;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.status-icon-good {
+ background-color: rgba(22, 163, 74, 0.15);
+ color: #16A34A;
+}
+
+.status-icon-warn {
+ background-color: rgba(217, 119, 6, 0.15);
+ color: #D97706;
+}
+
+.status-icon-neutral {
+ background-color: rgba(100, 116, 139, 0.15);
+ color: #64748B;
}
.status-text {
@@ -554,25 +723,61 @@ onMounted(() => {
color: #94A3B8;
}
+.trend-chart-scroll {
+ width: 100%;
+ margin-top: 8rpx;
+}
+
+.trend-chart-empty {
+ width: 100%;
+ height: 240rpx;
+ margin-top: 8rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #F8FAFC;
+ border-radius: 16rpx;
+}
+
+.trend-chart-empty-text {
+ font-size: 26rpx;
+ color: #94A3B8;
+}
+
.trend-chart {
display: flex;
align-items: flex-end;
justify-content: space-between;
height: 240rpx;
gap: 12rpx;
+ padding: 36rpx 8rpx 0;
+ box-sizing: border-box;
}
.trend-bar-item {
flex: 1;
+ min-width: 56rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
position: relative;
+ flex-shrink: 0;
+ overflow: visible;
+}
+
+.trend-bar-wrap {
+ width: 32rpx;
+ height: 180rpx;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ flex-shrink: 0;
}
.trend-bar {
width: 32rpx;
+ min-height: 12rpx;
background: linear-gradient(180deg, #34D399 0%, #10B981 100%);
border-radius: 12rpx 12rpx 8rpx 8rpx;
}
@@ -616,40 +821,35 @@ onMounted(() => {
}
.savings-icon {
- width: 32rpx;
- height: 32rpx;
- border-radius: 8rpx;
- background-color: #FDE68A;
+ width: 56rpx;
+ height: 56rpx;
+ border-radius: 16rpx;
+ background: linear-gradient(135deg, #FDE68A 0%, #FBBF24 100%);
position: relative;
display: flex;
align-items: center;
justify-content: center;
color: #D97706;
+ box-shadow: 0 8rpx 16rpx rgba(245, 158, 11, 0.2);
+ overflow: hidden;
}
.savings-icon.icon-muted {
- background-color: #F1F5F9;
+ background: #F1F5F9;
color: #94A3B8;
+ box-shadow: none;
}
.savings-icon.icon-low {
- color: #E59E0B;
+ background: linear-gradient(135deg, #FEF3C7 0%, #FCD34D 100%);
}
.savings-icon.icon-mid {
- color: #D97706;
+ background: linear-gradient(135deg, #FCD34D 0%, #F59E0B 100%);
}
.savings-icon.icon-strong {
- color: #B45309;
-}
-
-.icon-coin {
- width: 18rpx;
- height: 12rpx;
- border-radius: 4rpx;
- border: 3rpx solid currentColor;
- border-top-width: 6rpx;
+ background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
}
.savings-title {
@@ -658,6 +858,17 @@ onMounted(() => {
color: #111827;
}
+.savings-header-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4rpx;
+}
+
+.savings-subtitle {
+ font-size: 20rpx;
+ color: #94A3B8;
+}
+
.savings-body {
display: flex;
align-items: center;
@@ -697,6 +908,62 @@ onMounted(() => {
display: block;
}
+.icon-emoji {
+ font-size: 28rpx;
+ line-height: 1;
+}
+
+.savings-icon .icon-emoji {
+ font-size: 30rpx;
+}
+
+.health-icon .icon-emoji {
+ font-size: 26rpx;
+}
+
+.mini-icon .icon-emoji {
+ font-size: 26rpx;
+}
+
+.savings-metrics {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ margin-top: 14rpx;
+}
+
+.metric-chip {
+ flex: 1;
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ gap: 6rpx;
+ padding: 6rpx 12rpx;
+ border-radius: 12rpx;
+ background-color: #FFFBEB;
+ color: #B45309;
+ font-weight: 600;
+}
+
+.metric-chip-actual {
+ background-color: #EFF6FF;
+ color: #2563EB;
+}
+
+.metric-label {
+ font-size: 20rpx;
+ opacity: 0.8;
+}
+
+.metric-value {
+ font-size: 24rpx;
+}
+
+.metric-unit {
+ font-size: 20rpx;
+ opacity: 0.7;
+}
+
.savings-ring {
width: 120rpx;
height: 120rpx;
@@ -723,6 +990,26 @@ onMounted(() => {
color: #111827;
}
+.card-empty {
+ margin-top: 16rpx;
+ padding: 12rpx 16rpx;
+ border-radius: 16rpx;
+ background-color: #F8FAFC;
+ border: 2rpx dashed #E2E8F0;
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+}
+
+.card-empty-icon {
+ font-size: 24rpx;
+}
+
+.card-empty-text {
+ font-size: 22rpx;
+ color: #94A3B8;
+}
+
.health-card {
background-color: #FFFFFF;
border-radius: 28rpx;
@@ -746,52 +1033,34 @@ onMounted(() => {
}
.health-icon {
- width: 28rpx;
- height: 28rpx;
- background-color: #DCFCE7;
- border-radius: 8rpx;
+ width: 48rpx;
+ height: 48rpx;
+ background: linear-gradient(135deg, #DCFCE7 0%, #A7F3D0 100%);
+ border-radius: 14rpx;
position: relative;
display: flex;
align-items: center;
justify-content: center;
color: #16A34A;
+ box-shadow: 0 8rpx 16rpx rgba(16, 185, 129, 0.18);
}
.health-icon.icon-muted {
- background-color: #F1F5F9;
+ background: #F1F5F9;
color: #94A3B8;
+ box-shadow: none;
}
.health-icon.icon-low {
- color: #16A34A;
+ background: linear-gradient(135deg, #DCFCE7 0%, #86EFAC 100%);
}
.health-icon.icon-mid {
- color: #16A34A;
+ background: linear-gradient(135deg, #BBF7D0 0%, #4ADE80 100%);
}
.health-icon.icon-strong {
- color: #0F766E;
-}
-
-.icon-plus-vertical,
-.icon-plus-horizontal {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background-color: currentColor;
- border-radius: 999rpx;
-}
-
-.icon-plus-vertical {
- width: 4rpx;
- height: 16rpx;
-}
-
-.icon-plus-horizontal {
- width: 16rpx;
- height: 4rpx;
+ background: linear-gradient(135deg, #34D399 0%, #10B981 100%);
}
.health-title {
@@ -810,6 +1079,73 @@ onMounted(() => {
border: 2rpx solid #BBF7D0;
}
+.health-badge-muted {
+ background-color: #F1F5F9;
+ color: #64748B;
+ border-color: #E2E8F0;
+}
+
+.health-overview {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+}
+
+.health-metric {
+ flex: 1;
+ background-color: #F0FDF4;
+ border-radius: 18rpx;
+ padding: 16rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 6rpx;
+ border: 2rpx solid #DCFCE7;
+}
+
+.health-metric-icon {
+ font-size: 22rpx;
+}
+
+.health-metric-value {
+ font-size: 26rpx;
+ font-weight: 700;
+ color: #0F172A;
+}
+
+.health-metric-label {
+ font-size: 20rpx;
+ color: #64748B;
+}
+
+.health-ring {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.health-ring-inner {
+ width: 88rpx;
+ height: 88rpx;
+ border-radius: 50%;
+ background-color: #FFFFFF;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 2rpx;
+ box-shadow: 0 6rpx 16rpx rgba(16, 185, 129, 0.12);
+}
+
+.ring-label {
+ font-size: 18rpx;
+ color: #94A3B8;
+}
+
.health-list {
display: flex;
flex-direction: column;
@@ -898,43 +1234,29 @@ onMounted(() => {
}
.mini-icon-fire.icon-low {
- color: #FB923C;
+ background-color: #FFE8D2;
}
.mini-icon-fire.icon-mid {
- color: #F97316;
+ background-color: #FFD8B5;
}
.mini-icon-fire.icon-strong {
- color: #EA580C;
+ background-color: #FDBA74;
}
.mini-icon-block.icon-low {
- color: #818CF8;
+ background-color: #E0E7FF;
}
.mini-icon-block.icon-mid {
- color: #6366F1;
+ background-color: #C7D2FE;
}
.mini-icon-block.icon-strong {
- color: #4F46E5;
+ background-color: #A5B4FC;
}
-.icon-flame {
- width: 16rpx;
- height: 20rpx;
- background-color: currentColor;
- border-radius: 50% 50% 50% 50%;
- transform: rotate(45deg);
-}
-
-.icon-shield {
- width: 18rpx;
- height: 18rpx;
- border-radius: 50%;
- border: 3rpx solid currentColor;
-}
.mini-label {
font-size: 22rpx;
diff --git a/stores/logs.js b/stores/logs.js
index 4d94c4a..1fa3261 100644
--- a/stores/logs.js
+++ b/stores/logs.js
@@ -88,7 +88,7 @@ export const useLogsStore = defineStore('logs', {
}
}
- // 获取显示日期
+ // 获取显示日期(用本地日期,避免 UTC 导致差一天)
let displayDate = ''
if (log.smoke_time) {
displayDate = log.smoke_time.split('T')[0]
@@ -96,7 +96,10 @@ export const useLogsStore = defineStore('logs', {
const date = typeof log.createtime === 'number'
? new Date(log.createtime * 1000)
: new Date(log.createtime)
- displayDate = date.toISOString().split('T')[0]
+ const y = date.getFullYear()
+ const m = String(date.getMonth() + 1).padStart(2, '0')
+ const d = String(date.getDate()).padStart(2, '0')
+ displayDate = `${y}-${m}-${d}`
}
return {
diff --git a/utils/time.js b/utils/time.js
index 9504a16..77a4a16 100644
--- a/utils/time.js
+++ b/utils/time.js
@@ -59,14 +59,13 @@ export function getGreeting() {
}
export function isToday(dateStr) {
- const today = new Date().toISOString().split('T')[0]
- return dateStr === today
+ return dateStr === formatDate(new Date())
}
export function isYesterday(dateStr) {
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
- return dateStr === yesterday.toISOString().split('T')[0]
+ return dateStr === formatDate(yesterday)
}
export function daysBetween(date1, date2) {