Files
spring-festival-greetings/pages/index/index.vue
2026-02-05 23:43:51 +08:00

693 lines
16 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="spring-page" :style="{ paddingTop: getBavBarHeight() + 'px' }">
<!-- 顶部 Banner -->
<view class="hero">
<view class="hero-badge">
<text class="badge-dot"></text>
<text class="badge-text">{{ countdownText }}</text>
</view>
<view class="hero-title">
<text class="year">2026</text>
<text class="main">新春祝福</text>
</view>
<text class="hero-sub">新年快乐万事如意!</text>
<!-- <image class="hero-decor" src="https://file.lihailezzc.com/resource/58c8d19e5f2d9c958a7b8b9f44b8c3e3.png" mode="aspectFill" /> -->
</view>
<!-- 功能入口宫格 -->
<view class="feature-grid">
<view
v-for="(item, idx) in features"
:key="idx"
class="feature-item"
@tap="onFeatureTap(item)"
>
<image
class="feature-icon"
:src="item.icon"
mode="aspectFill"
:style="{
backgroundColor: idx < 2 ? '#FEF2F2' : '#FFFBEC',
}"
/>
<view class="feature-texts">
<text class="feature-title">{{ item.title }}</text>
<text class="feature-sub">{{ item.subtitle }}</text>
</view>
</view>
</view>
<!-- 今日灵感 -->
<view class="daily-section">
<view class="daily-header">
<text class="daily-title">今日灵感</text>
<text class="daily-date">{{ todayDate }}</text>
</view>
<view class="daily-card">
<view class="daily-content">
<text class="quote-mark"></text>
<text class="daily-text">{{ dailyGreeting }}</text>
<text class="quote-mark right"></text>
</view>
<view class="daily-actions">
<!-- <view class="action-btn copy" @tap="copyGreeting">
<text class="icon"></text> 复制
</view> -->
<view class="action-btn use" @tap="useGreeting">
<text class="icon"></text> 去制作
</view>
</view>
</view>
</view>
<!-- 大家都在用网格布局 -->
<view class="section">
<view class="section-header">
<view class="section-bar"></view>
<text class="section-title">大家都在用</text>
<!-- <text class="section-more" @tap="onMore('use')">查看更多 ></text> -->
</view>
<view class="use-grid">
<view
v-for="(card, i) in recommendList"
:key="i"
class="use-card"
@tap="onCardClick(card)"
>
<view class="card-cover-wrap">
<image :src="card.imageUrl" class="card-cover" mode="aspectFill" />
<view v-if="card.tag" class="card-tag" :class="`tag--${card.tag}`">
{{ getTagText(card.tag) }}
</view>
</view>
<view class="card-info">
<view class="card-title">{{ card.title }}</view>
<view class="card-desc">{{ card.content }}</view>
<view class="card-footer">
<view class="cta-btn" @tap.stop="onCardClick(card)">
{{ getCtaText(card.type) }}
</view>
</view>
</view>
</view>
</view>
<view v-if="loading" class="loading-text">加载中...</view>
<view v-if="!hasMore && recommendList.length > 0" class="no-more-text"
>没有更多了</view
>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue";
import {
onPullDownRefresh,
onShareAppMessage,
onReachBottom,
onLoad,
} from "@dcloudio/uni-app";
import { getBavBarHeight } from "@/utils/system";
import { getRecommendList, getRandomGreeting } from "@/api/system";
import { getShareToken, saveViewRequest } from "@/utils/common.js";
const countdownText = ref("");
const updateCountdown = () => {
const now = new Date();
const springFestival = new Date("2026-02-17T00:00:00"); // 2026春节
// 只比较日期,忽略时分秒
now.setHours(0, 0, 0, 0);
springFestival.setHours(0, 0, 0, 0);
const diffTime = now.getTime() - springFestival.getTime();
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (days < 0) {
countdownText.value = `距春节还有 ${Math.abs(days)}`;
} else if (days === 0) {
countdownText.value = "大年初一";
} else {
const cnNums = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
if (days < 10) {
countdownText.value = `大年初${cnNums[days]}`;
} else if (days < 15) {
const sub = days + 1 - 10;
const subCn = ["一", "二", "三", "四", "五"][sub - 1];
countdownText.value = `正月十${subCn}`;
} else if (days === 14) {
// days=14是第15天
countdownText.value = "元宵节";
} else {
countdownText.value = "蛇年大吉";
}
}
};
const todayDate = ref("");
const dailyGreeting = ref("");
onLoad((options) => {
if (options.shareToken) saveViewRequest(options.shareToken, "index");
updateCountdown();
getRandomGreetingText();
fetchRecommendList();
const date = new Date();
todayDate.value = `${date.getMonth() + 1}${date.getDate()}`;
// Daily Inspiration Logic
// const startOfYear = new Date(date.getFullYear(), 0, 0);
// const diff = date - startOfYear;
// const oneDay = 1000 * 60 * 60 * 24;
// const dayOfYear = Math.floor(diff / oneDay);
// const index = dayOfYear % inspirationList.length;
// dailyGreeting.value = inspirationList[index];
});
onShareAppMessage(async () => {
const shareToken = await getShareToken("index");
return {
title: "新春祝福",
path: `/pages/index/index?shareToken=${shareToken}`,
imageUrl:
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
};
});
const getRandomGreetingText = async () => {
const content = await getRandomGreeting();
dailyGreeting.value =
content || "烟火起,照人间,举杯敬此年。烟花落,看人间,家家户户皆团圆。";
};
const useGreeting = () => {
uni.setStorageSync("TEMP_BLESSING_TEXT", dailyGreeting.value);
uni.switchTab({
url: "/pages/make/index",
});
};
const features = ref([
{
title: "新春祝福卡片",
subtitle: "定制专属贺卡吧",
icon: "/static/icon/celebrate.png",
type: "card",
},
{
title: "新年运势",
subtitle: "抽取新年关键词",
icon: "/static/icon/yunshi.png",
type: "fortune",
},
{
title: "新春头像",
subtitle: "焕上节日新饰",
icon: "/static/icon/guashi.png",
type: "avatar_decor",
},
{
title: "精美壁纸",
subtitle: "获取精美壁纸",
icon: "/static/icon/bizhi.png",
type: "wallpaper",
},
]);
const recommendList = ref([]);
const page = ref(1);
const hasMore = ref(true);
const loading = ref(false);
const fetchRecommendList = async (isRefresh = false) => {
if (loading.value || (!hasMore.value && !isRefresh)) return;
loading.value = true;
if (isRefresh) {
page.value = 1;
hasMore.value = true;
}
try {
const res = await getRecommendList(page.value);
const list = res?.list || [];
if (isRefresh) {
recommendList.value = list;
} else {
recommendList.value = [...recommendList.value, ...list];
}
if (res.hasNext !== undefined) {
hasMore.value = res.hasNext;
} else {
// Fallback if API doesn't return hasNext
if (list.length < 10) hasMore.value = false;
}
if (list.length > 0) {
page.value++;
} else {
hasMore.value = false;
}
} catch (e) {
console.error(e);
} finally {
loading.value = false;
}
};
const getTagText = (tag) => {
const map = {
hot: "热门",
new: "新款",
featured: "精选",
hot2: "爆款",
};
return map[tag] || tag;
};
const getCtaText = (type) => {
const map = {
frame: "去制作",
decor: "去装饰",
avatar: "去查看",
card: "去写祝福",
fortune: "去抽取",
};
return map[type] || "立即查看";
};
const onCardClick = (card) => {
// 构造传递的数据
const query = `recommendId=${card.recommendId || ""}&type=${card.type || ""}&imageUrl=${encodeURIComponent(card.imageUrl || "")}`;
if (
card.scene === "avatar_download" ||
["frame", "decor", "avatar"].includes(card.type)
) {
uni.navigateTo({
url: `/pages/avatar/index?${query}`,
});
return;
}
// Default fallback based on type
if (card.type === "card") {
// 贺卡制作通常是 Tab 页,通过 Storage 传递参数
uni.setStorageSync("RECOMMEND_CARD_DATA", {
recommendId: card.recommendId,
imageUrl: card.imageUrl,
type: card.type,
});
uni.switchTab({ url: "/pages/make/index" });
} else if (card.type === "fortune") {
uni.navigateTo({ url: "/pages/fortune/index" });
} else {
// 默认跳转到头像页
uni.navigateTo({
url: `/pages/avatar/index?${query}`,
});
}
};
const onFeatureTap = (item) => {
if (item.type === "fortune") {
uni.navigateTo({ url: "/pages/fortune/index" });
return;
}
if (item.type === "card") {
uni.switchTab({ url: "/pages/make/index" });
return;
}
if (item.type === "avatar_decor" || item.type === "avatar_frame") {
uni.navigateTo({ url: "/pages/avatar/index" });
return;
}
if (item.type === "wallpaper") {
uni.navigateTo({ url: "/pages/wallpaper/index" });
return;
}
uni.showToast({ title: `进入:${item.title}`, icon: "none" });
};
onReachBottom(() => {
fetchRecommendList();
});
onPullDownRefresh(async () => {
await fetchRecommendList(true);
setTimeout(() => {
uni.stopPullDownRefresh();
uni.showToast({ title: "已为你更新内容", icon: "success" });
}, 600);
});
</script>
<style lang="scss" scoped>
.spring-page {
min-height: 100vh;
box-sizing: border-box;
background: #f8f6f6;
}
/* 顶部 Banner */
.hero {
position: relative;
margin: 24rpx auto;
padding: 32rpx;
height: 324rpx;
width: 92vw;
border-radius: 24rpx;
color: #fff;
background: url("http://file.lihailezzc.com/77ea2597-569c-4f13-b5af-22606742adcfssss.jpg");
background-size: cover;
overflow: hidden;
.hero-badge {
display: inline-flex;
align-items: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 999rpx;
padding: 8rpx 16rpx;
font-size: 22rpx;
.badge-dot {
margin-right: 8rpx;
}
}
.hero-title {
margin-top: 24rpx;
display: flex;
align-items: baseline;
.year {
font-size: 44rpx;
font-weight: 700;
margin-right: 12rpx;
}
.main {
font-size: 44rpx;
font-weight: 700;
}
}
.hero-sub {
margin-top: 8rpx;
font-size: 24rpx;
opacity: 0.9;
}
.hero-decor {
position: absolute;
right: 12rpx;
bottom: 12rpx;
width: 160rpx;
height: 160rpx;
opacity: 0.3;
border-radius: 16rpx;
}
}
/* 功能入口宫格 - 2x2 */
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 20rpx;
padding: 0 24rpx;
margin-top: 8rpx;
.feature-item {
position: relative;
display: flex;
align-items: left;
flex-direction: column;
padding: 20rpx;
border-radius: 18rpx;
background: #fff;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
}
.feature-item::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 60rpx;
height: 60rpx;
border-bottom-left-radius: 80rpx;
z-index: 1;
}
.feature-item:nth-child(1)::after,
.feature-item:nth-child(2)::after {
background: #fef2f2; /* 可换成你的主题粉 */
}
.feature-item:nth-child(3)::after,
.feature-item:nth-child(4)::after {
background: #fffbec; /* 温柔一点的黄 */
}
.feature-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
padding: 5rpx;
}
.feature-texts {
margin-top: 20rpx;
display: flex;
flex-direction: column;
.feature-title {
font-size: 28rpx;
color: #222;
font-weight: 600;
}
.feature-sub {
font-size: 22rpx;
color: #888;
margin-top: 4rpx;
}
}
}
/* 今日灵感 */
.daily-section {
margin: 24rpx 24rpx 0;
.daily-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.daily-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.daily-date {
font-size: 24rpx;
color: #999;
font-family: monospace;
}
}
.daily-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 59, 48, 0.08);
position: relative;
overflow: hidden;
}
.daily-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 8rpx;
height: 100%;
background: #ff3b30;
}
.daily-content {
position: relative;
padding: 10rpx 20rpx;
.quote-mark {
font-size: 60rpx;
color: #ff3b30;
opacity: 0.2;
position: absolute;
line-height: 1;
font-family: serif;
}
.quote-mark:first-child {
top: -10rpx;
left: -10rpx;
}
.quote-mark.right {
bottom: -20rpx;
right: 0;
}
.daily-text {
font-size: 30rpx;
color: #444;
line-height: 1.8;
font-style: italic;
display: block;
text-align: justify;
}
}
.daily-actions {
display: flex;
justify-content: flex-end;
margin-top: 30rpx;
gap: 20rpx;
.action-btn {
display: flex;
align-items: center;
padding: 12rpx 24rpx;
border-radius: 999rpx;
font-size: 24rpx;
transition: all 0.2s;
.icon {
margin-right: 6rpx;
font-size: 26rpx;
}
}
.action-btn.copy {
background: #f5f5f5;
color: #666;
}
.action-btn.use {
background: #ff3b30;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(255, 59, 48, 0.3);
}
}
}
/* 通用区块标题 */
.section {
margin-top: 28rpx;
}
.section-header {
display: flex;
align-items: center;
padding: 0 24rpx;
.section-bar {
width: 10rpx;
height: 30rpx;
border-radius: 6rpx;
background: #ff3b30;
margin-right: 12rpx;
}
.section-title {
font-size: 28rpx;
color: #222;
flex: 1;
font-weight: 600;
}
.section-more {
font-size: 24rpx;
color: #ff3b30;
}
}
/* 大家都在用 - 网格列表 */
.use-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 24rpx;
margin-top: 16rpx;
padding-bottom: 40rpx;
}
.use-card {
background: #fff;
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
}
.card-cover-wrap {
position: relative;
width: 100%;
padding-bottom: 120%; /* 竖向卡片 */
background: #f5f5f5;
}
.card-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.card-tag {
position: absolute;
top: 12rpx;
left: 12rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-size: 20rpx;
color: #fff;
font-weight: 500;
&.tag--hot {
background: linear-gradient(135deg, #ff3b30, #ff9500);
}
&.tag--featured {
background: linear-gradient(135deg, #007aff, #5ac8fa);
}
&.tag--hot2 {
background: linear-gradient(135deg, #ff2d55, #ff375f);
}
&.tag--new {
background: linear-gradient(135deg, #5856d6, #af52de);
}
&.tag--default {
background: rgba(0, 0, 0, 0.5);
}
}
.card-info {
padding: 16rpx;
flex: 1;
display: flex;
flex-direction: column;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-desc {
font-size: 22rpx;
color: #999;
line-height: 1.4;
margin-bottom: 16rpx;
/* 限制2行 */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.card-footer {
margin-top: auto;
display: flex;
justify-content: flex-end;
}
.cta-btn {
font-size: 22rpx;
color: #ff3b30;
background: rgba(255, 59, 48, 0.08);
padding: 8rpx 20rpx;
border-radius: 999rpx;
font-weight: 500;
}
.loading-text,
.no-more-text {
text-align: center;
font-size: 24rpx;
color: #999;
padding: 20rpx 0;
width: 100%;
}
</style>