- 将应用名称从"喝了么"更改为"干杯日记" - 更新所有相关文件中的品牌标识和描述信息 - 升级HaveADrink依赖包从1.0.3到1.0.4版本 - 在pages.json中新增分享个人资料页面配置 - 重构DrinkCard组件UI,增加装饰背景、酒类图标支持、饮酒感受徽章等功能 - 优化API错误处理逻辑,增加静默模式支持 - 修改白酒类别图标为 urn emoji,黄酒图标为 tea emoji - 添加分享功能按钮并优化页面返回交互体验 - 引入新的工具函数用于获取酒类图标和路径判断 - 更新主题混入、常量定义等基础配置文件 - 调整全局样式和设计令牌以匹配新品牌形象
493 lines
11 KiB
Vue
493 lines
11 KiB
Vue
<template>
|
||
<view :class="themeClass" class="page-container share-page">
|
||
<!-- 氛围背景 -->
|
||
<view class="ambient">
|
||
<view class="glow glow-1"></view>
|
||
<view class="glow glow-2"></view>
|
||
</view>
|
||
|
||
<!-- 酒桌名片 -->
|
||
<view class="biz-card">
|
||
<!-- 品牌条 -->
|
||
<view class="card-brand">
|
||
<text class="brand-mark">🍻 干杯日记</text>
|
||
<text class="brand-label">饮 酒 名 片</text>
|
||
</view>
|
||
|
||
<!-- 身份区 -->
|
||
<view class="identity">
|
||
<view class="id-avatar-wrap">
|
||
<image
|
||
v-if="info.avatar && !avatarError"
|
||
class="id-avatar"
|
||
:src="info.avatar"
|
||
mode="aspectFill"
|
||
@error="avatarError = true"
|
||
></image>
|
||
<view v-else class="id-avatar id-avatar-fallback">
|
||
<text class="id-avatar-emoji">🥃</text>
|
||
</view>
|
||
</view>
|
||
<text class="id-name">{{ info.nick }}</text>
|
||
<text class="id-slogan">这是我的饮酒档案,来交个酒友吧</text>
|
||
</view>
|
||
|
||
<!-- 票券撕边分割线 -->
|
||
<view class="perf-divider">
|
||
<view class="perf-hole perf-hole-l"></view>
|
||
<view class="perf-line"></view>
|
||
<view class="perf-hole perf-hole-r"></view>
|
||
</view>
|
||
|
||
<!-- 数据存根 -->
|
||
<view class="stats-stub">
|
||
<view class="stub-item">
|
||
<text class="stub-num text-num">{{ anim.days }}</text>
|
||
<text class="stub-label">打卡天数</text>
|
||
</view>
|
||
<view class="stub-sep"></view>
|
||
<view class="stub-item">
|
||
<text class="stub-num text-num">{{ anim.records }}</text>
|
||
<text class="stub-label">饮酒次数</text>
|
||
</view>
|
||
<view class="stub-sep"></view>
|
||
<view class="stub-item">
|
||
<text class="stub-num stub-num-amber text-num">{{ anim.cups }}</text>
|
||
<text class="stub-label">标准杯</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 连续记录 + 最爱酒类 -->
|
||
<view class="extra-row">
|
||
<view class="extra-chip">
|
||
<text class="extra-chip-emoji">{{ info.streakType === 'drank' ? '🔥' : '💪' }}</text>
|
||
<text class="extra-chip-text">{{ info.streakType === 'drank' ? '连续饮酒' : '连续戒酒' }} {{ info.streak }} 天</text>
|
||
</view>
|
||
<view v-if="favName" class="extra-chip">
|
||
<text class="extra-chip-emoji">{{ favEmoji }}</text>
|
||
<text class="extra-chip-text">最爱{{ favName }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- CTA -->
|
||
<button class="cta-btn" @click="enterApp">
|
||
<text class="cta-text">我也想记 · 打开干杯日记</text>
|
||
</button>
|
||
</view>
|
||
|
||
<text class="page-footer">让每一杯酒都有迹可循</text>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import themeMixin from '../../common/theme-mixin'
|
||
import { DRINK_CATEGORIES } from '../../common/constants'
|
||
|
||
export default {
|
||
mixins: [themeMixin],
|
||
data() {
|
||
return {
|
||
info: {
|
||
nick: '酒友',
|
||
avatar: '',
|
||
days: 0,
|
||
records: 0,
|
||
cups: 0,
|
||
streak: 0,
|
||
streakType: 'drank',
|
||
fav: ''
|
||
},
|
||
anim: { days: 0, records: 0, cups: 0 },
|
||
avatarError: false
|
||
}
|
||
},
|
||
computed: {
|
||
favCat() {
|
||
return DRINK_CATEGORIES.find(c => c.id === this.info.fav) || null
|
||
},
|
||
favName() {
|
||
return this.favCat ? this.favCat.name : ''
|
||
},
|
||
favEmoji() {
|
||
return this.favCat ? this.favCat.emoji : '🍶'
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.parseOptions(options)
|
||
},
|
||
onReady() {
|
||
this.animateStats()
|
||
},
|
||
// 好友可继续转发这张名片(病毒式传播,数据保持原始分享者)
|
||
onShareAppMessage() {
|
||
return {
|
||
title: `${this.info.nick}的饮酒名片 - 干杯日记`,
|
||
path: `/pages/share-profile/share-profile?${this.buildQuery()}`
|
||
}
|
||
},
|
||
onShareTimeline() {
|
||
return {
|
||
title: `${this.info.nick}的饮酒名片 - 干杯日记`,
|
||
query: this.buildQuery()
|
||
}
|
||
},
|
||
methods: {
|
||
parseOptions(options) {
|
||
if (!options) return
|
||
try {
|
||
if (options.nick) this.info.nick = decodeURIComponent(options.nick)
|
||
if (options.avatar) this.info.avatar = decodeURIComponent(options.avatar)
|
||
this.info.days = parseInt(options.days) || 0
|
||
this.info.records = parseInt(options.records) || 0
|
||
this.info.cups = parseFloat(options.cups) || 0
|
||
this.info.streak = parseInt(options.streak) || 0
|
||
if (options.st) this.info.streakType = options.st === 'drank' ? 'drank' : 'abstain'
|
||
if (options.fav) this.info.fav = String(options.fav)
|
||
} catch (e) {
|
||
console.warn('名片参数解析失败:', e)
|
||
}
|
||
},
|
||
buildQuery() {
|
||
const parts = [`nick=${encodeURIComponent(this.info.nick)}`]
|
||
if (this.info.avatar) parts.push(`avatar=${encodeURIComponent(this.info.avatar)}`)
|
||
parts.push(`days=${this.info.days}`)
|
||
parts.push(`records=${this.info.records}`)
|
||
parts.push(`cups=${this.info.cups}`)
|
||
parts.push(`streak=${this.info.streak}`)
|
||
parts.push(`st=${this.info.streakType}`)
|
||
if (this.info.fav) parts.push(`fav=${this.info.fav}`)
|
||
return parts.join('&')
|
||
},
|
||
// 数字滚动动画
|
||
animateStats() {
|
||
const t = { days: this.info.days, records: this.info.records, cups: this.info.cups }
|
||
const hasDecimal = t.cups % 1 !== 0
|
||
const duration = 900
|
||
const start = Date.now()
|
||
const timer = setInterval(() => {
|
||
const p = Math.min((Date.now() - start) / duration, 1)
|
||
const ease = 1 - Math.pow(1 - p, 3)
|
||
this.anim.days = Math.round(t.days * ease)
|
||
this.anim.records = Math.round(t.records * ease)
|
||
this.anim.cups = hasDecimal
|
||
? Math.round(t.cups * ease * 10) / 10
|
||
: Math.round(t.cups * ease)
|
||
if (p >= 1) clearInterval(timer)
|
||
}, 16)
|
||
},
|
||
enterApp() {
|
||
const isLoggedIn = uni.getStorageSync('is_logged_in')
|
||
if (isLoggedIn === 'true') {
|
||
uni.switchTab({ url: '/pages/index/index' })
|
||
} else {
|
||
uni.reLaunch({ url: '/pages/login/login' })
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.share-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: $sp-xl;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 氛围光 */
|
||
.ambient {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
|
||
.glow {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(60rpx);
|
||
}
|
||
|
||
.glow-1 {
|
||
width: 480rpx;
|
||
height: 480rpx;
|
||
top: -120rpx;
|
||
right: -100rpx;
|
||
background: radial-gradient(circle, rgba(232,168,56,0.16) 0%, transparent 70%);
|
||
animation: glow-drift 6s ease-in-out infinite alternate;
|
||
}
|
||
|
||
.glow-2 {
|
||
width: 400rpx;
|
||
height: 400rpx;
|
||
bottom: -80rpx;
|
||
left: -120rpx;
|
||
background: radial-gradient(circle, rgba(139,133,184,0.12) 0%, transparent 70%);
|
||
animation: glow-drift 7s ease-in-out infinite alternate-reverse;
|
||
}
|
||
|
||
@keyframes glow-drift {
|
||
from { transform: translate(0, 0) scale(1); }
|
||
to { transform: translate(30rpx, 40rpx) scale(1.12); }
|
||
}
|
||
|
||
/* 名片主体 */
|
||
.biz-card {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
position: relative;
|
||
z-index: 2;
|
||
background: linear-gradient(165deg, var(--card-gradient-start, #1A1510) 0%, var(--card-gradient-end, #0D0B08) 100%);
|
||
border: 1rpx solid rgba(232,168,56,0.28);
|
||
border-radius: $radius-xl;
|
||
padding: $sp-xl $sp-xl $sp-2xl;
|
||
box-shadow: $shadow-amber;
|
||
overflow: hidden;
|
||
animation: card-in 0.6s $ease-out both;
|
||
}
|
||
|
||
@keyframes card-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(60rpx) scale(0.96);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
}
|
||
|
||
/* 品牌条 */
|
||
.card-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: $sp-xl;
|
||
}
|
||
|
||
.brand-mark {
|
||
font-size: $fs-sm;
|
||
color: $text-secondary;
|
||
font-weight: $fw-medium;
|
||
}
|
||
|
||
.brand-label {
|
||
font-size: $fs-xs;
|
||
color: $amber;
|
||
letter-spacing: 4rpx;
|
||
font-weight: $fw-medium;
|
||
padding: 6rpx 16rpx;
|
||
border: 1rpx solid rgba(232,168,56,0.35);
|
||
border-radius: $radius-full;
|
||
}
|
||
|
||
/* 身份区 */
|
||
.identity {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: $sp-md 0 $sp-xl;
|
||
}
|
||
|
||
.id-avatar-wrap {
|
||
position: relative;
|
||
margin-bottom: $sp-lg;
|
||
}
|
||
|
||
.id-avatar-wrap::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -16rpx;
|
||
left: -16rpx;
|
||
right: -16rpx;
|
||
bottom: -16rpx;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, rgba(232,168,56,0.22) 0%, transparent 70%);
|
||
filter: blur(12rpx);
|
||
}
|
||
|
||
.id-avatar {
|
||
width: 168rpx;
|
||
height: 168rpx;
|
||
border-radius: 50%;
|
||
border: 4rpx solid $amber;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.id-avatar-fallback {
|
||
background: $bg-elevated;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.id-avatar-emoji {
|
||
font-size: 84rpx;
|
||
}
|
||
|
||
.id-name {
|
||
font-size: $fs-2xl;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
margin-bottom: $sp-sm;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.id-slogan {
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
/* 票券撕边 */
|
||
.perf-divider {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0 (-$sp-xl);
|
||
padding: 0 $sp-md;
|
||
}
|
||
|
||
.perf-line {
|
||
flex: 1;
|
||
border-top: 2rpx dashed rgba(232,168,56,0.3);
|
||
}
|
||
|
||
.perf-hole {
|
||
position: absolute;
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
border-radius: 50%;
|
||
background: $bg-base;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
.perf-hole-l {
|
||
left: -22rpx;
|
||
}
|
||
|
||
.perf-hole-r {
|
||
right: -22rpx;
|
||
}
|
||
|
||
/* 数据存根 */
|
||
.stats-stub {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: $sp-xl $sp-md;
|
||
}
|
||
|
||
.stub-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: $sp-xs;
|
||
}
|
||
|
||
.stub-num {
|
||
font-size: $fs-3xl;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
line-height: $lh-tight;
|
||
}
|
||
|
||
.stub-num-amber {
|
||
color: $amber;
|
||
}
|
||
|
||
.stub-label {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.stub-sep {
|
||
width: 1rpx;
|
||
height: 56rpx;
|
||
background: rgba(232,168,56,0.2);
|
||
}
|
||
|
||
/* 标签行 */
|
||
.extra-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: $sp-md;
|
||
padding: 0 $sp-md $sp-xl;
|
||
}
|
||
|
||
.extra-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: $sp-xs;
|
||
padding: $sp-sm $sp-lg;
|
||
background: rgba(232,168,56,0.1);
|
||
border: 1rpx solid rgba(232,168,56,0.22);
|
||
border-radius: $radius-full;
|
||
}
|
||
|
||
.extra-chip-emoji {
|
||
font-size: $fs-base;
|
||
}
|
||
|
||
.extra-chip-text {
|
||
font-size: $fs-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
/* CTA */
|
||
.cta-btn {
|
||
margin: 0 $sp-md;
|
||
background: linear-gradient(135deg, $amber-light, $amber, $amber-deep);
|
||
border-radius: $radius-full;
|
||
padding: $sp-md 0;
|
||
border: none;
|
||
line-height: 1.4;
|
||
animation: cta-glow 2.4s ease-in-out infinite;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.97);
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
|
||
@keyframes cta-glow {
|
||
0%, 100% { box-shadow: 0 8rpx 28rpx rgba(232,168,56,0.28); }
|
||
50% { box-shadow: 0 8rpx 44rpx rgba(232,168,56,0.5); }
|
||
}
|
||
|
||
.cta-text {
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $text-on-amber;
|
||
}
|
||
|
||
/* 底部 */
|
||
.page-footer {
|
||
position: relative;
|
||
z-index: 2;
|
||
margin-top: $sp-2xl;
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
letter-spacing: 4rpx;
|
||
animation: card-in 0.6s $ease-out 0.15s both;
|
||
}
|
||
</style>
|