564 lines
12 KiB
Vue
564 lines
12 KiB
Vue
<template>
|
||
<view class="greeting-page">
|
||
<NavBar title="我的新春祝福" background="transparent" />
|
||
|
||
<!-- Header Stats -->
|
||
<view class="header-stats">
|
||
<view class="stats-card">
|
||
<view class="stats-left">
|
||
<view class="icon-circle">
|
||
<text class="sparkle-emoji">✨</text>
|
||
</view>
|
||
<view class="stats-info">
|
||
<text class="label">已累计创作</text>
|
||
<view class="value-wrap">
|
||
<text class="value">{{ totalCount }}</text>
|
||
<text class="unit">份祝福</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="divider"></view>
|
||
<view class="stats-right">
|
||
<text class="label">马年运势</text>
|
||
<text class="value red-text">一马当先</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- List Section -->
|
||
<view class="list-section">
|
||
<view class="section-header">
|
||
<text class="section-title">祝福库</text>
|
||
</view>
|
||
|
||
<view class="list-grid">
|
||
<view
|
||
v-for="item in list"
|
||
:key="item.id"
|
||
class="card-item"
|
||
@tap="onPreview(item)"
|
||
>
|
||
<view class="card-image-wrap">
|
||
<image :src="item.imageUrl" mode="aspectFill" class="card-img" />
|
||
<view class="year-badge" v-if="item.year">{{ item.year }}</view>
|
||
<view class="draft-overlay" v-if="item.status === 'draft'">
|
||
<text class="lock-icon">🔒</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-info">
|
||
<view class="card-title">{{ getTitle(item) }}</view>
|
||
<view class="card-date">{{ formatDate(item.updatedAt) }}</view>
|
||
<view class="card-footer">
|
||
<view class="tag" :class="getTagClass(item)">{{
|
||
getTagText(item)
|
||
}}</view>
|
||
<view class="actions">
|
||
<button
|
||
class="action-btn"
|
||
open-type="share"
|
||
:data-item="item"
|
||
@tap.stop
|
||
>
|
||
<text class="action-emoji">🔗</text>
|
||
</button>
|
||
<!-- <view class="action-btn" @tap.stop="onMore(item)">
|
||
<text class="action-emoji">⋯</text>
|
||
</view> -->
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Loading State -->
|
||
<view class="loading-state" v-if="loading">
|
||
<text>加载中...</text>
|
||
</view>
|
||
<view class="empty-state" v-if="!loading && list.length === 0">
|
||
<text>暂无祝福记录</text>
|
||
</view>
|
||
|
||
<view class="footer-note" v-if="!loading && list.length > 0">
|
||
<text>2026 丙午马年 · 祝福管理助手</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- FAB -->
|
||
<view class="fab-btn" @tap="onMake">
|
||
<view class="fab-content">
|
||
<text class="fab-emoji">✍️</text>
|
||
<text>新春制作</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from "vue";
|
||
import {
|
||
onPullDownRefresh,
|
||
onReachBottom,
|
||
onShareAppMessage,
|
||
} from "@dcloudio/uni-app";
|
||
import { getMyCard } from "@/api/mine.js";
|
||
import NavBar from "@/components/NavBar/NavBar.vue";
|
||
import { getShareToken } from "@/utils/common.js";
|
||
|
||
const list = ref([]);
|
||
const page = ref(1);
|
||
const loading = ref(false);
|
||
const hasMore = ref(true);
|
||
const totalCount = ref(0);
|
||
|
||
onMounted(() => {
|
||
fetchList(true);
|
||
});
|
||
|
||
onPullDownRefresh(() => {
|
||
fetchList(true);
|
||
});
|
||
|
||
onReachBottom(() => {
|
||
if (hasMore.value && !loading.value) {
|
||
fetchList();
|
||
}
|
||
});
|
||
|
||
onShareAppMessage(async (options) => {
|
||
if (options.from === "button") {
|
||
const shareToken = await getShareToken(
|
||
"card_generate",
|
||
options?.target?.dataset?.item?.id,
|
||
);
|
||
return {
|
||
title: "我刚做了一张祝福卡片,送给你",
|
||
path: "/pages/detail/index?shareToken=" + shareToken,
|
||
imageUrl:
|
||
"https://file.lihailezzc.com/resource/bf9faeddb7ff55a5cd3d435779d56556.png",
|
||
};
|
||
} else {
|
||
const shareToken = await getShareToken("greeting_page", "");
|
||
return {
|
||
title: "新年好运已送达 🎊|祝福卡·头像·壁纸",
|
||
path: `/pages/index/index?shareToken=${shareToken}`,
|
||
imageUrl:
|
||
"https://file.lihailezzc.com/resource/8dd026d76ef7a63d123b7fd698fb989b.png",
|
||
};
|
||
}
|
||
});
|
||
|
||
const fetchList = async (reset = false) => {
|
||
if (loading.value) return;
|
||
if (reset) {
|
||
page.value = 1;
|
||
hasMore.value = true;
|
||
}
|
||
if (!hasMore.value) return;
|
||
|
||
loading.value = true;
|
||
try {
|
||
const res = await getMyCard(page.value);
|
||
const dataList = res?.list || [];
|
||
totalCount.value = res?.totalCount || 0;
|
||
|
||
if (reset) {
|
||
list.value = dataList;
|
||
} else {
|
||
list.value = [...list.value, ...dataList];
|
||
}
|
||
|
||
hasMore.value = res.hasNext;
|
||
if (hasMore.value) {
|
||
page.value++;
|
||
}
|
||
} catch (e) {
|
||
console.error("Failed to fetch greeting list", e);
|
||
} finally {
|
||
loading.value = false;
|
||
uni.stopPullDownRefresh();
|
||
}
|
||
};
|
||
|
||
const formatDate = (dateStr) => {
|
||
if (!dateStr) return "";
|
||
const date = new Date(dateStr);
|
||
const y = date.getFullYear();
|
||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||
const d = String(date.getDate()).padStart(2, "0");
|
||
return `${y}-${m}-${d}`;
|
||
};
|
||
|
||
const getTagText = (item) => {
|
||
// if (item.status === "draft") return "草稿";
|
||
return item?.title?.name || item.festival || "新春快乐";
|
||
};
|
||
|
||
const getTitle = (item) => {
|
||
const title =
|
||
(item?.blessingTo || "祝您") + (item?.content?.content || "新春快乐");
|
||
return title.length > 10 ? title.substring(0, 10) + "..." : title;
|
||
};
|
||
|
||
const getTagClass = (item) => {
|
||
if (item.status === "draft") return "tag-draft";
|
||
const tagMap = {
|
||
万事如意: "tag-spring",
|
||
新春快乐: "tag-gold",
|
||
新春大吉: "tag-horse",
|
||
钱包鼓鼓: "tag-ink",
|
||
福气旺旺: "tag-spring",
|
||
龙马精神: "tag-horse",
|
||
马年纳祥: "tag-horse",
|
||
福马迎春: "tag-horse",
|
||
};
|
||
return tagMap[item?.title?.name || item.festival] || "tag-gold";
|
||
};
|
||
|
||
const onPreview = (item) => {
|
||
if (!item.imageUrl) return;
|
||
uni.previewImage({
|
||
urls: [item.imageUrl],
|
||
current: item.imageUrl,
|
||
});
|
||
};
|
||
|
||
const onMake = () => {
|
||
uni.switchTab({
|
||
url: "/pages/make/index",
|
||
});
|
||
};
|
||
|
||
// const onShare = (item) => {
|
||
// uni.showToast({ title: "分享功能开发中", icon: "none" });
|
||
// };
|
||
|
||
const onMore = (item) => {
|
||
uni.showActionSheet({
|
||
itemList: ["编辑", "删除"],
|
||
success: (res) => {
|
||
if (res.tapIndex === 0) {
|
||
// Edit
|
||
} else if (res.tapIndex === 1) {
|
||
onDelete(item);
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
const onDelete = (item) => {
|
||
uni.showModal({
|
||
title: "提示",
|
||
content: "确定要删除这条祝福吗?",
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
list.value = list.value.filter((i) => i.id !== item.id);
|
||
totalCount.value = Math.max(0, totalCount.value - 1);
|
||
uni.showToast({ title: "删除成功", icon: "none" });
|
||
}
|
||
},
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.greeting-page {
|
||
min-height: 100vh;
|
||
background: #fbf9f2;
|
||
padding-bottom: 120rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.header-stats {
|
||
padding: 30rpx 40rpx;
|
||
margin-top: 20rpx;
|
||
|
||
.stats-card {
|
||
background: #fff;
|
||
border-radius: 40rpx;
|
||
padding: 40rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.02);
|
||
|
||
.stats-left {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.icon-circle {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50%;
|
||
background: #fff5f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 24rpx;
|
||
|
||
.sparkle-emoji {
|
||
font-size: 50rpx;
|
||
}
|
||
}
|
||
|
||
.stats-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.value-wrap {
|
||
display: flex;
|
||
align-items: baseline;
|
||
|
||
.value {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.unit {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.divider {
|
||
width: 1rpx;
|
||
height: 80rpx;
|
||
background: #f0f0f0;
|
||
}
|
||
|
||
.stats-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding-left: 20rpx;
|
||
|
||
.label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
|
||
&.red-text {
|
||
color: #ff4d4f;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.list-section {
|
||
padding: 0 40rpx;
|
||
|
||
.section-header {
|
||
margin: 40rpx 0 24rpx;
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #7c6d5d;
|
||
}
|
||
}
|
||
|
||
.list-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 30rpx;
|
||
}
|
||
}
|
||
|
||
.card-item {
|
||
background: #fff;
|
||
border-radius: 48rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.03);
|
||
|
||
.card-image-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
padding-bottom: 133%; // 3:4 aspect ratio
|
||
background: #fdf3e7;
|
||
|
||
.card-img {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
left: 20rpx;
|
||
right: 20rpx;
|
||
bottom: 20rpx;
|
||
width: auto;
|
||
height: auto;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.year-badge {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
left: 20rpx;
|
||
background: #ff4d4f;
|
||
color: #fff;
|
||
font-size: 20rpx;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.draft-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.05);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.icon {
|
||
font-size: 40rpx;
|
||
opacity: 0.3;
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-info {
|
||
padding: 24rpx;
|
||
|
||
.card-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.card-date {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.tag {
|
||
font-size: 20rpx;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
|
||
&.tag-gold {
|
||
background: #fff8e6;
|
||
color: #ffb800;
|
||
}
|
||
&.tag-horse {
|
||
background: #fff1f0;
|
||
color: #ff4d4f;
|
||
}
|
||
&.tag-ink {
|
||
background: #f0f5ff;
|
||
color: #2f54eb;
|
||
}
|
||
&.tag-draft {
|
||
background: #f5f5f5;
|
||
color: #bfbfbf;
|
||
}
|
||
&.tag-spring {
|
||
background: #fff1f0;
|
||
color: #ff4d4f;
|
||
}
|
||
}
|
||
|
||
.actions {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
|
||
.action-btn {
|
||
background: transparent;
|
||
border: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
line-height: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
outline: none;
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
.action-emoji {
|
||
font-size: 32rpx;
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.fab-btn {
|
||
position: fixed;
|
||
bottom: 60rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 100;
|
||
|
||
.fab-content {
|
||
background: #ff4d4f;
|
||
padding: 20rpx 48rpx;
|
||
border-radius: 100rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
box-shadow: 0 10rpx 30rpx rgba(255, 77, 79, 0.3);
|
||
|
||
.fab-emoji {
|
||
font-size: 36rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
text {
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.9;
|
||
transform: translateX(-50%) scale(0.95);
|
||
}
|
||
}
|
||
|
||
.footer-note {
|
||
text-align: center;
|
||
padding: 60rpx 0 40rpx;
|
||
font-size: 24rpx;
|
||
color: #ccc;
|
||
}
|
||
|
||
.loading-state,
|
||
.empty-state {
|
||
grid-column: span 2;
|
||
text-align: center;
|
||
padding: 100rpx 0;
|
||
color: #999;
|
||
font-size: 28rpx;
|
||
}
|
||
</style>
|