optimize: avatar page share reward

This commit is contained in:
zzc
2026-01-28 08:55:59 +08:00
parent aee386da51
commit 2fa6584e0c
4 changed files with 165 additions and 42 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) => { export const avatarDownloadRecord = async (data) => {
return request({ return request({
url: "/api/blessing/avatar/download", url: "/api/blessing/avatar/download",

View File

@@ -117,7 +117,8 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { getBavBarHeight } from "@/utils/system"; 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 = const defaultAvatar =
"https://file.lihailezzc.com/resource/d9b329082b32f8305101f708593a4882.png"; "https://file.lihailezzc.com/resource/d9b329082b32f8305101f708593a4882.png";
@@ -234,7 +235,7 @@ const goToWallpaper = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.avatar-detail-page { .avatar-detail-page {
min-height: 100vh; min-height: 100vh;
background: #fff0f5; /* Light Pink Background */ background: #ffffff;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -245,7 +246,7 @@ const goToWallpaper = () => {
right: 0; right: 0;
z-index: 100; z-index: 100;
box-sizing: border-box; box-sizing: border-box;
background: #fff0f5; background: #ffffff;
} }
.nav-content { .nav-content {
@@ -271,9 +272,6 @@ const goToWallpaper = () => {
margin-right: 50rpx; /* Balance back button */ margin-right: 50rpx; /* Balance back button */
} }
.content-scroll {
}
.content-wrap { .content-wrap {
padding: 30rpx 40rpx 60rpx; padding: 30rpx 40rpx 60rpx;
} }
@@ -326,16 +324,17 @@ const goToWallpaper = () => {
/* Main Card */ /* Main Card */
.main-card { .main-card {
background: linear-gradient(180deg, #ffffff 0%, #fff5f5 100%); background: #ffffff;
border-radius: 40rpx; border-radius: 40rpx;
padding: 24rpx; 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; margin-bottom: 60rpx;
border: 2rpx solid #f5f5f5;
} }
.card-inner { .card-inner {
position: relative; position: relative;
background: #fffaf0; background: #fff9f9;
border-radius: 30rpx; border-radius: 30rpx;
padding: 60rpx; padding: 60rpx;
display: flex; display: flex;
@@ -348,7 +347,7 @@ const goToWallpaper = () => {
height: 400rpx; height: 400rpx;
border-radius: 20rpx; border-radius: 20rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1); box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
border: 8rpx solid #d63333; border: 8rpx solid #ff3b30;
} }
.loading-box { .loading-box {
@@ -358,7 +357,7 @@ const goToWallpaper = () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: #999; color: #999;
background: #eee; background: #f0f0f0;
border-radius: 20rpx; border-radius: 20rpx;
} }
@@ -381,14 +380,14 @@ const goToWallpaper = () => {
.card-footer-text { .card-footer-text {
margin-top: 40rpx; margin-top: 40rpx;
font-size: 28rpx; font-size: 28rpx;
color: #d63333; color: #ff3b30;
font-weight: bold; font-weight: bold;
display: flex; display: flex;
align-items: center; align-items: center;
background: #fff; background: #fff;
padding: 10rpx 30rpx; padding: 10rpx 30rpx;
border-radius: 99rpx; 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 { .card-footer-text .icon {
@@ -423,8 +422,8 @@ const goToWallpaper = () => {
} }
.secondary-btn { .secondary-btn {
background: #eaeaea; background: #f5f5f5;
color: #666; color: #333;
} }
.btn .icon { .btn .icon {
@@ -478,10 +477,9 @@ const goToWallpaper = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background: #fff; background: #f8f8f8;
border-radius: 24rpx; border-radius: 24rpx;
padding: 20rpx; padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
} }
.frame-img-box { .frame-img-box {
@@ -490,7 +488,7 @@ const goToWallpaper = () => {
margin-bottom: 16rpx; margin-bottom: 16rpx;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
background: #f9f9f9; background: #fff;
} }
.frame-img { .frame-img {
@@ -506,19 +504,18 @@ const goToWallpaper = () => {
/* Wallpaper Banner */ /* Wallpaper Banner */
.wallpaper-banner { .wallpaper-banner {
background: #fff; background: #f8f8f8;
border-radius: 24rpx; border-radius: 24rpx;
padding: 30rpx; padding: 30rpx;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 60rpx; margin-bottom: 60rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
} }
.banner-icon { .banner-icon {
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
background: #fff0f5; background: #fff;
border-radius: 20rpx; border-radius: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -569,18 +566,18 @@ const goToWallpaper = () => {
.footer-line .line { .footer-line .line {
width: 60rpx; width: 60rpx;
height: 2rpx; height: 2rpx;
background: #ccc; background: #eee;
} }
.footer-line .text { .footer-line .text {
font-size: 20rpx; font-size: 20rpx;
color: #999; color: #ccc;
margin: 0 20rpx; margin: 0 20rpx;
letter-spacing: 2rpx; letter-spacing: 2rpx;
} }
.footer-sub { .footer-sub {
font-size: 20rpx; font-size: 20rpx;
color: #ccc; color: #ddd;
} }
</style> </style>

View File

@@ -107,6 +107,8 @@
</view> </view>
<canvas <canvas
type="2d"
id="avatarCanvas"
canvas-id="avatarCanvas" canvas-id="avatarCanvas"
class="hidden-canvas" class="hidden-canvas"
style="width: 600px; height: 600px" style="width: 600px; height: 600px"
@@ -162,7 +164,13 @@ import {
getAvatarSystemList, getAvatarSystemList,
getAvatarFrameList, getAvatarFrameList,
getAvatarDecorList, getAvatarDecorList,
avatarCreateComplete,
} from "@/api/avatar.js"; } from "@/api/avatar.js";
import {
saveRecordRequest,
getShareToken,
generateObjectId,
} from "@/utils/common.js";
const userStore = useUserStore(); const userStore = useUserStore();
const loginPopupRef = ref(null); const loginPopupRef = ref(null);
@@ -313,6 +321,12 @@ const loadMoreAvatars = async () => {
} }
}; };
const createAvatarId = () => {
const id = generateObjectId();
avatarCreateComplete({ id });
return id;
};
const selectMoreAvatar = (url) => { const selectMoreAvatar = (url) => {
currentAvatar.value = url; currentAvatar.value = url;
closeMorePopup(); closeMorePopup();
@@ -417,7 +431,109 @@ const goBack = () => {
uni.navigateBack(); 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;
const avatarPath = await loadCanvasImage(currentAvatar.value);
ctx.clearRect(0, 0, size, size);
ctx.drawImage(avatarPath, 0, 0, size, size);
if (selectedFrame.value) {
const framePath = await loadCanvasImage(selectedFrame.value);
ctx.drawImage(framePath, 0, 0, size, size);
}
if (selectedDecor.value) {
const decorPath = await loadCanvasImage(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();
}
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 () => { const saveAndUse = async () => {
saveByCanvas();
console.log(111111);
return;
if (!isLoggedIn.value) {
loginPopupRef.value.open();
return;
}
const abilityRes = await abilityCheck("avatar_download"); const abilityRes = await abilityCheck("avatar_download");
if (!abilityRes.canUse) { if (!abilityRes.canUse) {
if ( if (
@@ -437,9 +553,10 @@ const saveAndUse = async () => {
return; return;
} }
// 调用avatarDownloadRecord API记录下载次数 // 调用avatarDownloadRecord API记录下载次数
await avatarDownloadRecord({ // await avatarDownloadRecord({
avatarUrl: currentAvatar.value, // avatarUrl: currentAvatar.value,
}); // });
// saveRecordRequest('', )
const ctx = uni.createCanvasContext("avatarCanvas"); const ctx = uni.createCanvasContext("avatarCanvas");
const size = 600; const size = 600;
const avatarPath = await loadImage(currentAvatar.value); const avatarPath = await loadImage(currentAvatar.value);
@@ -487,13 +604,16 @@ const share = () => {
}; };
onShareAppMessage(async () => { onShareAppMessage(async () => {
const deviceInfo = getDeviceInfo(); getShareReward({ scene: "avatar_download" });
const shareTokenRes = await createShareToken({ if (!isLoggedIn.value) {
targetId: "", const shareTokenRes = await getShareToken("avatar_download_not_login", "");
scene: "avatar_download", return {
...deviceInfo, title: "新春祝福",
}); path: `/pages/index/index?shareToken=${shareTokenRes.shareToken}`,
getRewardByShare(); };
}
const id = createAvatarId();
const shareTokenRes = await getShareToken("avatar_download", id);
return { return {
title: "制作我的新春头像", title: "制作我的新春头像",
@@ -503,13 +623,6 @@ onShareAppMessage(async () => {
}; };
}); });
const getRewardByShare = async () => {
const res = await getShareReward({ scene: "avatar_download" });
if (res.success) {
uni.showToast({ title: "分享成功,可下载头像" });
}
};
// onShareTimeline(() => { // onShareTimeline(() => {
// return { // return {
// title: "制作我的新春头像", // title: "制作我的新春头像",

View File

@@ -1,5 +1,5 @@
import { getDeviceInfo } from "@/utils/system"; import { getDeviceInfo } from "@/utils/system";
import { saveRecord, viewRecord } from "@/api/system"; import { saveRecord, viewRecord, createShareToken } from "@/api/system";
export const generateObjectId = ( export const generateObjectId = (
m = Math, 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 });
};