Files
hejiu/pages/share-profile/share-profile.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

493 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="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>