Compare commits
15 Commits
526e2aad70
...
239db8e609
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
239db8e609 | ||
|
|
72ebb8253d | ||
|
|
fcc728465b | ||
|
|
9c1382c503 | ||
|
|
e664356a5f | ||
|
|
ad40704642 | ||
|
|
82fc0f80a5 | ||
|
|
b2e41c9a7b | ||
|
|
b6091de037 | ||
|
|
7c80f9a6a6 | ||
|
|
b2b59cb61a | ||
|
|
7d0b79bd16 | ||
|
|
13d1ea604e | ||
|
|
bd45082544 | ||
|
|
52af2aad8f |
@@ -22,3 +22,10 @@ export const updateUserInfo = async (body) => {
|
||||
data: body,
|
||||
});
|
||||
};
|
||||
|
||||
export const reportPrivacy = async () => {
|
||||
// return request({
|
||||
// url: "/api/common/privacy/report",
|
||||
// method: "POST",
|
||||
// });
|
||||
};
|
||||
|
||||
@@ -52,3 +52,10 @@ export const getRecommendList = async (page = 1) => {
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
export const msgCheckApi = async (content) => {
|
||||
return request({
|
||||
url: "/api/common/msg-check?content=" + content,
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
<template>
|
||||
<uni-popup ref="popupRef" type="bottom" :safe-area="false">
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">登录授权</text>
|
||||
</view>
|
||||
<view>
|
||||
<uni-popup ref="popupRef" type="bottom" :safe-area="false">
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">登录授权</text>
|
||||
</view>
|
||||
|
||||
<view class="avatar-nickname">
|
||||
<button
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
class="avatar-selector custom-button"
|
||||
>
|
||||
<image v-if="avatarUrl" :src="avatarUrl" class="avatar-preview" />
|
||||
<text v-else>点击获取头像</text>
|
||||
<view class="avatar-nickname">
|
||||
<button
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
class="avatar-selector custom-button"
|
||||
>
|
||||
<image v-if="avatarUrl" :src="avatarUrl" class="avatar-preview" />
|
||||
<text v-else>点击获取头像</text>
|
||||
</button>
|
||||
<input
|
||||
class="nickname-input"
|
||||
type="nickname"
|
||||
v-model="nickname"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn custom-button" @tap="confirmLogin">
|
||||
确认登录
|
||||
</button>
|
||||
<input
|
||||
class="nickname-input"
|
||||
type="nickname"
|
||||
v-model="nickname"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<button class="confirm-btn custom-button" @tap="confirmLogin">
|
||||
确认登录
|
||||
</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
<!-- 隐私协议弹窗 -->
|
||||
<PrivacyPopup ref="privacyRef" @agree="onPrivacyAgree" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -36,8 +41,10 @@ import { getPlatformProvider } from "@/utils/system";
|
||||
import { uploadImage } from "@/utils/common";
|
||||
import { apiLogin } from "@/api/auth.js";
|
||||
import { wxLogin } from "@/utils/login.js";
|
||||
import PrivacyPopup from "@/components/PrivacyPopup/PrivacyPopup.vue";
|
||||
|
||||
const popupRef = ref(null);
|
||||
const privacyRef = ref(null);
|
||||
const avatarUrl = ref("");
|
||||
const nickname = ref("");
|
||||
|
||||
@@ -46,19 +53,65 @@ const userStore = useUserStore();
|
||||
const emit = defineEmits(["logind"]);
|
||||
|
||||
const festivalNames = [
|
||||
'春意','福星','小福','新禧','瑞雪','花灯','喜乐','元宝','春芽','年年',
|
||||
'花灯','月圆','灯影','小灯','星灯','彩灯',
|
||||
'清风','微风','小晴','碧波','流泉',
|
||||
'月光','玉轮','桂香','秋叶','星河','小月','露华','秋水',
|
||||
'雪落','冰晶','暖阳','小雪','冬影','雪花','松影'
|
||||
"春意",
|
||||
"福星",
|
||||
"小福",
|
||||
"新禧",
|
||||
"瑞雪",
|
||||
"花灯",
|
||||
"喜乐",
|
||||
"元宝",
|
||||
"春芽",
|
||||
"年年",
|
||||
"花灯",
|
||||
"月圆",
|
||||
"灯影",
|
||||
"小灯",
|
||||
"星灯",
|
||||
"彩灯",
|
||||
"清风",
|
||||
"微风",
|
||||
"小晴",
|
||||
"碧波",
|
||||
"流泉",
|
||||
"月光",
|
||||
"玉轮",
|
||||
"桂香",
|
||||
"秋叶",
|
||||
"星河",
|
||||
"小月",
|
||||
"露华",
|
||||
"秋水",
|
||||
"雪落",
|
||||
"冰晶",
|
||||
"暖阳",
|
||||
"小雪",
|
||||
"冬影",
|
||||
"雪花",
|
||||
"松影",
|
||||
];
|
||||
|
||||
const getFestivalName = () => {
|
||||
const idx = Math.floor(Math.random() * festivalNames.length);
|
||||
return festivalNames[idx];
|
||||
}
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
const open = async () => {
|
||||
console.log(22223333);
|
||||
// #ifdef MP-WEIXIN
|
||||
const isAgreed = await privacyRef.value.check();
|
||||
console.log(1111, isAgreed);
|
||||
if (isAgreed) {
|
||||
popupRef.value.open();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
popupRef.value.open();
|
||||
// #endif
|
||||
};
|
||||
|
||||
const onPrivacyAgree = () => {
|
||||
popupRef.value.open();
|
||||
};
|
||||
|
||||
@@ -75,7 +128,9 @@ const confirmLogin = async () => {
|
||||
const platform = getPlatformProvider();
|
||||
if (platform === "mp-weixin") {
|
||||
const code = await wxLogin();
|
||||
const imageUrl = avatarUrl.value ? await uploadImage(avatarUrl.value) : "";
|
||||
const imageUrl = avatarUrl.value
|
||||
? await uploadImage(avatarUrl.value)
|
||||
: "";
|
||||
|
||||
const loginRes = await apiLogin({
|
||||
code,
|
||||
|
||||
205
components/PrivacyPopup/PrivacyPopup.vue
Normal file
205
components/PrivacyPopup/PrivacyPopup.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<view v-if="show" class="privacy-popup">
|
||||
<view class="mask"></view>
|
||||
<view class="content">
|
||||
<view class="title">用户隐私保护指引</view>
|
||||
<view class="desc">
|
||||
感谢您使用本小程序。在使用前,请您仔细阅读
|
||||
<text class="link" @click="openPrivacyContract">{{
|
||||
privacyContractName
|
||||
}}</text>
|
||||
。当您点击“同意”并开始使用产品服务时,即表示您已理解并同意该条款内容,该条款将对您产生法律约束力。如您拒绝,将无法进入小程序。
|
||||
</view>
|
||||
<view class="btns">
|
||||
<button class="btn refuse" @click="handleDisagree">拒绝</button>
|
||||
<button
|
||||
id="agree-btn"
|
||||
class="btn agree"
|
||||
open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handleAgree"
|
||||
>
|
||||
同意
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { reportPrivacy } from "@/api/auth.js";
|
||||
|
||||
const show = ref(false);
|
||||
const privacyContractName = ref("《用户隐私保护指引》");
|
||||
|
||||
const emit = defineEmits(["agree"]);
|
||||
let resolveCheck = null;
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const check = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (uni.getPrivacySetting) {
|
||||
uni.getPrivacySetting({
|
||||
success: (res) => {
|
||||
if (res.needAuthorization) {
|
||||
privacyContractName.value =
|
||||
res.privacyContractName || "《用户隐私保护指引》";
|
||||
show.value = true;
|
||||
resolveCheck = resolve;
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("getPrivacySetting fail:", err);
|
||||
resolve(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
// #endif
|
||||
|
||||
// Only for WeChat Mini Program
|
||||
// #ifdef MP-WEIXIN
|
||||
// onMounted(() => {
|
||||
// check();
|
||||
// });
|
||||
// #endif
|
||||
|
||||
const openPrivacyContract = () => {
|
||||
uni.openPrivacyContract({
|
||||
success: () => {},
|
||||
fail: (err) => {
|
||||
console.error("openPrivacyContract fail", err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleAgree = async (e) => {
|
||||
if (!show.value) return; // Prevent double execution
|
||||
console.log("handleAgree triggered", e);
|
||||
// 1. Save to local storage
|
||||
uni.setStorageSync("hasAgreedPrivacy", true);
|
||||
|
||||
// 2. Hide popup
|
||||
show.value = false;
|
||||
|
||||
emit("agree");
|
||||
|
||||
// 3. Resolve the check promise
|
||||
if (resolveCheck) {
|
||||
resolveCheck(true);
|
||||
resolveCheck = null;
|
||||
}
|
||||
|
||||
// 4. Call server API
|
||||
try {
|
||||
await reportPrivacy();
|
||||
} catch (e) {
|
||||
console.error("Report privacy failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisagree = () => {
|
||||
// Exit mini program
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.exitMiniProgram({
|
||||
success: () => {
|
||||
console.log("Exit success");
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
check,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.privacy-popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
text-align: justify;
|
||||
|
||||
.link {
|
||||
color: #576b95;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.btn {
|
||||
width: 240rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.refuse {
|
||||
background: #f2f2f2;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.agree {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -51,6 +51,7 @@
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin": {
|
||||
"appid": "wx3fcc07a061af049a",
|
||||
"__usePrivacyCheck__": true,
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
|
||||
@@ -48,9 +48,15 @@
|
||||
</view>
|
||||
<scroll-view scroll-x class="avatar-scroll" show-scrollbar="false">
|
||||
<view class="avatar-list">
|
||||
<view class="avatar-card upload-card" @tap="useWeChatAvatar">
|
||||
<view class="upload-icon">📷</view>
|
||||
<text class="upload-text">微信头像</text>
|
||||
<view class="avatar-card upload-card">
|
||||
<button
|
||||
class="wechat-avatar-btn"
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
>
|
||||
<view class="upload-icon">📷</view>
|
||||
<text class="upload-text">微信头像</text>
|
||||
</button>
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, i) in systemAvatars"
|
||||
@@ -465,6 +471,26 @@ const handleLogind = async () => {
|
||||
// Logic after successful login if needed
|
||||
};
|
||||
|
||||
const onChooseAvatar = async (e) => {
|
||||
const avatarUrl = e.detail.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
|
||||
uni.showLoading({ title: "上传中...", mask: true });
|
||||
try {
|
||||
const imageUrl = await uploadImage(avatarUrl);
|
||||
|
||||
currentAvatar.value = {
|
||||
id: "wechat_" + Date.now(),
|
||||
imageUrl: imageUrl,
|
||||
};
|
||||
uni.hideLoading();
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: e || "上传失败", icon: "none" });
|
||||
console.error("Upload avatar error", e);
|
||||
}
|
||||
};
|
||||
|
||||
const useWeChatAvatar = () => {
|
||||
if (!isLoggedIn.value) {
|
||||
loginPopupRef.value.open();
|
||||
@@ -832,28 +858,49 @@ const loadImage = (url) => {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.avatar-card.active {
|
||||
outline: 4rpx solid #ff3b30;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff5f5;
|
||||
border: 2rpx dashed #ffccc7;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.upload-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.upload-text {
|
||||
font-size: 22rpx;
|
||||
color: #ff3b30;
|
||||
font-weight: 500;
|
||||
&.active {
|
||||
outline: 4rpx solid #ff3b30;
|
||||
}
|
||||
|
||||
&.upload-card {
|
||||
background: #fff5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #ffccc7;
|
||||
box-sizing: border-box;
|
||||
|
||||
.wechat-avatar-btn {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: normal;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 22rpx;
|
||||
color: #ff3b30;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-thumb {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="detail-page" >
|
||||
<NavBar title="祝福贺卡" />
|
||||
<view class="detail-page">
|
||||
<NavBar title="祝福贺卡" />
|
||||
|
||||
<scroll-view scroll-y class="content-scroll">
|
||||
<view class="content-wrap">
|
||||
@@ -46,7 +46,7 @@
|
||||
</view>
|
||||
|
||||
<!-- Recommendations -->
|
||||
<view class="recommend-section">
|
||||
<!-- <view class="recommend-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">大家都在玩的头像挂饰</text>
|
||||
<text class="more-link">查看更多</text>
|
||||
@@ -65,24 +65,38 @@
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- Banner -->
|
||||
<view class="banner-card">
|
||||
<view class="wallpaper-banner" @tap="goToFortune">
|
||||
<view class="banner-icon">
|
||||
<image
|
||||
src="/static/logo.png"
|
||||
mode="aspectFit"
|
||||
style="width: 100%; height: 100%"
|
||||
v-if="false"
|
||||
/>
|
||||
<view class="placeholder-icon">☺</view>
|
||||
<text>🏮</text>
|
||||
</view>
|
||||
<view class="banner-content">
|
||||
<view class="banner-title">领取我的马年头像框</view>
|
||||
<view class="banner-desc">定制专属新春社交形象</view>
|
||||
<text class="banner-title">去抽取新年运势</text>
|
||||
<text class="banner-desc">每日一签,开启你的新年好运</text>
|
||||
</view>
|
||||
<button class="banner-btn">去领取</button>
|
||||
<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>
|
||||
</view>
|
||||
<text class="banner-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- Footer -->
|
||||
@@ -170,6 +184,29 @@ const decorList = ref([
|
||||
img: "https://file.lihailezzc.com/resource/1463f294244c11cf274a5eaae115872a.jpeg",
|
||||
},
|
||||
]);
|
||||
const goToMake = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/avatar/index",
|
||||
});
|
||||
};
|
||||
|
||||
const goToFortune = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/fortune/index",
|
||||
});
|
||||
};
|
||||
|
||||
const goToGreeting = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/avatar/index",
|
||||
});
|
||||
};
|
||||
|
||||
const goToWallpaper = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/wallpaper/index",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -229,6 +266,53 @@ const decorList = ref([
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.wallpaper-banner {
|
||||
background: #f8f8f8;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Card Container */
|
||||
.card-container {
|
||||
background: #fff;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="fortune-detail-page">
|
||||
<NavBar title="2026 灵马贺岁" :transparent="true"/>
|
||||
<NavBar title="" :transparent="true" color="#ffd700" />
|
||||
|
||||
<!-- 顶部提示条 -->
|
||||
<!-- <view class="top-banner" v-if="inviterName">
|
||||
@@ -44,7 +44,7 @@
|
||||
}}</view>
|
||||
<view class="content-desc">
|
||||
{{
|
||||
fortuneData.desc ||
|
||||
fortuneData.content ||
|
||||
"灵马奔腾,瑞气盈门。此签预示您在2026年如同千里骏马,不仅拥有敏锐的洞察力,更有贵人暗中相助。事业将如破竹之势,学业更有意外惊喜,心之所向,皆能圆满。"
|
||||
}}
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="fortune-page" >
|
||||
<NavBar title="2026 新年运势" :transparent="true" color="#ffd700"/>
|
||||
<view class="fortune-page">
|
||||
<NavBar title="2026 新年运势" :transparent="true" color="#ffd700" />
|
||||
|
||||
<!-- 初始状态:签筒 -->
|
||||
<view class="state-initial" v-if="status !== 'result'">
|
||||
@@ -166,9 +166,10 @@ onShareAppMessage(async () => {
|
||||
});
|
||||
getRewardByShare();
|
||||
return {
|
||||
title: "新春祝福",
|
||||
title: "马年运势我已经抽过了,你的会是什么?",
|
||||
path: `${cardId.value ? `/pages/fortune/detail?shareToken=${shareTokenRes.shareToken}` : `/pages/fortune/index?shareToken=${shareTokenRes.shareToken}`}`,
|
||||
imageUrl: "/static/images/bg.jpg",
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -592,7 +593,7 @@ const saveCard = () => {
|
||||
/* 结果状态 */
|
||||
.state-result {
|
||||
width: 100%;
|
||||
padding: 20px 30px;
|
||||
padding: 130px 30px 20px;
|
||||
animation: fadeIn 0.8s ease-out;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="record-page">
|
||||
<NavBar title="我的运势记录" />
|
||||
<NavBar title="我的运势记录" />
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
@@ -18,7 +18,7 @@
|
||||
<view class="stats-body">
|
||||
<view class="stats-row">
|
||||
<text class="stats-label">已收集</text>
|
||||
<text class="stats-num">{{ records.length }}</text>
|
||||
<text class="stats-num">{{ totalCount }}</text>
|
||||
<text class="stats-label">张好运卡</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
@@ -46,22 +46,20 @@
|
||||
>
|
||||
<view class="item-image-box">
|
||||
<image
|
||||
:src="item.imageUrl"
|
||||
:src="getThumbUrl(item.imageUrl)"
|
||||
mode="aspectFill"
|
||||
class="item-image"
|
||||
/>
|
||||
<view class="item-tag" :class="getTagClass(item.tag)">
|
||||
{{ item.tag || "大吉" }}
|
||||
<view class="item-tag" :class="getTagClass(item.fortuneLevel)">
|
||||
{{ getFortuneName(item.fortuneLevel) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-info">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<view class="item-footer">
|
||||
<text class="item-date">{{ item.date }}</text>
|
||||
<view class="item-link">
|
||||
<text>详情</text>
|
||||
<text class="arrow">→</text>
|
||||
</view>
|
||||
<text class="item-date">{{
|
||||
formatDate(item.day, "YYYY-MM-DD")
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -90,11 +88,11 @@ import { ref, computed } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { getList } from "@/api/fortune.js";
|
||||
import NavBar from "@/components/NavBar/NavBar.vue";
|
||||
|
||||
import { formatDate } from "@/utils/date.js";
|
||||
// 状态管理
|
||||
const records = ref([]);
|
||||
const page = ref(1);
|
||||
const curCount = ref(0);
|
||||
const totalCount = ref(0);
|
||||
const loading = ref(false);
|
||||
const hasMore = ref(true);
|
||||
|
||||
@@ -105,14 +103,30 @@ const progressWidth = computed(() => {
|
||||
return `${percentage}%`;
|
||||
});
|
||||
|
||||
const getTagClass = (tag) => {
|
||||
const getFortuneName = (level) => {
|
||||
const map = {
|
||||
大吉: "tag-gold",
|
||||
安康: "tag-green",
|
||||
顺遂: "tag-red",
|
||||
上吉: "tag-orange",
|
||||
1: "吉签",
|
||||
2: "中吉签",
|
||||
3: "上吉签",
|
||||
4: "上上签",
|
||||
5: "大吉签",
|
||||
};
|
||||
return map[tag] || "tag-gold";
|
||||
return map[level] || "吉签";
|
||||
};
|
||||
|
||||
const getTagClass = (level) => {
|
||||
const map = {
|
||||
1: "tag-blue",
|
||||
2: "tag-green",
|
||||
3: "tag-orange",
|
||||
4: "tag-red",
|
||||
5: "tag-gold",
|
||||
};
|
||||
return map[level] || "tag-blue";
|
||||
};
|
||||
|
||||
const getThumbUrl = (url) => {
|
||||
return `${url}?imageView2/1/w/360/h/480/q/80`;
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
@@ -129,7 +143,7 @@ const loadData = async () => {
|
||||
page.value++;
|
||||
// 简单判断是否还有更多数据
|
||||
hasMore.value = res.hasNext;
|
||||
curCount.value = res.totalCount || 0;
|
||||
totalCount.value = res.totalCount || 0;
|
||||
} else {
|
||||
hasMore.value = false;
|
||||
}
|
||||
@@ -145,19 +159,13 @@ const loadData = async () => {
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
console.log(666666666);
|
||||
loadData();
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const goDetail = (item) => {
|
||||
// 传递数据到详情页
|
||||
const data = encodeURIComponent(JSON.stringify(item));
|
||||
uni.navigateTo({
|
||||
url: `/pages/fortune/detail?data=${data}`,
|
||||
uni.previewImage({
|
||||
current: item.imageUrl,
|
||||
urls: records.value.map((r) => r.imageUrl),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -307,17 +315,22 @@ onLoad(() => {
|
||||
color: #fff;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.tag-gold {
|
||||
background: rgba(212, 175, 55, 0.9);
|
||||
.tag-blue {
|
||||
background: rgba(0, 122, 255, 0.9);
|
||||
}
|
||||
.tag-green {
|
||||
background: rgba(46, 139, 87, 0.9);
|
||||
}
|
||||
.tag-red {
|
||||
background: rgba(178, 34, 34, 0.9);
|
||||
background: rgba(52, 199, 89, 0.9);
|
||||
}
|
||||
.tag-orange {
|
||||
background: rgba(255, 140, 0, 0.9);
|
||||
background: rgba(255, 149, 0, 0.9);
|
||||
}
|
||||
.tag-red {
|
||||
background: rgba(255, 59, 48, 0.9);
|
||||
}
|
||||
.tag-gold {
|
||||
background: rgba(212, 175, 55, 0.9);
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.item-info {
|
||||
|
||||
@@ -358,7 +358,8 @@ onShareAppMessage(() => {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/detail/index",
|
||||
imageUrl: "/static/images/bg.jpg",
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
|
||||
success: function (res) {
|
||||
uni.showToast({ title: "分享成功", icon: "success" });
|
||||
},
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
<view class="step-num-wrap">
|
||||
<view class="step-line" v-if="idx > 0"></view>
|
||||
<view class="step-num">
|
||||
<text v-if="activeTool === tool.type && showPanel">{{ tool.icon }}</text>
|
||||
<text v-if="activeTool === tool.type && showPanel">{{
|
||||
tool.icon
|
||||
}}</text>
|
||||
<text v-else>{{ tool.step }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -62,12 +64,17 @@
|
||||
fontSize: fontSize + 'rpx',
|
||||
lineHeight: fontSize * 1.5 + 'rpx',
|
||||
}"
|
||||
>{{ targetName + "\n " + blessingText.content }}</text
|
||||
>{{
|
||||
(targetName || "") + "\n " + (blessingText.content || "")
|
||||
}}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="user"
|
||||
:style="{ left: 160 + userOffsetX + 'rpx', bottom: 40 - userOffsetY + 'rpx' }"
|
||||
:style="{
|
||||
left: 160 + userOffsetX + 'rpx',
|
||||
bottom: 40 - userOffsetY + 'rpx',
|
||||
}"
|
||||
@touchstart.stop="handleUserTouchStart"
|
||||
@touchmove.stop="handleUserTouchMove"
|
||||
@touchend.stop="handleUserTouchEnd"
|
||||
@@ -113,31 +120,45 @@
|
||||
<!-- 弹出编辑面板 -->
|
||||
<view class="panel-container" :class="{ show: showPanel }">
|
||||
<view class="panel-mask" @tap="closePanel"></view>
|
||||
<view class="panel-content" :class="{ 'glass-effect': activeTool === 'text' || activeTool === 'position' }">
|
||||
<view
|
||||
class="panel-content"
|
||||
:class="{
|
||||
'glass-effect': activeTool === 'text' || activeTool === 'position',
|
||||
}"
|
||||
>
|
||||
<view class="panel-handle" @tap="closePanel"></view>
|
||||
|
||||
<!-- 标题选择区 -->
|
||||
<view v-if="activeTool === 'title'" class="section">
|
||||
<view class="section-title">
|
||||
<text>选择标题</text>
|
||||
</view>
|
||||
<view class="tpl-scroll">
|
||||
<view class="tpl-grid">
|
||||
<view
|
||||
v-for="(title, i) in titles"
|
||||
:key="i"
|
||||
class="tpl-card title-card"
|
||||
:class="{ selected: title?.id === currentTitle?.id }"
|
||||
@tap="selectTitle(title)"
|
||||
>
|
||||
<image :src="title.imageUrl" class="title-cover" mode="aspectFit" />
|
||||
<view v-if="title?.id === currentTitle?.id" class="tpl-check">✔</view>
|
||||
</view>
|
||||
<!-- 标题选择区 -->
|
||||
<view v-if="activeTool === 'title'" class="section">
|
||||
<view class="section-title">
|
||||
<text>选择标题</text>
|
||||
</view>
|
||||
<view class="tpl-scroll">
|
||||
<view class="tpl-grid">
|
||||
<view
|
||||
v-for="(title, i) in titles"
|
||||
:key="i"
|
||||
class="tpl-card title-card"
|
||||
:class="{ selected: title?.id === currentTitle?.id }"
|
||||
@tap="selectTitle(title)"
|
||||
>
|
||||
<image
|
||||
:src="title.imageUrl"
|
||||
class="title-cover"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-if="title?.id === currentTitle?.id" class="tpl-check"
|
||||
>✔</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loadingTitles" class="loading-more">加载中...</view>
|
||||
<view
|
||||
v-else-if="!hasMoreTitles && titles.length > 0"
|
||||
class="no-more"
|
||||
>没有更多了</view
|
||||
>
|
||||
</view>
|
||||
<view v-if="loadingTitles" class="loading-more">加载中...</view>
|
||||
<view v-else-if="!hasMoreTitles && titles.length > 0" class="no-more">没有更多了</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 模板区 -->
|
||||
@@ -154,7 +175,11 @@
|
||||
:class="{ selected: tpl?.id === currentTemplate?.id }"
|
||||
@tap="applyTemplate(tpl)"
|
||||
>
|
||||
<image :src="tpl.imageUrl" class="tpl-cover" mode="aspectFill" />
|
||||
<image
|
||||
:src="tpl.imageUrl"
|
||||
class="tpl-cover"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="tpl-name">{{ tpl.name }}</view>
|
||||
<view v-if="tpl?.id === currentTemplate?.id" class="tpl-check"
|
||||
>✔</view
|
||||
@@ -168,12 +193,10 @@
|
||||
>没有更多了</view
|
||||
>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 文字编辑 -->
|
||||
<view v-if="activeTool === 'text'" class="section text-edit-section">
|
||||
<!-- 祝贺对象 -->
|
||||
<view class="form-item">
|
||||
<text class="label">祝福对象</text>
|
||||
<input
|
||||
@@ -182,6 +205,7 @@
|
||||
placeholder="请输入称呼"
|
||||
placeholder-style="color:#ccc"
|
||||
maxlength="10"
|
||||
@blur="handleTargetNameBlur"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@@ -193,7 +217,11 @@
|
||||
<text class="refresh-icon">↻</text> 换一批
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-x class="greeting-scroll" show-scrollbar="false">
|
||||
<scroll-view
|
||||
scroll-x
|
||||
class="greeting-scroll"
|
||||
show-scrollbar="false"
|
||||
>
|
||||
<view class="greeting-list">
|
||||
<view
|
||||
v-for="(text, index) in displayedGreetings"
|
||||
@@ -222,7 +250,8 @@
|
||||
v-model="signatureName"
|
||||
placeholder="请输入署名"
|
||||
placeholder-style="color:#ccc"
|
||||
maxlength="5"
|
||||
maxlength="10"
|
||||
@blur="handleSignatureBlur"
|
||||
/>
|
||||
<text class="edit-icon">✎</text>
|
||||
</view>
|
||||
@@ -272,7 +301,9 @@
|
||||
:style="{ background: color }"
|
||||
@tap="selectedColor = color"
|
||||
>
|
||||
<view v-if="selectedColor === color" class="color-check">✔</view>
|
||||
<view v-if="selectedColor === color" class="color-check"
|
||||
>✔</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -284,7 +315,7 @@
|
||||
<text>调整位置</text>
|
||||
</view>
|
||||
|
||||
<view class="form-item" style="margin-top: 20rpx;">
|
||||
<view class="form-item" style="margin-top: 20rpx">
|
||||
<text class="label">祝福语气泡 (上下)</text>
|
||||
<slider
|
||||
:value="bubbleOffsetY"
|
||||
@@ -342,7 +373,9 @@
|
||||
:style="{ background: color }"
|
||||
@tap="signatureColor = color"
|
||||
>
|
||||
<view v-if="signatureColor === color" class="color-check">✔</view>
|
||||
<view v-if="signatureColor === color" class="color-check"
|
||||
>✔</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -364,7 +397,7 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { getBavBarHeight, getDeviceInfo } from "@/utils/system";
|
||||
import { generateObjectId } from "@/utils/common";
|
||||
import { generateObjectId, getShareToken } from "@/utils/common";
|
||||
|
||||
import {
|
||||
createCardTmp,
|
||||
@@ -373,7 +406,12 @@ import {
|
||||
getCardTemplateContentList,
|
||||
getCardTemplateTitleList,
|
||||
} from "@/api/make";
|
||||
import { createShareToken, abilityCheck, getShareReward } from "@/api/system";
|
||||
import {
|
||||
createShareToken,
|
||||
abilityCheck,
|
||||
getShareReward,
|
||||
msgCheckApi,
|
||||
} from "@/api/system";
|
||||
import {
|
||||
onShareAppMessage,
|
||||
onLoad,
|
||||
@@ -395,7 +433,7 @@ const cardId = ref("");
|
||||
|
||||
// 标题相关
|
||||
const titles = ref([]);
|
||||
const currentTitle = ref(null);
|
||||
const currentTitle = ref(titles.value[0]);
|
||||
const titlePage = ref(1);
|
||||
const loadingTitles = ref(false);
|
||||
const hasMoreTitles = ref(true);
|
||||
@@ -408,9 +446,9 @@ const titleState = ref({
|
||||
const titleStyle = computed(() => {
|
||||
return {
|
||||
transform: `translate(${titleState.value.offsetX}rpx, ${titleState.value.offsetY}rpx) scale(${titleState.value.scale})`,
|
||||
top: '40rpx',
|
||||
pointerEvents: 'auto',
|
||||
transition: 'none'
|
||||
top: "40rpx",
|
||||
pointerEvents: "auto",
|
||||
transition: "none",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -514,7 +552,38 @@ const handleTitleTouchMove = (e) => {
|
||||
};
|
||||
|
||||
const targetName = ref("祝您");
|
||||
const oldTargetName = ref("祝您");
|
||||
const signatureName = ref(userStore?.userInfo?.nickName || "xxx");
|
||||
const oldSignatureName = ref(userStore?.userInfo?.nickName || "xxx");
|
||||
|
||||
const handleTargetNameBlur = async () => {
|
||||
if (!targetName.value || targetName.value === oldTargetName.value) return;
|
||||
const res = await msgCheckApi(targetName.value);
|
||||
if (!res.success) {
|
||||
uni.showToast({
|
||||
title: res.message || "消息不符合发布规范,请稍作修改后再试",
|
||||
icon: "none",
|
||||
});
|
||||
targetName.value = oldTargetName.value;
|
||||
} else {
|
||||
oldTargetName.value = targetName.value;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignatureBlur = async () => {
|
||||
if (!signatureName.value || signatureName.value === oldSignatureName.value)
|
||||
return;
|
||||
const res = await msgCheckApi(signatureName.value);
|
||||
if (!res.success) {
|
||||
uni.showToast({
|
||||
title: res.message || "消息不符合发布规范,请稍作修改后再试",
|
||||
icon: "none",
|
||||
});
|
||||
signatureName.value = oldSignatureName.value;
|
||||
} else {
|
||||
oldSignatureName.value = signatureName.value;
|
||||
}
|
||||
};
|
||||
const userAvatar = ref(
|
||||
userStore?.userInfo?.avatarUrl ||
|
||||
"https://file.lihailezzc.com/resource/96023631c6ab9c3496b7620097af3d6f.png",
|
||||
@@ -560,7 +629,7 @@ const fontList = [
|
||||
name: "中圆",
|
||||
family: "ZhongYuan",
|
||||
url: "https://file.lihailezzc.com/ddcd9621740449a29c329f573bc1d0c5.woff2", // 示例地址
|
||||
}
|
||||
},
|
||||
];
|
||||
const selectedFont = ref(fontList[0]);
|
||||
const loadedFonts = ref(new Set()); // 记录已加载的字体
|
||||
@@ -756,35 +825,46 @@ const loadMoreTemplates = () => {
|
||||
getTemplateList(true);
|
||||
};
|
||||
|
||||
onShareAppMessage(async () => {
|
||||
onShareAppMessage(async (options) => {
|
||||
getShareReward({ scene: "card_generate" });
|
||||
if (!isLoggedIn.value) {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
}
|
||||
// 1. 确保有 cardId (如果内容有变动,最好是新建)
|
||||
const id = createCard();
|
||||
if (!id) {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
}
|
||||
if (options.from === "button") {
|
||||
if (!isLoggedIn.value) {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
}
|
||||
// 1. 确保有 cardId (如果内容有变动,最好是新建)
|
||||
const id = createCard();
|
||||
if (!id) {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
}
|
||||
|
||||
const deviceInfo = getDeviceInfo();
|
||||
const shareTokenRes = await createShareToken({
|
||||
scene: "card_generate",
|
||||
targetId: id,
|
||||
...deviceInfo,
|
||||
});
|
||||
shareOrSave(id);
|
||||
return {
|
||||
title: "我刚做了一张祝福卡片,送给你",
|
||||
path: "/pages/detail/index?shareToken=" + shareTokenRes.shareToken,
|
||||
imageUrl: "/static/images/share.jpg",
|
||||
};
|
||||
const deviceInfo = getDeviceInfo();
|
||||
const shareTokenRes = await createShareToken({
|
||||
scene: "card_generate",
|
||||
targetId: id,
|
||||
...deviceInfo,
|
||||
});
|
||||
shareOrSave(id);
|
||||
return {
|
||||
title: "我刚做了一张祝福卡片,送给你",
|
||||
path: "/pages/detail/index?shareToken=" + shareTokenRes.shareToken,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/13ec1134e6614feadeeaaa9ef21ea96e.png",
|
||||
};
|
||||
} else {
|
||||
const shareTokenRes = await getShareToken("card_generate_index", "");
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: `/pages/index/index?shareToken=${shareTokenRes.shareToken}`,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const displayedGreetings = ref([]);
|
||||
@@ -838,6 +918,7 @@ const selectTitle = (title) => {
|
||||
currentTitle.value = null;
|
||||
} else {
|
||||
currentTitle.value = title;
|
||||
|
||||
// 切换标题时重置位置和缩放
|
||||
titleState.value = {
|
||||
offsetX: 0,
|
||||
@@ -912,6 +993,7 @@ const shareOrSave = async (id) => {
|
||||
blessingTo: targetName.value,
|
||||
blessingFrom: signatureName.value,
|
||||
templateId: currentTemplate.value?.id || "",
|
||||
titleId: currentTitle?.value?.id || "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -957,69 +1039,71 @@ const saveByCanvas = async (save = true) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 辅助函数:rpx 转 px (基于预览容器宽度 506rpx 对应 Canvas 540px)
|
||||
const r2p = (rpx) => (rpx * 540) / 506;
|
||||
// 辅助函数:rpx 转 px (基于预览容器宽度 506rpx 对应 Canvas 540px)
|
||||
const r2p = (rpx) => (rpx * 540) / 506;
|
||||
|
||||
try {
|
||||
// 1️⃣ 画背景
|
||||
// ⭐ 先加载背景图
|
||||
const [bgImg, avatarImg, titleImg] = await Promise.all([
|
||||
loadCanvasImage(currentTemplate?.value?.imageUrl),
|
||||
loadCanvasImage(userAvatar.value),
|
||||
currentTitle.value ? loadCanvasImage(currentTitle.value.imageUrl) : Promise.resolve(null),
|
||||
]);
|
||||
try {
|
||||
// 1️⃣ 画背景
|
||||
// ⭐ 先加载背景图
|
||||
const [bgImg, avatarImg, titleImg] = await Promise.all([
|
||||
loadCanvasImage(currentTemplate?.value?.imageUrl),
|
||||
loadCanvasImage(userAvatar.value),
|
||||
currentTitle.value
|
||||
? loadCanvasImage(currentTitle.value.imageUrl)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
ctx.drawImage(bgImg, 0, 0, W, H);
|
||||
ctx.drawImage(bgImg, 0, 0, W, H);
|
||||
|
||||
// 2️⃣ 半透明遮罩
|
||||
ctx.fillStyle = "rgba(0,0,0,0.08)";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
// 2️⃣ 半透明遮罩
|
||||
ctx.fillStyle = "rgba(0,0,0,0.08)";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// 3️⃣ 标题图片
|
||||
if (titleImg) {
|
||||
const previewBaseWidth = 400; // rpx
|
||||
const drawWidth = r2p(previewBaseWidth) * titleState.value.scale;
|
||||
const drawHeight = (titleImg.height / titleImg.width) * drawWidth;
|
||||
|
||||
// 计算绘制起点:居中 + 偏移量
|
||||
const titleX = (W - drawWidth) / 2 + r2p(titleState.value.offsetX);
|
||||
const titleY = r2p(40) + r2p(titleState.value.offsetY);
|
||||
|
||||
ctx.drawImage(titleImg, titleX, titleY, drawWidth, drawHeight);
|
||||
}
|
||||
// 3️⃣ 标题图片
|
||||
if (titleImg) {
|
||||
const previewBaseWidth = 400; // rpx
|
||||
const drawWidth = r2p(previewBaseWidth) * titleState.value.scale;
|
||||
const drawHeight = (titleImg.height / titleImg.width) * drawWidth;
|
||||
|
||||
// 4️⃣ 祝福语气泡
|
||||
// 预览中 .bubble 有 padding: 40rpx,且 .card-overlay 有 padding: 30rpx
|
||||
// 意味着文字距离容器边缘至少有 70rpx
|
||||
drawBubbleText(ctx, {
|
||||
text: targetName.value + "\n " + blessingText.value.content,
|
||||
x: 0,
|
||||
y: r2p(230 + bubbleOffsetY.value),
|
||||
maxWidth: r2p(bubbleMaxWidth.value), // 预览中 bubble-text 的宽度
|
||||
canvasWidth: W,
|
||||
fontSize: r2p(fontSize.value),
|
||||
lineHeight: r2p(fontSize.value * 1.6), // 预览中是 1.6
|
||||
padding: r2p(40 + 30), // 内部 padding 40 + 容器 padding 30
|
||||
backgroundColor: "transparent",
|
||||
textColor: selectedColor.value,
|
||||
fontFamily: selectedFont.value.family,
|
||||
});
|
||||
// 计算绘制起点:居中 + 偏移量
|
||||
const titleX = (W - drawWidth) / 2 + r2p(titleState.value.offsetX);
|
||||
const titleY = r2p(40) + r2p(titleState.value.offsetY);
|
||||
|
||||
// 5️⃣ 用户信息
|
||||
// 预览中 user 是 absolute, left: 160 + offsetX, bottom: 40 - offsetY
|
||||
drawUserBubble(ctx, {
|
||||
x: r2p(160 + userOffsetX.value),
|
||||
bottom: r2p(40 - userOffsetY.value),
|
||||
canvasHeight: H,
|
||||
avatarImg: avatarImg,
|
||||
username: signatureName.value,
|
||||
desc: "送上祝福",
|
||||
textColor: signatureColor.value,
|
||||
avatarSize: r2p(64),
|
||||
padding: r2p(15),
|
||||
fontSizeName: r2p(24),
|
||||
fontSizeDesc: r2p(20),
|
||||
});
|
||||
ctx.drawImage(titleImg, titleX, titleY, drawWidth, drawHeight);
|
||||
}
|
||||
|
||||
// 4️⃣ 祝福语气泡
|
||||
// 预览中 .bubble 有 padding: 40rpx,且 .card-overlay 有 padding: 30rpx
|
||||
// 意味着文字距离容器边缘至少有 70rpx
|
||||
drawBubbleText(ctx, {
|
||||
text: targetName.value + "\n " + blessingText.value.content,
|
||||
x: 0,
|
||||
y: r2p(230 + bubbleOffsetY.value),
|
||||
maxWidth: r2p(bubbleMaxWidth.value), // 预览中 bubble-text 的宽度
|
||||
canvasWidth: W,
|
||||
fontSize: r2p(fontSize.value),
|
||||
lineHeight: r2p(fontSize.value * 1.6), // 预览中是 1.6
|
||||
padding: r2p(40 + 30), // 内部 padding 40 + 容器 padding 30
|
||||
backgroundColor: "transparent",
|
||||
textColor: selectedColor.value,
|
||||
fontFamily: selectedFont.value.family,
|
||||
});
|
||||
|
||||
// 5️⃣ 用户信息
|
||||
// 预览中 user 是 absolute, left: 160 + offsetX, bottom: 40 - offsetY
|
||||
drawUserBubble(ctx, {
|
||||
x: r2p(160 + userOffsetX.value),
|
||||
bottom: r2p(40 - userOffsetY.value),
|
||||
canvasHeight: H,
|
||||
avatarImg: avatarImg,
|
||||
username: signatureName.value,
|
||||
desc: "送上祝福",
|
||||
textColor: signatureColor.value,
|
||||
avatarSize: r2p(64),
|
||||
padding: r2p(15),
|
||||
fontSizeName: r2p(24),
|
||||
fontSizeDesc: r2p(20),
|
||||
});
|
||||
|
||||
// 6️⃣ 输出
|
||||
uni.canvasToTempFilePath({
|
||||
@@ -1209,11 +1293,11 @@ function drawUserBubble(ctx, options) {
|
||||
const textX = avatarX + avatarSize + padding;
|
||||
const totalTextHeight = fontSizeName + fontSizeDesc + 4;
|
||||
const textStartY = drawY + (bubbleHeight - totalTextHeight) / 2;
|
||||
|
||||
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.font = `${fontSizeName}px 'PingFang SC'`;
|
||||
ctx.fillText(username, textX, textStartY);
|
||||
|
||||
|
||||
ctx.font = `${fontSizeDesc}px 'PingFang SC'`;
|
||||
ctx.globalAlpha = 0.6;
|
||||
ctx.fillText(desc, textX, textStartY + fontSizeName + 4);
|
||||
@@ -1670,7 +1754,7 @@ function drawRoundRect(ctx, x, y, w, h, r, color) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.position-section{
|
||||
.position-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.greeting-card.active .greeting-text {
|
||||
@@ -1783,8 +1867,9 @@ function drawRoundRect(ctx, x, y, w, h, r, color) {
|
||||
.btn.secondary {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
color: #333;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05),
|
||||
inset 0 0 0 2rpx #eee;
|
||||
box-shadow:
|
||||
0 8rpx 20rpx rgba(0, 0, 0, 0.05),
|
||||
inset 0 0 0 2rpx #eee;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="avatar-page" >
|
||||
<view class="avatar-page">
|
||||
<!-- Navbar -->
|
||||
<NavBar title="我的头像制作" />
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-info">
|
||||
<text class="item-name">{{
|
||||
<!-- <text class="item-name">{{
|
||||
item.decorName || getDefaultName(item)
|
||||
}}</text>
|
||||
}}</text> -->
|
||||
<text class="item-date">{{ formatDate(item.createdAt) }} 制作</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<view class="greeting-page" >
|
||||
<NavBar title="我的祝福" />
|
||||
<view class="greeting-page">
|
||||
<NavBar title="我的新春祝福" background="transparent" />
|
||||
|
||||
<!-- Header Stats -->
|
||||
<view class="header-stats">
|
||||
<view class="stats-card">
|
||||
<view class="stats-left">
|
||||
<view class="icon-circle">
|
||||
<text>✨</text>
|
||||
<text class="sparkle-emoji">✨</text>
|
||||
</view>
|
||||
<view class="stats-info">
|
||||
<text class="label">已累计创作</text>
|
||||
@@ -28,69 +28,66 @@
|
||||
<!-- List Section -->
|
||||
<view class="list-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">祝福列表</text>
|
||||
<text class="section-tip">左滑删除记录</text>
|
||||
<text class="section-title">祝福库</text>
|
||||
</view>
|
||||
|
||||
<view class="list-container">
|
||||
<view v-for="item in list" :key="item.id" class="list-item-wrap">
|
||||
<view class="swipe-container">
|
||||
<!-- Delete Action (Behind) -->
|
||||
<view class="delete-action" @tap.stop="onDelete(item)">
|
||||
<text>删除</text>
|
||||
<view class="list-grid">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="card-item"
|
||||
@tap="onPreview(item)"
|
||||
>
|
||||
<view class="card-image-wrap">
|
||||
<image :src="item.imageUrl" mode="aspectFill" class="card-img" />
|
||||
<view class="year-badge" v-if="item.year">{{ item.year }}</view>
|
||||
<view class="draft-overlay" v-if="item.status === 'draft'">
|
||||
<text class="lock-icon">🔒</text>
|
||||
</view>
|
||||
|
||||
<!-- Card Content (Front) -->
|
||||
<view
|
||||
class="card-item"
|
||||
:style="{
|
||||
transform: `translateX(${item.translateX || 0}px)`,
|
||||
transition: item.useTransition ? 'transform 0.3s' : 'none',
|
||||
}"
|
||||
@touchstart="onTouchStart($event, item)"
|
||||
@touchmove="onTouchMove($event, item)"
|
||||
@touchend="onTouchEnd($event, item)"
|
||||
>
|
||||
<image :src="item.imageUrl" mode="aspectFill" class="card-img" />
|
||||
<view class="card-content">
|
||||
<view class="card-title"
|
||||
>{{ item.blessingTo
|
||||
}}{{
|
||||
item.blessingFrom ? item.blessingFrom : "好友"
|
||||
}}身体健康</view
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<view class="card-title">{{ getTitle(item) }}</view>
|
||||
<view class="card-date">{{ formatDate(item.updatedAt) }}</view>
|
||||
<view class="card-footer">
|
||||
<view class="tag" :class="getTagClass(item)">{{
|
||||
getTagText(item)
|
||||
}}</view>
|
||||
<view class="actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
open-type="share"
|
||||
:data-item="item"
|
||||
@tap.stop
|
||||
>
|
||||
<view class="card-date"
|
||||
>更新时间:{{ formatDate(item.updatedAt) }}</view
|
||||
>
|
||||
<view class="tags">
|
||||
<view class="tag yellow" v-if="item.festival">{{
|
||||
item.festival
|
||||
}}</view>
|
||||
<view class="tag red" v-if="item.year">{{ item.year }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-actions">
|
||||
<view class="action-btn" @tap.stop="onShare(item)">
|
||||
<text class="icon">🔗</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap.stop="onEdit(item)">
|
||||
<text class="icon">✎</text>
|
||||
</view>
|
||||
<text class="action-emoji">🔗</text>
|
||||
</button>
|
||||
<!-- <view class="action-btn" @tap.stop="onMore(item)">
|
||||
<text class="action-emoji">⋯</text>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading State -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view class="empty-state" v-if="!loading && list.length === 0">
|
||||
<text>暂无祝福记录</text>
|
||||
</view>
|
||||
<view class="no-more" v-if="!loading && !hasMore && list.length > 0">
|
||||
<text>没有更多了</text>
|
||||
</view>
|
||||
<!-- Loading State -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view class="empty-state" v-if="!loading && list.length === 0">
|
||||
<text>暂无祝福记录</text>
|
||||
</view>
|
||||
|
||||
<view class="footer-note" v-if="!loading && list.length > 0">
|
||||
<text>2026 丙午马年 · 祝福管理助手</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- FAB -->
|
||||
<view class="fab-btn" @tap="onMake">
|
||||
<view class="fab-content">
|
||||
<text class="fab-emoji">✍️</text>
|
||||
<text>新春制作</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -98,96 +95,56 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
|
||||
import {
|
||||
onPullDownRefresh,
|
||||
onReachBottom,
|
||||
onShareAppMessage,
|
||||
} from "@dcloudio/uni-app";
|
||||
import { getMyCard } from "@/api/mine.js";
|
||||
import NavBar from "@/components/NavBar/NavBar.vue";
|
||||
import { getShareToken } from "@/utils/common.js";
|
||||
|
||||
const navBarTop = ref(0);
|
||||
const navBarHeight = ref(44);
|
||||
const list = ref([]);
|
||||
const page = ref(1);
|
||||
const loading = ref(false);
|
||||
const hasMore = ref(true);
|
||||
const isRefreshing = ref(false);
|
||||
const totalCount = ref(0);
|
||||
|
||||
const deleteOptions = ref([
|
||||
{
|
||||
text: "删除",
|
||||
style: {
|
||||
backgroundColor: "#ff3b30",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Swipe Logic
|
||||
const startX = ref(0);
|
||||
const activeItem = ref(null);
|
||||
const MAX_SWIPE_WIDTH = 80;
|
||||
|
||||
const onTouchStart = (e, item) => {
|
||||
if (e.touches.length > 1) return;
|
||||
// Close other items
|
||||
if (activeItem.value && activeItem.value.id !== item.id) {
|
||||
activeItem.value.translateX = 0;
|
||||
activeItem.value.useTransition = true;
|
||||
}
|
||||
|
||||
startX.value = e.touches[0].clientX;
|
||||
item.useTransition = false;
|
||||
activeItem.value = item;
|
||||
};
|
||||
|
||||
const onTouchMove = (e, item) => {
|
||||
if (e.touches.length > 1) return;
|
||||
const currentX = e.touches[0].clientX;
|
||||
const deltaX = currentX - startX.value;
|
||||
|
||||
// Allow swiping left (negative) up to -MAX_SWIPE_WIDTH
|
||||
// If already open (translateX = -80), deltaX needs to be adjusted
|
||||
// But simpler: just use delta from 0 position.
|
||||
// Actually, standard swipe logic needs to account for current position.
|
||||
// For simplicity: assume always starting from 0 (closed) or -80 (open).
|
||||
// But if we start drag from open state, we need to handle it.
|
||||
|
||||
// Let's stick to "start from 0" logic for now, assuming auto-close.
|
||||
// If item is already open, and we swipe right, we close it.
|
||||
|
||||
// Re-calculate based on initial offset if we want to support dragging from open.
|
||||
// For now: simple close-on-touch-other logic covers most cases.
|
||||
// We assume startX is from a state where it is either 0 or -80.
|
||||
// But `item.translateX` might be -80.
|
||||
|
||||
let targetX = deltaX;
|
||||
if (item.translateX === -MAX_SWIPE_WIDTH) {
|
||||
targetX = -MAX_SWIPE_WIDTH + deltaX;
|
||||
}
|
||||
|
||||
if (targetX < -MAX_SWIPE_WIDTH) targetX = -MAX_SWIPE_WIDTH;
|
||||
if (targetX > 0) targetX = 0;
|
||||
|
||||
item.translateX = targetX;
|
||||
};
|
||||
|
||||
const onTouchEnd = (e, item) => {
|
||||
item.useTransition = true;
|
||||
if (item.translateX < -30) {
|
||||
item.translateX = -MAX_SWIPE_WIDTH;
|
||||
} else {
|
||||
item.translateX = 0;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchList(true);
|
||||
});
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
onRefresh();
|
||||
fetchList(true);
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
loadMore();
|
||||
if (hasMore.value && !loading.value) {
|
||||
fetchList();
|
||||
}
|
||||
});
|
||||
|
||||
onShareAppMessage(async (options) => {
|
||||
if (options.from === "button") {
|
||||
const shareTokenRes = await getShareToken(
|
||||
"card_generate",
|
||||
options?.target?.dataset?.item?.id,
|
||||
);
|
||||
return {
|
||||
title: "我刚做了一张祝福卡片,送给你",
|
||||
path: "/pages/detail/index?shareToken=" + shareTokenRes.shareToken,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/13ec1134e6614feadeeaaa9ef21ea96e.png",
|
||||
};
|
||||
} else {
|
||||
const shareTokenRes = await getShareToken("greeting_page", "");
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: `/pages/index/index?shareToken=${shareTokenRes.shareToken}`,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const fetchList = async (reset = false) => {
|
||||
@@ -216,27 +173,12 @@ const fetchList = async (reset = false) => {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch greeting list", e);
|
||||
uni.showToast({ title: "加载失败", icon: "none" });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
isRefreshing.value = false;
|
||||
uni.stopPullDownRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
fetchList();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
isRefreshing.value = true;
|
||||
fetchList(true);
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return "";
|
||||
const date = new Date(dateStr);
|
||||
@@ -246,75 +188,115 @@ const formatDate = (dateStr) => {
|
||||
return `${y}-${m}-${d}`;
|
||||
};
|
||||
|
||||
const getTagText = (item) => {
|
||||
// if (item.status === "draft") return "草稿";
|
||||
return item?.title?.name || item.festival || "新春快乐";
|
||||
};
|
||||
|
||||
const getTitle = (item) => {
|
||||
const title =
|
||||
(item?.blessingTo || "祝您") + (item?.content?.content || "新春快乐");
|
||||
return title.length > 10 ? title.substring(0, 10) + "..." : title;
|
||||
};
|
||||
|
||||
const getTagClass = (item) => {
|
||||
if (item.status === "draft") return "tag-draft";
|
||||
const tagMap = {
|
||||
万事如意: "tag-spring",
|
||||
新春快乐: "tag-gold",
|
||||
新春大吉: "tag-horse",
|
||||
钱包鼓鼓: "tag-ink",
|
||||
福气旺旺: "tag-spring",
|
||||
龙马精神: "tag-horse",
|
||||
马年纳祥: "tag-horse",
|
||||
福马迎春: "tag-horse",
|
||||
};
|
||||
return tagMap[item?.title?.name || item.festival] || "tag-gold";
|
||||
};
|
||||
|
||||
const onPreview = (item) => {
|
||||
if (!item.imageUrl) return;
|
||||
uni.previewImage({
|
||||
urls: [item.imageUrl],
|
||||
current: item.imageUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const onMake = () => {
|
||||
uni.switchTab({
|
||||
url: "/pages/make/index",
|
||||
});
|
||||
};
|
||||
|
||||
// const onShare = (item) => {
|
||||
// uni.showToast({ title: "分享功能开发中", icon: "none" });
|
||||
// };
|
||||
|
||||
const onMore = (item) => {
|
||||
uni.showActionSheet({
|
||||
itemList: ["编辑", "删除"],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
// Edit
|
||||
} else if (res.tapIndex === 1) {
|
||||
onDelete(item);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (item) => {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要删除这条祝福吗?",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// Implement delete API call here
|
||||
// For now just remove from list locally
|
||||
list.value = list.value.filter((i) => i.id !== item.id);
|
||||
totalCount.value = Math.max(0, totalCount.value - 1);
|
||||
uni.showToast({ title: "删除成功", icon: "none" });
|
||||
} else {
|
||||
// Reset swipe state if cancelled
|
||||
item.translateX = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onShare = (item) => {
|
||||
// Implement share logic
|
||||
uni.showToast({ title: "分享功能开发中", icon: "none" });
|
||||
};
|
||||
|
||||
const onEdit = (item) => {
|
||||
// Implement edit logic
|
||||
uni.showToast({ title: "编辑功能开发中", icon: "none" });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.greeting-page {
|
||||
min-height: 100vh;
|
||||
background: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fbf9f2;
|
||||
padding-bottom: 120rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header-stats {
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
margin-top: 44px; // Initial offset for fixed nav
|
||||
padding: 30rpx 40rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.stats-card {
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
border-radius: 40rpx;
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.02);
|
||||
|
||||
.stats-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: #fff0f0;
|
||||
background: #fff5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
margin-right: 24rpx;
|
||||
|
||||
text {
|
||||
font-size: 24px;
|
||||
.sparkle-emoji {
|
||||
font-size: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +305,9 @@ const onEdit = (item) => {
|
||||
flex-direction: column;
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.value-wrap {
|
||||
@@ -333,14 +315,14 @@ const onEdit = (item) => {
|
||||
align-items: baseline;
|
||||
|
||||
.value {
|
||||
font-size: 24px;
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 4px;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
@@ -348,30 +330,29 @@ const onEdit = (item) => {
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background: #eee;
|
||||
margin: 0 20px;
|
||||
width: 1rpx;
|
||||
height: 80rpx;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.stats-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 80px;
|
||||
padding-left: 20rpx;
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.red-text {
|
||||
color: #ff3b30;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,89 +360,86 @@ const onEdit = (item) => {
|
||||
}
|
||||
|
||||
.list-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 20px;
|
||||
padding: 0 40rpx;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
margin: 40rpx 0 24rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.section-tip {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #7c6d5d;
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
padding-bottom: 40px;
|
||||
.list-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item-wrap {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.swipe-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #ff3b30; // Delete background color
|
||||
}
|
||||
|
||||
.delete-action {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 48rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.03);
|
||||
|
||||
.card-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 12px;
|
||||
margin-right: 16px;
|
||||
background: #f5f5f5;
|
||||
.card-image-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 133%; // 3:4 aspect ratio
|
||||
background: #fdf3e7;
|
||||
|
||||
.card-img {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.year-badge {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.draft-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
font-size: 40rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
.card-info {
|
||||
padding: 24rpx;
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 8rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
@@ -469,68 +447,117 @@ const onEdit = (item) => {
|
||||
}
|
||||
|
||||
.card-date {
|
||||
font-size: 12px;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tags {
|
||||
.card-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.yellow {
|
||||
background: #fff8e1;
|
||||
color: #ffb300;
|
||||
&.tag-gold {
|
||||
background: #fff8e6;
|
||||
color: #ffb800;
|
||||
}
|
||||
|
||||
&.red {
|
||||
background: #ffebee;
|
||||
color: #ff3b30;
|
||||
&.tag-horse {
|
||||
background: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
&.tag-ink {
|
||||
background: #f0f5ff;
|
||||
color: #2f54eb;
|
||||
}
|
||||
&.tag-draft {
|
||||
background: #f5f5f5;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
&.tag-spring {
|
||||
background: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
&.blue {
|
||||
background: #e3f2fd;
|
||||
color: #2196f3;
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
|
||||
.action-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-emoji {
|
||||
font-size: 32rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
.fab-btn {
|
||||
position: fixed;
|
||||
bottom: 60rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
|
||||
.fab-content {
|
||||
background: #ff4d4f;
|
||||
padding: 20rpx 48rpx;
|
||||
border-radius: 100rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 77, 79, 0.3);
|
||||
|
||||
.action-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.fab-emoji {
|
||||
font-size: 36rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
transform: translateX(-50%) scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
text-align: center;
|
||||
padding: 60rpx 0 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state,
|
||||
.no-more {
|
||||
.empty-state {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding: 100rpx 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="row-2" v-if="isLoggedIn">
|
||||
<text class="arrow-icon">➤</text>
|
||||
<text class="stats-text"
|
||||
<!-- <text class="arrow-icon">➤</text> -->
|
||||
<!-- <text class="stats-text"
|
||||
>已发送 <text class="num">3</text> 条新春祝福</text
|
||||
>
|
||||
> -->
|
||||
</view>
|
||||
<view class="row-2" v-else>
|
||||
<text class="stats-text">点击登录解锁更多功能</text>
|
||||
@@ -136,9 +136,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import {
|
||||
onShareAppMessage,
|
||||
} from "@dcloudio/uni-app";
|
||||
import { onShareAppMessage } from "@dcloudio/uni-app";
|
||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
@@ -155,6 +153,7 @@ const defaultAvatarUrl =
|
||||
const userInfo = computed(() => ({
|
||||
nickName: userStore.userInfo.nickName || "点击登录",
|
||||
avatarUrl: userStore.userInfo.avatarUrl || defaultAvatarUrl,
|
||||
isVip: userStore.userInfo.isVip || false,
|
||||
}));
|
||||
|
||||
const isLoggedIn = computed(() => !!userStore.userInfo.nickName);
|
||||
@@ -166,11 +165,11 @@ onMounted(() => {
|
||||
navBarHeight.value = 44;
|
||||
});
|
||||
|
||||
onShareAppMessage( () => {
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: "新年好运已送达 🎊|祝福卡·头像·壁纸",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
title: "新年好运已送达 🎊|祝福卡·头像·壁纸",
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
});
|
||||
|
||||
const handleUserClick = () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="vip-page" >
|
||||
<view class="vip-page">
|
||||
<NavBar title="会员中心" />
|
||||
|
||||
<!-- Content -->
|
||||
@@ -120,10 +120,20 @@ const selectedPlanIndex = ref(1);
|
||||
const plans = ref([]);
|
||||
|
||||
const benefits = [
|
||||
{ name: "高级模板", icon: "star-filled", color: "#ff3b30", bg: "#fff0f0" },
|
||||
{ name: "无限制下载", icon: "image-filled", color: "#ff6b00", bg: "#fff7e6" },
|
||||
{ name: "马年头像框", icon: "medal-filled", color: "#bfa46f", bg: "#fffbe6" },
|
||||
{ name: "高速渲染", icon: "upload-filled", color: "#ff3b30", bg: "#fff0f0" },
|
||||
{ name: "纯净无广", icon: "star-filled", color: "#ff3b30", bg: "#fff0f0" },
|
||||
{ name: "多次下载", icon: "image-filled", color: "#ff6b00", bg: "#fff7e6" },
|
||||
{
|
||||
name: "会员精选头像",
|
||||
icon: "medal-filled",
|
||||
color: "#bfa46f",
|
||||
bg: "#fffbe6",
|
||||
},
|
||||
{
|
||||
name: "会员精选模版",
|
||||
icon: "upload-filled",
|
||||
color: "#ff3b30",
|
||||
bg: "#fff0f0",
|
||||
},
|
||||
{
|
||||
name: "数据永久保存",
|
||||
icon: "paperplane-filled",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<view
|
||||
class="wallpaper-page"
|
||||
>
|
||||
<NavBar title="精美壁纸" />
|
||||
<view class="wallpaper-page">
|
||||
<NavBar title="新春精美壁纸" />
|
||||
|
||||
<!-- Category Tabs -->
|
||||
<view class="category-tabs">
|
||||
@@ -37,18 +35,12 @@
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
:src="getThumbUrl(item.imageUrl)"
|
||||
:src="getThumbUrl(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>
|
||||
<button
|
||||
class="action-btn share"
|
||||
open-type="share"
|
||||
@@ -57,6 +49,12 @@
|
||||
>
|
||||
<text class="icon">➦</text>
|
||||
</button>
|
||||
<view
|
||||
class="action-btn download"
|
||||
@tap.stop="downloadWallpaper(item)"
|
||||
>
|
||||
<text class="icon">↓</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -82,7 +80,6 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { getBavBarHeight } from "@/utils/system";
|
||||
import { getWallpaperList, getWallpaperCategoryList } from "@/api/wallpaper.js";
|
||||
import {
|
||||
saveRemoteImageToLocal,
|
||||
@@ -122,6 +119,8 @@ onShareAppMessage(async (options) => {
|
||||
return {
|
||||
title: "新春祝福",
|
||||
path: `/pages/index/index?shareToken=${shareTokenRes.shareToken}`,
|
||||
imageUrl:
|
||||
"https://file.lihailezzc.com/resource/cfed2edbfa19250b836a87a4bbf0d5ad.png",
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -131,7 +130,7 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
const getThumbUrl = (url) => {
|
||||
return `${url}?imageView2/1/w/340/h/600/q/80`;
|
||||
return `${url}?imageView2/1/w/340/h/600/q/80`;
|
||||
};
|
||||
|
||||
const fetchCategories = async () => {
|
||||
@@ -245,46 +244,21 @@ const shareWallpaper = (item) => {};
|
||||
<style lang="scss" scoped>
|
||||
.wallpaper-page {
|
||||
height: 100vh;
|
||||
background-color: #7a0909; /* Dark Red Background */
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
/* background: #7A0909; */
|
||||
.category-tabs {
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #eeeeee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back {
|
||||
font-size: 50rpx;
|
||||
margin-right: 24rpx;
|
||||
line-height: 1;
|
||||
color: #ffd700; /* Gold */
|
||||
/* 增大点击区域 */
|
||||
padding: 20rpx;
|
||||
margin-left: -20rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffd700; /* Gold */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin-right: 50rpx; /* Balance back button */
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
padding: 20rpx 0;
|
||||
/* background-color: #7A0909; */
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
@@ -292,51 +266,54 @@ const shareWallpaper = (item) => {};
|
||||
|
||||
.tabs-content {
|
||||
display: inline-flex;
|
||||
padding: 0 24rpx;
|
||||
gap: 20rpx;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
color: #ffd700;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 2rpx solid transparent;
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 30rpx;
|
||||
color: #999999;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.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);
|
||||
color: #e60012;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80rpx;
|
||||
height: 4rpx;
|
||||
background-color: #e60012;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 30rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 340rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 24rpx;
|
||||
border-radius: 32rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||
background: #333;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.wallpaper-img {
|
||||
@@ -357,22 +334,29 @@ const shareWallpaper = (item) => {};
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn.share .icon {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.action-btn .icon {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-btn.share .icon {
|
||||
font-size: 28rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
@@ -380,7 +364,7 @@ const shareWallpaper = (item) => {};
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: #999999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
static/images/.DS_Store
vendored
Normal file
BIN
static/images/.DS_Store
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
@@ -21,8 +21,12 @@ export const uploadImage = (filePath) => {
|
||||
if (res.statusCode < 400) {
|
||||
try {
|
||||
const keyJson = JSON.parse(res.data);
|
||||
const url = `https://file.lihailezzc.com/${keyJson?.data.key}`;
|
||||
resolve(url);
|
||||
if (keyJson?.data?.result === "auditFailed") {
|
||||
reject("图片不符合发布规范,请稍作修改后再试");
|
||||
} else {
|
||||
const url = `https://file.lihailezzc.com/${keyJson?.data.key}`;
|
||||
resolve(url);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user