Files
hejiu/components/DrinkCard.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

465 lines
11 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="card-root">
<!-- 装饰背景层 -->
<view class="card-decor">
<view class="decor-circle decor-circle-1"></view>
<view class="decor-circle decor-circle-2"></view>
<view class="decor-circle decor-circle-3"></view>
<view class="decor-glow"></view>
</view>
<!-- 顶部日期 -->
<view class="card-top">
<view class="date-line"></view>
<text class="header-date">{{ record.date }}</text>
<view class="date-line"></view>
</view>
<!-- 主视觉区照片或图标 -->
<view v-if="photos.length > 0" class="card-photos">
<view v-if="photos.length === 1" class="photo-single">
<image :src="photos[0]" mode="aspectFill"></image>
</view>
<view v-else class="photo-grid">
<view
v-for="(photo, i) in photos"
:key="i"
class="photo-cell"
:class="{ 'photo-span': i === 0 && photos.length % 2 !== 0 }"
>
<image :src="photo" mode="aspectFill"></image>
</view>
</view>
</view>
<view v-else class="card-hero">
<view class="hero-ring"></view>
<view class="hero-inner">
<image v-if="isIconPath(mainIcon)" :src="mainIcon" class="hero-icon-img" mode="aspectFit"></image>
<text v-else class="hero-emoji">{{ mainIcon }}</text>
</view>
</view>
<!-- 饮酒感受徽章 -->
<view v-if="feelingData" class="feeling-badge">
<text class="feeling-emoji">{{ feelingData.emoji }}</text>
<text class="feeling-name">{{ feelingData.name }}</text>
<text class="feeling-desc">{{ feelingData.desc }}</text>
</view>
<!-- 酒水清单 -->
<view class="drink-list">
<view class="list-title-row">
<view class="list-dot"></view>
<text class="list-title">今夜菜单</text>
<view class="list-dot"></view>
</view>
<view v-for="(drink, i) in record.drinks" :key="i" class="drink-row">
<view class="drink-left">
<image v-if="isIconPath(getCatIcon(drink.category))" :src="getCatIcon(drink.category)" class="drink-icon-img" mode="aspectFit"></image>
<text v-else class="drink-emoji">{{ getCatEmoji(drink.category) }}</text>
<text class="drink-name">{{ drink.brand }}</text>
</view>
<view class="drink-dots"></view>
<text class="drink-amount">{{ formatAmount(drink) }}</text>
</view>
<view v-if="record.food" class="drink-row">
<view class="drink-left">
<text class="drink-emoji">🍲</text>
<text class="drink-name">{{ getFoodName() }}</text>
</view>
<view class="drink-dots"></view>
<text class="drink-amount">佐酒</text>
</view>
</view>
<!-- 分割装饰 -->
<view class="divider-decor">
<view class="divider-line"></view>
<text class="divider-icon">🥂</text>
<view class="divider-line"></view>
</view>
<!-- 酒言酒语 -->
<view class="quote-section">
<text class="quote-mark">"</text>
<text class="quote-text">{{ quote }}</text>
<text class="quote-mark quote-mark-end">"</text>
</view>
<!-- 品牌底栏 -->
<view class="card-brand">
<view class="brand-left">
<text class="brand-name">干杯日记</text>
<text class="brand-slogan">记录每一杯的故事</text>
</view>
<view class="brand-qr"></view>
</view>
</view>
</template>
<script>
import { DRINK_CATEGORIES, FOOD_CATEGORIES, DRINK_QUOTES, FEELINGS } from '../common/constants'
import { getCatIcon, isIconPath } from '../common/utils'
const UNIT_NAMES = { ml: 'ml', liang: '两', bottle: '瓶', cup: '杯', can: '听', shot: 'shot' }
export default {
props: {
record: { type: Object, required: true }
},
data() {
return {
quote: DRINK_QUOTES[Math.floor(Math.random() * DRINK_QUOTES.length)]
}
},
computed: {
photos() {
return (this.record && this.record.photos) || []
},
mainIcon() {
const first = (this.record.drinks || [])[0]
return first ? getCatIcon(first.category) : '🥃'
},
feelingData() {
return FEELINGS.find(fe => fe.id === this.record.feeling) || null
}
},
methods: {
getCatIcon,
isIconPath,
getCatEmoji(catId) {
const cat = DRINK_CATEGORIES.find(c => c.id === catId)
return cat ? cat.emoji : '🥃'
},
getFoodName() {
const food = FOOD_CATEGORIES.find(f => f.id === this.record.food?.category)
if (!food) return this.record.food?.name || '美食'
return this.record.food.name ? `${food.name}·${this.record.food.name}` : food.name
},
formatAmount(drink) {
const unitName = UNIT_NAMES[drink.unit] || drink.unit
return `${drink.amount}${unitName}`
}
}
}
</script>
<style lang="scss" scoped>
.card-root {
position: relative;
width: 620rpx;
border-radius: 40rpx;
overflow: hidden;
background: linear-gradient(175deg, #1C1428 0%, #0F0A1A 40%, #0A0710 100%);
border: 2rpx solid rgba(232, 168, 56, 0.2);
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.6), 0 0 80rpx rgba(232, 168, 56, 0.08), inset 0 1rpx 0 rgba(255, 255, 255, 0.06);
display: flex;
flex-direction: column;
padding: 56rpx 48rpx 40rpx;
}
/* === 装饰背景 === */
.card-decor {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
overflow: hidden;
}
.decor-circle {
position: absolute;
border-radius: 50%;
border: 1.5rpx solid rgba(232, 168, 56, 0.12);
}
.decor-circle-1 {
width: 300rpx; height: 300rpx;
top: -80rpx; right: -60rpx;
}
.decor-circle-2 {
width: 200rpx; height: 200rpx;
bottom: 200rpx; left: -60rpx;
border-color: rgba(139, 133, 184, 0.1);
}
.decor-circle-3 {
width: 120rpx; height: 120rpx;
top: 320rpx; right: 40rpx;
background: rgba(232, 168, 56, 0.03);
}
.decor-glow {
position: absolute;
width: 400rpx; height: 400rpx;
top: -100rpx; left: 50%;
transform: translateX(-50%);
background: radial-gradient(circle, rgba(232, 168, 56, 0.06) 0%, transparent 70%);
border-radius: 50%;
}
/* === 顶部日期 === */
.card-top {
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
margin-bottom: 40rpx;
position: relative;
z-index: 1;
}
.date-line {
width: 60rpx;
height: 2rpx;
background: linear-gradient(90deg, transparent, rgba(232, 168, 56, 0.5));
}
.date-line:last-child {
background: linear-gradient(90deg, rgba(232, 168, 56, 0.5), transparent);
}
.header-date {
font-size: 22rpx;
color: rgba(232, 168, 56, 0.8);
font-family: $font-num;
letter-spacing: 6rpx;
font-weight: $fw-medium;
}
/* === 照片区域 === */
.card-photos {
position: relative;
z-index: 1;
margin-bottom: 32rpx;
}
.photo-single {
width: 100%;
height: 340rpx;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
image { width: 100%; height: 100%; }
}
.photo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
}
.photo-cell {
aspect-ratio: 1;
border-radius: 16rpx;
overflow: hidden;
image { width: 100%; height: 100%; }
}
.photo-span {
grid-column: span 2;
aspect-ratio: 16 / 9;
}
/* === 主视觉图标 === */
.card-hero {
display: flex;
justify-content: center;
align-items: center;
padding: 24rpx 0 32rpx;
position: relative;
z-index: 1;
}
.hero-ring {
position: absolute;
width: 220rpx; height: 220rpx;
border-radius: 50%;
border: 2rpx solid rgba(232, 168, 56, 0.15);
animation: pulse-ring 3s ease-in-out infinite;
}
@keyframes pulse-ring {
0%, 100% { transform: scale(1); opacity: 0.6; }
50% { transform: scale(1.08); opacity: 1; }
}
.hero-inner {
width: 180rpx; height: 180rpx;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, rgba(232, 168, 56, 0.15), rgba(232, 168, 56, 0.04));
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(232, 168, 56, 0.1), inset 0 0 40rpx rgba(232, 168, 56, 0.05);
}
.hero-emoji {
font-size: 88rpx;
line-height: 1;
}
.hero-icon-img {
width: 110rpx;
height: 110rpx;
}
/* === 感受徽章 === */
.feeling-badge {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
margin: 0 auto 32rpx;
padding: 14rpx 32rpx;
background: linear-gradient(135deg, rgba(232, 168, 56, 0.12), rgba(232, 168, 56, 0.04));
border: 1.5rpx solid rgba(232, 168, 56, 0.25);
border-radius: 999rpx;
position: relative;
z-index: 1;
}
.feeling-emoji {
font-size: 32rpx;
}
.feeling-name {
font-size: 26rpx;
color: #E8A838;
font-weight: $fw-bold;
letter-spacing: 2rpx;
}
.feeling-desc {
font-size: 20rpx;
color: rgba(232, 168, 56, 0.6);
}
/* === 酒水清单 === */
.drink-list {
position: relative;
z-index: 1;
padding: 32rpx;
background: rgba(255, 255, 255, 0.02);
border-radius: 24rpx;
border: 1.5rpx solid rgba(255, 255, 255, 0.05);
margin-bottom: 32rpx;
}
.list-title-row {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
margin-bottom: 28rpx;
}
.list-dot {
width: 8rpx; height: 8rpx;
border-radius: 50%;
background: rgba(232, 168, 56, 0.5);
}
.list-title {
font-size: 22rpx;
color: rgba(232, 168, 56, 0.7);
letter-spacing: 8rpx;
font-weight: $fw-medium;
}
.drink-row {
display: flex;
align-items: center;
padding: 14rpx 0;
}
.drink-left {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.drink-icon-img {
width: 34rpx; height: 34rpx;
flex-shrink: 0;
}
.drink-emoji {
font-size: 28rpx;
flex-shrink: 0;
}
.drink-name {
font-size: 28rpx;
color: #FFFFFF;
font-weight: $fw-medium;
}
.drink-dots {
flex: 1;
height: 2rpx;
margin: 0 16rpx;
background: repeating-linear-gradient(90deg, rgba(255,255,255,0.15) 0, rgba(255,255,255,0.15) 4rpx, transparent 4rpx, transparent 10rpx);
}
.drink-amount {
font-size: 26rpx;
color: #E8A838;
font-family: $font-num;
font-weight: $fw-bold;
flex-shrink: 0;
}
/* === 分割装饰 === */
.divider-decor {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 32rpx;
position: relative;
z-index: 1;
}
.divider-line {
flex: 1;
height: 1.5rpx;
background: linear-gradient(90deg, transparent, rgba(232, 168, 56, 0.2), transparent);
}
.divider-icon {
font-size: 28rpx;
opacity: 0.7;
}
/* === 酒言酒语 === */
.quote-section {
text-align: center;
padding: 0 16rpx;
margin-bottom: 40rpx;
position: relative;
z-index: 1;
}
.quote-mark {
font-size: 48rpx;
color: rgba(232, 168, 56, 0.3);
font-family: Georgia, serif;
line-height: 1;
}
.quote-mark-end {
display: block;
text-align: right;
}
.quote-text {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.85);
line-height: 1.8;
letter-spacing: 2rpx;
padding: 8rpx 0;
font-style: italic;
}
/* === 品牌底栏 === */
.card-brand {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 32rpx;
border-top: 1.5rpx solid rgba(255, 255, 255, 0.05);
position: relative;
z-index: 1;
}
.brand-left {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.brand-name {
font-size: 26rpx;
color: rgba(232, 168, 56, 0.8);
font-weight: $fw-bold;
letter-spacing: 4rpx;
}
.brand-slogan {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.3);
letter-spacing: 2rpx;
}
.brand-qr {
width: 80rpx;
height: 80rpx;
border-radius: 12rpx;
border: 1.5rpx solid rgba(232, 168, 56, 0.2);
background: rgba(232, 168, 56, 0.04);
}
</style>