Files
spring-festival-greetings/pages/index/index.vue
2026-02-24 20:49:37 +08:00

846 lines
19 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="home-container"
:style="{
paddingTop: statusBarHeight + 12 + 'px',
}"
>
<!-- 顶部用户信息栏 -->
<view class="header-section">
<view class="user-info">
<image
class="user-avatar"
:src="userInfo.avatarUrl || '/static/default-avatar.png'"
mode="aspectFill"
/>
<view class="user-texts">
<text class="greeting-info">{{ greetingText }}</text>
<text class="user-name" @tap="handleLogin">
{{ userInfo.nickName || "点击登录" }}
</text>
</view>
</view>
</view>
<!-- 主卡片 (Lucky Status) -->
<view class="main-card">
<view class="card-bg-decor"></view>
<view class="card-top">
<view class="lucky-status">
<text class="status-label">LUCKY STATUS</text>
<text class="status-value"
>{{ luckyScore }}<text class="percent">%</text></text
>
</view>
<view class="date-box">
<view class="date-row">
<uni-icons type="calendar" size="14" color="#8b5a5a" />
<text class="date-text"
>{{ lunarDate.year }}-{{ lunarDate.month
}}{{ lunarDate.day }}</text
>
</view>
<view class="yi-ji-row">
<text class="label"></text
><text class="content">{{ lunarDate.yi }}</text>
</view>
<view class="yi-ji-row">
<text class="label"></text
><text class="content">{{ lunarDate.ji }}</text>
</view>
</view>
</view>
<view class="card-center">
<view class="lucky-color-tag"> 幸运色{{ luckyColor }} </view>
</view>
<view class="card-bottom">
<button class="open-lucky-btn" @tap="onOpenLucky">
<uni-icons
type="star-filled"
size="18"
color="#d81e06"
style="margin-right: 12rpx"
/>
<text>开启今日好运</text>
</button>
<view class="progress-section">
<view class="progress-info">
<text>已连续领好运 {{ continuousDays }} </text>
<text>{{ currentMonthDays }} / {{ totalMonthDays }}</text>
</view>
<view class="progress-bar-bg">
<view
class="progress-bar-fill"
:style="{
width: (currentMonthDays / totalMonthDays) * 100 + '%',
}"
></view>
</view>
</view>
</view>
</view>
<!-- 大家都在用 -->
<view class="section-container compact-section">
<view class="section-header">
<view class="title-left-decor"></view>
<uni-icons
type="heart-filled"
size="18"
color="#d81e06"
style="margin-right: 8rpx"
/>
<text class="section-title">大家都在用</text>
</view>
<view class="feature-row">
<view class="feature-item" @tap="navTo('/pages/avatar/download')">
<view class="left-content">
<view class="icon-wrap yellow-bg">
<image
src="/static/icon/guashi.png"
mode="aspectFit"
class="feature-icon"
/>
</view>
<view class="text-content">
<text class="feature-name">制作今日头像</text>
<!-- <text class="feature-desc">限定 · 如意边框</text> -->
</view>
</view>
</view>
<view class="feature-item" @tap="navTo('/pages/wallpaper/index')">
<view class="left-content">
<view class="icon-wrap blue-bg">
<image
src="/static/icon/bizhi.png"
mode="aspectFit"
class="feature-icon"
/>
</view>
<view class="text-content">
<text class="feature-name">好运祝福壁纸</text>
<!-- <text class="feature-desc">高清 · 福气盈门</text> -->
</view>
</view>
</view>
</view>
</view>
<!-- 排行榜单 -->
<view class="section-container">
<view class="section-header">
<view class="title-left-decor"></view>
<text class="section-title">排行榜单</text>
<view class="rank-tabs-mini">
<view
class="rank-tab-mini"
:class="{ active: currentTab === 'all' }"
@tap="switchTab('all')"
>全部</view
>
<view
class="rank-tab-mini"
:class="{ active: currentTab === 'wallpaper' }"
@tap="switchTab('wallpaper')"
>壁纸</view
>
<view
class="rank-tab-mini"
:class="{ active: currentTab === 'frame' }"
@tap="switchTab('frame')"
>头像</view
>
<view
class="rank-tab-mini"
:class="{ active: currentTab === 'card' }"
@tap="switchTab('card')"
>贺卡</view
>
</view>
</view>
<!-- Ranking List -->
<view class="rank-list">
<view
class="rank-item"
v-for="(item, index) in rankingList"
:key="item.id"
@tap="onRankItemTap(item)"
>
<view class="rank-num" :class="'rank-' + (index + 1)">{{
index + 1
}}</view>
<image :src="item.thumb" mode="aspectFill" class="rank-thumb" />
<view class="rank-info">
<view class="rank-title-row">
<text class="rank-title">{{ item.title }}</text>
</view>
<view class="rank-meta">
<view class="tag-hot" v-if="item.isHot">HOT</view>
<text class="usage-count">{{ item.usageCount }} 人在用</text>
</view>
</view>
<view class="rank-likes">
<uni-icons type="heart-filled" size="16" color="#a85a5a" />
<text class="likes-num">{{ item.likes }}</text>
</view>
</view>
</view>
<view class="rank-footer"> 查看前 10 </view>
</view>
<!-- Floating Share Button -->
<!-- <button class="float-share-btn" open-type="share">
<uni-icons type="upload" size="18" color="#d81e06" />
<text>分享今日运势</text>
</button> -->
<view class="bottom-spacer"></view>
<!-- 登录弹窗 -->
<LoginPopup />
<!-- 运势抽奖弹窗 -->
<LuckyPopup ref="luckyPopupRef" />
</view>
</template>
<script setup>
import { ref, onMounted, computed } from "vue";
import { getStatusBarHeight } from "@/utils/system";
import { onShareAppMessage, onShareTimeline, onShow } from "@dcloudio/uni-app";
import { useUserStore } from "@/stores/user";
import { getRecommendList } from "@/api/system";
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
import LuckyPopup from "@/components/LuckyPopup/LuckyPopup.vue";
const userStore = useUserStore();
const statusBarHeight = ref(getStatusBarHeight());
const loginPopupRef = ref(null);
const luckyPopupRef = ref(null);
const userInfo = computed(() => userStore?.userInfo || {});
const isLoggedIn = computed(() => !!userStore.userInfo.nickName);
const greetingText = computed(() => {
const hour = new Date().getHours();
if (hour < 6) return "凌晨好";
if (hour < 9) return "早上好";
if (hour < 12) return "上午好";
if (hour < 14) return "中午好";
if (hour < 17) return "下午好";
if (hour < 19) return "傍晚好";
return "晚上好";
});
const handleLogin = () => {
if (!userInfo.value.nickName) {
uni.$emit("show-login-popup");
}
};
const luckyScore = ref(98);
const luckyColor = ref("赤金朱红");
const continuousDays = ref(1); // 连续天数
const currentMonthDays = ref(new Date().getDate()); // 当前月签到天数(模拟为当天日期)
const totalMonthDays = computed(() => {
const date = new Date();
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}); // 当前月总天数
const currentTab = ref("all");
// 模拟农历数据
const lunarDate = ref({
year: "2026",
month: "正月",
day: "初一",
yi: "开光、祈福、求嗣",
ji: "安葬、动土",
});
const rankingList = ref([]);
onMounted(() => {
fetchRankingData();
});
onShow(() => {
if (userStore.userInfo) {
// userInfo.value = userStore.userInfo; // 已改为 computed
}
});
const fetchRankingData = async () => {
// 模拟数据或调用 API
// 实际项目中可以调用 getRecommendList 并根据 currentTab 筛选
// 这里先使用 Mock 数据展示 UI
rankingList.value = [
{
id: 1,
title: "龙马精神限定框",
thumb: "https://file.lihailezzc.com/resource/avatar_frame_horse_gold.png",
usageCount: "2.4w",
likes: "12.8k",
isHot: true,
type: "frame",
},
{
id: 2,
title: "锦绣江山动态壁纸",
thumb: "https://file.lihailezzc.com/resource/wallpaper_snow_red.png",
usageCount: "1.8w",
likes: "9.2k",
isHot: false,
type: "wallpaper",
},
{
id: 3,
title: "万事大吉贺卡模板",
thumb: "https://file.lihailezzc.com/resource/card_template_1.png",
usageCount: "9.5k",
likes: "4.1k",
isHot: false,
type: "card",
},
];
};
const switchTab = (tab) => {
currentTab.value = tab;
// TODO: 根据 tab 筛选或重新请求数据
// 此处仅做 UI 演示,实际需对接筛选逻辑
};
const onNoticeTap = () => {
uni.showToast({ title: "暂无新消息", icon: "none" });
};
const onWalletTap = () => {
uni.navigateTo({ url: "/pages/mine/vip" });
};
const onOpenLucky = () => {
if (!isLoggedIn.value) {
uni.$emit("show-login-popup");
return;
}
luckyPopupRef.value?.open();
};
const navTo = (url) => {
uni.navigateTo({ url });
};
const onRankItemTap = (item) => {
if (item.type === "frame") {
uni.navigateTo({ url: `/pages/avatar/index?id=${item.id}` });
} else if (item.type === "card") {
uni.switchTab({ url: "/pages/make/index" });
} else if (item.type === "wallpaper") {
uni.navigateTo({ url: `/pages/wallpaper/index` });
}
};
onShareAppMessage(() => {
return {
title: "开启你的2026新春好运",
path: "/pages/index/index",
};
});
onShareTimeline(() => {
return {
title: "开启你的2026新春好运",
};
});
</script>
<style lang="scss" scoped>
.home-container {
min-height: 100vh;
background-color: #fbfbf9; /* 柔和的米色背景 */
padding-left: 32rpx;
padding-right: 32rpx;
padding-bottom: 120rpx;
box-sizing: border-box;
}
/* 顶部用户信息 */
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.user-info {
display: flex;
align-items: center;
.user-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
border: 2rpx solid #fff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
margin-right: 16rpx;
}
.user-texts {
display: flex;
flex-direction: column;
.greeting-info {
font-size: 20rpx;
color: #999;
margin-bottom: 2rpx;
}
.user-name {
font-size: 26rpx;
font-weight: bold;
color: #333;
}
}
}
}
/* 主卡片 (Lucky Status) */
.main-card {
position: relative;
width: 100%;
height: 640rpx;
background: linear-gradient(180deg, #fcece8 0%, #f7dcd6 100%);
border-radius: 40rpx;
padding: 40rpx;
box-sizing: border-box;
box-shadow: 0 20rpx 40rpx rgba(189, 87, 87, 0.15);
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
margin-bottom: 48rpx;
.card-bg-decor {
position: absolute;
top: -50rpx;
right: -50rpx;
width: 300rpx;
height: 300rpx;
background: radial-gradient(
circle,
rgba(255, 255, 255, 0.4) 0%,
rgba(255, 255, 255, 0) 70%
);
border-radius: 50%;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
z-index: 2;
.lucky-status {
display: flex;
flex-direction: column;
.status-label {
font-size: 22rpx;
color: #a85a5a;
letter-spacing: 2rpx;
font-weight: 600;
margin-bottom: 8rpx;
}
.status-value {
font-size: 88rpx;
font-weight: bold;
color: #a85a5a;
line-height: 1;
.percent {
font-size: 40rpx;
margin-left: 4rpx;
}
}
}
.date-box {
background: rgba(255, 255, 255, 0.6);
border-radius: 20rpx;
padding: 20rpx;
backdrop-filter: blur(10rpx);
.date-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.date-text {
font-size: 24rpx;
color: #8b5a5a;
font-weight: bold;
margin-left: 8rpx;
}
}
.yi-ji-row {
font-size: 20rpx;
margin-bottom: 4rpx;
color: #666;
.label {
color: #999;
}
.content {
color: #555;
}
}
}
}
.card-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
z-index: 2;
.lucky-color-tag {
background: #fff;
padding: 12rpx 36rpx;
border-radius: 40rpx;
font-size: 26rpx;
color: #d81e06;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(216, 30, 6, 0.15);
letter-spacing: 2rpx;
}
}
.card-bottom {
z-index: 2;
.open-lucky-btn {
width: 100%;
height: 108rpx;
background: #fff;
border-radius: 54rpx;
display: flex;
align-items: center;
justify-content: center;
color: #d81e06;
font-size: 34rpx;
font-weight: 800;
margin-bottom: 32rpx;
box-shadow: 0 8rpx 20rpx rgba(216, 30, 6, 0.2);
letter-spacing: 2rpx;
&::after {
border: none;
}
}
.progress-section {
.progress-info {
display: flex;
justify-content: space-between;
font-size: 20rpx;
color: #a85a5a;
margin-bottom: 12rpx;
opacity: 0.8;
}
.progress-bar-bg {
width: 100%;
height: 8rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 4rpx;
overflow: hidden;
.progress-bar-fill {
height: 100%;
background: #d81e06; // 或渐变色
border-radius: 4rpx;
}
}
}
}
}
/* 通用 Section 样式 */
.section-container {
margin-bottom: 48rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.title-left-decor {
width: 6rpx;
height: 28rpx;
background: #d81e06;
border-radius: 4rpx;
margin-right: 12rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
flex: 1;
}
.header-right {
display: flex;
align-items: center;
.refresh-text {
font-size: 22rpx;
color: #999;
margin-left: 6rpx;
}
}
}
}
/* 大家都在用 */
.compact-section {
.section-header {
margin-bottom: 20rpx;
}
}
.feature-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
.feature-item {
width: 48%;
background: #fff;
border-radius: 24rpx;
padding: 20rpx;
display: flex;
align-items: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
box-sizing: border-box;
margin-bottom: 20rpx;
.left-content {
display: flex;
align-items: center;
width: 100%;
.icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
flex-shrink: 0;
&.yellow-bg {
background: #fff8e1;
}
&.blue-bg {
background: #e3f2fd;
}
.feature-icon {
width: 36rpx;
height: 36rpx;
}
}
.text-content {
display: flex;
flex-direction: column;
overflow: hidden;
.feature-name {
font-size: 26rpx;
font-weight: bold;
color: #333;
margin-bottom: 2rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.feature-desc {
font-size: 20rpx;
color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
/* 排行榜单 */
.rank-tabs-mini {
display: flex;
margin-left: auto; /* Push to right */
background: #eee;
border-radius: 30rpx;
padding: 4rpx;
.rank-tab-mini {
padding: 8rpx 20rpx;
border-radius: 26rpx;
font-size: 22rpx;
color: #666;
transition: all 0.3s;
&.active {
background: #fff;
color: #333;
font-weight: bold;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
}
}
.rank-list {
background: #fff;
border-radius: 32rpx;
padding: 16rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
.rank-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 2rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.rank-num {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: bold;
color: #999;
margin-right: 24rpx;
border-radius: 50%;
&.rank-1 {
background: #ffd700;
color: #fff;
}
&.rank-2 {
background: #c0c0c0;
color: #fff;
}
&.rank-3 {
background: #cd7f32;
color: #fff;
}
}
.rank-thumb {
width: 88rpx;
height: 88rpx;
border-radius: 16rpx;
background: #eee;
margin-right: 24rpx;
}
.rank-info {
flex: 1;
.rank-title-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.rank-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
}
.rank-meta {
display: flex;
align-items: center;
.tag-hot {
font-size: 18rpx;
color: #d81e06;
background: #ffebee;
padding: 2rpx 8rpx;
border-radius: 8rpx;
margin-right: 12rpx;
font-weight: 600;
}
.usage-count {
font-size: 22rpx;
color: #999;
}
}
}
.rank-likes {
display: flex;
align-items: center;
.likes-num {
font-size: 24rpx;
color: #a85a5a;
margin-left: 8rpx;
font-weight: 500;
}
}
}
}
.rank-footer {
text-align: center;
font-size: 22rpx;
color: #ccc;
padding: 24rpx 0;
}
/* 悬浮分享按钮 */
.float-share-btn {
position: fixed;
right: 32rpx;
bottom: 60rpx; /* 避开 tabBar */
background: #fff;
border-radius: 40rpx;
padding: 16rpx 32rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 24rpx rgba(216, 30, 6, 0.2);
border: 2rpx solid #ffebee;
z-index: 100;
text {
font-size: 26rpx;
color: #d81e06;
font-weight: 600;
margin-left: 12rpx;
}
}
.bottom-spacer {
height: 120rpx;
}
</style>