Files
hejiu/pages/share-profile/share-profile.vue
T
cg d335ec290a refactor(app): 重构应用主题和页面结构
- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
2026-09-23 20:56:14 +08:00

494 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="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 { DRINK_CATEGORIES } from '../../common/constants'
export default {
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.reLaunch({ url: '/pages/main/main?tab=0' })
} else {
// 未登录以游客身份进入首页浏览,不强制跳登录页
uni.setStorageSync('is_first_launch', 'false')
uni.setStorageSync('is_guest', 'true')
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
}
}
}
</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>