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
+143
View File
@@ -0,0 +1,143 @@
/* 喝了么 - API 服务层
* 基于 HaveADrink SDK 封装后端接口
*/
import HaveADrink from 'HaveADrink'
// ==========================================
// 后端服务地址(切换环境只需改这里)
// ==========================================
const API_HOST = 'https://dev.wash-painting.cn'
// ==========================================
// HTTP 请求函数(供 SDK 内部调用)
// 参考 goodBooth 项目的 fetchsomething
// ==========================================
function httpRequest(url, params) {
console.log('[API] Request:', params.method, url, 'data:', JSON.stringify(params.data))
return new Promise((resolve, reject) => {
// 自动注入 Authorization token
const headers = Object.assign({}, params.headers || {})
const token = uni.getStorageSync('auth_token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
uni.request({
url: url,
method: params.method,
params: params.query || undefined,
data: params.data,
header: headers,
success: (res) => {
const code = res.statusCode
if (code === 200 || code === 201 || code === 204) {
resolve(res.data)
} else if (code === 401) {
// token 过期,清除登录态并跳转登录页
uni.removeStorageSync('auth_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('is_logged_in')
uni.reLaunch({ url: '/pages/login/login' })
reject({ msg: '登录已过期,请重新登录' })
} else {
const errMsg = (res.data && res.data.msg) || (res.data && res.data.errmsg) || `请求失败(${code})`
reject({ msg: errMsg, code })
}
},
fail: (err) => {
reject({ msg: err.errMsg || '网络请求失败' })
}
})
})
}
// ==========================================
// 文件上传函数(供 SDK 内部调用)
// ==========================================
function uploadRequest(url, params) {
return new Promise((resolve, reject) => {
const headers = Object.assign({}, params.headers || {})
const token = uni.getStorageSync('auth_token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
uni.uploadFile({
url: url,
filePath: params.data.fileName || params.data.filePath,
name: params.data.name || 'file',
header: headers,
formData: params.data.formData || {},
success: (uploadRes) => {
try {
const data = typeof uploadRes.data === 'string'
? JSON.parse(uploadRes.data)
: uploadRes.data
resolve(data)
} catch (e) {
resolve(uploadRes.data)
}
},
fail: (err) => {
reject({ msg: err.errMsg || '上传失败' })
}
})
})
}
// ==========================================
// 初始化 SDK 实例
// ==========================================
const client = new HaveADrink({
host: API_HOST,
http_request: httpRequest,
upload: uploadRequest,
token: uni.getStorageSync('auth_token') || ''
})
// ==========================================
// 导出 SDK 实例(各页面直接 import 使用)
// ==========================================
export default client
// ==========================================
// Token 管理辅助函数
// ==========================================
/** 登录成功后保存 token 并同步到 SDK */
export function saveAuthTokens(data) {
if (data.token) {
uni.setStorageSync('auth_token', data.token)
client.setToken(data.token)
}
if (data.refresh_token) {
uni.setStorageSync('refresh_token', data.refresh_token)
}
}
/** 清除所有认证信息 */
export function clearAuth() {
uni.removeStorageSync('auth_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('is_logged_in')
uni.removeStorageSync('is_guest')
uni.removeStorageSync('user_info')
client.setToken('')
}
/** 尝试用 refresh_token 刷新 token */
export async function refreshAuthToken() {
const refreshToken = uni.getStorageSync('refresh_token')
if (!refreshToken) return false
try {
const res = await client.RefreshToken({ refresh_token: refreshToken })
if (res && res.token) {
saveAuthTokens(res)
return true
}
return false
} catch (e) {
console.warn('刷新 token 失败:', e)
return false
}
}
+28
View File
@@ -0,0 +1,28 @@
/* 喝了么 - 主题混入 (Theme Mixin)
* 每个页面混入此mixin,自动在onShow时刷新日夜主题
* 页面根视图需绑定 :class="themeClass"
*/
import { getCurrentTheme, applyTheme } from './utils'
export default {
data() {
return {
themeClass: 'theme-dark',
currentTheme: 'dark'
}
},
created() {
this._refreshTheme()
},
onShow() {
this._refreshTheme()
},
methods: {
_refreshTheme() {
const theme = getCurrentTheme()
this.currentTheme = theme
this.themeClass = theme === 'light' ? 'theme-light' : 'theme-dark'
applyTheme(theme)
}
}
}
+83
View File
@@ -190,6 +190,89 @@ export function calcStats(records) {
}
}
/**
* 主题系统:日夜自动切换
* 白天:6:00-16:00 → light
* 夜间:16:00-6:00 → dark
*/
export function getCurrentTheme() {
const hour = new Date().getHours()
return (hour >= 6 && hour < 16) ? 'light' : 'dark'
}
export function applyTheme(theme) {
// 同步存储当前主题
uni.setStorageSync('current_theme', theme)
// 应用导航栏和TabBar主题
applyNavBarTheme(theme)
applyTabBarTheme(theme)
}
export function applyNavBarTheme(theme) {
const colors = {
dark: { backgroundColor: '#0B0B14', frontColor: '#ffffff' },
light: { backgroundColor: '#FAF7F2', frontColor: '#000000' }
}
const c = colors[theme] || colors.dark
try {
uni.setNavigationBarColor({
frontColor: c.frontColor,
backgroundColor: c.backgroundColor,
animation: { duration: 300, timingFunc: 'easeInOut' }
})
} catch (e) {}
}
export function applyTabBarTheme(theme) {
// 仅在 tabBar 页面上调用 setTabBarStyle,避免报错
const tabBarPages = ['pages/index/index', 'pages/profile/profile']
const pages = getCurrentPages()
if (!pages.length) return
const currentPath = pages[pages.length - 1].route
if (!tabBarPages.includes(currentPath)) return
const styles = {
dark: {
color: '#9494AC',
selectedColor: '#E8A838',
backgroundColor: '#151520',
borderStyle: 'black'
},
light: {
color: '#B5AEA0',
selectedColor: '#D49530',
backgroundColor: '#FFFFFF',
borderStyle: 'white'
}
}
const s = styles[theme] || styles.dark
try {
uni.setTabBarStyle({
color: s.color,
selectedColor: s.selectedColor,
backgroundColor: s.backgroundColor,
borderStyle: s.borderStyle
})
} catch (e) {}
}
export function getThemeColors(theme) {
const isDark = theme !== 'light'
return {
textPrimary: isDark ? '#FFFFFF' : '#2C2416',
textSecondary: isDark ? '#C0C0D2' : '#8C8474',
textTertiary: isDark ? '#9494AC' : '#B5AEA0',
amber: isDark ? '#E8A838' : '#D49530',
bgBase: isDark ? '#0B0B14' : '#FAF7F2',
bgCard: isDark ? '#1A1A28' : '#FFFFFF',
cardGradStart: isDark ? '#1A1510' : '#F5F0E8',
cardGradEnd: isDark ? '#0D0B08' : '#EDE8DF',
borderSubtle: isDark ? 'rgba(232,168,56,0.15)' : 'rgba(212,149,48,0.2)',
bgPlaceholder: isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.04)',
emojiBg: isDark ? 'rgba(232,168,56,0.08)' : 'rgba(212,149,48,0.1)'
}
}
/**
* 检查成就解锁
*/