docs(api): 更新API接口文档并调整应用配置
- 添加完整的后端接口文档,包含用户、打卡记录、统计等模块 - 修复应用名称从"喝酒了么"统一更正为"喝了么" - 重构App.vue中的启动逻辑,增加数据迁移和路由守卫功能 - 调整DrinkCalendar组件的UI间距和尺寸参数 - 优化DrinkCard组件的品牌展示文案 - 修改manifest.json中的应用描述和微信小程序配置 - 调整pages.json中的页面路径顺序和tabBar配置 - 更新全局样式文件中的应用名称标识 - 修改mock数据中的用户头像默认值 - 优化卡片导出功能的绘制逻辑和视觉效果
This commit is contained in:
+136
-87
@@ -87,8 +87,8 @@ export default {
|
||||
if (this.saving) return
|
||||
this.saving = true
|
||||
try {
|
||||
await this.drawExportCard()
|
||||
const path = await this.exportImg()
|
||||
const contentH = await this.drawExportCard()
|
||||
const path = await this.exportImg(contentH)
|
||||
await this.saveImg(path)
|
||||
uni.showToast({ title: '已保存到相册', icon: 'success' })
|
||||
} catch (e) {
|
||||
@@ -105,64 +105,92 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// ===== 简洁导出卡片绘制 =====
|
||||
// ===== 海报导出绘制(优化版) =====
|
||||
async drawExportCard() {
|
||||
const ctx = uni.createCanvasContext('shareCanvas', this)
|
||||
const W = 750, H = 1200
|
||||
const P = 56 // 全局padding增大
|
||||
const rec = this.record
|
||||
const P = 50 // padding
|
||||
const tpl = this.currentTemplate
|
||||
|
||||
// 配色
|
||||
// 模板配色体系
|
||||
const TH = {
|
||||
amber: { bg: '#111111', line: '#2A2218', accent: '#E8A838', tagBg: '#1E1A14' },
|
||||
aurora: { bg: '#0E0E16', line: '#1E1E2E', accent: '#5EC6A0', tagBg: '#141420' },
|
||||
film: { bg: '#100F0D', line: '#252218', accent: '#C8B48C', tagBg: '#1A1814' }
|
||||
amber: { bg: '#111111', bgGrad: '#1A1510', line: '#2A2218', accent: '#E8A838', accentLight: '#F5C963', tagBg: '#1E1A14', tagBorder: '#3A3228' },
|
||||
aurora: { bg: '#0E0E16', bgGrad: '#141420', line: '#1E1E2E', accent: '#5EC6A0', accentLight: '#8EDCBE', tagBg: '#141420', tagBorder: '#2A2A3E' },
|
||||
film: { bg: '#100F0D', bgGrad: '#181610', line: '#252218', accent: '#C8B48C', accentLight: '#DED0B0', tagBg: '#1A1814', tagBorder: '#352E22' }
|
||||
}
|
||||
const c = TH[tpl] || TH.amber
|
||||
const WHITE = '#F0F0F0'
|
||||
const GRAY = '#999999'
|
||||
const DIM = '#666666'
|
||||
const F = 'PingFang SC'
|
||||
const WHITE = '#F2F2F2'
|
||||
const SUB = '#B0B0B0'
|
||||
const GRAY = '#888888'
|
||||
const DIM = '#5A5A5A'
|
||||
const F = '-apple-system, PingFang SC, Helvetica Neue, sans-serif'
|
||||
|
||||
// 背景
|
||||
// ===== 背景绘制 =====
|
||||
ctx.setFillStyle(c.bg)
|
||||
ctx.fillRect(0, 0, W, H)
|
||||
|
||||
// 顶部细线装饰
|
||||
// 底部微渐变装饰
|
||||
ctx.setFillStyle(c.bgGrad)
|
||||
ctx.setGlobalAlpha(0.5)
|
||||
ctx.fillRect(0, H * 0.6, W, H * 0.4)
|
||||
ctx.setGlobalAlpha(1)
|
||||
// 顶部强调线条(加粗)
|
||||
ctx.setFillStyle(c.accent)
|
||||
ctx.fillRect(0, 0, W, 4)
|
||||
ctx.fillRect(0, 0, W, 5)
|
||||
|
||||
let y = P + 10
|
||||
let y = P + 16
|
||||
|
||||
// === 日期行 ===
|
||||
ctx.font = `normal 26px ${F}`
|
||||
ctx.setFillStyle(GRAY)
|
||||
ctx.fillText(rec.date || '', P, y + 20)
|
||||
// ===== 顶部行:日期药丸 + 标准杯 =====
|
||||
// 日期药丸样式
|
||||
const dateStr = rec.date || ''
|
||||
ctx.font = `500 22px ${F}`
|
||||
const dateW = ctx.measureText(dateStr).width + 28
|
||||
const pillH = 36, pillR = 18
|
||||
ctx.setFillStyle(c.tagBg)
|
||||
this.rr(ctx, P, y - 4, dateW, pillH, pillR, 'fill')
|
||||
ctx.setStrokeStyle(c.tagBorder)
|
||||
ctx.setLineWidth(1)
|
||||
this.rr(ctx, P, y - 4, dateW, pillH, pillR, 'stroke')
|
||||
ctx.font = `500 22px ${F}`
|
||||
ctx.setFillStyle(SUB)
|
||||
ctx.fillText(dateStr, P + 14, y + 18)
|
||||
|
||||
// 标准杯(右对齐)
|
||||
ctx.font = `bold 40px ${F}`
|
||||
ctx.setFillStyle(c.accent)
|
||||
// 标准杯:数字+文字垂直堆叠(右对齐)
|
||||
const num = String(rec.standardCupsTotal)
|
||||
ctx.font = `800 44px ${F}`
|
||||
ctx.setFillStyle(c.accent)
|
||||
const nw = ctx.measureText(num).width
|
||||
ctx.fillText(num, W - P - nw - ctx.measureText(' 标准杯').width - 6, y + 24)
|
||||
ctx.font = `normal 22px ${F}`
|
||||
ctx.font = `normal 18px ${F}`
|
||||
const lw = ctx.measureText('标准杯').width
|
||||
const cupBlockW = Math.max(nw, lw) + 4
|
||||
const cupX = W - P - cupBlockW
|
||||
ctx.font = `800 44px ${F}`
|
||||
ctx.setFillStyle(c.accent)
|
||||
ctx.fillText(num, cupX + (cupBlockW - nw) / 2, y + 16)
|
||||
ctx.font = `normal 18px ${F}`
|
||||
ctx.setFillStyle(GRAY)
|
||||
ctx.fillText(' 标准杯', W - P - ctx.measureText(' 标准杯').width, y + 22)
|
||||
y += 50
|
||||
ctx.fillText('标准杯', cupX + (cupBlockW - lw) / 2, y + 34)
|
||||
y += 58
|
||||
|
||||
// === 照片 ===
|
||||
// ===== 照片区域 =====
|
||||
const photos = rec.photos || []
|
||||
if (photos.length > 0) {
|
||||
const loaded = await Promise.all(photos.slice(0, 9).map(p => this.loadImg(p)))
|
||||
const ph = this.drawPhotos(ctx, loaded, P, y, W - P * 2, c.accent)
|
||||
// 照片底部渐变蒙版
|
||||
const gradH = 80
|
||||
const grd = ctx.createLinearGradient(P, y + ph - gradH, P, y + ph)
|
||||
grd.addColorStop(0, 'rgba(0,0,0,0)')
|
||||
grd.addColorStop(1, 'rgba(0,0,0,0.45)')
|
||||
ctx.setFillStyle(grd)
|
||||
ctx.fillRect(P, y + ph - gradH, W - P * 2, gradH)
|
||||
y += ph + 28
|
||||
}
|
||||
|
||||
// === 酒水信息行 ===
|
||||
// ===== 酒水信息行 =====
|
||||
const drinks = rec.drinks || []
|
||||
if (drinks.length > 0) {
|
||||
ctx.font = `bold 28px ${F}`
|
||||
ctx.font = `bold 30px ${F}`
|
||||
ctx.setFillStyle(WHITE)
|
||||
let line = ''
|
||||
drinks.forEach(d => {
|
||||
@@ -170,91 +198,109 @@ export default {
|
||||
const nm = cat ? cat.name : '酒水'
|
||||
line += (line ? ' · ' : '') + `${nm} ${d.brand} · ${d.standardCups}杯`
|
||||
})
|
||||
ctx.fillText(line, P, y + 22)
|
||||
y += 42
|
||||
ctx.fillText(line, P, y + 24)
|
||||
y += 48
|
||||
}
|
||||
|
||||
// === 配餐 + 感受 标签 ===
|
||||
// ===== 配餐 + 感受 胶囊标签 =====
|
||||
let tags = []
|
||||
let tagTypes = [] // 'food' | 'feeling'
|
||||
if (rec.food) {
|
||||
const food = FOOD_CATEGORIES.find(f => f.id === rec.food.category)
|
||||
if (food) tags.push(rec.food.name ? `${food.name} · ${rec.food.name}` : food.name)
|
||||
if (food) {
|
||||
tags.push(rec.food.name ? `${food.name} · ${rec.food.name}` : food.name)
|
||||
tagTypes.push('food')
|
||||
}
|
||||
}
|
||||
if (rec.feeling) {
|
||||
const f = FEELINGS.find(fe => fe.id === rec.feeling)
|
||||
if (f) tags.push(f.name)
|
||||
if (f) { tags.push(f.name); tagTypes.push('feeling') }
|
||||
}
|
||||
if (tags.length > 0) {
|
||||
let tx = P
|
||||
ctx.font = `normal 22px ${F}`
|
||||
tags.forEach(tag => {
|
||||
const tw = ctx.measureText(tag).width + 24
|
||||
tags.forEach((tag, i) => {
|
||||
const tw = ctx.measureText(tag).width + 32
|
||||
const th = 36
|
||||
// 胶囊背景+边框
|
||||
ctx.setFillStyle(c.tagBg)
|
||||
this.rr(ctx, tx, y, tw, 32, 6, 'fill')
|
||||
ctx.setFillStyle(GRAY)
|
||||
ctx.fillText(tag, tx + 12, y + 22)
|
||||
tx += tw + 10
|
||||
this.rr(ctx, tx, y, tw, th, th / 2, 'fill')
|
||||
ctx.setStrokeStyle(tagTypes[i] === 'food' ? c.accent : c.accentLight)
|
||||
ctx.setLineWidth(1)
|
||||
this.rr(ctx, tx, y, tw, th, th / 2, 'stroke')
|
||||
// 标签文字(场景用强调色,感受用次要白)
|
||||
ctx.setFillStyle(tagTypes[i] === 'food' ? c.accentLight : SUB)
|
||||
ctx.fillText(tag, tx + 16, y + 24)
|
||||
tx += tw + 14
|
||||
})
|
||||
y += 48
|
||||
y += 56
|
||||
}
|
||||
|
||||
// === 分隔线 ===
|
||||
// ===== 分隔线 =====
|
||||
ctx.setStrokeStyle(c.line)
|
||||
ctx.setLineWidth(1)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(P, y)
|
||||
ctx.lineTo(W - P, y)
|
||||
ctx.stroke()
|
||||
y += 24
|
||||
|
||||
// === 酒言酒语 ===
|
||||
const quote = DRINK_QUOTES[Math.floor(Math.random() * DRINK_QUOTES.length)]
|
||||
// 左侧装饰竖线
|
||||
ctx.setFillStyle(c.accent)
|
||||
this.rr(ctx, P, y, 4, 40, 2, 'fill')
|
||||
// 引号
|
||||
ctx.font = `bold 48px ${F}`
|
||||
ctx.setGlobalAlpha(0.2)
|
||||
ctx.fillText('"', P + 14, y + 38)
|
||||
ctx.setGlobalAlpha(1)
|
||||
// 引文
|
||||
ctx.font = `normal 26px ${F}`
|
||||
ctx.setFillStyle(GRAY)
|
||||
const dq = quote.length > 14 ? quote.substring(0, 14) + '...' : quote
|
||||
ctx.fillText(dq, P + 16, y + 28)
|
||||
y += 60
|
||||
|
||||
// === 底部品牌栏 ===
|
||||
ctx.setStrokeStyle(c.line)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(P, y)
|
||||
ctx.lineTo(W - P, y)
|
||||
ctx.stroke()
|
||||
y += 28
|
||||
|
||||
// 品牌名
|
||||
ctx.font = `bold 26px ${F}`
|
||||
// ===== 酒言酒语(优化装饰) =====
|
||||
const quote = DRINK_QUOTES[Math.floor(Math.random() * DRINK_QUOTES.length)]
|
||||
// 左侧装饰竖条(加宽、渐变感)
|
||||
ctx.setFillStyle(c.accent)
|
||||
this.rr(ctx, P, y + 2, 5, 48, 2.5, 'fill')
|
||||
// 装饰引号(大字、半透明)
|
||||
ctx.font = `bold 60px ${F}`
|
||||
ctx.setGlobalAlpha(0.15)
|
||||
ctx.setFillStyle(c.accentLight)
|
||||
ctx.fillText('\u201C', P + 16, y + 44)
|
||||
ctx.setGlobalAlpha(1)
|
||||
// 引文内容
|
||||
ctx.font = `italic 26px ${F}`
|
||||
ctx.setFillStyle(SUB)
|
||||
const maxQW = W - P * 2 - 40
|
||||
let dq = quote
|
||||
if (ctx.measureText(dq).width > maxQW) {
|
||||
while (dq.length > 0 && ctx.measureText(dq + '...').width > maxQW) dq = dq.slice(0, -1)
|
||||
dq += '...'
|
||||
}
|
||||
ctx.fillText(dq, P + 20, y + 32)
|
||||
y += 72
|
||||
|
||||
// ===== 底部品牌栏 =====
|
||||
ctx.setStrokeStyle(c.line)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(P, y)
|
||||
ctx.lineTo(W - P, y)
|
||||
ctx.stroke()
|
||||
y += 32
|
||||
|
||||
// 品牌名 + 副标语纵向排列
|
||||
ctx.font = `bold 28px ${F}`
|
||||
ctx.setFillStyle(WHITE)
|
||||
ctx.fillText('喝酒了么', P, y + 20)
|
||||
// 副标语
|
||||
ctx.fillText('喝了么', P, y + 20)
|
||||
ctx.font = `normal 20px ${F}`
|
||||
ctx.setFillStyle(DIM)
|
||||
ctx.fillText('记录每一杯的故事', P, y + 50)
|
||||
|
||||
// 二维码占位
|
||||
const qr = 80, qx = W - P - qr, qy = y
|
||||
// 二维码占位(优化样式)
|
||||
const qr = 80, qx = W - P - qr, qy = y + 2
|
||||
ctx.setFillStyle(c.tagBg)
|
||||
this.rr(ctx, qx, qy, qr, qr, 8, 'fill')
|
||||
ctx.setStrokeStyle(c.line)
|
||||
ctx.setLineWidth(1)
|
||||
this.rr(ctx, qx, qy, qr, qr, 8, 'stroke')
|
||||
ctx.font = `normal 18px ${F}`
|
||||
ctx.setFillStyle(DIM)
|
||||
this.rr(ctx, qx, qy, qr, qr, 10, 'fill')
|
||||
ctx.setStrokeStyle(c.tagBorder)
|
||||
ctx.setLineWidth(1.5)
|
||||
this.rr(ctx, qx, qy, qr, qr, 10, 'stroke')
|
||||
ctx.font = `500 16px ${F}`
|
||||
ctx.setFillStyle(GRAY)
|
||||
const sw = ctx.measureText('扫码').width
|
||||
ctx.fillText('扫码', qx + (qr - sw) / 2, qy + qr / 2 + 7)
|
||||
ctx.fillText('扫码', qx + (qr - sw) / 2, qy + qr / 2 + 6)
|
||||
|
||||
// 计算实际内容高度(用于动态裁剪导出)
|
||||
const contentH = qy + qr + P
|
||||
|
||||
return new Promise(resolve => {
|
||||
ctx.draw(false, () => setTimeout(resolve, 600))
|
||||
ctx.draw(false, () => setTimeout(() => resolve(contentH), 600))
|
||||
})
|
||||
},
|
||||
|
||||
@@ -338,11 +384,14 @@ export default {
|
||||
return [3, 3, 3]
|
||||
},
|
||||
|
||||
// 导出
|
||||
exportImg() {
|
||||
// 导出(动态裁剪到实际内容区域)
|
||||
exportImg(contentH) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cropH = Math.min(contentH || 1200, 1200)
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: 'shareCanvas', quality: 1,
|
||||
x: 0, y: 0, width: 750, height: cropH,
|
||||
destWidth: 750, destHeight: cropH,
|
||||
success: res => resolve(res.tempFilePath),
|
||||
fail: err => reject({ msg: '导出失败: ' + (err.errMsg || '') })
|
||||
}, this)
|
||||
@@ -368,11 +417,11 @@ export default {
|
||||
},
|
||||
|
||||
shareToFriend() { uni.showToast({ title: '点击右上角分享', icon: 'none' }) },
|
||||
goHome() { uni.reLaunch({ url: '/pages/index/index' }) },
|
||||
goHome() { uni.switchTab({ url: '/pages/index/index' }) },
|
||||
goBack() { uni.navigateBack() }
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '今晚的酒局记录 - 喝酒了么', path: '/pages/index/index' }
|
||||
return { title: '今晚的酒局记录 - 喝了么', path: '/pages/index/index' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+69
-99
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<view class="page-container home safe-bottom">
|
||||
<view class="page-container home">
|
||||
<!-- 顶部问候 -->
|
||||
<view class="greeting-bar">
|
||||
<view class="greeting-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="greeting-left">
|
||||
<text class="greeting-text">{{ greeting }},{{ userName }}</text>
|
||||
<text class="greeting-sub">今晚喝一杯?</text>
|
||||
</view>
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<image v-if="userAvatar" class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<view v-else class="avatar avatar-placeholder">
|
||||
<text class="avatar-placeholder-icon">👤</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心CTA - 首页最醒目位置 -->
|
||||
@@ -81,21 +84,9 @@
|
||||
<text>过量饮酒有害健康 · 请理性记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航占位 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item tab-active" @click="goHome">
|
||||
<text class="tab-icon">🏠</text>
|
||||
<text class="tab-label">首页</text>
|
||||
</view>
|
||||
<view class="tab-center" @click="goRecord">
|
||||
<view class="tab-fab">
|
||||
<text class="tab-fab-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-item" @click="goProfile">
|
||||
<text class="tab-icon">👤</text>
|
||||
<text class="tab-label">我的</text>
|
||||
</view>
|
||||
<!-- 悬浮记录按钮 -->
|
||||
<view class="fab-record" @click="goRecord">
|
||||
<text class="fab-icon">+</text>
|
||||
</view>
|
||||
|
||||
<!-- 日期记录弹窗 -->
|
||||
@@ -141,14 +132,16 @@ export default {
|
||||
components: { DrinkCalendar },
|
||||
data() {
|
||||
const now = new Date()
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
return {
|
||||
statusBarHeight: sysInfo.statusBarHeight || 25,
|
||||
calendarYear: now.getFullYear(),
|
||||
calendarMonth: now.getMonth() + 1,
|
||||
records: [],
|
||||
stats: {},
|
||||
greeting: '',
|
||||
userName: '',
|
||||
userAvatar: '/static/logo.png',
|
||||
userAvatar: '',
|
||||
showDayDetail: false,
|
||||
selectedDate: '',
|
||||
selectedRecord: null
|
||||
@@ -181,7 +174,7 @@ export default {
|
||||
this.records = getRecords()
|
||||
const user = getUserInfo()
|
||||
this.userName = user.nickname || '酒友'
|
||||
this.userAvatar = user.avatar || '/static/logo.png'
|
||||
this.userAvatar = user.avatar || ''
|
||||
this.greeting = getGreeting()
|
||||
this.stats = calcStats(this.records)
|
||||
},
|
||||
@@ -207,11 +200,8 @@ export default {
|
||||
goRecord() {
|
||||
uni.navigateTo({ url: '/pages/record/record' })
|
||||
},
|
||||
goHome() {
|
||||
// 已在首页
|
||||
},
|
||||
goProfile() {
|
||||
uni.navigateTo({ url: '/pages/profile/profile' })
|
||||
uni.switchTab({ url: '/pages/profile/profile' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +209,7 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home {
|
||||
padding: $sp-lg;
|
||||
padding: $sp-md;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
@@ -227,50 +217,64 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $sp-xl 0 $sp-lg;
|
||||
padding: $sp-md 0 $sp-sm;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
display: block;
|
||||
font-size: $fs-xl;
|
||||
font-size: $fs-lg;
|
||||
font-weight: $fw-bold;
|
||||
}
|
||||
|
||||
.greeting-sub {
|
||||
display: block;
|
||||
font-size: $fs-sm;
|
||||
font-size: $fs-xs;
|
||||
color: $text-secondary;
|
||||
margin-top: $sp-xs;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: $radius-full;
|
||||
border: 4rpx solid $amber-glow;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
background: $bg-elevated;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 4rpx dashed rgba(232,168,56,0.3);
|
||||
}
|
||||
|
||||
.avatar-placeholder-icon {
|
||||
font-size: 40rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.calendar-section {
|
||||
margin-bottom: $sp-lg;
|
||||
padding: $sp-lg;
|
||||
margin-bottom: $sp-md;
|
||||
padding: $sp-md;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: $sp-sm;
|
||||
margin-bottom: $sp-lg;
|
||||
gap: $sp-xs;
|
||||
margin-bottom: $sp-md;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: $sp-lg $sp-sm;
|
||||
padding: $sp-md $sp-xs;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: block;
|
||||
font-size: $fs-2xl;
|
||||
margin-bottom: $sp-xs;
|
||||
font-size: $fs-xl;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
@@ -279,11 +283,12 @@ export default {
|
||||
}
|
||||
|
||||
.week-summary {
|
||||
margin-bottom: $sp-lg;
|
||||
margin-bottom: $sp-md;
|
||||
padding: $sp-md;
|
||||
}
|
||||
|
||||
.week-header {
|
||||
margin-bottom: $sp-lg;
|
||||
margin-bottom: $sp-sm;
|
||||
}
|
||||
|
||||
.week-stats {
|
||||
@@ -297,9 +302,9 @@ export default {
|
||||
|
||||
.week-stat-value {
|
||||
display: block;
|
||||
font-size: $fs-2xl;
|
||||
font-size: $fs-xl;
|
||||
font-weight: $fw-bold;
|
||||
margin-bottom: $sp-xs;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.week-stat-label {
|
||||
@@ -314,7 +319,7 @@ export default {
|
||||
}
|
||||
|
||||
.health-ref {
|
||||
margin-top: $sp-lg;
|
||||
margin-top: $sp-md;
|
||||
position: relative;
|
||||
height: 12rpx;
|
||||
background: $bg-elevated;
|
||||
@@ -339,10 +344,10 @@ export default {
|
||||
/* 核心CTA - 首页英雄区域 */
|
||||
.hero-cta {
|
||||
position: relative;
|
||||
margin-bottom: $sp-lg;
|
||||
border-radius: $radius-xl;
|
||||
margin-bottom: $sp-md;
|
||||
border-radius: $radius-lg;
|
||||
overflow: hidden;
|
||||
padding: $sp-xl $sp-lg;
|
||||
padding: $sp-md $sp-lg;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
@@ -381,11 +386,11 @@ export default {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $sp-lg;
|
||||
gap: $sp-md;
|
||||
}
|
||||
|
||||
.hero-cta-emoji {
|
||||
font-size: 72rpx;
|
||||
font-size: 56rpx;
|
||||
}
|
||||
|
||||
.hero-cta-text {
|
||||
@@ -394,7 +399,7 @@ export default {
|
||||
|
||||
.hero-cta-title {
|
||||
display: block;
|
||||
font-size: $fs-2xl;
|
||||
font-size: $fs-xl;
|
||||
font-weight: $fw-black;
|
||||
color: $text-on-amber;
|
||||
letter-spacing: -1rpx;
|
||||
@@ -402,13 +407,13 @@ export default {
|
||||
|
||||
.hero-cta-sub {
|
||||
display: block;
|
||||
font-size: $fs-sm;
|
||||
font-size: $fs-xs;
|
||||
color: rgba(11, 11, 20, 0.6);
|
||||
margin-top: $sp-xs;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.hero-cta-arrow {
|
||||
font-size: 56rpx;
|
||||
font-size: 44rpx;
|
||||
color: $text-on-amber;
|
||||
opacity: 0.5;
|
||||
font-weight: $fw-bold;
|
||||
@@ -416,58 +421,18 @@ export default {
|
||||
|
||||
.bottom-health-tip {
|
||||
text-align: center;
|
||||
padding: $sp-lg 0;
|
||||
padding-bottom: 160rpx;
|
||||
padding: $sp-sm 0;
|
||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
|
||||
font-size: $fs-xs;
|
||||
color: $text-tertiary;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* 底部TabBar */
|
||||
.tab-bar {
|
||||
/* 悬浮记录按钮 */
|
||||
.fab-record {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: $bg-card;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-top: 2rpx solid rgba(255,255,255,0.04);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: $sp-xs;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: $fs-xl;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: $fs-xs;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
.tab-active {
|
||||
.tab-icon { opacity: 1; }
|
||||
.tab-label { color: $amber; }
|
||||
}
|
||||
|
||||
.tab-center {
|
||||
position: relative;
|
||||
top: -20rpx;
|
||||
}
|
||||
|
||||
.tab-fab {
|
||||
right: 40rpx;
|
||||
bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(135deg, $amber-light, $amber-deep);
|
||||
@@ -476,9 +441,14 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-amber;
|
||||
z-index: 50;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-fab-icon {
|
||||
.fab-icon {
|
||||
font-size: $fs-2xl;
|
||||
font-weight: $fw-bold;
|
||||
color: $text-on-amber;
|
||||
|
||||
+303
-25
@@ -6,7 +6,7 @@
|
||||
<view class="brand-icon">
|
||||
<text class="brand-emoji">🍻</text>
|
||||
</view>
|
||||
<text class="brand-name">喝酒了么</text>
|
||||
<text class="brand-name">喝了么</text>
|
||||
<text class="brand-slogan">让每一杯酒都有迹可循</text>
|
||||
</view>
|
||||
|
||||
@@ -27,11 +27,42 @@
|
||||
<button class="btn-primary notice-btn" @click="acceptNotice">我已了解</button>
|
||||
</view>
|
||||
|
||||
<!-- 主登录按钮 -->
|
||||
<!-- 主登录区域 -->
|
||||
<view v-else class="login-actions">
|
||||
<button class="btn-cta login-btn" @click="handleLogin">
|
||||
<text class="login-btn-icon">📱</text>
|
||||
<text class="login-btn-text">微信一键登录</text>
|
||||
<!-- 头像 + 昵称填写(微信新规范) -->
|
||||
<view class="profile-form card">
|
||||
<view class="form-title-row">
|
||||
<text class="form-title">完善你的资料</text>
|
||||
</view>
|
||||
|
||||
<!-- 头像选择 -->
|
||||
<view class="avatar-row">
|
||||
<text class="form-label">头像</text>
|
||||
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image v-if="avatarUrl" class="avatar-preview" :src="avatarUrl" mode="aspectFill"></image>
|
||||
<view v-else class="avatar-placeholder">
|
||||
<text class="avatar-placeholder-icon">👤</text>
|
||||
</view>
|
||||
<text class="avatar-edit">{{ avatarUrl ? '换头像' : '选头像' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 昵称输入 -->
|
||||
<view class="nickname-row">
|
||||
<text class="form-label">昵称</text>
|
||||
<input
|
||||
type="nickname"
|
||||
class="nickname-input"
|
||||
v-model="nickname"
|
||||
placeholder="点击获取微信昵称"
|
||||
@blur="onNicknameBlur"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认登录 -->
|
||||
<button class="btn-cta login-btn" :loading="loginLoading" :disabled="!agreed || loginLoading" @click="handleLogin">
|
||||
<text class="login-btn-text">{{ loginLoading ? '登录中...' : '完成并进入' }}</text>
|
||||
</button>
|
||||
|
||||
<view class="login-divider">
|
||||
@@ -40,9 +71,20 @@
|
||||
<view class="login-divider-line"></view>
|
||||
</view>
|
||||
|
||||
<button class="btn-secondary" @click="handleGuest">
|
||||
<button class="btn-secondary" :disabled="loginLoading" @click="handleGuest">
|
||||
先看看再说
|
||||
</button>
|
||||
|
||||
<!-- 协议勾选 -->
|
||||
<view class="agreement-check" @click="agreed = !agreed">
|
||||
<view class="check-box" :class="{ 'check-box-active': agreed }">
|
||||
<text v-if="agreed" class="check-mark">✓</text>
|
||||
</view>
|
||||
<text class="agreement-check-text">我已阅读并同意</text>
|
||||
<text class="agreement-check-text text-amber" @click.stop="openAgreement('user')">《用户协议》</text>
|
||||
<text class="agreement-check-text">和</text>
|
||||
<text class="agreement-check-text text-amber" @click.stop="openAgreement('privacy')">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -51,7 +93,7 @@
|
||||
<view class="pref-card card-elevated">
|
||||
<view class="pref-header">
|
||||
<text class="text-h2">你的饮酒偏好</text>
|
||||
<text class="text-caption">可跳过,稍后在设置中修改</text>
|
||||
<text class="text-caption">选择你常喝的酒类(可多选)</text>
|
||||
</view>
|
||||
|
||||
<view class="pref-section">
|
||||
@@ -86,13 +128,13 @@
|
||||
|
||||
<view class="pref-actions">
|
||||
<button class="btn-text" @click="skipPreferences">跳过</button>
|
||||
<button class="btn-primary" @click="savePreferences">保存并进入</button>
|
||||
<button class="btn-primary" :loading="prefSaving" @click="savePreferences">{{ selectedCategories.length > 0 || selectedFrequency ? '保存并进入' : '进入首页' }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部协议 -->
|
||||
<view class="agreement">
|
||||
<!-- 底部协议(非登录状态时显示) -->
|
||||
<view v-if="showNotice" class="agreement">
|
||||
<text class="text-tiny">登录即代表同意</text>
|
||||
<text class="text-tiny text-amber">《用户协议》</text>
|
||||
<text class="text-tiny">和</text>
|
||||
@@ -109,6 +151,11 @@ export default {
|
||||
return {
|
||||
showNotice: true,
|
||||
showPreferences: false,
|
||||
loginLoading: false,
|
||||
prefSaving: false,
|
||||
agreed: false,
|
||||
avatarUrl: '',
|
||||
nickname: '',
|
||||
drinkCategories: DRINK_CATEGORIES,
|
||||
selectedCategories: [],
|
||||
selectedFrequency: '',
|
||||
@@ -124,15 +171,94 @@ export default {
|
||||
acceptNotice() {
|
||||
this.showNotice = false
|
||||
},
|
||||
handleLogin() {
|
||||
// Mock登录,直接显示偏好设置
|
||||
this.showPreferences = true
|
||||
|
||||
// ===== 微信新规范:头像选择 =====
|
||||
onChooseAvatar(e) {
|
||||
if (e.detail && e.detail.avatarUrl) {
|
||||
this.avatarUrl = e.detail.avatarUrl
|
||||
}
|
||||
},
|
||||
|
||||
// ===== 微信新规范:昵称输入 =====
|
||||
onNicknameBlur(e) {
|
||||
// 用户输入内容会自动更新到 v-model,这里可以做额外处理
|
||||
if (e.detail && e.detail.value) {
|
||||
this.nickname = e.detail.value
|
||||
}
|
||||
},
|
||||
|
||||
// ===== 确认登录 =====
|
||||
async handleLogin() {
|
||||
if (this.loginLoading) return
|
||||
if (!this.agreed) {
|
||||
uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none', duration: 2000 })
|
||||
return
|
||||
}
|
||||
this.loginLoading = true
|
||||
try {
|
||||
// 获取微信登录code(用于后续服务端鉴权)
|
||||
const loginRes = await this.wxLogin().catch(() => null)
|
||||
|
||||
const userInfo = {
|
||||
id: `user_${Date.now()}`,
|
||||
nickname: this.nickname.trim() || '酒友',
|
||||
avatar: this.avatarUrl || '',
|
||||
wxCode: (loginRes && loginRes.code) || '',
|
||||
joinedAt: new Date().toISOString().slice(0, 10),
|
||||
preferences: null
|
||||
}
|
||||
// 暂存用户信息,登录完成标记延迟到 finishLogin
|
||||
uni.setStorageSync('user_info', JSON.stringify(userInfo))
|
||||
// 显示偏好设置(此时不算正式登录)
|
||||
this.showPreferences = true
|
||||
} catch (e) {
|
||||
console.warn('登录失败,降级为本地登录:', e)
|
||||
const userInfo = {
|
||||
id: `user_${Date.now()}`,
|
||||
nickname: this.nickname.trim() || '酒友',
|
||||
avatar: '',
|
||||
joinedAt: new Date().toISOString().slice(0, 10),
|
||||
preferences: null
|
||||
}
|
||||
// 暂存用户信息,登录完成标记延迟到 finishLogin
|
||||
uni.setStorageSync('user_info', JSON.stringify(userInfo))
|
||||
this.showPreferences = true
|
||||
} finally {
|
||||
this.loginLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 微信login获取code
|
||||
wxLogin() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: res => resolve(res),
|
||||
fail: err => reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// ===== 游客模式 =====
|
||||
handleGuest() {
|
||||
if (this.loginLoading) return
|
||||
uni.setStorageSync('is_logged_in', 'true')
|
||||
uni.setStorageSync('is_guest', 'true')
|
||||
uni.setStorageSync('is_first_launch', 'false')
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
// 游客也设置一个基础用户信息
|
||||
const guestInfo = {
|
||||
id: `guest_${Date.now()}`,
|
||||
nickname: '游客模式',
|
||||
avatar: '',
|
||||
isGuest: true,
|
||||
joinedAt: new Date().toISOString().slice(0, 10),
|
||||
preferences: null
|
||||
}
|
||||
uni.setStorageSync('user_info', JSON.stringify(guestInfo))
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
},
|
||||
|
||||
// ===== 偏好选择 =====
|
||||
toggleCategory(id) {
|
||||
const idx = this.selectedCategories.indexOf(id)
|
||||
if (idx > -1) {
|
||||
@@ -141,22 +267,38 @@ export default {
|
||||
this.selectedCategories.push(id)
|
||||
}
|
||||
},
|
||||
|
||||
skipPreferences() {
|
||||
this.finishLogin()
|
||||
},
|
||||
savePreferences() {
|
||||
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||||
user.preferences = {
|
||||
favoriteCategories: this.selectedCategories,
|
||||
frequency: this.selectedFrequency
|
||||
|
||||
async savePreferences() {
|
||||
if (this.prefSaving) return
|
||||
this.prefSaving = true
|
||||
try {
|
||||
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||||
user.preferences = {
|
||||
favoriteCategories: this.selectedCategories,
|
||||
frequency: this.selectedFrequency
|
||||
}
|
||||
uni.setStorageSync('user_info', JSON.stringify(user))
|
||||
await new Promise(r => setTimeout(r, 300)) // 短暂反馈
|
||||
this.finishLogin()
|
||||
} finally {
|
||||
this.prefSaving = false
|
||||
}
|
||||
uni.setStorageSync('user_info', JSON.stringify(user))
|
||||
this.finishLogin()
|
||||
},
|
||||
|
||||
finishLogin() {
|
||||
uni.setStorageSync('is_logged_in', 'true')
|
||||
uni.setStorageSync('is_first_launch', 'false')
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
},
|
||||
|
||||
// ===== 协议查看 =====
|
||||
openAgreement(type) {
|
||||
const title = type === 'user' ? '用户协议' : '隐私政策'
|
||||
uni.showToast({ title: `${title}页面开发中`, icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,6 +405,93 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 资料表单 */
|
||||
.profile-form {
|
||||
padding: $sp-xl;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-title-row {
|
||||
margin-bottom: $sp-lg;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: $fs-lg;
|
||||
font-weight: $fw-bold;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.avatar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $sp-md 0;
|
||||
border-bottom: 2rpx solid rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.avatar-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $sp-sm;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
&::after { display: none; }
|
||||
}
|
||||
|
||||
.avatar-preview {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: $radius-full;
|
||||
border: 3rpx solid $amber;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: $radius-full;
|
||||
border: 3rpx dashed rgba(232,168,56,0.4);
|
||||
background: $bg-elevated;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-placeholder-icon {
|
||||
font-size: 48rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.avatar-edit {
|
||||
font-size: $fs-sm;
|
||||
color: $amber;
|
||||
}
|
||||
|
||||
.nickname-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $sp-lg;
|
||||
padding: $sp-md 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: $fs-base;
|
||||
color: $text-secondary;
|
||||
flex-shrink: 0;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.nickname-input {
|
||||
flex: 1;
|
||||
font-size: $fs-base;
|
||||
color: $text-primary;
|
||||
padding: $sp-xs 0;
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
.login-actions {
|
||||
display: flex;
|
||||
@@ -275,10 +504,10 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $sp-sm;
|
||||
}
|
||||
|
||||
.login-btn-icon {
|
||||
font-size: $fs-xl;
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn-text {
|
||||
@@ -303,6 +532,43 @@ export default {
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
/* 协议勾选 */
|
||||
.agreement-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: $sp-xs;
|
||||
padding-top: $sp-xs;
|
||||
}
|
||||
|
||||
.check-box {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: $radius-sm;
|
||||
border: 2rpx solid $text-tertiary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all $duration-fast $ease-out;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.check-box-active {
|
||||
background: $amber;
|
||||
border-color: $amber;
|
||||
}
|
||||
|
||||
.check-mark {
|
||||
font-size: $fs-xs;
|
||||
color: $text-on-amber;
|
||||
font-weight: $fw-bold;
|
||||
}
|
||||
|
||||
.agreement-check-text {
|
||||
font-size: $fs-xs;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
/* 偏好设置弹层 */
|
||||
.pref-overlay {
|
||||
position: fixed;
|
||||
@@ -315,12 +581,14 @@ export default {
|
||||
align-items: flex-end;
|
||||
z-index: 100;
|
||||
padding: $sp-lg;
|
||||
animation: fadeIn $duration-base $ease-out;
|
||||
}
|
||||
|
||||
.pref-card {
|
||||
width: 100%;
|
||||
padding: $sp-xl;
|
||||
border-radius: $radius-xl $radius-xl 0 0;
|
||||
animation: slideUp $duration-base $ease-out;
|
||||
}
|
||||
|
||||
.pref-header {
|
||||
@@ -391,4 +659,14 @@ export default {
|
||||
gap: $sp-xs;
|
||||
padding: $sp-lg 0;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
+64
-77
@@ -3,7 +3,10 @@
|
||||
<!-- 用户头部 -->
|
||||
<view class="profile-header">
|
||||
<view class="profile-glow"></view>
|
||||
<image class="profile-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||
<image v-if="user.avatar" class="profile-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||
<view v-else class="profile-avatar profile-avatar-placeholder">
|
||||
<text class="profile-avatar-icon">👤</text>
|
||||
</view>
|
||||
<text class="profile-name">{{ user.nickname }}</text>
|
||||
<text class="profile-joined">加入于 {{ user.joinedAt || '2026' }}</text>
|
||||
</view>
|
||||
@@ -73,26 +76,19 @@
|
||||
<view class="divider"></view>
|
||||
<view class="settings-item" @click="showComingSoon('关于')">
|
||||
<text class="settings-icon">ℹ️</text>
|
||||
<text class="settings-name">关于喝酒了么</text>
|
||||
<text class="settings-name">关于喝了么</text>
|
||||
<text class="settings-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部TabBar -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item" @click="goHome">
|
||||
<text class="tab-icon">🏠</text>
|
||||
<text class="tab-label">首页</text>
|
||||
</view>
|
||||
<view class="tab-center" @click="goRecord">
|
||||
<view class="tab-fab">
|
||||
<text class="tab-fab-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-item tab-active" @click="goProfile">
|
||||
<text class="tab-icon">👤</text>
|
||||
<text class="tab-label">我的</text>
|
||||
</view>
|
||||
<!-- 登出/登录按钮 -->
|
||||
<view class="logout-area">
|
||||
<button v-if="isGuest" class="btn-ghost logout-btn" @click="goLogin">
|
||||
登录账号,同步数据
|
||||
</button>
|
||||
<button v-else class="btn-text logout-btn" @click="handleLogout">
|
||||
退出登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 成就详情弹窗 -->
|
||||
@@ -124,7 +120,8 @@ export default {
|
||||
stats: {},
|
||||
achievementList: [],
|
||||
showDetail: false,
|
||||
detailAchievement: {}
|
||||
detailAchievement: {},
|
||||
isGuest: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -135,6 +132,7 @@ export default {
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.checkLoginState()
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
@@ -152,13 +150,38 @@ export default {
|
||||
uni.showToast({ title: `${name}即将上线`, icon: 'none' })
|
||||
},
|
||||
goHome() {
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
},
|
||||
goRecord() {
|
||||
uni.navigateTo({ url: '/pages/record/record' })
|
||||
},
|
||||
goProfile() {
|
||||
// 已在个人页面
|
||||
checkLoginState() {
|
||||
const isLoggedIn = uni.getStorageSync('is_logged_in')
|
||||
const isGuest = uni.getStorageSync('is_guest')
|
||||
if (!isLoggedIn || isLoggedIn !== 'true') {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
return
|
||||
}
|
||||
this.isGuest = isGuest === 'true'
|
||||
},
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
content: '退出后本地数据将保留,重新登录后可同步',
|
||||
confirmText: '退出',
|
||||
confirmColor: '#FF6B6B',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync('is_logged_in')
|
||||
uni.removeStorageSync('is_guest')
|
||||
uni.removeStorageSync('user_info')
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
goLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +190,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.profile-page {
|
||||
padding: $sp-lg;
|
||||
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
@@ -199,6 +222,20 @@ export default {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.profile-avatar-placeholder {
|
||||
background: $bg-elevated;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-style: dashed;
|
||||
border-color: rgba(232,168,56,0.4);
|
||||
}
|
||||
|
||||
.profile-avatar-icon {
|
||||
font-size: 80rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-size: $fs-xl;
|
||||
font-weight: $fw-bold;
|
||||
@@ -309,65 +346,15 @@ export default {
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
/* TabBar */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: $bg-card;
|
||||
/* 登出区域 */
|
||||
.logout-area {
|
||||
padding: $sp-xl 0 $sp-3xl;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-top: 2rpx solid rgba(255,255,255,0.04);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: $sp-xs;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: $fs-xl;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: $fs-xs;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
.tab-active {
|
||||
.tab-icon { opacity: 1; }
|
||||
.tab-label { color: $amber; }
|
||||
}
|
||||
|
||||
.tab-center {
|
||||
position: relative;
|
||||
top: -20rpx;
|
||||
}
|
||||
|
||||
.tab-fab {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(135deg, $amber-light, $amber-deep);
|
||||
border-radius: $radius-full;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-amber;
|
||||
}
|
||||
|
||||
.tab-fab-icon {
|
||||
font-size: $fs-2xl;
|
||||
font-weight: $fw-bold;
|
||||
color: $text-on-amber;
|
||||
.logout-btn {
|
||||
min-width: 300rpx;
|
||||
}
|
||||
|
||||
/* 成就详情弹窗 */
|
||||
|
||||
Reference in New Issue
Block a user