Files
spring-festival-greetings/pages/detail/index.vue
2026-03-13 02:26:22 +08:00

683 lines
14 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="detail-page">
<NavBar title="祝福贺卡" />
<scroll-view scroll-y class="content-scroll">
<view class="content-wrap">
<!-- User Info -->
<view class="user-header">
<image
class="avatar"
:src="
cardDetail?.from?.avatar ||
'https://file.lihailezzc.com/resource/b48c41054c2633c478463ac1b1f1ca23.png'
"
mode="aspectFill"
/>
<view class="user-meta">
<view class="user-name">{{ cardDetail?.blessingFrom }}</view>
<view class="user-msg">给你发来了一条专属祝福</view>
<view class="year-tag">
<text class="fire-icon">🎉</text> {{ cardDetail?.year || 2026 }}
{{ cardDetail?.festival || "丙午马年" }}
</view>
</view>
<!-- Music Control -->
<view
v-if="cardDetail?.musicUrl"
class="music-control"
:class="{ playing: isBgmPlaying }"
@tap="toggleBgm"
>
<text class="music-icon">{{ isBgmPlaying ? "🎵" : "🔇" }}</text>
</view>
</view>
<!-- Card Preview Area -->
<view class="card-container">
<view class="card-inner">
<image
class="card-img"
:src="cardDetail?.imageUrl"
mode="widthFix"
/>
</view>
</view>
<!-- Action Buttons -->
<view class="action-buttons">
<button class="btn primary" @tap="makeGreeting">
<text class="btn-icon"></text> 我也要制作回赠祝福
</button>
<button class="btn secondary" @tap="saveCard">
<text class="btn-icon">📥</text> 保存这张精美贺卡
</button>
</view>
<!-- Recommendations -->
<!-- <view class="recommend-section">
<view class="section-header">
<text class="section-title">大家都在玩的头像挂饰</text>
<text class="more-link">查看更多</text>
</view>
<scroll-view scroll-x class="decor-scroll" show-scrollbar="false">
<view class="decor-list">
<view
class="decor-item"
v-for="(item, index) in decorList"
:key="index"
>
<view class="decor-img-box" :class="'style-' + (index % 3)">
<image :src="item.img" mode="aspectFit" class="decor-img" />
</view>
<text class="decor-name">{{ item.name }}</text>
</view>
</view>
</scroll-view>
</view> -->
<!-- Banner -->
<view class="wallpaper-banner" @tap="goToFortune">
<view class="banner-icon">
<text>🏮</text>
</view>
<view class="banner-content">
<text class="banner-title">去抽取今日运势</text>
<text class="banner-desc">每日一签开启你的好运</text>
</view>
<text class="banner-arrow"></text>
</view>
<view class="wallpaper-banner" @tap="goToGreeting">
<view class="banner-icon">
<text>🧧</text>
</view>
<view class="banner-content">
<text class="banner-title">换个好看头像吧</text>
<text class="banner-desc">精选头像合集找到你的专属风格</text>
</view>
<text class="banner-arrow"></text>
</view>
<view class="wallpaper-banner" @tap="goToWallpaper">
<view class="banner-icon">
<text>🖼</text>
</view>
<view class="banner-content">
<text class="banner-title">去挑选精美壁纸</text>
<text class="banner-desc">换上精选壁纸让手机也焕新</text>
</view>
<text class="banner-arrow"></text>
</view>
<!-- Footer -->
<view class="page-footer">
<view class="footer-line">
<text class="line"></text>
<text class="text">HAPPY EVERY DAY</text>
<text class="line"></text>
</view>
<view class="footer-sub">专属祝福 · 传递温情</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onUnmounted } from "vue";
import {
onLoad,
onHide,
onUnload,
onShareAppMessage,
onShareTimeline,
} from "@dcloudio/uni-app";
import { getPageDetail, getShareReward } from "@/api/system";
import { getShareToken, saveViewRequest } from "@/utils/common.js";
import NavBar from "@/components/NavBar/NavBar.vue";
const cardId = ref("");
const cardDetail = ref({});
const isBgmPlaying = ref(false);
const innerAudioContext = uni.createInnerAudioContext();
const initBgm = (url) => {
if (!url) return;
innerAudioContext.src = url;
innerAudioContext.loop = true;
innerAudioContext.autoplay = true;
innerAudioContext.onPlay(() => {
isBgmPlaying.value = true;
});
innerAudioContext.onPause(() => {
isBgmPlaying.value = false;
});
innerAudioContext.onStop(() => {
isBgmPlaying.value = false;
});
innerAudioContext.onError((res) => {
console.error("BGM播放错误:", res);
isBgmPlaying.value = false;
});
};
const toggleBgm = () => {
if (isBgmPlaying.value) {
innerAudioContext.pause();
} else {
innerAudioContext.play();
}
};
onLoad(async (options) => {
if (options.shareToken) {
const card = await getPageDetail(options.shareToken);
cardId.value = card.id;
cardDetail.value = card;
saveViewRequest(options.shareToken, "card_generate", card.id);
if (card.musicUrl) {
initBgm(card.musicUrl);
}
}
});
onHide(() => {
if (isBgmPlaying.value) {
innerAudioContext.pause();
}
});
onUnload(() => {
innerAudioContext.destroy();
});
onShareAppMessage(async () => {
const token = await getShareToken("card_generate", cardDetail.value?.id);
getShareReward({ scene: "card_generate" });
return {
title: "送你一张精美的专属祝福卡片 🎊",
path: `/pages/detail/index?shareToken=${token || ""}`,
imageUrl:
cardDetail.value?.imageUrl ||
"https://file.lihailezzc.com/resource/8dd026d76ef7a63d123b7fd698fb989b.png",
};
});
onShareTimeline(async () => {
const token = await getShareToken("card_generate", cardDetail.value?.id);
return {
title: "送你一张精美的专属祝福卡片 🎊",
query: `shareToken=${token}`,
imageUrl:
cardDetail.value?.imageUrl ||
"https://file.lihailezzc.com/resource/8dd026d76ef7a63d123b7fd698fb989b.png",
};
});
const makeGreeting = () => {
uni.switchTab({ url: "/pages/make/index" });
};
const saveCard = async () => {
uni.showLoading({ title: "保存中..." });
const localPath = await loadImage(cardDetail?.value?.imageUrl || "");
uni.saveImageToPhotosAlbum({
filePath: localPath,
success() {
uni.hideLoading();
uni.showToast({ title: "已保存到相册" });
},
fail(err) {
uni.hideLoading();
// handleSaveAuth(err);
},
});
};
const loadImage = (url) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: url,
success: (res) => {
resolve(res.path); // 本地路径
},
fail: (err) => {
reject(err);
},
});
});
};
const decorList = ref([
{
name: "如意马年",
img: "https://file.lihailezzc.com/resource/1463f294244c11cf274a5eaae115872a.jpeg",
},
{
name: "大吉大利",
img: "https://file.lihailezzc.com/resource/1463f294244c11cf274a5eaae115872a.jpeg",
},
{
name: "春意盎然",
img: "https://file.lihailezzc.com/resource/1463f294244c11cf274a5eaae115872a.jpeg",
},
{
name: "万事顺遂",
img: "https://file.lihailezzc.com/resource/1463f294244c11cf274a5eaae115872a.jpeg",
},
]);
const goToMake = () => {
uni.navigateTo({
url: "/pages/avatar/index",
});
};
const goToFortune = () => {
uni.navigateTo({
url: "/pages/fortune/index",
});
};
const goToGreeting = () => {
uni.navigateTo({
url: "/pages/avatar/index",
});
};
const goToWallpaper = () => {
uni.navigateTo({
url: "/pages/wallpaper/index",
});
};
</script>
<style lang="scss" scoped>
.detail-page {
min-height: 100vh;
background: #fff;
background-image: linear-gradient(to bottom, #fff6f6 0%, #fff 400rpx);
box-sizing: border-box;
}
.content-scroll {
height: 100%;
}
.content-wrap {
padding: 20rpx 30rpx 60rpx;
}
/* Music Control */
.music-control {
margin-left: auto;
width: 64rpx;
height: 64rpx;
background: rgba(255, 59, 48, 0.05);
border: 2rpx solid rgba(255, 59, 48, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.playing {
animation: rotate 3s linear infinite;
background: rgba(255, 59, 48, 0.1);
border-color: rgba(255, 59, 48, 0.2);
}
}
.music-icon {
font-size: 32rpx;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* User Header */
.user-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
margin-right: 24rpx;
border: 4rpx solid rgba(215, 180, 150, 0.3);
}
.user-meta {
display: flex;
flex-direction: column;
}
.user-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.user-msg {
font-size: 24rpx;
color: #666;
margin-top: 4rpx;
}
.year-tag {
display: inline-flex;
align-items: center;
margin-top: 8rpx;
font-size: 20rpx;
color: #ff3b30;
background: rgba(255, 59, 48, 0.08);
padding: 4rpx 12rpx;
border-radius: 8rpx;
align-self: flex-start;
}
.fire-icon {
margin-right: 6rpx;
}
.wallpaper-banner {
background: #f8f8f8;
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 60rpx;
}
.banner-icon {
width: 80rpx;
height: 80rpx;
background: #fff;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
}
.banner-icon text {
font-size: 40rpx;
color: #ff3b30;
}
.banner-content {
flex: 1;
}
.banner-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.banner-desc {
font-size: 22rpx;
color: #999;
}
.banner-arrow {
font-size: 36rpx;
color: #ccc;
}
/* Card Container */
.card-container {
background: #fff;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.08);
margin-bottom: 40rpx;
margin: 24rpx auto;
}
.card-inner {
position: relative;
margin: 0 auto;
width: 540rpx;
height: 960rpx;
background: #f0f0f0;
}
.card-img {
width: 100%;
height: 100%;
}
.card-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 40rpx;
color: #fff;
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
}
.card-year {
font-size: 36rpx;
letter-spacing: 10rpx;
opacity: 0.9;
text-align: center;
margin-bottom: 10rpx;
}
.card-sub {
font-size: 20rpx;
text-align: center;
opacity: 0.8;
margin-bottom: 40rpx;
}
.card-greeting-en {
font-size: 20rpx;
opacity: 0.8;
}
.card-greeting-cn {
font-size: 40rpx;
font-weight: bold;
line-height: 1.5;
margin-top: 10rpx;
}
.card-footer {
padding: 30rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
}
.card-info {
display: flex;
flex-direction: column;
}
.card-info .label {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.card-info .value {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.qr-code {
width: 80rpx;
height: 80rpx;
background: #ffecec;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.qr-icon {
color: #ff3b30;
font-size: 40rpx;
}
/* Buttons */
.action-buttons {
margin-bottom: 50rpx;
}
.btn {
height: 96rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
margin-bottom: 24rpx;
border: none;
}
.btn.primary {
background: #ff3b30;
color: #fff;
box-shadow: 0 12rpx 24rpx rgba(255, 59, 48, 0.3);
}
.btn.secondary {
background: #f5f5f5;
color: #333;
}
.btn-icon {
margin-right: 12rpx;
font-size: 36rpx;
}
/* Recommendations */
.recommend-section {
margin-bottom: 40rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.more-link {
font-size: 24rpx;
color: #ff3b30;
}
.decor-list {
display: flex;
}
.decor-item {
margin-right: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 140rpx;
}
.decor-img-box {
width: 140rpx;
height: 140rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12rpx;
border: 4rpx solid transparent;
}
.style-0 {
border-color: #ff3b30;
background: #fff0f0;
}
.style-1 {
border-color: #d4a017;
background: #fffcf0;
}
.style-2 {
border-color: #ffadd2;
background: #fff0f5;
}
.decor-img {
width: 100rpx;
height: 100rpx;
}
.decor-name {
font-size: 22rpx;
color: #666;
}
/* Banner */
.banner-card {
background: #fff6f6;
border-radius: 24rpx;
padding: 24rpx;
display: flex;
align-items: center;
border: 2rpx solid #ffebeb;
margin-bottom: 60rpx;
}
.banner-icon {
width: 80rpx;
height: 80rpx;
background: #ff3b30;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.placeholder-icon {
color: #fff;
font-size: 40rpx;
}
.banner-content {
flex: 1;
}
.banner-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.banner-desc {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.banner-btn {
background: #ff3b30;
color: #fff;
font-size: 24rpx;
padding: 10rpx 30rpx;
border-radius: 999rpx;
line-height: 1.5;
margin: 0;
}
/* Footer */
.page-footer {
text-align: center;
padding-bottom: 40rpx;
}
.footer-line {
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 24rpx;
font-weight: bold;
letter-spacing: 2rpx;
margin-bottom: 8rpx;
}
.line {
width: 60rpx;
height: 2rpx;
background: #ddd;
margin: 0 20rpx;
}
.footer-sub {
font-size: 20rpx;
color: #ccc;
}
</style>