Files
hejiu/pages/record/record.vue
T
cg 9f5c69e1e5 feat(app): 项目重命名为干杯日记并优化UI设计
- 将应用名称从"喝了么"更改为"干杯日记"
- 更新所有相关文件中的品牌标识和描述信息
- 升级HaveADrink依赖包从1.0.3到1.0.4版本
- 在pages.json中新增分享个人资料页面配置
- 重构DrinkCard组件UI,增加装饰背景、酒类图标支持、饮酒感受徽章等功能
- 优化API错误处理逻辑,增加静默模式支持
- 修改白酒类别图标为 urn emoji,黄酒图标为 tea emoji
- 添加分享功能按钮并优化页面返回交互体验
- 引入新的工具函数用于获取酒类图标和路径判断
- 更新主题混入、常量定义等基础配置文件
- 调整全局样式和设计令牌以匹配新品牌形象
2026-07-20 22:12:27 +08:00

1310 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view :class="themeClass" class="page-container record-page safe-bottom">
<!-- 顶部进度 -->
<view class="step-header" :style="{ paddingTop: (statusBarHeight + 12) + 'px' }">
<view class="step-back" @click="handleBack">
<text class="step-back-arrow"></text>
</view>
<view class="step-progress">
<view
v-for="i in totalSteps"
:key="i"
class="step-dot"
:class="{
'step-dot-active': i === currentStep,
'step-dot-done': i < currentStep
}"
></view>
</view>
<text class="step-label">{{ currentStep }}/{{ totalSteps }}</text>
</view>
<!-- 步骤标题 -->
<view class="step-title-area">
<text class="step-title">{{ stepTitles[currentStep - 1] }}</text>
<text class="step-subtitle">{{ stepSubtitles[currentStep - 1] }}</text>
</view>
<!-- Step 1: 添加酒水选酒+记量合一 -->
<scroll-view v-if="currentStep === 1" class="step-content" scroll-y enhanced :show-scrollbar="false">
<!-- 酒类选择网格 -->
<view class="category-grid">
<view
v-for="cat in drinkCategories"
:key="cat.id"
class="category-item"
:class="{ 'category-active': currentDrink.category === cat.id }"
@click="selectCategory(cat)"
>
<image v-if="isIconPath(cat.icon)" :src="cat.icon" class="category-icon-img" mode="aspectFit"></image>
<text v-else class="category-emoji">{{ cat.emoji }}</text>
<text class="category-name">{{ cat.name }}</text>
</view>
</view>
<!-- 品牌 + 用量选酒后展示 -->
<view v-if="currentDrink.category" class="drink-form card">
<!-- 品牌选择 -->
<text class="section-label">品牌</text>
<view class="brand-tags">
<view
v-for="brand in brandList"
:key="brand.name"
class="tag"
:class="{ 'tag-active': currentDrink.brand === brand.name }"
@click="selectBrand(brand)"
>
<text>{{ brand.name }}</text>
</view>
</view>
<view class="input-row">
<input
class="input-field"
v-model="currentDrink.brand"
placeholder="或输入其他品牌"
placeholder-class="input-placeholder"
/>
</view>
<!-- 产品名称 -->
<text class="section-label" style="margin-top: 16rpx;">产品名称</text>
<view class="input-row">
<input
class="input-field"
v-model="currentDrink.product"
placeholder="如:飞天茅台53度"
placeholder-class="input-placeholder"
/>
</view>
<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>
<!-- 单位切换 -->
<view class="unit-switcher">
<view
v-for="u in units"
:key="u.id"
class="unit-btn"
:class="{ 'unit-active': currentDrink.unit === u.id }"
@click="currentDrink.unit = u.id; onAmountChange()"
>
<text>{{ u.name }}</text>
<text v-if="u.id !== 'ml'" class="unit-ml">{{ u.toMl }}ml</text>
</view>
</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>
</view>
</view>
<!-- 已添加的酒水列表 -->
<view v-if="drinks.length > 0" class="added-drinks">
<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">
<view class="added-drink-name">
<image v-if="isIconPath(getCatIcon(d.category))" :src="getCatIcon(d.category)" class="inline-icon-img" mode="aspectFit"></image>
<text v-else>{{ getCatEmoji(d.category) }}</text>
<text>{{ d.brand }} {{ d.product }} · {{ d.customAmount || d.amount }}{{ getUnitLabel(d.customUnit || d.unit) }}</text>
</view>
<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 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-if="drinks.length > 0" class="health-hint">
<text class="text-tiny">💡 可继续添加更多酒水或点击下一步</text>
</view>
<!-- 底部占位 -->
<view style="height: 180rpx;"></view>
</scroll-view>
<!-- 标准杯说明弹窗 -->
<view v-if="showCupInfo" class="cup-info-overlay" @click="showCupInfo = false">
<view class="cup-info-card card-elevated" @click.stop>
<view class="cup-info-header">
<text class="cup-info-title">什么是标准杯」?</text>
<text class="cup-info-close" @click="showCupInfo = false"></text>
</view>
<view class="cup-info-body">
<text class="cup-info-desc">标准杯是国际通用的饮酒量度量单位方便你比较不同酒类的实际酒精摄入量</text>
<view class="cup-info-formula">
<text class="cup-info-formula-text">1 标准杯 = 10g 纯酒精</text>
</view>
<text class="cup-info-subtitle">常见换算参考</text>
<view class="cup-info-table">
<view class="cup-info-row">
<text class="cup-info-cell">🥃 白酒 (52°) 1</text>
<text class="cup-info-cell cup-info-val"> 2.1 </text>
</view>
<view class="cup-info-row">
<text class="cup-info-cell">🍺 啤酒 (5°) 1</text>
<text class="cup-info-cell cup-info-val"> 2.0 </text>
</view>
<view class="cup-info-row">
<text class="cup-info-cell">🍷 红酒 (13°) 1</text>
<text class="cup-info-cell cup-info-val"> 1.5 </text>
</view>
<view class="cup-info-row">
<text class="cup-info-cell">🥂 威士忌 (40°) 1shot</text>
<text class="cup-info-cell cup-info-val"> 1.0 </text>
</view>
</view>
<view class="cup-info-tip">
<text class="cup-info-tip-text">💡 中国居民膳食指南建议成年人每日饮酒不超过 2 标准杯</text>
</view>
</view>
</view>
</view>
<!-- Step 2: 配餐 -->
<view v-if="currentStep === 2" class="step-content">
<view class="food-grid">
<view
v-for="food in foodCategories"
:key="food.id"
class="food-item"
:class="{ 'food-active': selectedFood === food.id }"
@click="selectedFood = food.id"
>
<text class="food-emoji">{{ food.emoji }}</text>
<text class="food-name">{{ food.name }}</text>
</view>
</view>
<view v-if="selectedFood && selectedFood !== 'none'" class="food-detail">
<text class="section-label">具体吃了什么</text>
<input
class="input-field"
v-model="foodName"
placeholder="如:毛肚、黄喉、肥牛..."
placeholder-class="input-placeholder"
/>
</view>
</view>
<!-- Step 3: 感受 -->
<view v-if="currentStep === 3" class="step-content">
<view class="feeling-grid">
<view
v-for="f in feelings"
:key="f.id"
class="feeling-item"
:class="{ 'feeling-active': selectedFeeling === f.id }"
@click="selectedFeeling = f.id"
>
<text class="feeling-emoji">{{ f.emoji }}</text>
<text class="feeling-name">{{ f.name }}</text>
<text class="feeling-desc">{{ f.desc }}</text>
</view>
</view>
<view class="feeling-skip">
<text class="text-caption">不想记录感受可以直接下一步</text>
</view>
</view>
<!-- Step 4: 照片 -->
<view v-if="currentStep === 4" class="step-content">
<view class="photo-grid">
<view
v-for="(photo, index) in photos"
:key="index"
class="photo-item"
>
<image :src="photo" mode="aspectFill" class="photo-img"></image>
<view class="photo-remove" @click="removePhoto(index)">
<text></text>
</view>
</view>
<view v-if="photos.length < 6" class="photo-add" @click="choosePhoto">
<text class="photo-add-icon">📷</text>
<text class="photo-add-text">拍照/相册</text>
</view>
</view>
<view class="photo-tip">
<text class="text-caption">最多选择6张照片记录今夜的精彩瞬间</text>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<button
v-if="currentStep > 1"
class="btn-secondary"
@click="prevStep"
>上一步</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"
@click="nextStep"
:disabled="!canProceed"
>下一步</button>
<button
v-else
class="btn-cta"
@click="publishRecord"
style="flex: none; width: 55%;"
>保存并生成卡片</button>
</view>
</view>
</template>
<script>
import {
DRINK_CATEGORIES, DRINK_UNITS, BRANDS, FOOD_CATEGORIES,
FEELINGS, VISIBILITY_OPTIONS
} from '../../common/constants'
import { calcStandardCups, unitToMl, formatDate, getCatIcon, isIconPath } from '../../common/utils'
import client from '../../common/api'
import { addRecord } from '../../common/mock-data'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const sysInfo = uni.getSystemInfoSync()
return {
statusBarHeight: sysInfo.statusBarHeight || 25,
currentStep: 1,
totalSteps: 4,
stepTitles: ['添加酒水', '配餐', '感受', '照片'],
stepSubtitles: ['喝了什么酒?喝了多少?', '配了什么菜?', '感觉如何?', '拍张照片吧'],
drinkCategories: DRINK_CATEGORIES,
units: DRINK_UNITS,
foodCategories: FOOD_CATEGORIES,
feelings: FEELINGS,
visibilityOptions: VISIBILITY_OPTIONS,
// Step 1
currentDrink: { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 },
drinks: [],
brandList: [],
amountStr: '',
degreeStr: '',
// Step 2
selectedFood: '',
foodName: '',
// Step 3
selectedFeeling: '',
// Step 4
photos: [],
// Step 5
visibility: 'friends',
// 补打卡日期
backfillDate: '',
// 标准杯说明弹窗
showCupInfo: false
}
},
computed: {
currentCups() {
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
return calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
},
totalCups() {
return this.drinks.reduce((sum, d) => sum + d.standardCups, 0)
},
canAddDrink() {
return this.currentDrink.category && this.currentDrink.brand && parseFloat(this.amountStr) > 0
},
canProceed() {
switch (this.currentStep) {
case 1: return this.drinks.length > 0
case 2: return true // 配餐可选
case 3: return true // 感受可选
case 4: return true // 照片可选
default: return true
}
}
},
onLoad(options) {
if (options && options.date) {
this.backfillDate = options.date
}
},
onShareAppMessage() {
return {
title: '干杯日记 - 记录每一杯酒',
path: '/pages/index/index'
}
},
onShareTimeline() {
return {
title: '干杯日记 - 记录每一杯酒',
query: ''
}
},
onBackPress() {
if (this.currentStep > 1 || this.hasFormData()) {
uni.showModal({
title: '放弃记录?',
content: '当前填写的内容将不会保存',
confirmText: '继续填写',
cancelText: '放弃',
success: (res) => {
if (res.cancel) {
uni.navigateBack()
}
}
})
return true // 阻止默认返回
}
return false
},
methods: {
selectCategory(cat) {
this.currentDrink.category = cat.id
this.currentDrink.degree = cat.defaultDegree
this.degreeStr = String(cat.defaultDegree)
this.brandList = BRANDS[cat.id] || []
},
selectBrand(brand) {
this.currentDrink.brand = brand.name
this.currentDrink.product = brand.product
this.currentDrink.degree = brand.degree
this.degreeStr = String(brand.degree)
},
onAmountChange() {
// 响应式计算
},
getCatEmoji(catId) {
const cat = this.drinkCategories.find(c => c.id === catId)
return cat ? cat.emoji : '🥃'
},
getCatIcon,
isIconPath,
getUnitLabel(unitId) {
if (!unitId) return ''
const u = DRINK_UNITS.find(u => u.id === unitId)
return u ? u.name : unitId
},
getFoodName() {
const food = this.foodCategories.find(f => f.id === this.selectedFood)
if (!food) return ''
return this.foodName ? `${food.name}·${this.foodName}` : food.name
},
getFeelingEmoji() {
const f = this.feelings.find(f => f.id === this.selectedFeeling)
return f ? f.emoji : ''
},
getFeelingName() {
const f = this.feelings.find(f => f.id === this.selectedFeeling)
return f ? f.name : ''
},
choosePhoto() {
uni.chooseImage({
count: 6 - this.photos.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.photos = [...this.photos, ...res.tempFilePaths]
}
})
},
removePhoto(index) {
this.photos.splice(index, 1)
},
removeDrink(index) {
this.drinks.splice(index, 1)
},
handleBack() {
if (this.currentStep > 1) {
this.prevStep()
} else if (this.hasFormData()) {
uni.showModal({
title: '放弃记录?',
content: '当前填写的内容将不会保存',
confirmText: '继续填写',
cancelText: '放弃',
success: (res) => {
if (res.cancel) {
uni.navigateBack()
}
}
})
} else {
uni.navigateBack()
}
},
hasFormData() {
return this.drinks.length > 0 ||
this.currentDrink.brand ||
this.currentDrink.category ||
this.selectedFood ||
this.selectedFeeling ||
this.photos.length > 0
},
prevStep() {
if (this.currentStep > 1) this.currentStep--
},
nextStep() {
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),
customAmount: parseFloat(this.amountStr) || 0,
customUnit: this.currentDrink.unit
})
// 重置表单,准备添加下一种
this.currentDrink = { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 }
this.amountStr = ''
this.degreeStr = ''
this.brandList = []
},
async publishRecord() {
const recordData = {
date: this.backfillDate || formatDate(new Date(), 'YYYY-MM-DD'),
mode: 'drank',
drinks: this.drinks.map(d => ({
category: d.category,
brand: d.brand,
product: d.product || '',
amount: d.amount,
unit: d.unit,
degree: d.degree,
standardCups: d.standardCups,
customAmount: d.customAmount || d.amount,
customUnit: d.customUnit || d.unit
})),
food: this.selectedFood ? { category: this.selectedFood, name: this.foodName || '' } : null,
feeling: this.selectedFeeling || null,
photos: this.photos,
visibility: 'friends',
quote: ''
}
try {
uni.showLoading({ title: '保存中...' })
// 调用后端创建记录
const resp = await client.CreateRecord(recordData)
const saved = resp.data || resp
uni.hideLoading()
// 跳转到卡片页,传递记录ID
uni.navigateTo({
url: `/pages/card/card?recordId=${saved.id}`
})
} catch (e) {
uni.hideLoading()
console.warn('创建记录失败,降级本地保存:', e)
uni.showToast({ title: e.msg || '保存失败,已本地保存', icon: 'none', duration: 2000 })
// 降级:本地保存
const saved = addRecord(recordData)
uni.navigateTo({
url: `/pages/card/card?recordId=${saved.id}`
})
}
}
}
}
</script>
<style lang="scss" scoped>
.record-page {
padding: $sp-lg;
color: $text-primary;
display: flex;
flex-direction: column;
}
/* 步骤头部 */
.step-header {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-md 0;
padding-top: 0;
}
.step-back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-full;
background: $bg-card;
}
.step-back-arrow {
font-size: $fs-xl;
color: $text-secondary;
font-weight: $fw-bold;
}
.step-progress {
flex: 1;
display: flex;
gap: $sp-xs;
}
.step-dot {
flex: 1;
height: 6rpx;
border-radius: $radius-full;
background: $bg-elevated;
transition: all $duration-base $ease-out;
&.step-dot-done {
background: $amber;
}
&.step-dot-active {
background: $amber-light;
}
}
.step-label {
font-size: $fs-sm;
color: $text-tertiary;
font-family: $font-num;
}
.step-title-area {
margin-bottom: $sp-xl;
}
.step-title {
display: block;
font-size: $fs-2xl;
font-weight: $fw-black;
color: $text-primary;
margin-bottom: $sp-xs;
}
.step-subtitle {
font-size: $fs-sm;
color: $text-secondary;
}
.step-content {
flex: 1;
}
/* Step 1: 酒类选择 */
.category-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-md;
margin-bottom: $sp-xl;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
padding: $sp-lg $sp-sm;
background: $bg-card;
border-radius: $radius-lg;
border: 2rpx solid transparent;
transition: all $duration-fast $ease-out;
&.category-active {
border-color: $amber;
background: $amber-glow;
}
&:active {
transform: scale(0.95);
}
}
.category-emoji {
font-size: 56rpx;
margin-bottom: $sp-sm;
}
.category-icon-img {
width: 56rpx;
height: 56rpx;
margin-bottom: $sp-sm;
}
.category-name {
font-size: $fs-sm;
font-weight: $fw-medium;
color: $text-primary;
}
/* Step 1: 添加酒水(合并选酒+记量) */
.drink-form {
margin-bottom: $sp-lg;
padding: $sp-lg;
}
.section-label {
display: block;
font-size: $fs-sm;
font-weight: $fw-medium;
color: $text-secondary;
margin-bottom: $sp-md;
}
.brand-tags {
display: flex;
flex-wrap: wrap;
gap: $sp-sm;
margin-bottom: $sp-md;
}
.input-row {
margin-bottom: $sp-sm;
}
.input-field {
background: $bg-elevated;
border-radius: $radius-md;
padding: $sp-md $sp-lg;
color: $text-primary;
font-size: $fs-base;
border: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.08));
}
.input-placeholder {
color: $text-tertiary;
}
.cup-info-btn {
width: 36rpx;
height: 36rpx;
background: $bg-elevated;
border-radius: $radius-full;
display: flex;
align-items: center;
justify-content: center;
}
.cup-info-icon {
font-size: $fs-xs;
color: $text-tertiary;
font-weight: $fw-bold;
}
.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 {
display: flex;
align-items: baseline;
gap: 6rpx;
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);
.unit-ml {
color: rgba(232, 168, 56, 0.6);
}
}
}
.unit-ml {
font-size: 20rpx;
color: $text-tertiary;
font-family: $font-num;
}
.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;
}
/* 标准杯说明弹窗 */
.cup-info-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--overlay-modal, rgba(0, 0, 0, 0.7));
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
padding: $sp-xl;
}
.cup-info-card {
width: 100%;
max-width: 620rpx;
padding: $sp-xl;
border-radius: $radius-xl;
}
.cup-info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $sp-lg;
}
.cup-info-title {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $text-primary;
}
.cup-info-close {
font-size: $fs-lg;
color: $text-tertiary;
padding: $sp-xs;
}
.cup-info-body {}
.cup-info-desc {
display: block;
font-size: $fs-sm;
color: $text-secondary;
line-height: $lh-loose;
margin-bottom: $sp-lg;
}
.cup-info-formula {
background: $amber-glow;
border-radius: $radius-md;
padding: $sp-md;
text-align: center;
margin-bottom: $sp-lg;
}
.cup-info-formula-text {
font-size: $fs-md;
font-weight: $fw-bold;
color: $amber;
font-family: $font-num;
}
.cup-info-subtitle {
display: block;
font-size: $fs-sm;
font-weight: $fw-medium;
color: $text-secondary;
margin-bottom: $sp-md;
}
.cup-info-table {
margin-bottom: $sp-lg;
}
.cup-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: $sp-sm 0;
border-bottom: 2rpx solid var(--border-micro, rgba(255, 255, 255, 0.05));
}
.cup-info-cell {
font-size: $fs-sm;
color: $text-primary;
}
.cup-info-val {
color: $amber;
font-weight: $fw-bold;
font-family: $font-num;
}
.cup-info-tip {
background: rgba(94, 198, 160, 0.1);
border-radius: $radius-md;
padding: $sp-md;
}
.cup-info-tip-text {
font-size: $fs-xs;
color: $mint;
line-height: $lh-loose;
}
.added-drinks {
margin-bottom: $sp-lg;
}
.added-drink-item {
padding: $sp-md;
margin-bottom: $sp-sm;
}
.total-cups-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: $sp-md $sp-lg;
background: $bg-card;
border-radius: $radius-lg;
margin-bottom: $sp-md;
}
.total-cups-value {
font-size: $fs-xl;
font-weight: $fw-bold;
}
.health-warning {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-md;
background: rgba(255, 107, 107, 0.1);
border: 2rpx solid rgba(255, 107, 107, 0.2);
}
.health-warning-icon {
font-size: $fs-xl;
}
.health-warning-text {
font-size: $fs-sm;
color: $coral;
flex: 1;
}
.health-hint {
text-align: center;
padding: $sp-sm;
}
/* Step 3: 配餐 */
.food-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-md;
margin-bottom: $sp-xl;
}
.food-item {
display: flex;
flex-direction: column;
align-items: center;
padding: $sp-lg $sp-sm;
background: $bg-card;
border-radius: $radius-lg;
border: 2rpx solid transparent;
transition: all $duration-fast $ease-out;
&.food-active {
border-color: $amber;
background: $amber-glow;
}
&:active {
transform: scale(0.95);
}
}
.food-emoji {
font-size: 56rpx;
margin-bottom: $sp-sm;
}
.food-name {
font-size: $fs-sm;
font-weight: $fw-medium;
color: $text-primary;
}
.food-detail {
margin-top: $sp-lg;
}
/* Step 4: 感受 */
.feeling-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $sp-lg;
margin-bottom: $sp-xl;
}
.feeling-item {
display: flex;
flex-direction: column;
align-items: center;
padding: $sp-xl $sp-lg;
background: $bg-card;
border-radius: $radius-xl;
border: 3rpx solid transparent;
transition: all $duration-base $ease-bounce;
&.feeling-active {
border-color: $amber;
background: $amber-glow;
transform: scale(1.03);
}
&:active {
transform: scale(0.96);
}
}
.feeling-emoji {
font-size: 80rpx;
margin-bottom: $sp-md;
}
.feeling-name {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $text-primary;
margin-bottom: $sp-xs;
}
.feeling-desc {
font-size: $fs-xs;
color: $text-secondary;
}
.feeling-skip {
text-align: center;
padding: $sp-lg;
}
/* Step 5: 照片 */
.photo-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-md;
margin-bottom: $sp-lg;
}
.photo-item {
aspect-ratio: 1;
border-radius: $radius-lg;
overflow: hidden;
position: relative;
}
.photo-img {
width: 100%;
height: 100%;
}
.photo-remove {
position: absolute;
top: $sp-sm;
right: $sp-sm;
width: 44rpx;
height: 44rpx;
background: var(--overlay-mask, rgba(0, 0, 0, 0.6));
border-radius: $radius-full;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: $fs-sm;
}
.photo-add {
aspect-ratio: 1;
background: $bg-card;
border-radius: $radius-lg;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 3rpx dashed var(--border-dashed, rgba(255, 255, 255, 0.12));
&:active {
background: $bg-card-alt;
}
}
.photo-add-icon {
font-size: $fs-3xl;
color: $text-tertiary;
margin-bottom: $sp-sm;
opacity: 0.5;
}
.photo-add-text {
font-size: $fs-xs;
color: $text-tertiary;
}
.photo-tip {
text-align: center;
color: $text-secondary;
}
/* Step 6: 发布 */
.visibility-section {
margin-bottom: $sp-xl;
}
.visibility-options {
display: flex;
flex-direction: column;
gap: $sp-sm;
}
.visibility-item {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-lg;
background: $bg-card;
border-radius: $radius-lg;
border: 2rpx solid transparent;
transition: all $duration-fast $ease-out;
&.visibility-active {
border-color: $amber;
background: $amber-glow;
}
}
.visibility-radio {
width: 32rpx;
height: 32rpx;
border-radius: $radius-full;
border: 3rpx solid $text-tertiary;
transition: all $duration-fast $ease-out;
&.radio-checked {
border-color: $amber;
background: $amber;
box-shadow: inset 0 0 0 6rpx $bg-base;
}
}
.visibility-name {
display: block;
font-size: $fs-base;
font-weight: $fw-medium;
color: $text-primary;
}
.visibility-desc {
display: block;
font-size: $fs-xs;
color: $text-secondary;
margin-top: $sp-xs;
}
/* 底部操作栏 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: $sp-md;
padding: $sp-lg;
padding-bottom: calc(env(safe-area-inset-bottom) + $sp-lg);
background: linear-gradient(to top, $bg-base 80%, transparent);
z-index: 50;
.btn-secondary, .btn-primary, .btn-add-drink {
flex: 1;
}
}
.btn-placeholder {
flex: 1;
}
.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-name {
display: flex;
align-items: center;
gap: $sp-xs;
flex: 1;
min-width: 0;
}
.inline-icon-img {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.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>