Compare commits

...

6 Commits

Author SHA1 Message Date
zzc
227a5b035e fix: recommend list 2026-01-28 15:55:26 +08:00
zzc
e3edccbd65 fix: recommend list 2026-01-28 15:44:30 +08:00
zzc
b22bbb8f7c optimize: avatar page share reward 2026-01-28 10:55:39 +08:00
zzc
29498e4994 optimize: avatar page share reward 2026-01-28 10:35:31 +08:00
zzc
4a1cbf1f5a optimize: avatar page share reward 2026-01-28 09:31:58 +08:00
zzc
2fa6584e0c optimize: avatar page share reward 2026-01-28 08:55:59 +08:00
7 changed files with 485 additions and 202 deletions

View File

@@ -21,6 +21,14 @@ export const getAvatarFrameList = async (page = 1) => {
});
};
export const avatarCreateComplete = async (data) => {
return request({
url: "/api/blessing/avatar/create/complete",
method: "POST",
data,
});
};
export const avatarDownloadRecord = async (data) => {
return request({
url: "/api/blessing/avatar/download",

View File

@@ -45,3 +45,10 @@ export const viewRecord = async (data) => {
data,
});
};
export const getRecommendList = async (page = 1) => {
return request({
url: `/api/blessing/recommend/list?page=${page}`,
method: "get",
});
};

View File

@@ -47,7 +47,6 @@
<text>加载中...</text>
</view>
<!-- Decorative Elements -->
<view class="decor-tag">🐰</view>
<view class="card-footer-text">
<text class="icon">🌸</text> 2026 丙午马年限定
</view>
@@ -59,9 +58,6 @@
<button class="btn primary-btn" @tap="goToMake">
<text class="icon">🎨</text> 我也要领同款制作
</button>
<button class="btn secondary-btn" @tap="saveImage">
<text class="icon">📥</text> 保存到相册
</button>
</view>
<!-- Recommended Frames -->
@@ -89,13 +85,33 @@
</view>
<!-- 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="goToWallpaper">
<view class="banner-icon">
<text>🖼</text>
</view>
<view class="banner-content">
<text class="banner-title">去挑选更多壁纸</text>
<text class="banner-desc">新年新气象全套皮肤限时领</text>
<text class="banner-title">去挑选新年壁纸</text>
<text class="banner-desc">精选新年壁纸让手机也过年</text>
</view>
<text class="banner-arrow"></text>
</view>
@@ -117,7 +133,8 @@
import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { getBavBarHeight } from "@/utils/system";
import { getAvatarFrameList, getPageDetail } from "@/api/avatar.js";
import { getAvatarFrameList } from "@/api/avatar.js";
import { getPageDetail } from "@/api/system.js";
const defaultAvatar =
"https://file.lihailezzc.com/resource/d9b329082b32f8305101f708593a4882.png";
@@ -187,43 +204,22 @@ const previewImage = () => {
}
};
const saveImage = () => {
if (!detailData.value?.imageUrl) return;
uni.showLoading({ title: "保存中..." });
uni.downloadFile({
url: detailData.value.imageUrl,
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
// uni.hideLoading();
uni.showToast({ title: "保存成功", icon: "success" });
},
fail: () => {
// uni.hideLoading();
uni.showToast({ title: "保存失败", icon: "none" });
},
});
} else {
// uni.hideLoading();
uni.showToast({ title: "下载失败", icon: "none" });
}
},
fail: () => {
// uni.hideLoading();
uni.showToast({ title: "下载失败", icon: "none" });
},
});
};
const goToMake = () => {
uni.navigateTo({
url: "/pages/avatar/index",
});
};
const goToFortune = () => {
uni.navigateTo({
url: "/pages/fortune/index",
});
};
const goToGreeting = () => {
uni.switchTab({ url: "/pages/make/index" });
};
const goToWallpaper = () => {
uni.navigateTo({
url: "/pages/wallpaper/index",
@@ -234,7 +230,7 @@ const goToWallpaper = () => {
<style lang="scss" scoped>
.avatar-detail-page {
min-height: 100vh;
background: #fff0f5; /* Light Pink Background */
background: #ffffff;
box-sizing: border-box;
}
@@ -245,7 +241,7 @@ const goToWallpaper = () => {
right: 0;
z-index: 100;
box-sizing: border-box;
background: #fff0f5;
background: #ffffff;
}
.nav-content {
@@ -271,9 +267,6 @@ const goToWallpaper = () => {
margin-right: 50rpx; /* Balance back button */
}
.content-scroll {
}
.content-wrap {
padding: 30rpx 40rpx 60rpx;
}
@@ -326,16 +319,17 @@ const goToWallpaper = () => {
/* Main Card */
.main-card {
background: linear-gradient(180deg, #ffffff 0%, #fff5f5 100%);
background: #ffffff;
border-radius: 40rpx;
padding: 24rpx;
box-shadow: 0 20rpx 60rpx rgba(255, 59, 48, 0.15);
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.06);
margin-bottom: 60rpx;
border: 2rpx solid #f5f5f5;
}
.card-inner {
position: relative;
background: #fffaf0;
background: #fff9f9;
border-radius: 30rpx;
padding: 60rpx;
display: flex;
@@ -348,7 +342,7 @@ const goToWallpaper = () => {
height: 400rpx;
border-radius: 20rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
border: 8rpx solid #d63333;
border: 8rpx solid #ff3b30;
}
.loading-box {
@@ -358,7 +352,7 @@ const goToWallpaper = () => {
align-items: center;
justify-content: center;
color: #999;
background: #eee;
background: #f0f0f0;
border-radius: 20rpx;
}
@@ -381,14 +375,14 @@ const goToWallpaper = () => {
.card-footer-text {
margin-top: 40rpx;
font-size: 28rpx;
color: #d63333;
color: #ff3b30;
font-weight: bold;
display: flex;
align-items: center;
background: #fff;
padding: 10rpx 30rpx;
border-radius: 99rpx;
box-shadow: 0 4rpx 10rpx rgba(214, 51, 51, 0.1);
box-shadow: 0 4rpx 10rpx rgba(255, 59, 48, 0.1);
}
.card-footer-text .icon {
@@ -423,8 +417,8 @@ const goToWallpaper = () => {
}
.secondary-btn {
background: #eaeaea;
color: #666;
background: #f5f5f5;
color: #333;
}
.btn .icon {
@@ -478,10 +472,9 @@ const goToWallpaper = () => {
display: flex;
flex-direction: column;
align-items: center;
background: #fff;
background: #f8f8f8;
border-radius: 24rpx;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
}
.frame-img-box {
@@ -490,7 +483,7 @@ const goToWallpaper = () => {
margin-bottom: 16rpx;
border-radius: 50%;
overflow: hidden;
background: #f9f9f9;
background: #fff;
}
.frame-img {
@@ -506,19 +499,18 @@ const goToWallpaper = () => {
/* Wallpaper Banner */
.wallpaper-banner {
background: #fff;
background: #f8f8f8;
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 60rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
}
.banner-icon {
width: 80rpx;
height: 80rpx;
background: #fff0f5;
background: #fff;
border-radius: 20rpx;
display: flex;
align-items: center;
@@ -569,18 +561,18 @@ const goToWallpaper = () => {
.footer-line .line {
width: 60rpx;
height: 2rpx;
background: #ccc;
background: #eee;
}
.footer-line .text {
font-size: 20rpx;
color: #999;
color: #ccc;
margin: 0 20rpx;
letter-spacing: 2rpx;
}
.footer-sub {
font-size: 20rpx;
color: #ccc;
color: #ddd;
}
</style>

View File

@@ -6,17 +6,21 @@
</view>
<view class="preview-card">
<view class="preview-square">
<image class="avatar-img" :src="currentAvatar" mode="aspectFill" />
<image
class="avatar-img"
:src="currentAvatar?.imageUrl"
mode="aspectFill"
/>
<image
v-if="selectedFrame"
class="frame-img"
:src="selectedFrame"
:src="selectedFrame?.imageUrl"
mode="aspectFill"
/>
<image
v-if="selectedDecor"
class="decor-img"
:src="selectedDecor"
:src="selectedDecor?.imageUrl"
mode="aspectFit"
:style="decorStyle"
@touchstart.stop="onTouchStart"
@@ -58,7 +62,11 @@
:class="{ active: currentAvatar === item }"
@tap="currentAvatar = item"
>
<image :src="item" class="avatar-thumb" mode="aspectFill" />
<image
:src="item.imageUrl"
class="avatar-thumb"
mode="aspectFill"
/>
<view v-if="currentAvatar === item" class="check"></view>
</view>
</view>
@@ -88,7 +96,7 @@
:class="{ active: selectedFrame === frame }"
@tap="toggleFrame(frame)"
>
<image :src="frame" class="grid-img" mode="aspectFill" />
<image :src="frame.imageUrl" class="grid-img" mode="aspectFill" />
<view v-if="selectedFrame === frame" class="check"></view>
</view>
</view>
@@ -101,12 +109,14 @@
:class="{ active: selectedDecor === decor }"
@tap="selectedDecor = decor"
>
<image :src="decor" class="grid-img" mode="aspectFit" />
<image :src="decor.imageUrl" class="grid-img" mode="aspectFit" />
<view v-if="selectedDecor === decor" class="check"></view>
</view>
</view>
<canvas
type="2d"
id="avatarCanvas"
canvas-id="avatarCanvas"
class="hidden-canvas"
style="width: 600px; height: 600px"
@@ -134,7 +144,7 @@
class="popup-item"
@tap="selectMoreAvatar(item)"
>
<image :src="item" class="popup-img" mode="aspectFill" />
<image :src="item.imageUrl" class="popup-img" mode="aspectFill" />
</view>
</view>
<view v-if="loading" class="loading-text">加载中...</view>
@@ -162,7 +172,14 @@ import {
getAvatarSystemList,
getAvatarFrameList,
getAvatarDecorList,
avatarCreateComplete,
} from "@/api/avatar.js";
import {
saveRecordRequest,
getShareToken,
generateObjectId,
uploadImage,
} from "@/utils/common.js";
const userStore = useUserStore();
const loginPopupRef = ref(null);
@@ -182,9 +199,9 @@ const decorPage = ref(1);
const decorHasNext = ref(true);
const decorLoading = ref(false);
const currentAvatar = ref("");
const selectedFrame = ref("");
const selectedDecor = ref("");
const currentAvatar = ref(null);
const selectedFrame = ref(null);
const selectedDecor = ref(null);
const activeTab = ref("frame");
// More Popup logic
@@ -201,7 +218,12 @@ const loadFrames = async () => {
const res = await getAvatarFrameList(framePage.value);
const list = res?.list || [];
if (list.length > 0) {
frames.value.push(...list.map((item) => item.imageUrl));
frames.value.push(
...list.map((item) => ({
id: item.id,
imageUrl: item.imageUrl,
})),
);
framePage.value++;
}
if (typeof res.hasNext !== "undefined") {
@@ -223,7 +245,12 @@ const loadDecors = async () => {
const res = await getAvatarDecorList(decorPage.value);
const list = res?.list || [];
if (list.length > 0) {
decors.value.push(...list.map((item) => item.imageUrl));
decors.value.push(
...list.map((item) => ({
id: item.id,
imageUrl: item.imageUrl,
})),
);
decorPage.value++;
}
if (typeof res.hasNext !== "undefined") {
@@ -244,7 +271,9 @@ const initSystemAvatars = async () => {
const list = res?.list || [];
if (list.length > 0) {
// 取前3个展示在首页
systemAvatars.value = list.slice(0, 3).map((item) => item.imageUrl);
systemAvatars.value = list
.slice(0, 3)
.map((item) => ({ id: item.id, imageUrl: item.imageUrl }));
// 默认选中第一个
if (systemAvatars.value.length > 0) {
currentAvatar.value = systemAvatars.value[0];
@@ -255,10 +284,36 @@ const initSystemAvatars = async () => {
}
};
onLoad(() => {
onLoad((options) => {
initSystemAvatars();
loadFrames();
loadDecors();
// 处理推荐跳转参数
if (options && options.recommendId && options.type && options.imageUrl) {
const recommendItem = {
id: options.recommendId,
imageUrl: decodeURIComponent(options.imageUrl),
};
if (options.type === "frame") {
activeTab.value = "frame";
selectedFrame.value = recommendItem;
// 如果 frames 列表中有这个 id更新引用以保持选中状态可选但推荐
const found = frames.value.find((f) => f.id === recommendItem.id);
if (found) selectedFrame.value = found;
} else if (options.type === "decor") {
activeTab.value = "decor";
selectedDecor.value = recommendItem;
const found = decors.value.find((d) => d.id === recommendItem.id);
if (found) selectedDecor.value = found;
} else if (options.type === "avatar") {
currentAvatar.value = recommendItem;
// 检查系统头像列表
const found = systemAvatars.value.find((a) => a.id === recommendItem.id);
if (found) currentAvatar.value = found;
}
}
});
onReachBottom(() => {
@@ -292,7 +347,10 @@ const loadMoreAvatars = async () => {
const list = res?.list || [];
if (list.length > 0) {
const newAvatars = list.map((item) => item.imageUrl);
const newAvatars = list.map((item) => ({
id: item.id,
imageUrl: item.imageUrl,
}));
moreAvatars.value.push(...newAvatars);
page.value++;
}
@@ -313,14 +371,20 @@ const loadMoreAvatars = async () => {
}
};
const selectMoreAvatar = (url) => {
currentAvatar.value = url;
const createAvatarId = () => {
const id = generateObjectId();
// avatarCreateComplete({ id });
return id;
};
const selectMoreAvatar = (item) => {
currentAvatar.value = item;
closeMorePopup();
};
const toggleFrame = (frame) => {
if (selectedFrame.value === frame) {
selectedFrame.value = "";
selectedFrame.value = null;
} else {
selectedFrame.value = frame;
}
@@ -409,7 +473,10 @@ const useWeChatAvatar = () => {
if (!isLoggedIn.value) {
loginPopupRef.value.open();
} else {
currentAvatar.value = userStore.userInfo.avatarUrl;
currentAvatar.value = {
id: "wechat_" + Date.now(),
imageUrl: userStore.userInfo.avatarUrl,
};
}
};
@@ -417,7 +484,108 @@ const goBack = () => {
uni.navigateBack();
};
const saveByCanvas = async (save = true) => {
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery();
query
.select("#avatarCanvas")
.fields({ node: true, size: true })
.exec(async (res) => {
if (!res[0] || !res[0].node) {
reject("Canvas not found");
return;
}
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
const loadCanvasImage = (url) => {
return new Promise((resolve, reject) => {
const img = canvas.createImage();
img.onload = () => resolve(img);
img.onerror = (e) => reject(e);
img.src = url;
});
};
// 初始化画布尺寸
const size = 600;
canvas.width = size;
canvas.height = size;
const avatarPath = await loadCanvasImage(currentAvatar.value.imageUrl);
ctx.clearRect(0, 0, size, size);
ctx.drawImage(avatarPath, 0, 0, size, size);
if (selectedFrame.value) {
const framePath = await loadCanvasImage(selectedFrame.value.imageUrl);
ctx.drawImage(framePath, 0, 0, size, size);
}
if (selectedDecor.value) {
const decorPath = await loadCanvasImage(selectedDecor.value.imageUrl);
ctx.save();
// 映射 rpx 坐标到 Canvas 坐标 (假设 1rpx = 1 unit for 600x600 canvas logic)
// Canvas size is 600, Preview is 600rpx. Ratio is 1:1 in logical space.
ctx.translate(decorState.value.x, decorState.value.y);
ctx.rotate((decorState.value.rotate * Math.PI) / 180);
const scale = decorState.value.scale;
// 绘制图片,宽高 240
ctx.drawImage(
decorPath,
-120 * scale,
-120 * scale,
240 * scale,
240 * scale,
);
ctx.restore();
}
uni.canvasToTempFilePath({
canvas: canvas, // Canvas 2D 必须传 canvas 实例
width: 600,
height: 600,
destWidth: 600,
destHeight: 600,
success: (res) => {
if (save) saveImage(res.tempFilePath);
resolve(res.tempFilePath);
},
fail: (err) => reject(err),
});
// ctx.draw(false, () => {
// uni.canvasToTempFilePath({
// canvasId: "avatarCanvas",
// success: (res) => {
// uni.saveImageToPhotosAlbum({
// filePath: res.tempFilePath,
// success: () => {
// uni.showToast({ title: "已保存到相册", icon: "success" });
// },
// });
// },
// });
// });
});
});
};
const saveImage = (path) => {
uni.saveImageToPhotosAlbum({
filePath: path,
success() {
uni.showToast({ title: "已保存到相册" });
},
fail() {
uni.showModal({
title: "提示",
content: "请授权保存到相册",
});
},
});
};
const saveAndUse = async () => {
if (!isLoggedIn.value) {
loginPopupRef.value.open();
return;
}
const abilityRes = await abilityCheck("avatar_download");
if (!abilityRes.canUse) {
if (
@@ -436,65 +604,86 @@ const saveAndUse = async () => {
});
return;
}
const tempPath = saveByCanvas(true);
const id = createAvatarId();
saveRecordRequest(tempPath, id, "avatar_download");
completeCardInfo(id);
return;
// 调用avatarDownloadRecord API记录下载次数
await avatarDownloadRecord({
avatarUrl: currentAvatar.value,
});
const ctx = uni.createCanvasContext("avatarCanvas");
const size = 600;
const avatarPath = await loadImage(currentAvatar.value);
ctx.clearRect(0, 0, size, size);
ctx.drawImage(avatarPath, 0, 0, size, size);
if (selectedFrame.value) {
const framePath = await loadImage(selectedFrame.value);
ctx.drawImage(framePath, 0, 0, size, size);
}
if (selectedDecor.value) {
const decorPath = await loadImage(selectedDecor.value);
ctx.save();
// 映射 rpx 坐标到 Canvas 坐标 (假设 1rpx = 1 unit for 600x600 canvas logic)
// Canvas size is 600, Preview is 600rpx. Ratio is 1:1 in logical space.
ctx.translate(decorState.value.x, decorState.value.y);
ctx.rotate((decorState.value.rotate * Math.PI) / 180);
const scale = decorState.value.scale;
// 绘制图片,宽高 240
ctx.drawImage(
decorPath,
-120 * scale,
-120 * scale,
240 * scale,
240 * scale,
);
ctx.restore();
}
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: "avatarCanvas",
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({ title: "已保存到相册", icon: "success" });
},
});
},
});
});
// await avatarDownloadRecord({
// avatarUrl: currentAvatar.value,
// });
// saveRecordRequest('', )
// const ctx = uni.createCanvasContext("avatarCanvas");
// const size = 600;
// const avatarPath = await loadImage(currentAvatar.value);
// ctx.clearRect(0, 0, size, size);
// ctx.drawImage(avatarPath, 0, 0, size, size);
// if (selectedFrame.value) {
// const framePath = await loadImage(selectedFrame.value);
// ctx.drawImage(framePath, 0, 0, size, size);
// }
// if (selectedDecor.value) {
// const decorPath = await loadImage(selectedDecor.value);
// ctx.save();
// // 映射 rpx 坐标到 Canvas 坐标 (假设 1rpx = 1 unit for 600x600 canvas logic)
// // Canvas size is 600, Preview is 600rpx. Ratio is 1:1 in logical space.
// ctx.translate(decorState.value.x, decorState.value.y);
// ctx.rotate((decorState.value.rotate * Math.PI) / 180);
// const scale = decorState.value.scale;
// // 绘制图片,宽高 240
// ctx.drawImage(
// decorPath,
// -120 * scale,
// -120 * scale,
// 240 * scale,
// 240 * scale,
// );
// ctx.restore();
// }
// ctx.draw(false, () => {
// uni.canvasToTempFilePath({
// canvasId: "avatarCanvas",
// success: (res) => {
// uni.saveImageToPhotosAlbum({
// filePath: res.tempFilePath,
// success: () => {
// uni.showToast({ title: "已保存到相册", icon: "success" });
// },
// });
// },
// });
// });
};
const share = () => {
uni.showToast({ title: "已生成,可在相册分享", icon: "none" });
const completeCardInfo = async (id) => {
const tempPath = await saveByCanvas(false);
const imageUrl = await uploadImage(tempPath);
avatarCreateComplete({
id,
imageUrl,
avatarId: currentAvatar?.value?.id,
decorId: selectedDecor?.value?.id,
frameId: selectedFrame?.value?.id,
});
};
onShareAppMessage(async () => {
const deviceInfo = getDeviceInfo();
const shareTokenRes = await createShareToken({
targetId: "",
scene: "avatar_download",
...deviceInfo,
});
getRewardByShare();
getShareReward({ scene: "avatar_download" });
if (!isLoggedIn.value) {
const shareTokenRes = await getShareToken("avatar_download_not_login", "");
return {
title: "新春祝福",
path: `/pages/index/index?shareToken=${shareTokenRes.shareToken}`,
};
}
const id = createAvatarId();
// const shareTokenRes = {
// shareToken: "iFmK8WjRm6TK",
// };
const shareTokenRes = await getShareToken("avatar_download", id);
completeCardInfo(id);
return {
title: "制作我的新春头像",
path: `/pages/avatar/detail?shareToken=${shareTokenRes.shareToken}`,
@@ -503,13 +692,6 @@ onShareAppMessage(async () => {
};
});
const getRewardByShare = async () => {
const res = await getShareReward({ scene: "avatar_download" });
if (res.success) {
uni.showToast({ title: "分享成功,可下载头像" });
}
};
// onShareTimeline(() => {
// return {
// title: "制作我的新春头像",

View File

@@ -69,40 +69,45 @@
</view>
<view class="use-grid">
<view
v-for="(card, i) in popularCards"
v-for="(card, i) in recommendList"
:key="i"
class="use-card"
@tap="previewCard(card)"
@tap="onCardClick(card)"
>
<view class="card-cover-wrap">
<image :src="card.cover" class="card-cover" mode="aspectFill" />
<view
v-if="card.tag"
class="card-tag"
:class="`tag--${card.tagType || 'default'}`"
>
{{ card.tag }}
<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.desc }}</view>
<view class="card-desc">{{ card.content }}</view>
<view class="card-footer">
<view class="cta-btn" @tap.stop="onCta(card)">
{{ card.cta }}
<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 } from "@dcloudio/uni-app";
import {
onPullDownRefresh,
onShareAppMessage,
onReachBottom,
} from "@dcloudio/uni-app";
import { getBavBarHeight } from "@/utils/system";
import { getRecommendList } from "@/api/system";
const countdownText = ref("");
@@ -188,6 +193,8 @@ onMounted(() => {
const index = dayOfYear % inspirationList.length;
dailyGreeting.value = inspirationList[index];
fetchRecommendList();
});
const features = ref([
@@ -217,45 +224,99 @@ const features = ref([
},
]);
const popularCards = ref([
{
title: "招财进宝金框",
tag: "热门",
tagType: "hot",
desc: "2026马年限定汉字金框金光闪烁财运亨通。适合送亲友的新春祝福。",
cta: "立即制作",
cover:
"https://file.lihailezzc.com/9a929a32-439f-453b-b603-fda7b04cbe08.png",
},
{
title: "大吉大利卡片",
tag: "精选",
tagType: "featured",
desc: "经典红色大拜年卡片,适合送长辈、老师、同学,传递满满的新春喜气。",
cta: "去写祝福",
cover:
"https://file.lihailezzc.com/b5fe8ffb-5901-48d2-94fb-48191e36cbf5.png",
},
{
title: "合家团圆模板",
tag: "爆款",
tagType: "hot2",
desc: "一键生成合家福贺图,支持换装、特效装饰、朋友圈海报等。",
cta: "开始创作",
cover:
"https://file.lihailezzc.com/91cd1611-bb87-442b-a338-24e9d79e4ee9.png",
type: "video",
},
{
title: "福气满满",
tag: "新款",
tagType: "new",
desc: "福字当头,好运连连。送给最爱的人。",
cta: "立即制作",
cover:
"https://file.lihailezzc.com/resource/b48c41054c2633c478463ac1b1f1ca23.png",
},
]);
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") {
@@ -277,20 +338,16 @@ const onFeatureTap = (item) => {
uni.showToast({ title: `进入:${item.title}`, icon: "none" });
};
const previewCard = (card) => {
uni.previewImage({ urls: [card.cover] });
};
onReachBottom(() => {
fetchRecommendList();
});
const onMore = () => {
uni.showToast({ title: "更多模板即将上线~", icon: "none" });
};
const onCta = (card) => {
// uni.showToast({ title: `${card.cta} · ${card.title}`, icon: "none" });
uni.switchTab({ url: "/pages/make/index" });
};
onPullDownRefresh(() => {
onPullDownRefresh(async () => {
await fetchRecommendList(true);
setTimeout(() => {
uni.stopPullDownRefresh();
uni.showToast({ title: "已为你更新内容", icon: "success" });
@@ -647,4 +704,13 @@ onShareAppMessage(() => {
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>

View File

@@ -403,10 +403,33 @@ onLoad((options) => {
});
onShow(() => {
const recommendData = uni.getStorageSync("RECOMMEND_CARD_DATA");
if (recommendData) {
uni.removeStorageSync("RECOMMEND_CARD_DATA");
if (recommendData.imageUrl) {
const tpl = {
id: recommendData.recommendId,
imageUrl: recommendData.imageUrl,
name: "推荐模板", // 暂时使用通用名称,如果需要可以从接口获取更多信息
};
// 切换到模板 Tab
activeTool.value = "template";
// 应用模板
currentTemplate.value = tpl;
// 如果模板列表中存在,更新引用
const found = templates.value.find((t) => t.id === tpl.id);
if (found) currentTemplate.value = found;
}
}
const tempBlessing = uni.getStorageSync("TEMP_BLESSING_TEXT");
if (tempBlessing) {
blessingText.value = { content: tempBlessing, id: "" };
uni.removeStorageSync("TEMP_BLESSING_TEXT");
activeTool.value = "text";
}
});

View File

@@ -1,5 +1,5 @@
import { getDeviceInfo } from "@/utils/system";
import { saveRecord, viewRecord } from "@/api/system";
import { saveRecord, viewRecord, createShareToken } from "@/api/system";
export const generateObjectId = (
m = Math,
@@ -87,3 +87,8 @@ export const saveRemoteImageToLocal = (imageUrl) => {
},
});
};
export const getShareToken = async (scene, targetId) => {
const deviceInfo = getDeviceInfo();
return await createShareToken({ scene, targetId, ...deviceInfo });
};