Compare commits
8 Commits
87877d147d
...
aee386da51
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aee386da51 | ||
|
|
eb72b5556b | ||
|
|
115562de53 | ||
|
|
eb37c2d295 | ||
|
|
ce3a53067b | ||
|
|
ee4f1b85c9 | ||
|
|
3bb5127d8f | ||
|
|
3226b35c4f |
@@ -21,7 +21,6 @@ export const getAvatarFrameList = async (page = 1) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const avatarDownloadRecord = async (data) => {
|
||||
return request({
|
||||
url: "/api/blessing/avatar/download",
|
||||
|
||||
9
api/mine.js
Normal file
9
api/mine.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { request } from "@/utils/request.js";
|
||||
|
||||
export const sendFeedback = async (data) => {
|
||||
return request({
|
||||
url: "/api/common/feedback",
|
||||
method: "POST",
|
||||
data,
|
||||
});
|
||||
};
|
||||
@@ -29,3 +29,19 @@ export const getShareReward = async (data) => {
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const saveRecord = async (data) => {
|
||||
return request({
|
||||
url: "/api/blessing/save/record",
|
||||
method: "POST",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const viewRecord = async (data) => {
|
||||
return request({
|
||||
url: "/api/blessing/view/record",
|
||||
method: "POST",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
15
api/wallpaper.js
Normal file
15
api/wallpaper.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { request } from "@/utils/request.js";
|
||||
|
||||
export const getWallpaperList = async (categoryId, page = 1) => {
|
||||
return request({
|
||||
url: `/api/blessing/wallpaper/list?categoryId=${categoryId}&page=${page}`,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getWallpaperCategoryList = async () => {
|
||||
return request({
|
||||
url: `/api/blessing/wallpaper/category/list`,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
24
pages.json
24
pages.json
@@ -33,6 +33,14 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/avatar/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "头像详情",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/detail/index",
|
||||
"style": {
|
||||
@@ -64,6 +72,22 @@
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/feedback/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "意见反馈",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wallpaper/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "精美壁纸",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
BIN
pages/.DS_Store
vendored
Normal file
BIN
pages/.DS_Store
vendored
Normal file
Binary file not shown.
586
pages/avatar/detail.vue
Normal file
586
pages/avatar/detail.vue
Normal file
@@ -0,0 +1,586 @@
|
||||
<template>
|
||||
<view class="avatar-detail-page" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- Custom Navbar -->
|
||||
<view
|
||||
class="nav-bar"
|
||||
:style="{
|
||||
height: navBarHeight + 'px',
|
||||
paddingTop: statusBarHeight + 'px',
|
||||
}"
|
||||
>
|
||||
<view class="nav-content">
|
||||
<view class="back" @tap="goBack">‹</view>
|
||||
<text class="nav-title">新春头像详情</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-wrap">
|
||||
<!-- User Info -->
|
||||
<view class="user-info-section" v-if="detailData">
|
||||
<image
|
||||
:src="detailData.from?.avatar || defaultAvatar"
|
||||
class="user-avatar"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="user-text">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{
|
||||
detailData.from?.nickname || "神秘用户"
|
||||
}}</text>
|
||||
<view class="tag">马年专属</view>
|
||||
</view>
|
||||
<text class="action-text">换上了新春头像</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main Image Card -->
|
||||
<view class="main-card">
|
||||
<view class="card-inner">
|
||||
<image
|
||||
v-if="detailData?.imageUrl"
|
||||
:src="detailData.imageUrl"
|
||||
class="generated-avatar"
|
||||
mode="aspectFill"
|
||||
@tap="previewImage"
|
||||
/>
|
||||
<view class="loading-box" v-else>
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<!-- Decorative Elements -->
|
||||
<view class="decor-tag">🐰</view>
|
||||
<view class="card-footer-text">
|
||||
<text class="icon">🌸</text> 2026 丙午马年限定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<view class="action-group">
|
||||
<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 -->
|
||||
<view class="section recommended-section">
|
||||
<view class="section-header">
|
||||
<view class="left">
|
||||
<view class="bar"></view>
|
||||
<text class="title">热门新春头像框</text>
|
||||
</view>
|
||||
<text class="more" @tap="goToMake">查看全部</text>
|
||||
</view>
|
||||
<view class="frame-grid">
|
||||
<view
|
||||
class="frame-item"
|
||||
v-for="(item, index) in frameList"
|
||||
:key="index"
|
||||
@tap="goToMake"
|
||||
>
|
||||
<view class="frame-img-box">
|
||||
<image :src="item.url" class="frame-img" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="frame-name">{{ item.name || "新春相框" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Wallpaper Banner -->
|
||||
<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>
|
||||
</view>
|
||||
<text class="banner-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- Footer -->
|
||||
<view class="page-footer">
|
||||
<view class="footer-line">
|
||||
<text class="line"></text>
|
||||
<text class="text">2026 HAPPY NEW YEAR</text>
|
||||
<text class="line"></text>
|
||||
</view>
|
||||
<text class="footer-sub">新春祝福 · 传递温情</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { getBavBarHeight } from "@/utils/system";
|
||||
import { getAvatarFrameList, getPageDetail } from "@/api/avatar.js";
|
||||
|
||||
const defaultAvatar =
|
||||
"https://file.lihailezzc.com/resource/d9b329082b32f8305101f708593a4882.png";
|
||||
const detailData = ref(null);
|
||||
const frameList = ref([]);
|
||||
const shareToken = ref("");
|
||||
|
||||
const navBarHeight = ref(64);
|
||||
const statusBarHeight = ref(20);
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.shareToken) {
|
||||
shareToken.value = options.shareToken;
|
||||
fetchDetail();
|
||||
}
|
||||
fetchFrames();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
statusBarHeight.value = sysInfo.statusBarHeight;
|
||||
navBarHeight.value = getBavBarHeight();
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
// Check if can go back, otherwise go home
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({ url: "/pages/index/index" });
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
// uni.showLoading({ title: "加载中..." });
|
||||
const res = await getPageDetail(shareToken.value);
|
||||
if (res) {
|
||||
detailData.value = res;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// uni.showToast({ title: "获取详情失败", icon: "none" });
|
||||
} finally {
|
||||
// uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const fetchFrames = async () => {
|
||||
try {
|
||||
const res = await getAvatarFrameList(1);
|
||||
if (res) {
|
||||
const list = Array.isArray(res) ? res : res.list || [];
|
||||
frameList.value = list.slice(0, 3); // Take first 3
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const previewImage = () => {
|
||||
if (detailData.value?.imageUrl) {
|
||||
uni.previewImage({
|
||||
urls: [detailData.value.imageUrl],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 goToWallpaper = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/wallpaper/index",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar-detail-page {
|
||||
min-height: 100vh;
|
||||
background: #fff0f5; /* Light Pink Background */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.back {
|
||||
font-size: 50rpx;
|
||||
margin-right: 24rpx;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin-right: 50rpx; /* Balance back button */
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
padding: 30rpx 40rpx 60rpx;
|
||||
}
|
||||
|
||||
/* User Info */
|
||||
.user-info-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #fff;
|
||||
margin-right: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.user-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 99rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Main Card */
|
||||
.main-card {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fff5f5 100%);
|
||||
border-radius: 40rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(255, 59, 48, 0.15);
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
position: relative;
|
||||
background: #fffaf0;
|
||||
border-radius: 30rpx;
|
||||
padding: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.generated-avatar {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
|
||||
border: 8rpx solid #d63333;
|
||||
}
|
||||
|
||||
.loading-box {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
background: #eee;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.decor-tag {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
background: #fff;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
.card-footer-text {
|
||||
margin-top: 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #d63333;
|
||||
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);
|
||||
}
|
||||
|
||||
.card-footer-text .icon {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.action-group {
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 100rpx;
|
||||
border-radius: 99rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
box-shadow: 0 10rpx 20rpx rgba(255, 59, 48, 0.3);
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background: #eaeaea;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn .icon {
|
||||
margin-right: 16rpx;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
/* Recommended Section */
|
||||
.section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-header .bar {
|
||||
width: 8rpx;
|
||||
height: 32rpx;
|
||||
background: #ff3b30;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.section-header .title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-header .more {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.frame-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.frame-item {
|
||||
width: 200rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.frame-img-box {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.frame-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.frame-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Wallpaper Banner */
|
||||
.wallpaper-banner {
|
||||
background: #fff;
|
||||
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;
|
||||
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 */
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.footer-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.footer-line .line {
|
||||
width: 60rpx;
|
||||
height: 2rpx;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.footer-line .text {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
margin: 0 20rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.footer-sub {
|
||||
font-size: 20rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -497,7 +497,7 @@ onShareAppMessage(async () => {
|
||||
|
||||
return {
|
||||
title: "制作我的新春头像",
|
||||
path: `/pages/avatar/index?shareToken=${shareTokenRes.shareToken}`,
|
||||
path: `/pages/avatar/detail?shareToken=${shareTokenRes.shareToken}`,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/b48c41054c2633c478463ac1b1f1ca23.png", // 使用默认封面或 popularCards 的封面
|
||||
};
|
||||
@@ -507,7 +507,6 @@ const getRewardByShare = async () => {
|
||||
const res = await getShareReward({ scene: "avatar_download" });
|
||||
if (res.success) {
|
||||
uni.showToast({ title: "分享成功,可下载头像" });
|
||||
checkDrawStatus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ import { ref, onMounted } from "vue";
|
||||
import { getBavBarHeight } from "@/utils/system";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { getPageDetail } from "@/api/system.js";
|
||||
import { saveViewRequest } from "@/utils/common.js";
|
||||
|
||||
const navBarHeight = ref(44);
|
||||
const statusBarHeight = ref(20);
|
||||
@@ -125,6 +126,7 @@ onLoad(async (options) => {
|
||||
const card = await getPageDetail(options.shareToken);
|
||||
cardId.value = card.id;
|
||||
cardDetail.value = card;
|
||||
saveViewRequest(options.shareToken, "card_generate", card.id);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
352
pages/feedback/index.vue
Normal file
352
pages/feedback/index.vue
Normal file
@@ -0,0 +1,352 @@
|
||||
<template>
|
||||
<view class="feedback-page" :style="{ paddingTop: getBavBarHeight() + 'px' }">
|
||||
<!-- Custom Navbar -->
|
||||
<view class="nav-bar">
|
||||
<view class="back" @tap="goBack">‹</view>
|
||||
<text class="nav-title">意见反馈</text>
|
||||
</view>
|
||||
|
||||
<view class="content-wrap">
|
||||
<!-- Type Selector -->
|
||||
<view class="form-item">
|
||||
<view class="label">反馈类型</view>
|
||||
<view class="type-list">
|
||||
<view
|
||||
v-for="(item, index) in feedbackTypes"
|
||||
:key="index"
|
||||
class="type-chip"
|
||||
:class="{ active: formData.type === item.value }"
|
||||
@tap="formData.type = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Content Input -->
|
||||
<view class="form-item">
|
||||
<view class="label">反馈内容 <text class="required">*</text></view>
|
||||
<view class="textarea-box">
|
||||
<textarea
|
||||
v-model="formData.content"
|
||||
class="textarea"
|
||||
placeholder="请输入您的反馈意见,我们将为您不断改进..."
|
||||
placeholder-class="placeholder"
|
||||
maxlength="200"
|
||||
/>
|
||||
<text class="counter">{{ formData.content.length }}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Image Upload -->
|
||||
<view class="form-item">
|
||||
<view class="label"
|
||||
>图片上传 <text class="optional">(选填,最多3张)</text></view
|
||||
>
|
||||
<view class="image-grid">
|
||||
<view
|
||||
v-for="(img, index) in formData.images"
|
||||
:key="index"
|
||||
class="image-item"
|
||||
>
|
||||
<image
|
||||
:src="img"
|
||||
mode="aspectFill"
|
||||
class="thumb"
|
||||
@tap="previewImage(index)"
|
||||
/>
|
||||
<view class="delete-btn" @tap.stop="deleteImage(index)">×</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="formData.images.length < 3"
|
||||
class="upload-btn"
|
||||
@tap="chooseImage"
|
||||
>
|
||||
<text class="plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<view class="submit-wrap">
|
||||
<button
|
||||
class="submit-btn"
|
||||
:class="{ disabled: !isValid }"
|
||||
:disabled="!isValid"
|
||||
@tap="submitFeedback"
|
||||
>
|
||||
提交反馈
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { getBavBarHeight, getDeviceInfo } from "@/utils/system";
|
||||
import { sendFeedback } from "@/api/mine.js";
|
||||
import { uploadImage } from "@/utils/common.js";
|
||||
|
||||
const feedbackTypes = [
|
||||
{ label: "功能建议", value: 1 },
|
||||
{ label: "问题反馈", value: 2 },
|
||||
{ label: "投诉", value: 3 },
|
||||
{ label: "其他", value: 4 },
|
||||
];
|
||||
|
||||
const formData = ref({
|
||||
type: 1,
|
||||
content: "",
|
||||
images: [],
|
||||
});
|
||||
|
||||
const isValid = computed(() => {
|
||||
return formData.value.content.trim().length > 0;
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const chooseImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 3 - formData.value.images.length,
|
||||
sizeType: ["compressed"],
|
||||
sourceType: ["album", "camera"],
|
||||
success: (res) => {
|
||||
formData.value.images = [...formData.value.images, ...res.tempFilePaths];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteImage = (index) => {
|
||||
formData.value.images.splice(index, 1);
|
||||
};
|
||||
|
||||
const previewImage = (index) => {
|
||||
uni.previewImage({
|
||||
urls: formData.value.images,
|
||||
current: index,
|
||||
});
|
||||
};
|
||||
|
||||
const submitFeedback = async () => {
|
||||
if (!isValid.value) return;
|
||||
|
||||
uni.showLoading({ title: "提交中..." });
|
||||
|
||||
try {
|
||||
const uploadedImages = [];
|
||||
// Upload all images first
|
||||
for (const img of formData.value.images) {
|
||||
const url = await uploadImage(img);
|
||||
uploadedImages.push(url);
|
||||
}
|
||||
|
||||
// Submit with real URLs
|
||||
const deviceInfo = getDeviceInfo();
|
||||
|
||||
sendFeedback({
|
||||
type: formData.value.type,
|
||||
content: formData.value.content,
|
||||
images: uploadedImages,
|
||||
deviceInfo: deviceInfo,
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "感谢您的反馈",
|
||||
icon: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "提交失败", icon: "none" });
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.feedback-page {
|
||||
min-height: 100vh;
|
||||
background: #f9f9f9;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
background: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.back {
|
||||
font-size: 40rpx;
|
||||
margin-right: 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.required {
|
||||
color: #ff3b30;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
.optional {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* Type Selector */
|
||||
.type-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.type-chip {
|
||||
padding: 12rpx 32rpx;
|
||||
background: #fff;
|
||||
border-radius: 999rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
.type-chip.active {
|
||||
background: #fff0f0;
|
||||
color: #ff3b30;
|
||||
border-color: #ff3b30;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Textarea */
|
||||
.textarea-box {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
}
|
||||
.placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
.counter {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
right: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Image Grid */
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24rpx;
|
||||
}
|
||||
.image-item {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
position: relative;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom-left-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
.upload-btn {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #ddd;
|
||||
}
|
||||
.plus {
|
||||
font-size: 60rpx;
|
||||
color: #ddd;
|
||||
font-weight: 300;
|
||||
margin-top: -8rpx;
|
||||
}
|
||||
|
||||
/* Submit Button */
|
||||
.submit-wrap {
|
||||
margin-top: 60rpx;
|
||||
}
|
||||
.submit-btn {
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
height: 88rpx;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 20rpx rgba(255, 59, 48, 0.2);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.submit-btn.disabled {
|
||||
background: #ffccc7;
|
||||
box-shadow: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.submit-btn::after {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -93,6 +93,7 @@
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { getPageDetail } from "@/api/system.js";
|
||||
import { saveViewRequest } from "@/utils/common.js";
|
||||
|
||||
const inviterName = ref("");
|
||||
const inviterAvatar = ref("");
|
||||
@@ -122,6 +123,7 @@ const loadPageDetail = async (shareToken) => {
|
||||
fortuneData.value = data;
|
||||
inviterName.value = data?.from?.nickname || "";
|
||||
inviterAvatar.value = data?.from?.avatar || "";
|
||||
saveViewRequest(shareToken, "fortune_draw", data.fortuneId);
|
||||
};
|
||||
|
||||
const goHome = () => {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
{{ status === "shaking" ? "抽取中..." : "立即抽取" }}
|
||||
</button>
|
||||
|
||||
<view class="footer-info">
|
||||
<view v-if="isLoggedIn" class="footer-info">
|
||||
<text class="info-icon">ⓘ</text>
|
||||
<text v-if="allowShareCount - useShareCount > 0">
|
||||
今日还有 {{ remainingCount }} 次抽取机会,分享可增加次数
|
||||
@@ -107,11 +107,11 @@
|
||||
</view>
|
||||
|
||||
<!-- Canvas 用于生成图片 (隐藏) -->
|
||||
<canvas
|
||||
<!-- <canvas
|
||||
canvas-id="shareCanvas"
|
||||
class="share-canvas"
|
||||
style="width: 300px; height: 500px; position: fixed; left: 9999px"
|
||||
></canvas>
|
||||
></canvas> -->
|
||||
|
||||
<LoginPopup ref="loginPopupRef" @logind="handleLogind" />
|
||||
</view>
|
||||
@@ -126,6 +126,7 @@ import { drawFortune } from "@/api/fortune.js";
|
||||
import { createShareToken, getShareReward } from "@/api/system.js";
|
||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { saveRemoteImageToLocal, saveRecordRequest } from "@/utils/common.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const loginPopupRef = ref(null);
|
||||
@@ -190,11 +191,9 @@ const checkDrawStatus = async () => {
|
||||
if (!isLoggedIn.value) return;
|
||||
|
||||
const res = await abilityCheck("fortune_draw");
|
||||
if (res.canUse) {
|
||||
remainingCount.value = res.remain;
|
||||
remainingCount.value = res.remain || 0;
|
||||
allowShareCount.value = res.allowShareCount || 0;
|
||||
useShareCount.value = res.useShareCount || 0;
|
||||
}
|
||||
};
|
||||
|
||||
const currentFortune = ref({});
|
||||
@@ -262,107 +261,89 @@ const reset = () => {
|
||||
const saveCard = () => {
|
||||
if (currentFortune.value.imageUrl) {
|
||||
uni.showLoading({ title: "保存中..." });
|
||||
uni.downloadFile({
|
||||
url: currentFortune.value.imageUrl,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "已保存到相册" });
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
saveRemoteImageToLocal(currentFortune.value.imageUrl);
|
||||
saveRecordRequest(
|
||||
"",
|
||||
cardId.value,
|
||||
"fortune_draw",
|
||||
currentFortune.value.imageUrl,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: "生成中..." });
|
||||
// uni.showLoading({ title: "生成中..." });
|
||||
|
||||
const ctx = uni.createCanvasContext("shareCanvas");
|
||||
// const ctx = uni.createCanvasContext("shareCanvas");
|
||||
|
||||
// 绘制背景
|
||||
ctx.setFillStyle("#FFFBF0");
|
||||
ctx.fillRect(0, 0, 300, 500);
|
||||
// // 绘制背景
|
||||
// ctx.setFillStyle("#FFFBF0");
|
||||
// ctx.fillRect(0, 0, 300, 500);
|
||||
|
||||
// 绘制外边框
|
||||
ctx.setStrokeStyle("#E6CAA0");
|
||||
ctx.setLineWidth(1);
|
||||
ctx.strokeRect(0, 0, 300, 500);
|
||||
// // 绘制外边框
|
||||
// ctx.setStrokeStyle("#E6CAA0");
|
||||
// ctx.setLineWidth(1);
|
||||
// ctx.strokeRect(0, 0, 300, 500);
|
||||
|
||||
// 绘制内装饰边框
|
||||
ctx.setStrokeStyle("#D4AF37");
|
||||
ctx.setLineWidth(2);
|
||||
ctx.strokeRect(10, 10, 280, 480);
|
||||
// // 绘制内装饰边框
|
||||
// ctx.setStrokeStyle("#D4AF37");
|
||||
// ctx.setLineWidth(2);
|
||||
// ctx.strokeRect(10, 10, 280, 480);
|
||||
|
||||
// 绘制年份标签
|
||||
ctx.setFillStyle("#E63946");
|
||||
// 圆角矩形模拟(简化)
|
||||
ctx.fillRect(100, 40, 100, 24);
|
||||
ctx.setFillStyle("#FFFFFF");
|
||||
ctx.setFontSize(14);
|
||||
ctx.setTextAlign("center");
|
||||
ctx.fillText("2026 乙巳年", 150, 57);
|
||||
// // 绘制年份标签
|
||||
// ctx.setFillStyle("#E63946");
|
||||
// // 圆角矩形模拟(简化)
|
||||
// ctx.fillRect(100, 40, 100, 24);
|
||||
// ctx.setFillStyle("#FFFFFF");
|
||||
// ctx.setFontSize(14);
|
||||
// ctx.setTextAlign("center");
|
||||
// ctx.fillText("2026 乙巳年", 150, 57);
|
||||
|
||||
// 绘制标题
|
||||
ctx.setFillStyle("#C0392B");
|
||||
ctx.setFontSize(32);
|
||||
ctx.font = "bold 32px serif";
|
||||
ctx.fillText(currentFortune.value.title, 150, 120);
|
||||
// // 绘制标题
|
||||
// ctx.setFillStyle("#C0392B");
|
||||
// ctx.setFontSize(32);
|
||||
// ctx.font = "bold 32px serif";
|
||||
// ctx.fillText(currentFortune.value.title, 150, 120);
|
||||
|
||||
// 绘制分隔线
|
||||
ctx.setStrokeStyle("#D4AF37");
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(130, 140);
|
||||
ctx.lineTo(170, 140);
|
||||
ctx.stroke();
|
||||
// // 绘制分隔线
|
||||
// ctx.setStrokeStyle("#D4AF37");
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(130, 140);
|
||||
// ctx.lineTo(170, 140);
|
||||
// ctx.stroke();
|
||||
|
||||
// 绘制描述
|
||||
ctx.setFillStyle("#333333");
|
||||
ctx.setFontSize(16);
|
||||
// 简单换行处理(假设文字不长)
|
||||
ctx.fillText(currentFortune.value.desc, 150, 180);
|
||||
// // 绘制描述
|
||||
// ctx.setFillStyle("#333333");
|
||||
// ctx.setFontSize(16);
|
||||
// // 简单换行处理(假设文字不长)
|
||||
// ctx.fillText(currentFortune.value.desc, 150, 180);
|
||||
|
||||
// 绘制底部文字
|
||||
ctx.setFillStyle("#888888");
|
||||
ctx.setFontSize(12);
|
||||
ctx.fillText("旧岁千般皆如意,新年万事定称心。", 150, 220);
|
||||
// // 绘制底部文字
|
||||
// ctx.setFillStyle("#888888");
|
||||
// ctx.setFontSize(12);
|
||||
// ctx.fillText("旧岁千般皆如意,新年万事定称心。", 150, 220);
|
||||
|
||||
ctx.draw(false, () => {
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: "shareCanvas",
|
||||
success: (res) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "已保存到相册" });
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
console.error(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
// ctx.draw(false, () => {
|
||||
// uni.canvasToTempFilePath({
|
||||
// canvasId: "shareCanvas",
|
||||
// success: (res) => {
|
||||
// uni.saveImageToPhotosAlbum({
|
||||
// filePath: res.tempFilePath,
|
||||
// success: () => {
|
||||
// uni.hideLoading();
|
||||
// uni.showToast({ title: "已保存到相册" });
|
||||
// },
|
||||
// fail: () => {
|
||||
// uni.hideLoading();
|
||||
// uni.showToast({ title: "保存失败", icon: "none" });
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
// fail: (err) => {
|
||||
// uni.hideLoading();
|
||||
// console.error(err);
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ onMounted(() => {
|
||||
const features = ref([
|
||||
{
|
||||
title: "新春祝福卡片",
|
||||
subtitle: "定制专属贺卡",
|
||||
subtitle: "定制专属贺卡吧",
|
||||
icon: "/static/icon/celebrate.png",
|
||||
type: "card",
|
||||
},
|
||||
@@ -213,7 +213,7 @@ const features = ref([
|
||||
title: "精美壁纸",
|
||||
subtitle: "获取精美壁纸",
|
||||
icon: "/static/icon/bizhi.png",
|
||||
type: "video",
|
||||
type: "wallpaper",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -270,6 +270,10 @@ const onFeatureTap = (item) => {
|
||||
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" });
|
||||
};
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<uni-icons type="cloud-download" size="20" color="#888"></uni-icons>
|
||||
<view>保存</view>
|
||||
</button>
|
||||
<button open-type="share" class="btn primary" @tap="shareOrSave">
|
||||
<button open-type="share" class="btn primary">
|
||||
<uni-icons
|
||||
type="paperplane-filled"
|
||||
size="20"
|
||||
@@ -290,7 +290,7 @@ import {
|
||||
getCardTemplateList,
|
||||
getCardTemplateContentList,
|
||||
} from "@/api/make";
|
||||
import { createShareToken } from "@/api/system";
|
||||
import { createShareToken, abilityCheck, getShareReward } from "@/api/system";
|
||||
import {
|
||||
onShareAppMessage,
|
||||
onLoad,
|
||||
@@ -299,6 +299,7 @@ import {
|
||||
} from "@dcloudio/uni-app";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||
import { saveRecordRequest, uploadImage } from "@/utils/common.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const loginPopupRef = ref(null);
|
||||
@@ -481,6 +482,7 @@ const loadMoreTemplates = () => {
|
||||
};
|
||||
|
||||
onShareAppMessage(async () => {
|
||||
getShareReward({ scene: "card_generate" });
|
||||
if (!isLoggedIn.value) {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
@@ -502,6 +504,7 @@ onShareAppMessage(async () => {
|
||||
targetId: id,
|
||||
...deviceInfo,
|
||||
});
|
||||
shareOrSave(id);
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/detail/index?shareToken=" + shareTokenRes.shareToken,
|
||||
@@ -563,46 +566,52 @@ const toggleAvatarDecor = () => {
|
||||
uni.showToast({ title: "挂饰功能即将上线~", icon: "none" });
|
||||
};
|
||||
|
||||
const preview = () => {
|
||||
saveByCanvas();
|
||||
// uni.showToast({ title: '已保存到相册', icon: 'checkmarkempty' })
|
||||
};
|
||||
|
||||
const shareOrSave = async () => {
|
||||
const preview = async () => {
|
||||
if (!isLoggedIn.value) {
|
||||
loginPopupRef.value.open();
|
||||
return;
|
||||
}
|
||||
const abilityRes = await abilityCheck("card_generate");
|
||||
if (!abilityRes.canUse) {
|
||||
if (
|
||||
abilityRes?.blockType === "need_share" &&
|
||||
abilityRes?.message === "分享可继续"
|
||||
) {
|
||||
uni.showToast({
|
||||
title: "分享给好友可继续使用",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showToast({
|
||||
title: "您今日祝福卡下载次数已用完,直接分享给好友或者明日再试",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const tempPath = await saveByCanvas(true);
|
||||
id = createCard();
|
||||
shareOrSave(id);
|
||||
saveRecordRequest(tempPath, id, "card_generate");
|
||||
|
||||
// uni.showToast({ title: '已保存到相册', icon: 'checkmarkempty' })
|
||||
};
|
||||
|
||||
const shareOrSave = async (id) => {
|
||||
if (!id) id = createCard();
|
||||
|
||||
const tempPath = await saveByCanvas(false);
|
||||
const fileKeyRes = await uni.uploadFile({
|
||||
url: "https://api.ai-meng.com/api/common/upload",
|
||||
filePath: tempPath,
|
||||
name: "file", // 和后端接收文件字段名一致
|
||||
header: {
|
||||
"x-app-id": "69665538a49b8ae3be50fe5d",
|
||||
},
|
||||
});
|
||||
if (fileKeyRes.statusCode < 400) {
|
||||
const keyJson = JSON.parse(fileKeyRes.data);
|
||||
const url = `https://file.lihailezzc.com/${keyJson?.data.key}`;
|
||||
// const url =
|
||||
// "https://file.lihailezzc.com/resource/99c9f7e0086ed66d20bd1675b4ab22e9.png";
|
||||
// 1. 确保有 cardId
|
||||
if (!cardId.value) {
|
||||
createCard();
|
||||
}
|
||||
const imageUrl = await uploadImage(tempPath);
|
||||
|
||||
updateCard({
|
||||
id: cardId.value,
|
||||
imageUrl: url,
|
||||
id,
|
||||
imageUrl,
|
||||
status: 1,
|
||||
blessingId: blessingText.value?.id || "",
|
||||
blessingTo: targetName.value,
|
||||
blessingFrom: signatureName.value,
|
||||
templateId: currentTemplate.value?.id || "",
|
||||
});
|
||||
}
|
||||
// uni.showToast({ title: '已保存到相册并可分享', icon: 'none' })
|
||||
};
|
||||
|
||||
const showMore = () => {
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</view>
|
||||
|
||||
<!-- History -->
|
||||
<view class="section-title">历史记录</view>
|
||||
<!-- <view class="section-title">历史记录</view>
|
||||
<view class="menu-group">
|
||||
<view class="menu-item" @tap="navTo('history')">
|
||||
<view class="icon-box gray-bg"><text>🕒</text></view>
|
||||
@@ -79,7 +79,7 @@
|
||||
<text class="menu-text">历年祝福存档</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- Other Actions -->
|
||||
<view class="menu-group mt-30">
|
||||
@@ -88,6 +88,11 @@
|
||||
<text class="menu-text">分享给好友</text>
|
||||
<text class="arrow">›</text>
|
||||
</button>
|
||||
<view class="menu-item" @tap="navTo('feedback')">
|
||||
<view class="icon-left"><text class="feedback-icon">📝</text></view>
|
||||
<text class="menu-text">意见反馈</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @tap="navTo('help')">
|
||||
<view class="icon-left"><text class="help-icon">❓</text></view>
|
||||
<text class="menu-text">使用说明 / 帮助</text>
|
||||
@@ -161,6 +166,12 @@ const navTo = (page) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (page === "feedback") {
|
||||
uni.navigateTo({
|
||||
url: "/pages/feedback/index",
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showToast({ title: "功能开发中", icon: "none" });
|
||||
};
|
||||
</script>
|
||||
@@ -364,7 +375,8 @@ const navTo = (page) => {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
.share-icon,
|
||||
.help-icon {
|
||||
.help-icon,
|
||||
.feedback-icon {
|
||||
font-size: 36rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
350
pages/wallpaper/index.vue
Normal file
350
pages/wallpaper/index.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<view
|
||||
class="wallpaper-page"
|
||||
:style="{ paddingTop: getBavBarHeight() + 'px' }"
|
||||
>
|
||||
<!-- Custom Navbar -->
|
||||
<view class="nav-bar">
|
||||
<view class="back" @tap="goBack">‹</view>
|
||||
<text class="nav-title">新春精美壁纸</text>
|
||||
</view>
|
||||
|
||||
<!-- Category Tabs -->
|
||||
<view class="category-tabs">
|
||||
<scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
|
||||
<view class="tabs-content">
|
||||
<view
|
||||
v-for="(item, index) in categories"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: currentCategoryId === item.id }"
|
||||
@tap="switchCategory(item.id)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- Wallpaper Grid -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="wallpaper-scroll"
|
||||
@scrolltolower="loadMore"
|
||||
refresher-enabled
|
||||
:refresher-triggered="isRefreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<view class="grid-container">
|
||||
<view
|
||||
class="grid-item"
|
||||
v-for="(item, index) in wallpapers"
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
:src="item.imageUrl"
|
||||
mode="aspectFill"
|
||||
class="wallpaper-img"
|
||||
@tap="previewImage(index)"
|
||||
/>
|
||||
<view class="action-overlay">
|
||||
<view
|
||||
class="action-btn download"
|
||||
@tap.stop="downloadWallpaper(item)"
|
||||
>
|
||||
<text class="icon">↓</text>
|
||||
</view>
|
||||
<view class="action-btn share" @tap.stop="shareWallpaper(item)">
|
||||
<text class="icon">➦</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading State -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view class="empty-state" v-if="!loading && wallpapers.length === 0">
|
||||
<text>暂无壁纸</text>
|
||||
</view>
|
||||
<view
|
||||
class="no-more"
|
||||
v-if="!loading && !hasMore && wallpapers.length > 0"
|
||||
>
|
||||
<text>没有更多了</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { getBavBarHeight } from "@/utils/system";
|
||||
import { getWallpaperList, getWallpaperCategoryList } from "@/api/wallpaper.js";
|
||||
|
||||
const categories = ref([]);
|
||||
const currentCategoryId = ref(null);
|
||||
const wallpapers = ref([]);
|
||||
const page = ref(1);
|
||||
const loading = ref(false);
|
||||
const hasMore = ref(true);
|
||||
const isRefreshing = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchCategories();
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const res = await getWallpaperCategoryList();
|
||||
const list = Array.isArray(res) ? res : res?.list || [];
|
||||
if (list.length > 0) {
|
||||
categories.value = list;
|
||||
currentCategoryId.value = list[0].id;
|
||||
loadWallpapers(true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch categories", e);
|
||||
uni.showToast({ title: "获取分类失败", icon: "none" });
|
||||
}
|
||||
};
|
||||
|
||||
const switchCategory = (id) => {
|
||||
if (currentCategoryId.value === id) return;
|
||||
currentCategoryId.value = id;
|
||||
loadWallpapers(true);
|
||||
};
|
||||
|
||||
const loadWallpapers = async (reset = false) => {
|
||||
if (loading.value) return;
|
||||
if (reset) {
|
||||
page.value = 1;
|
||||
hasMore.value = true;
|
||||
wallpapers.value = [];
|
||||
}
|
||||
if (!hasMore.value) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getWallpaperList(currentCategoryId.value, page.value);
|
||||
const list = res?.list || [];
|
||||
hasMore.value = !!res?.hasNext;
|
||||
|
||||
if (reset) {
|
||||
wallpapers.value = list;
|
||||
} else {
|
||||
wallpapers.value = [...wallpapers.value, ...list];
|
||||
}
|
||||
|
||||
if (hasMore.value) {
|
||||
page.value++;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch wallpapers", e);
|
||||
uni.showToast({ title: "获取壁纸失败", icon: "none" });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
loadWallpapers();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
isRefreshing.value = true;
|
||||
loadWallpapers(true);
|
||||
};
|
||||
|
||||
const previewImage = (index) => {
|
||||
const urls = wallpapers.value.map((item) => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index,
|
||||
});
|
||||
};
|
||||
|
||||
const downloadWallpaper = (item) => {
|
||||
uni.showLoading({ title: "下载中..." });
|
||||
uni.downloadFile({
|
||||
url: item.url,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "保存成功", icon: "success" });
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
console.error(err);
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const shareWallpaper = (item) => {
|
||||
// uni.share is for App, specific provider.
|
||||
// For general sharing, we might just preview it or use button open-type="share" if it was a button component.
|
||||
// Since this is a custom UI, we can guide user to preview and long press, or just preview.
|
||||
// Or if on MP-Weixin, show share menu.
|
||||
uni.previewImage({
|
||||
urls: [item.url],
|
||||
});
|
||||
// uni.showToast({ title: '长按图片发送给朋友', icon: 'none' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wallpaper-page {
|
||||
height: 100vh;
|
||||
background-color: #7a0909; /* Dark Red Background */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
/* background: #7A0909; */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back {
|
||||
font-size: 50rpx;
|
||||
margin-right: 24rpx;
|
||||
line-height: 1;
|
||||
color: #ffd700; /* Gold */
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffd700; /* Gold */
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
padding: 20rpx 0;
|
||||
/* background-color: #7A0909; */
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
display: inline-flex;
|
||||
padding: 0 24rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
color: #ffd700;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background: linear-gradient(90deg, #ff3b30 0%, #ff9500 100%);
|
||||
color: #fff;
|
||||
border-color: #ffd700;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
|
||||
}
|
||||
|
||||
.wallpaper-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
/* padding: 24rpx; */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 24rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 340rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.wallpaper-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.action-overlay {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.action-btn .icon {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-btn.share .icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state,
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,89 @@
|
||||
import { getDeviceInfo } from "@/utils/system";
|
||||
import { saveRecord, viewRecord } from "@/api/system";
|
||||
|
||||
export const generateObjectId = (
|
||||
m = Math,
|
||||
d = Date,
|
||||
h = 16,
|
||||
s = (s) => m.floor(s).toString(h),
|
||||
) => s(d.now() / 1000) + " ".repeat(h).replace(/./g, () => s(m.random() * h));
|
||||
|
||||
export const uploadImage = (filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: "https://api.ai-meng.com/api/common/upload",
|
||||
filePath: filePath,
|
||||
name: "file",
|
||||
header: {
|
||||
"x-app-id": "69665538a49b8ae3be50fe5d",
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode < 400) {
|
||||
try {
|
||||
const keyJson = JSON.parse(res.data);
|
||||
const url = `https://file.lihailezzc.com/${keyJson?.data.key}`;
|
||||
resolve(url);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
} else {
|
||||
reject(new Error("Upload failed"));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const saveRecordRequest = async (path, targetId, scene, imageUrl) => {
|
||||
if (!imageUrl) {
|
||||
imageUrl = await uploadImage(path);
|
||||
}
|
||||
const deviceInfo = getDeviceInfo();
|
||||
saveRecord({
|
||||
scene,
|
||||
targetId,
|
||||
imageUrl,
|
||||
deviceInfo,
|
||||
});
|
||||
};
|
||||
|
||||
export const saveViewRequest = async (shareToken, scene, targetId) => {
|
||||
const deviceInfo = getDeviceInfo();
|
||||
viewRecord({
|
||||
shareToken,
|
||||
scene,
|
||||
targetId,
|
||||
deviceInfo,
|
||||
});
|
||||
};
|
||||
|
||||
export const saveRemoteImageToLocal = (imageUrl) => {
|
||||
uni.downloadFile({
|
||||
url: imageUrl,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "已保存到相册" });
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// const BASE_URL = 'https://apis.lihailezzc.com'
|
||||
const BASE_URL = 'http://127.0.0.1:3999'
|
||||
// const BASE_URL = "http://192.168.1.3:3999";
|
||||
// const BASE_URL = 'http://127.0.0.1:3999'
|
||||
const BASE_URL = "http://192.168.1.3:3999";
|
||||
// const BASE_URL = "http://192.168.31.253:3999";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
|
||||
@@ -59,5 +59,6 @@ export function getDeviceInfo() {
|
||||
language: info.language,
|
||||
version: info.version,
|
||||
SDKVersion: info.SDKVersion,
|
||||
appId: "69665538a49b8ae3be50fe5d",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user