feat(calendar): 更新日历接口并优化记录流程

- 将GetRecords接口替换为GetCalendar接口获取日历数据
- 重构首页日历数据显示逻辑,支持年月维度的数据获取
- 优化打卡记录流程,合并选酒和记量步骤为单一添加酒水步骤
- 移除登录页面的游客模式选项,简化首次启动逻辑
- 调整引导页逻辑,移除登录跳转改为直接进入首页
- 优化记录页面UI布局,添加状态栏安全距离适配
- 新增酒水列表管理功能,支持添加和删除已选酒水
- 更新依赖包HaveADrink至v1.0.3版本,同步API变更
This commit is contained in:
cg
2026-07-17 23:51:43 +08:00
parent a73bd79eb5
commit adf6b684b9
13 changed files with 520 additions and 451 deletions
+45 -4
View File
@@ -1,6 +1,6 @@
<template>
<view :class="themeClass" class="page-container card-page">
<view class="card-nav">
<view class="card-nav" :style="{ paddingTop: (statusBarHeight + 12) + 'px' }">
<view class="step-back" @click="goBack">
<text class="step-back-arrow"></text>
</view>
@@ -21,6 +21,9 @@
<button class="btn-secondary share-btn" @click="shareToFriend">
<text>分享给好友</text>
</button>
<button class="btn-text close-home-btn" @click="closeToHome">
<text>返回首页</text>
</button>
</view>
<canvas canvas-id="shareCanvas" class="export-canvas" :style="{width:'750px',height:'1600px'}"></canvas>
@@ -41,7 +44,9 @@ export default {
mixins: [themeMixin],
components: { DrinkCard },
data() {
const sysInfo = uni.getSystemInfoSync()
return {
statusBarHeight: sysInfo.statusBarHeight || 25,
record: null,
saving: false,
saved: false
@@ -410,8 +415,9 @@ export default {
shareToFriend() { uni.showToast({ title: '点击右上角分享', icon: 'none' }) },
goBack() {
console.log('goBack called, saved:', this.saved)
if (this.saved) {
uni.navigateBack()
this._doBack()
return
}
uni.showModal({
@@ -420,12 +426,34 @@ export default {
confirmText: '保存并离开',
cancelText: '直接离开',
success: async (res) => {
console.log('modal result:', res)
if (res.confirm) {
await this.saveToAlbum()
try { await this.saveToAlbum() } catch (e) { /* ignore */ }
}
uni.navigateBack()
this._doBack()
},
fail: (err) => {
console.error('showModal fail:', err)
this._doBack()
}
})
},
closeToHome() {
uni.switchTab({ url: '/pages/index/index' })
},
_doBack() {
console.log('_doBack called, pages:', getCurrentPages().length)
if (getCurrentPages().length > 1) {
uni.navigateBack({
fail: (err) => {
console.error('navigateBack fail:', err)
uni.reLaunch({ url: '/pages/index/index' })
}
})
} else {
console.log('no pages to go back to, reLaunch to index')
uni.reLaunch({ url: '/pages/index/index' })
}
}
},
onShareAppMessage() {
@@ -452,6 +480,7 @@ export default {
align-items: center;
gap: $sp-md;
padding: $sp-md $sp-lg;
padding-top: 0;
}
.step-back {
@@ -462,6 +491,7 @@ export default {
justify-content: center;
border-radius: $radius-full;
background: $bg-card;
flex-shrink: 0;
}
.step-back-arrow {
@@ -524,6 +554,17 @@ export default {
justify-content: center;
}
.close-home-btn {
width: 100%;
background: transparent;
border: none;
color: $text-tertiary;
font-size: $fs-sm;
padding: $sp-sm 0;
&::after { display: none; }
}
.export-canvas {
position: fixed;
left: -9999px;
+43 -32
View File
@@ -91,7 +91,7 @@
<view v-if="selectedRecord.mode === 'drank'" class="day-record-info">
<view class="day-drinks">
<view v-for="(drink, i) in selectedRecord.drinks" :key="i" class="day-drink-item">
<text>{{ drink.emoji || '🥃' }} {{ drink.brand }} {{ drink.product }}</text>
<text>{{ getCatEmoji(drink.category) }} {{ drink.brand }} {{ drink.product }}</text>
</view>
</view>
<view class="day-meta flex-between">
@@ -118,8 +118,9 @@
<script>
import DrinkCalendar from '../../components/DrinkCalendar.vue'
import client from '../../common/api'
import { GetCalendarReq } from 'HaveADrink'
import { getGreeting, formatDate, getWeekStart } from '../../common/utils'
import { FEELINGS } from '../../common/constants'
import { FEELINGS, DRINK_CATEGORIES } from '../../common/constants'
import themeMixin from '../../common/theme-mixin'
export default {
@@ -156,8 +157,7 @@ export default {
onShow() {
// 首次启动跳转引导页
const isFirst = uni.getStorageSync('is_first_launch')
const isLoggedIn = uni.getStorageSync('is_logged_in')
if (isFirst === 'true' || !isLoggedIn) {
if (isFirst === 'true') {
uni.reLaunch({ url: '/pages/onboarding/onboarding' })
return
}
@@ -171,7 +171,7 @@ export default {
const [userResp, statsResp, recordsResp] = await Promise.allSettled([
client.GetUserProfile({}),
client.GetStatsOverview({}),
client.GetRecords({ page: 1, pageSize: 100 })
client.GetCalendar(new GetCalendarReq(this.calendarYear, this.calendarMonth))
])
// 用户信息
@@ -207,20 +207,24 @@ export default {
}
}
// 记录列表
// 日历数据
if (recordsResp.status === 'fulfilled' && recordsResp.value) {
const list = recordsResp.value.list || recordsResp.value
this.records = Array.isArray(list) ? list.map(r => ({
...r,
mode: String(r.mode),
feeling: r.feeling ? String(r.feeling) : null,
visibility: r.visibility ? String(r.visibility) : 'private',
drinks: (r.drinks || []).map(d => ({
...d,
category: String(d.category),
unit: String(d.unit)
}))
})) : []
const calData = recordsResp.value.data || recordsResp.value
const days = calData.days || []
this.records = days.filter(d => d && d.hasRecord).map(d => ({
id: d.id || '',
date: d.date,
mode: String(d.mode),
standardCupsTotal: d.standardCupsTotal || 0,
drinks: (d.drinks || []).map(dr => ({
...dr,
category: String(dr.category)
})),
food: d.food ? { category: String(d.food.category), name: d.food.name || '' } : null,
feeling: d.feeling ? String(d.feeling) : null,
quote: d.quote || '',
visibility: d.visibility ? String(d.visibility) : 'friends'
}))
}
} catch (e) {
console.warn('加载首页数据失败:', e)
@@ -241,20 +245,23 @@ export default {
},
async loadRecordsForMonth(year, month) {
try {
const monthStr = `${year}-${String(month).padStart(2, '0')}`
const resp = await client.GetRecords({ page: 1, pageSize: 100, month: monthStr })
const list = resp.list || resp
this.records = Array.isArray(list) ? list.map(r => ({
...r,
mode: String(r.mode),
feeling: r.feeling ? String(r.feeling) : null,
visibility: r.visibility ? String(r.visibility) : 'private',
drinks: (r.drinks || []).map(d => ({
...d,
category: String(d.category),
unit: String(d.unit)
}))
})) : []
const resp = await client.GetCalendar(new GetCalendarReq(year, month))
const calData = resp.data || resp
const days = calData.days || []
this.records = days.filter(d => d && d.hasRecord).map(d => ({
id: d.id || '',
date: d.date,
mode: String(d.mode),
standardCupsTotal: d.standardCupsTotal || 0,
drinks: (d.drinks || []).map(dr => ({
...dr,
category: String(dr.category)
})),
food: d.food ? { category: String(d.food.category), name: d.food.name || '' } : null,
feeling: d.feeling ? String(d.feeling) : null,
quote: d.quote || '',
visibility: d.visibility ? String(d.visibility) : 'friends'
}))
} catch (e) {
console.warn('加载月度记录失败:', e)
}
@@ -268,6 +275,10 @@ export default {
const f = FEELINGS.find(f => f.id === feelingId)
return f ? `${f.emoji} ${f.name}` : ''
},
getCatEmoji(catId) {
const cat = DRINK_CATEGORIES.find(c => c.id === catId)
return cat ? cat.emoji : '🥃'
},
backfillDate() {
this.showDayDetail = false
uni.navigateTo({
-46
View File
@@ -65,16 +65,6 @@
<text class="login-btn-text">{{ loginLoading ? '登录中...' : '完成并进入' }}</text>
</button>
<view class="login-divider">
<view class="login-divider-line"></view>
<text class="login-divider-text"></text>
<view class="login-divider-line"></view>
</view>
<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 }">
@@ -196,25 +186,6 @@ export default {
})
},
// ===== 游客模式 =====
handleGuest() {
if (this.loginLoading) return
uni.setStorageSync('is_logged_in', 'true')
uni.setStorageSync('is_guest', 'true')
uni.setStorageSync('is_first_launch', 'false')
// 游客也设置一个基础用户信息
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' })
},
finishLogin() {
uni.setStorageSync('is_logged_in', 'true')
uni.setStorageSync('is_first_launch', 'false')
@@ -441,23 +412,6 @@ export default {
font-weight: $fw-bold;
}
.login-divider {
display: flex;
align-items: center;
gap: $sp-md;
}
.login-divider-line {
flex: 1;
height: 2rpx;
background: var(--bg-hover, rgba(255, 255, 255, 0.08));
}
.login-divider-text {
font-size: $fs-sm;
color: $text-tertiary;
}
/* 协议勾选 */
.agreement-check {
display: flex;
+2 -1
View File
@@ -108,7 +108,8 @@ export default {
}
},
goLogin() {
uni.reLaunch({ url: '/pages/login/login' })
uni.setStorageSync('is_first_launch', 'false')
uni.reLaunch({ url: '/pages/index/index' })
}
}
}
+243 -331
View File
@@ -1,7 +1,7 @@
<template>
<view :class="themeClass" class="page-container record-page safe-bottom">
<!-- 顶部进度 -->
<view class="step-header">
<view class="step-header" :style="{ paddingTop: (statusBarHeight + 12) + 'px' }">
<view class="step-back" @click="handleBack">
<text class="step-back-arrow"></text>
</view>
@@ -25,8 +25,8 @@
<text class="step-subtitle">{{ stepSubtitles[currentStep - 1] }}</text>
</view>
<!-- Step 1: 选酒 -->
<view v-if="currentStep === 1" class="step-content">
<!-- Step 1: 添加酒水选酒+记量合一 -->
<scroll-view v-if="currentStep === 1" class="step-content" scroll-y enhanced :show-scrollbar="false">
<!-- 酒类选择网格 -->
<view class="category-grid">
<view
@@ -41,8 +41,9 @@
</view>
</view>
<!-- 品牌选择 -->
<view v-if="currentDrink.category" class="brand-section">
<!-- 品牌 + 用量选酒后展示 -->
<view v-if="currentDrink.category" class="drink-form card">
<!-- 品牌选择 -->
<text class="section-label">品牌</text>
<view class="brand-tags">
<view
@@ -65,7 +66,7 @@
</view>
<!-- 产品名称 -->
<text class="section-label" style="margin-top: 24rpx;">产品名称</text>
<text class="section-label" style="margin-top: 16rpx;">产品名称</text>
<view class="input-row">
<input
class="input-field"
@@ -74,22 +75,31 @@
placeholder-class="input-placeholder"
/>
</view>
</view>
</view>
<!-- Step 2: 记量 -->
<view v-if="currentStep === 2" class="step-content">
<view class="amount-section card">
<view class="amount-row">
<input
class="amount-input text-num"
type="digit"
v-model="amountStr"
placeholder="0"
placeholder-class="input-placeholder"
@input="onAmountChange"
/>
<text class="amount-unit">{{ currentDrink.unit === 'ml' ? 'ml' : currentDrink.unit }}</text>
<view class="divider"></view>
<!-- 用量 + 度数 一行 -->
<view class="amount-degree-row">
<view class="amount-inline">
<input
class="amount-input text-num"
type="digit"
v-model="amountStr"
placeholder="0"
placeholder-class="input-placeholder"
@input="onAmountChange"
/>
<text class="amount-unit">{{ currentDrink.unit === 'ml' ? 'ml' : currentDrink.unit }}</text>
</view>
<view class="degree-inline">
<input
class="degree-input text-num"
type="digit"
v-model="degreeStr"
@input="onAmountChange"
/>
<text class="degree-symbol">°</text>
</view>
</view>
<!-- 单位切换 -->
@@ -105,60 +115,53 @@
</view>
</view>
<!-- 酒精度数 -->
<view class="degree-row">
<text class="section-label">酒精度数</text>
<view class="degree-input-wrap">
<input
class="degree-input text-num"
type="digit"
v-model="degreeStr"
@input="onAmountChange"
/>
<text class="degree-symbol">°</text>
</view>
</view>
<view class="divider"></view>
<!-- 标准杯显示 -->
<view class="standard-cup-result">
<view class="standard-cup-label-row">
<text class="standard-cup-label">本次饮酒量</text>
<view class="cup-info-btn" @click="showCupInfo = true">
<text class="cup-info-icon">?</text>
</view>
<view class="cup-row">
<text class="cup-label"></text>
<text class="cup-value text-num text-amber">{{ currentCups }} 标准杯</text>
<view class="cup-info-btn" @click="showCupInfo = true">
<text class="cup-info-icon">?</text>
</view>
<text class="standard-cup-value text-num text-amber">{{ currentCups }} 标准杯</text>
</view>
</view>
<!-- 已添加的酒水列表 -->
<view v-if="drinks.length > 0" class="added-drinks">
<text class="section-label">已添加的酒水</text>
<view class="added-drinks-header">
<text class="section-label">已添加的酒水</text>
<text class="added-count">{{ drinks.length }}</text>
</view>
<view v-for="(d, i) in drinks" :key="i" class="added-drink-item card">
<view class="flex-between">
<text>{{ getCatEmoji(d.category) }} {{ d.brand }} {{ d.product }}</text>
<text class="text-amber text-num">{{ d.standardCups }}</text>
<text>{{ getCatEmoji(d.category) }} {{ d.brand }} {{ d.product }} · {{ d.amount }}{{ d.unit }}</text>
<view class="added-drink-right">
<text class="text-amber text-num">{{ d.standardCups }}</text>
<view class="added-drink-remove" @click="removeDrink(i)">
<text class="remove-icon"></text>
</view>
</view>
</view>
</view>
<!-- 累计标准杯 -->
<view class="total-cups-bar">
<text class="text-caption">本次累计</text>
<text class="total-cups-value text-num text-amber">{{ totalCups }} 标准杯</text>
</view>
</view>
<!-- 累计标准杯 -->
<view class="total-cups-bar">
<text class="text-caption">本次累计</text>
<text class="total-cups-value text-num text-amber">{{ totalCups }} 标准杯</text>
</view>
<!-- 健康提醒 -->
<view v-if="totalCups > 3" class="health-warning card">
<view v-if="drinks.length > 0 && totalCups > 3" class="health-warning card">
<text class="health-warning-icon"></text>
<text class="health-warning-text">今日已饮用 {{ totalCups }} 标准杯建议每日不超过2标准杯</text>
</view>
<view v-else class="health-hint">
<text class="text-tiny">建议每日不超过2标准杯中国居民膳食指南</text>
<view v-else-if="drinks.length > 0" class="health-hint">
<text class="text-tiny">💡 可继续添加更多酒水或点击下一步</text>
</view>
</view>
<!-- 底部占位 -->
<view style="height: 180rpx;"></view>
</scroll-view>
<!-- 标准杯说明弹窗 -->
<view v-if="showCupInfo" class="cup-info-overlay" @click="showCupInfo = false">
@@ -198,8 +201,8 @@
</view>
</view>
<!-- Step 3: 配餐 -->
<view v-if="currentStep === 3" class="step-content">
<!-- Step 2: 配餐 -->
<view v-if="currentStep === 2" class="step-content">
<view class="food-grid">
<view
v-for="food in foodCategories"
@@ -224,8 +227,8 @@
</view>
</view>
<!-- Step 4: 感受 -->
<view v-if="currentStep === 4" class="step-content">
<!-- Step 3: 感受 -->
<view v-if="currentStep === 3" class="step-content">
<view class="feeling-grid">
<view
v-for="f in feelings"
@@ -244,8 +247,8 @@
</view>
</view>
<!-- Step 5: 照片 -->
<view v-if="currentStep === 5" class="step-content">
<!-- Step 4: 照片 -->
<view v-if="currentStep === 4" class="step-content">
<view class="photo-grid">
<view
v-for="(photo, index) in photos"
@@ -267,39 +270,6 @@
</view>
</view>
<!-- Step 6: 确认保存 -->
<view v-if="currentStep === 6" class="step-content">
<!-- 记录预览 -->
<view class="preview-section card">
<text class="section-label">记录预览</text>
<view class="preview-drinks">
<view v-for="(d, i) in allDrinks" :key="i" class="preview-item">
<text>{{ getCatEmoji(d.category) }} {{ d.brand }} {{ d.product }} · {{ d.amount }}{{ d.unit }}</text>
<text class="text-amber text-num">{{ d.standardCups }}</text>
</view>
</view>
<view class="divider"></view>
<view class="preview-meta">
<text v-if="selectedFood !== 'none'" class="preview-line">🍲 {{ getFoodName() }}</text>
<text v-if="selectedFeeling" class="preview-line">{{ getFeelingEmoji() }} {{ getFeelingName() }}</text>
<text class="preview-line">📍 {{ backfillDate || '今天' }}</text>
</view>
<view class="preview-total">
<text class="text-caption">总计</text>
<text class="text-amber text-num" style="font-size: 34rpx;">{{ totalCups }} 标准杯</text>
</view>
</view>
<!-- 保存后提示 -->
<view class="save-hint card">
<text class="save-hint-icon"></text>
<view class="save-hint-text">
<text class="save-hint-title">保存后可生成酒局卡片</text>
<text class="save-hint-desc">卡片可保存到相册发朋友圈或分享给好友</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<button
@@ -309,6 +279,14 @@
>上一步</button>
<view v-else class="btn-placeholder"></view>
<!-- Step 1 显示+ 添加按钮 -->
<button
v-if="currentStep === 1"
class="btn-add-drink"
:disabled="!canAddDrink"
@click="addDrinkToList"
>+ 添加</button>
<button
v-if="currentStep < totalSteps"
class="btn-primary"
@@ -322,11 +300,6 @@
style="flex: none; width: 55%;"
>保存并生成卡片</button>
</view>
<!-- 添加更多酒水按钮(Step 1) -->
<view v-if="currentStep === 1 && drinks.length > 0" class="add-more-bar">
<button class="btn-ghost" @click="resetCurrentDrink">+ 再添加一种酒</button>
</view>
</view>
</template>
@@ -343,11 +316,13 @@ import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const sysInfo = uni.getSystemInfoSync()
return {
statusBarHeight: sysInfo.statusBarHeight || 25,
currentStep: 1,
totalSteps: 6,
stepTitles: ['选择酒水', '记录用量', '配餐', '感受', '照片', '确认保存'],
stepSubtitles: ['喝了什么酒?', '喝了多少?', '配了什么菜?', '感觉如何?', '拍张照片吧', '保存后可生成分享卡片'],
totalSteps: 4,
stepTitles: ['添加酒水', '配餐', '感受', '照片'],
stepSubtitles: ['喝了什么酒?喝了多少?', '配了什么菜?', '感觉如何?', '拍张照片吧'],
drinkCategories: DRINK_CATEGORIES,
units: DRINK_UNITS,
@@ -359,22 +334,20 @@ export default {
currentDrink: { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 },
drinks: [],
brandList: [],
// Step 2
amountStr: '',
degreeStr: '',
// Step 3
// Step 2
selectedFood: '',
foodName: '',
// Step 4
// Step 3
selectedFeeling: '',
// Step 5
// Step 4
photos: [],
// Step 6
// Step 5
visibility: 'friends',
// 补打卡日期
@@ -390,30 +363,17 @@ export default {
return calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
},
totalCups() {
const addedCups = this.drinks.reduce((sum, d) => sum + d.standardCups, 0)
return Math.round((addedCups + this.currentCups) * 100) / 100
return this.drinks.reduce((sum, d) => sum + d.standardCups, 0)
},
allDrinks() {
const all = [...this.drinks]
if (this.amountStr && this.currentDrink.category) {
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
all.push({
...this.currentDrink,
amount: parseFloat(this.amountStr) || 0,
degree: parseFloat(this.degreeStr) || 0,
standardCups: calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
})
}
return all
canAddDrink() {
return this.currentDrink.category && this.currentDrink.brand && parseFloat(this.amountStr) > 0
},
canProceed() {
switch (this.currentStep) {
case 1: return this.currentDrink.category && this.currentDrink.brand
case 2: return parseFloat(this.amountStr) > 0
case 3: return true // 配餐可选
case 4: return true // 感受可选
case 5: return true // 照片可选
case 6: return true
case 1: return this.drinks.length > 0
case 2: return true // 配餐可选
case 3: return true // 感受可选
case 4: return true // 照片可选
default: return true
}
}
@@ -486,20 +446,8 @@ export default {
removePhoto(index) {
this.photos.splice(index, 1)
},
resetCurrentDrink() {
// 保存当前酒水到列表
if (this.amountStr && this.currentDrink.category) {
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
this.drinks.push({
...this.currentDrink,
amount: parseFloat(this.amountStr) || 0,
degree: parseFloat(this.degreeStr) || 0,
standardCups: calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
})
}
this.currentDrink = { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 }
this.amountStr = ''
this.degreeStr = ''
removeDrink(index) {
this.drinks.splice(index, 1)
},
handleBack() {
if (this.currentStep > 1) {
@@ -532,31 +480,28 @@ export default {
if (this.currentStep > 1) this.currentStep--
},
nextStep() {
// Step 1 -> 2: 保存酒水基础信息
if (this.currentStep === 1) {
if (!this.degreeStr) this.degreeStr = String(this.currentDrink.degree)
}
// Step 2 -> 3: 保存饮用量
if (this.currentStep === 2) {
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
this.drinks.push({
...this.currentDrink,
amount: parseFloat(this.amountStr) || 0,
degree: parseFloat(this.degreeStr) || 0,
standardCups: calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
})
// 重置当前输入
this.currentDrink = { ...this.currentDrink, amount: 0 }
this.amountStr = ''
}
if (this.currentStep < this.totalSteps) this.currentStep++
},
addDrinkToList() {
if (!this.canAddDrink) return
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
this.drinks.push({
...this.currentDrink,
amount: parseFloat(this.amountStr) || 0,
degree: parseFloat(this.degreeStr) || 0,
standardCups: calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
})
// 重置表单,准备添加下一种
this.currentDrink = { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 }
this.amountStr = ''
this.degreeStr = ''
this.brandList = []
},
async publishRecord() {
const allDrinks = this.allDrinks.length > 0 ? this.allDrinks : this.drinks
const recordData = {
date: this.backfillDate || formatDate(new Date(), 'YYYY-MM-DD'),
mode: 'drank',
drinks: allDrinks.map(d => ({
drinks: this.drinks.map(d => ({
category: d.category,
brand: d.brand,
product: d.product || '',
@@ -580,7 +525,7 @@ export default {
uni.hideLoading()
// 跳转到卡片页,传递记录ID
uni.redirectTo({
uni.navigateTo({
url: `/pages/card/card?recordId=${saved.id}`
})
} catch (e) {
@@ -589,7 +534,7 @@ export default {
uni.showToast({ title: e.msg || '保存失败,已本地保存', icon: 'none', duration: 2000 })
// 降级:本地保存
const saved = addRecord(recordData)
uni.redirectTo({
uni.navigateTo({
url: `/pages/card/card?recordId=${saved.id}`
})
}
@@ -612,6 +557,7 @@ export default {
align-items: center;
gap: $sp-md;
padding: $sp-md 0;
padding-top: 0;
}
.step-back {
@@ -718,8 +664,10 @@ export default {
color: $text-primary;
}
.brand-section {
margin-top: $sp-lg;
/* Step 1: 添加酒水(合并选酒+记量) */
.drink-form {
margin-bottom: $sp-lg;
padding: $sp-lg;
}
.section-label {
@@ -734,7 +682,7 @@ export default {
display: flex;
flex-wrap: wrap;
gap: $sp-sm;
margin-bottom: $sp-lg;
margin-bottom: $sp-md;
}
.input-row {
@@ -754,102 +702,6 @@ export default {
color: $text-tertiary;
}
/* Step 2: 记量 */
.amount-section {
margin-bottom: $sp-lg;
}
.amount-row {
display: flex;
align-items: baseline;
gap: $sp-sm;
margin-bottom: $sp-xl;
}
.amount-input {
font-size: $fs-3xl;
font-weight: $fw-black;
color: $text-primary;
flex: 1;
background: transparent;
border: none;
padding: 0;
height: 100rpx;
line-height: 100rpx;
}
.amount-unit {
font-size: $fs-lg;
color: $text-secondary;
}
.unit-switcher {
display: flex;
gap: $sp-sm;
margin-bottom: $sp-xl;
flex-wrap: wrap;
}
.unit-btn {
padding: $sp-sm $sp-lg;
background: $bg-elevated;
border-radius: $radius-full;
font-size: $fs-sm;
color: $text-secondary;
border: 2rpx solid transparent;
transition: all $duration-fast $ease-out;
&.unit-active {
background: $amber-glow;
color: $amber;
border-color: rgba(232,168,56,0.3);
}
}
.degree-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.degree-input-wrap {
display: flex;
align-items: center;
gap: $sp-xs;
}
.degree-input {
width: 120rpx;
text-align: center;
background: $bg-elevated;
border-radius: $radius-md;
padding: $sp-sm;
color: $text-primary;
font-size: $fs-lg;
}
.degree-symbol {
font-size: $fs-lg;
color: $text-secondary;
}
.standard-cup-result {
display: flex;
justify-content: space-between;
align-items: center;
}
.standard-cup-label {
font-size: $fs-base;
color: $text-secondary;
}
.standard-cup-label-row {
display: flex;
align-items: center;
gap: $sp-sm;
}
.cup-info-btn {
width: 36rpx;
height: 36rpx;
@@ -866,10 +718,96 @@ export default {
font-weight: $fw-bold;
}
.standard-cup-value {
font-size: $fs-xl;
.amount-degree-row {
display: flex;
align-items: center;
gap: $sp-lg;
margin-bottom: $sp-md;
}
.amount-inline {
display: flex;
align-items: baseline;
gap: $sp-sm;
flex: 1;
}
.degree-inline {
display: flex;
align-items: center;
gap: $sp-xs;
}
.cup-row {
display: flex;
align-items: center;
gap: $sp-xs;
padding: $sp-sm 0;
}
.cup-label {
font-size: $fs-sm;
color: $text-tertiary;
}
.cup-value {
font-size: $fs-md;
font-weight: $fw-bold;
}
.amount-input {
font-size: $fs-3xl;
font-weight: $fw-black;
color: $text-primary;
flex: 1;
background: transparent;
border: none;
padding: 0;
height: 80rpx;
line-height: 80rpx;
}
.amount-unit {
font-size: $fs-md;
color: $text-secondary;
}
.unit-switcher {
display: flex;
gap: $sp-sm;
margin-bottom: $sp-md;
flex-wrap: wrap;
}
.unit-btn {
padding: $sp-xs $sp-md;
background: $bg-elevated;
border-radius: $radius-full;
font-size: $fs-xs;
color: $text-secondary;
border: 2rpx solid transparent;
transition: all $duration-fast $ease-out;
&.unit-active {
background: $amber-glow;
color: $amber;
border-color: rgba(232,168,56,0.3);
}
}
.degree-input {
width: 100rpx;
text-align: center;
background: $bg-elevated;
border-radius: $radius-md;
padding: $sp-sm;
color: $text-primary;
font-size: $fs-lg;
}
.degree-symbol {
font-size: $fs-lg;
color: $text-secondary;
}
/* 标准杯说明弹窗 */
@@ -1245,67 +1183,6 @@ export default {
margin-top: $sp-xs;
}
.preview-section {
margin-bottom: $sp-lg;
}
.preview-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: $sp-sm 0;
}
.preview-meta {
margin-bottom: $sp-md;
}
.preview-line {
display: block;
font-size: $fs-base;
padding: $sp-xs 0;
color: $text-secondary;
}
.preview-total {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: $sp-md;
border-top: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.08));
}
/* 保存提示卡片 */
.save-hint {
display: flex;
align-items: flex-start;
gap: $sp-md;
margin-top: $sp-lg;
padding: $sp-lg;
background: rgba(232, 168, 56, 0.06);
border: 2rpx solid rgba(232, 168, 56, 0.15);
}
.save-hint-icon {
font-size: $fs-xl;
flex-shrink: 0;
}
.save-hint-title {
display: block;
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
margin-bottom: $sp-xs;
}
.save-hint-desc {
display: block;
font-size: $fs-sm;
color: $text-secondary;
line-height: $lh-loose;
}
/* 底部操作栏 */
.bottom-actions {
position: fixed;
@@ -1319,7 +1196,7 @@ export default {
background: linear-gradient(to top, $bg-base 80%, transparent);
z-index: 50;
.btn-secondary, .btn-primary {
.btn-secondary, .btn-primary, .btn-add-drink {
flex: 1;
}
}
@@ -1328,8 +1205,43 @@ export default {
flex: 1;
}
.add-more-bar {
padding: $sp-lg 0;
text-align: center;
.added-drinks-header {
display: flex;
align-items: center;
gap: $sp-sm;
margin-bottom: $sp-md;
.section-label {
margin-bottom: 0;
}
}
.added-count {
font-size: $fs-xs;
color: $amber;
background: $amber-glow;
padding: 2rpx $sp-sm;
border-radius: $radius-full;
}
.added-drink-right {
display: flex;
align-items: center;
gap: $sp-md;
}
.added-drink-remove {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-full;
background: var(--overlay-mask, rgba(255, 255, 255, 0.08));
}
.remove-icon {
font-size: $fs-xs;
color: $text-tertiary;
}
</style>