Files
spring-festival-greetings/pages/wallpaper/detail.vue
2026-01-28 21:13:27 +08:00

538 lines
12 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" :style="{ paddingTop: navHeight + 'px' }">
<!-- Navbar -->
<view
class="nav-bar"
:style="{ height: navHeight + 'px', paddingTop: statusBarHeight + 'px' }"
>
<view class="nav-content">
<view class="back" @tap="goBack">
<text class="back-icon"></text>
</view>
<text class="title">壁纸详情</text>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="content-container">
<!-- Sharer Info -->
<view class="sharer-info" v-if="detailData.from">
<image
:src="detailData.from.avatar || '/static/default-avatar.png'"
class="avatar"
mode="aspectFill"
/>
<view class="info-text">
<view class="nickname">{{
detailData.from.nickname || "神秘好友"
}}</view>
<view class="action-text">给你分享了一张2026新春精美壁纸</view>
<view class="sub-text"> 2026 且马贺岁</view>
</view>
</view>
<!-- Wallpaper Preview -->
<view class="preview-card">
<view class="preview-badge">PREVIEW</view>
<image
:src="detailData.imageUrl"
mode="widthFix"
class="main-image"
@tap="previewImage"
/>
</view>
<!-- Action Buttons -->
<view class="action-buttons">
<button class="btn primary-btn" @tap="goToIndex">
<text class="btn-icon"></text>
<text>我也要领同款壁纸</text>
</button>
</view>
<!-- More Wallpapers -->
<view class="more-section">
<view class="section-header">
<text class="section-title">更多精美新春壁纸</text>
<view class="more-link" @tap="goToIndex">
<text>查看更多</text>
<text class="arrow"></text>
</view>
</view>
<view class="more-grid">
<view
v-for="(item, index) in recommendList"
:key="index"
class="grid-item"
@tap="onRecommendClick(item)"
>
<image :src="item.imageUrl" mode="aspectFill" class="grid-img" />
<text class="item-title">{{ item.title || "新春壁纸" }}</text>
</view>
</view>
</view>
<!-- Fortune Banner -->
<!-- Wallpaper 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="goToAvatar">
<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="footer">
<view class="footer-divider">
<view class="line"></view>
<text class="footer-text">2026 HAPPY NEW YEAR</text>
<view class="line"></view>
</view>
<view class="footer-sub">新春祝福 · 传递温情</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { getBavBarHeight } from "@/utils/system";
import { getPageDetail } from "@/api/system";
import { getWallpaperList } from "@/api/wallpaper";
import { saveViewRequest } from "@/utils/common.js";
const navHeight = getBavBarHeight();
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const detailData = ref({
imageUrl: "",
from: null,
});
const recommendList = ref([]);
const shareToken = ref("");
onLoad(async (options) => {
if (options.shareToken) {
shareToken.value = options.shareToken;
await fetchDetail();
}
fetchRecommend();
});
const fetchDetail = async () => {
try {
const res = await getPageDetail(shareToken.value);
saveViewRequest(shareToken.value, "wallpaper_download", res.id);
if (res) {
detailData.value = res;
}
} catch (e) {
console.error("Failed to fetch detail", e);
uni.showToast({
title: "获取详情失败",
icon: "none",
});
}
};
const fetchRecommend = async () => {
try {
// Try to get wallpapers. Assuming empty category returns a default list or we use a known category if available.
// For now, passing empty string as categoryId.
const res = await getWallpaperList("", 1);
if (res && res.list) {
// Take first 3 items
recommendList.value = res.list.slice(0, 3);
}
} catch (e) {
console.error("Failed to fetch recommendations", e);
}
};
const goBack = () => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
uni.reLaunch({
url: "/pages/index/index",
});
}
};
const goToIndex = () => {
uni.navigateTo({
url: "/pages/wallpaper/index",
});
};
const goToFortune = () => {
uni.navigateTo({
url: "/pages/fortune/index",
});
};
const previewImage = () => {
if (detailData.value.imageUrl) {
uni.previewImage({
urls: [detailData.value.imageUrl],
});
}
};
const onRecommendClick = (item) => {
// If clicking a recommended item, maybe we just preview it or go to wallpaper list?
// Since this is a "detail" page for a specific share, clicking another wallpaper
// might normally go to its detail, but we don't have a generic detail page logic
// (unless we reuse this page with a new token, but these items might not have tokens).
// Best fallback: Preview it.
uni.previewImage({
urls: [item.imageUrl],
});
};
const goToGreeting = () => {
uni.switchTab({ url: "/pages/make/index" });
};
const goToAvatar = () => {
uni.navigateTo({
url: "/pages/avatar/index",
});
};
</script>
<style lang="scss" scoped>
.detail-page {
min-height: 100vh;
background: #ffffff;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 100;
background-color: transparent; // Make it transparent as per design
.nav-content {
height: 44px; // Standard nav height
display: flex;
align-items: center;
justify-content: center;
position: relative;
.back {
position: absolute;
left: 16px;
height: 100%;
display: flex;
align-items: center;
padding: 0 10px;
.back-icon {
font-size: 32px;
color: #333;
font-weight: 300;
}
}
.title {
font-size: 18px;
font-weight: 600;
color: #333;
}
}
}
.content-scroll {
flex: 1;
height: 0; // Important for scroll-view in flex container
}
.content-container {
padding: 20px;
padding-bottom: 40px;
}
.sharer-info {
display: flex;
align-items: center;
margin-bottom: 24px;
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
margin-right: 12px;
border: 2px solid #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.info-text {
.nickname {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.action-text {
font-size: 14px;
color: #666;
margin-bottom: 2px;
}
.sub-text {
font-size: 12px;
color: #ff3b30;
font-weight: 500;
}
}
}
.preview-card {
position: relative;
width: 100%;
background: #fff;
border-radius: 16px;
padding: 12px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.04);
border: 1px solid #f5f5f5;
margin-bottom: 24px;
.preview-badge {
position: absolute;
top: 24px;
right: 24px;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
color: #fff;
font-size: 10px;
padding: 4px 8px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
z-index: 10;
}
.main-image {
width: 100%;
border-radius: 12px;
display: block;
background-color: #fafafa; // Placeholder color
}
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 32px;
.btn {
width: 100%;
height: 52px;
border-radius: 26px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
border: none;
margin: 0;
transition: all 0.2s;
.btn-icon {
margin-right: 8px;
font-size: 18px;
}
&.primary-btn {
background: linear-gradient(90deg, #ff3b30 0%, #ff6b6b 100%);
color: #fff;
box-shadow: 0 8px 20px rgba(255, 59, 48, 0.15);
}
&.secondary-btn {
background: #f9f9f9;
color: #333;
border: 1px solid #eee;
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
}
.more-section {
margin-bottom: 32px;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.more-link {
display: flex;
align-items: center;
font-size: 14px;
color: #ff3b30;
.arrow {
margin-left: 2px;
font-size: 16px;
}
}
}
.more-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
.grid-item {
display: flex;
flex-direction: column;
.grid-img {
width: 100%;
aspect-ratio: 9/16;
border-radius: 8px;
margin-bottom: 8px;
background-color: #f5f5f5;
}
.item-title {
font-size: 12px;
color: #333;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.fortune-banner-wrap {
margin-bottom: 40px;
}
.wallpaper-banner {
background: #fafafa;
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 60rpx;
border: 1px solid #f5f5f5;
}
.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;
}
.footer {
text-align: center;
padding-bottom: 20px;
.footer-divider {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
.line {
width: 40px;
height: 1px;
background: #e0e0e0;
}
.footer-text {
margin: 0 12px;
font-size: 12px;
color: #999;
letter-spacing: 1px;
font-weight: 600;
}
}
.footer-sub {
font-size: 10px;
color: #ccc;
}
}
</style>