feat(theme): 实现日夜主题自动切换功能

- 添加日夜主题CSS变量定义,支持琥珀夜光和暖白琥珀两种风格
- 实现主题切换逻辑,根据时间自动切换白天(light)和夜间(dark)主题
- 在App.vue中集成主题初始化和token恢复功能
- 更新全局样式类应用主题颜色变量,包括卡片、文本、边框等
- 创建主题混入(mixin)供各页面使用,确保主题同步更新
- 实现导航栏和TabBar主题动态切换,提升用户体验
- 添加API服务层封装HaveADrink SDK,统一处理认证和请求
- 优化DrinkCard组件显示饮酒感受信息,丰富打卡记录展示
- 更新项目依赖配置,集成新的API客户端库
This commit is contained in:
cg
2026-07-16 22:47:38 +08:00
parent 145976f221
commit 860136bceb
22 changed files with 7051 additions and 155 deletions
+85 -21
View File
@@ -1,5 +1,5 @@
<template>
<view class="page-container card-page">
<view :class="themeClass" class="page-container card-page">
<view class="card-nav">
<view class="step-back" @click="goBack">
<text class="step-back-arrow"></text>
@@ -29,12 +29,16 @@
<script>
import DrinkCard from '../../components/DrinkCard.vue'
import client from '../../common/api'
import { getRecords } from '../../common/mock-data'
import { DRINK_CATEGORIES, FOOD_CATEGORIES, DRINK_QUOTES } from '../../common/constants'
import { getThemeColors, calcStandardCups, unitToMl } from '../../common/utils'
import { DRINK_CATEGORIES, FOOD_CATEGORIES, DRINK_QUOTES, FEELINGS } from '../../common/constants'
import themeMixin from '../../common/theme-mixin'
const UNIT_NAMES = { ml: 'ml', liang: '两', bottle: '瓶', cup: '杯', can: '听', shot: 'shot' }
export default {
mixins: [themeMixin],
components: { DrinkCard },
data() {
return {
@@ -43,12 +47,57 @@ export default {
saved: false
}
},
onLoad(options) {
const records = getRecords()
if (options && options.recordId) this.record = records.find(r => r.id === options.recordId)
if (!this.record) this.record = records.find(r => r.mode === 'drank') || this.fallback()
async onLoad(options) {
if (options && options.recordId) {
try {
const resp = await client.GetRecordDetail({ id: options.recordId })
const rec = resp.data || resp
if (rec) {
this.record = {
...rec,
mode: String(rec.mode),
feeling: rec.feeling ? String(rec.feeling) : null,
visibility: rec.visibility ? String(rec.visibility) : 'private',
drinks: (rec.drinks || []).map(d => ({
...d,
category: String(d.category),
unit: String(d.unit)
}))
}
}
} catch (e) {
console.warn('获取记录详情失败,降级本地:', e)
}
}
if (!this.record) this.record = this.loadLocalRecord(options.recordId) || this.fallback()
},
methods: {
loadLocalRecord(id) {
if (!id) return null
try {
const records = getRecords()
const found = records.find(r => r.id === id)
if (!found) return null
return {
...found,
mode: String(found.mode || 'drank'),
feeling: found.feeling ? String(found.feeling) : null,
visibility: found.visibility ? String(found.visibility) : 'private',
drinks: (found.drinks || []).map(d => {
const ml = unitToMl(d.amount || 0, d.unit || 'ml')
return {
...d,
category: String(d.category || d.categoryId || ''),
unit: String(d.unit || 'ml'),
standardCups: d.standardCups || calcStandardCups(ml, d.degree || 0)
}
})
}
} catch (e) {
console.warn('读取本地记录失败:', e)
return null
}
},
fallback() {
return {
date: new Date().toISOString().slice(0, 10), mode: 'drank',
@@ -86,10 +135,11 @@ export default {
const W = 750, H = 1600
const rec = this.record
const WHITE = '#F2F2F2'
const SUB = '#7E7E96'
const DIM = '#4A4A62'
const AMBER = '#E8A838'
const colors = getThemeColors(this.currentTheme)
const WHITE = colors.textPrimary
const SUB = colors.textSecondary
const DIM = colors.textTertiary
const AMBER = colors.amber
const F = '-apple-system, PingFang SC, Helvetica Neue, sans-serif'
const cardW = W
@@ -116,12 +166,13 @@ export default {
const drinks = rec.drinks || []
const drinkH = drinks.length * 52
const foodH = rec.food ? 52 : 0
const cardH = 48 + 56 + photoAreaH + 32 + drinkH + foodH + 16 + 40 + 36 + 56 + 80 + 40
const feelingH = rec.feeling ? 48 : 0
const cardH = 48 + 56 + photoAreaH + 32 + drinkH + foodH + feelingH + 16 + 40 + 36 + 56 + 80 + 40
// 卡片背景渐变(填满整个画布)
const grad = ctx.createLinearGradient(0, 0, cardW, cardY + cardH)
grad.addColorStop(0, '#1A1510')
grad.addColorStop(1, '#0D0B08')
grad.addColorStop(0, colors.cardGradStart)
grad.addColorStop(1, colors.cardGradEnd)
ctx.setFillStyle(grad)
ctx.fillRect(0, 0, W, cardH)
@@ -148,7 +199,7 @@ export default {
if (imgs[0]) {
try { ctx.drawImage(imgs[0], P, y, maxW, heroH) } catch (e) {}
} else {
ctx.setFillStyle('rgba(255,255,255,0.03)')
ctx.setFillStyle(colors.bgPlaceholder)
ctx.fillRect(P, y, maxW, heroH)
}
ctx.restore()
@@ -193,7 +244,7 @@ export default {
if (imgs[i]) {
try { ctx.drawImage(imgs[i], pos.x, pos.y, pos.w, pos.h) } catch (e) {}
} else {
ctx.setFillStyle('rgba(255,255,255,0.03)')
ctx.setFillStyle(colors.bgPlaceholder)
ctx.fillRect(pos.x, pos.y, pos.w, pos.h)
}
ctx.restore()
@@ -203,7 +254,7 @@ export default {
// emoji 圆形
const eR = 100
const eCx = W / 2, eCy = y + eR
ctx.setFillStyle('rgba(232,168,56,0.08)')
ctx.setFillStyle(colors.emojiBg)
ctx.beginPath()
ctx.arc(eCx, eCy, eR, 0, Math.PI * 2)
ctx.fill()
@@ -222,7 +273,7 @@ export default {
const emoji = cat ? cat.emoji : '🥃'
const name = `${emoji} ${d.brand}`
const unitName = UNIT_NAMES[d.unit] || d.unit
const detail = `${d.amount}${unitName}·${d.standardCups}`
const detail = `${d.amount}${unitName}`
ctx.font = `500 28px ${F}`
ctx.setFillStyle(WHITE)
@@ -246,11 +297,24 @@ export default {
y += 52
}
// 饮酒感受
if (rec.feeling) {
const feeling = FEELINGS.find(f => f.id === rec.feeling)
if (feeling) {
y += 8
const feelingText = `${feeling.emoji} ${feeling.name}`
ctx.font = `500 26px ${F}`
ctx.setFillStyle(AMBER)
ctx.fillText(feelingText, P, y)
y += 40
}
}
// 分割线(渐变)
y += 16
const lineGrad = ctx.createLinearGradient(P, y, W - P, y)
lineGrad.addColorStop(0, 'transparent')
lineGrad.addColorStop(0.5, 'rgba(232,168,56,0.15)')
lineGrad.addColorStop(0.5, colors.borderSubtle)
lineGrad.addColorStop(1, 'transparent')
ctx.setStrokeStyle(lineGrad)
ctx.setLineWidth(2)
@@ -290,10 +354,10 @@ export default {
const qrSize = 80
const qrX = W - P - qrSize
const qrY = y - 30
ctx.setStrokeStyle('rgba(232,168,56,0.15)')
ctx.setStrokeStyle(colors.borderSubtle)
ctx.setLineWidth(2)
this.rr(ctx, qrX, qrY, qrSize, qrSize, 8, 'stroke')
ctx.setFillStyle('rgba(255,255,255,0.03)')
ctx.setFillStyle(colors.bgPlaceholder)
this.rr(ctx, qrX, qrY, qrSize, qrSize, 8, 'fill')
return new Promise(resolve => {
@@ -453,7 +517,7 @@ export default {
border-radius: $radius-full;
font-size: $fs-base;
background: transparent;
border: 2rpx solid rgba(255, 255, 255, 0.1);
border: 2rpx solid var(--border-active, rgba(255, 255, 255, 0.12));
color: $text-secondary;
display: flex;
align-items: center;