feat(frontend): 全面前端更新、资源添加与问题修复
- 新增并更新多个静态图标和图片资源 - 修复多个页面的返回图标路径和格式问题 - 简化用户模块的手机号缓存逻辑,移除冗余的脱敏手机号回退方案 - 更新本地开发环境的API基础请求地址 - 优化底部导航栏,支持自定义图标尺寸和旋转效果 - 重做主题横幅的进度展示样式与布局 - 为工艺选择页面添加路由映射和更完善的跳转逻辑 - 为用户个人作品页面新增倒计时计时器和收益领取功能 - 升级藏品详情页面:优化布局、新增用户头像展示、链上数据切换功能,解决代码合并冲突 - 重构用户个人中心页面:更新UI样式、新增敏感信息显示切换开关,替换为新的引导弹窗
@ -129,7 +129,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<scroll-view v-else scroll-y class="content-scroll">
|
||||
<scroll-view v-else scroll-y class="content-scroll" :show-scrollbar="false">
|
||||
<view class="content-wrapper">
|
||||
<!-- 藏品卡片区域 -->
|
||||
<view class="card-section">
|
||||
@ -171,8 +171,9 @@
|
||||
<!-- 点赞 + 收益 + 倒计时行 -->
|
||||
<view class="card-meta-row">
|
||||
<!-- 点赞 -->
|
||||
<view class="like-area" @tap="handleLike">
|
||||
<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'" class="like-icon" mode="aspectFit"></image>
|
||||
<view v-if="assetData.display_status === 1" class="like-area" @tap="handleLike">
|
||||
<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'"
|
||||
class="like-icon" mode="aspectFit"></image>
|
||||
<text class="like-num">{{ likeCount }}</text>
|
||||
</view>
|
||||
|
||||
@ -183,7 +184,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 倒计时(如有) -->
|
||||
<view v-if="showCountdown" class="countdown-area">
|
||||
<view v-if="assetData.display_status === 1 && countdownText" class="countdown-area">
|
||||
<view class="countdown-pill">
|
||||
<text class="countdown-val">{{ countdownText }}</text>
|
||||
</view>
|
||||
@ -193,10 +194,19 @@
|
||||
|
||||
<!-- 信息行(创作者 + 铸造时间 + 来源) -->
|
||||
<view class="info-row">
|
||||
<view class="info-col">
|
||||
<view class="info-item">
|
||||
<text class="info-label">来源</text>
|
||||
<text class="info-value">{{ assetData.source || '铸造' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-col">
|
||||
<view class="info-item">
|
||||
<text class="info-label">创作者</text>
|
||||
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
||||
<view class="info-nickname">
|
||||
<image v-if="userAvatarUrl" class="info-avatar" :src="userAvatarUrl" mode="aspectFill"></image>
|
||||
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-col">
|
||||
@ -205,27 +215,16 @@
|
||||
<text class="info-value">{{ formattedAcquireTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-col">
|
||||
<view class="info-item">
|
||||
<text class="info-label">来源</text>
|
||||
<text class="info-value">{{ assetData.source || '铸造' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 创作者信息模块 -->
|
||||
<view class="creator-section" @tap="showLikeUsersModal = true">
|
||||
<!-- <view v-if="likeCount > 0" class="creator-section" @tap="showLikeUsersModal = true"> -->
|
||||
<!-- 点赞用户头像区域 -->
|
||||
<view class="liked-users-area">
|
||||
<view
|
||||
v-for="(user, index) in likedUsers"
|
||||
:key="index"
|
||||
class="liked-user-avatar"
|
||||
:style="{
|
||||
transform: `translate(${user.ellipseX || 0}rpx, ${user.ellipseY || 0}rpx) scale(${user.size || 1})`,
|
||||
zIndex: index < 2 ? index + 1 : (index < 4 ? index + 3 : index + 1)
|
||||
}"
|
||||
>
|
||||
<!-- <view class="liked-users-area">
|
||||
<view v-for="(user, index) in likedUsers" :key="index" class="liked-user-avatar" :style="{
|
||||
transform: `translate(${user.ellipseX || 0}rpx, ${user.ellipseY || 0}rpx) scale(${user.size || 1})`,
|
||||
zIndex: index < 2 ? index + 1 : (index < 4 ? index + 3 : index + 1)
|
||||
}">
|
||||
<image class="liked-user-image" :src="user.avatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
@ -233,10 +232,24 @@
|
||||
<text class="creator-title">TA们最近为你点过赞</text>
|
||||
<view class="creator-card">
|
||||
<text class="creator-tip">点击查看</text>
|
||||
<image class="creator-preview" src="/static/rank/activity-support-icon/tubiao.png" mode="aspectFill"></image>
|
||||
<image class="creator-preview" src="/static/rank/activity-support-icon/tubiao.png"
|
||||
mode="aspectFill"></image>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- </view> -->
|
||||
|
||||
<!-- 完整链上哈希显示遮罩层 -->
|
||||
<view v-if="showTxHash" class="txhash-mask" @tap="showTxHash = false">
|
||||
<view class="txhash-popup" @tap.stop>
|
||||
<view class="txhash-popup-header">
|
||||
<text class="txhash-popup-title">链上哈希</text>
|
||||
<view class="txhash-popup-close" @tap="showTxHash = false">
|
||||
<image class="close-icon" src="/static/icon/hide.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
<text class="txhash-popup-content" selectable @longpress="copyHash">{{ displayTxHash }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 链上数据 -->
|
||||
<view class="chain-section">
|
||||
@ -257,18 +270,19 @@
|
||||
<view class="chain-row">
|
||||
<text class="chain-label">区块链编号</text>
|
||||
<view class="chain-value-wrap">
|
||||
<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') : hiddenBlockNumber }}</text>
|
||||
<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') :
|
||||
hiddenBlockNumber }}</text>
|
||||
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
||||
<text class="toggle-icon">{{ showBlockNumber ? '👁' : '👁🗨' }}</text>
|
||||
<image class="toggle-icon" :src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chain-row">
|
||||
<text class="chain-label">交易哈希</text>
|
||||
<text class="chain-label">链上哈希</text>
|
||||
<view class="chain-value-wrap">
|
||||
<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash : hiddenTxHash }}</text>
|
||||
<text class="chain-value chain-hash" @longpress="copyHash">{{ hiddenTxHash }}</text>
|
||||
<view class="toggle-btn" @tap="showTxHash = !showTxHash">
|
||||
<text class="toggle-icon">{{ showTxHash ? '👁' : '👁🗨' }}</text>
|
||||
<image class="toggle-icon" :src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -278,30 +292,19 @@
|
||||
</scroll-view>
|
||||
|
||||
<!-- 点赞用户弹窗 -->
|
||||
<LikeUsersModal
|
||||
:visible="showLikeUsersModal"
|
||||
:tabs="['今日', '历史']"
|
||||
@close="showLikeUsersModal = false"
|
||||
@tab-change="handleLikeUsersTabChange"
|
||||
>
|
||||
<LikeUsersModal :visible="showLikeUsersModal" :tabs="['今日', '历史']" @close="showLikeUsersModal = false"
|
||||
@tab-change="handleLikeUsersTabChange">
|
||||
<template #content>
|
||||
<view class="like-users-list">
|
||||
<view v-for="(user, index) in displayedLikeUsers" :key="index" class="like-user-item" >
|
||||
<view v-for="(user, index) in displayedLikeUsers" :key="index" class="like-user-item">
|
||||
<image class="like-user-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||
<view class="like-user-info">
|
||||
<text class="like-user-name">{{ user.nickname }}</text>
|
||||
</view>
|
||||
<!-- 拜访按钮 -->
|
||||
<view
|
||||
class="visit-button"
|
||||
@tap="handleVisit"
|
||||
>
|
||||
<image
|
||||
class="visit-icon"
|
||||
src="/static/square/dianjibaifang.png"
|
||||
mode="aspectFit"
|
||||
lazy-load
|
||||
></image>
|
||||
<view class="visit-button" @tap="handleVisit">
|
||||
<image class="visit-icon" src="/static/square/dianjibaifang.png" mode="aspectFit" lazy-load>
|
||||
</image>
|
||||
<text class="visit-text">点击拜访</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -391,6 +394,22 @@ const isLiked = ref(false);
|
||||
const likeCount = ref(0);
|
||||
const liking = ref(false);
|
||||
|
||||
// 当前用户头像
|
||||
const userAvatarUrl = ref('');
|
||||
|
||||
// 加载当前用户信息
|
||||
const loadCurrentUser = () => {
|
||||
try {
|
||||
const userStr = uni.getStorageSync('user');
|
||||
if (userStr) {
|
||||
const userInfo = JSON.parse(userStr);
|
||||
userAvatarUrl.value = userInfo?.avatar_url || '';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析用户信息失败:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 点赞用户头像数据(最多6个,按椭圆排列,每个头像有独立的偏移量配置)
|
||||
// avatar: 头像地址, ellipseX: 椭圆X轴坐标, ellipseY: 椭圆Y轴坐标, size: 缩放比例
|
||||
const likedUsers = ref([
|
||||
@ -398,8 +417,8 @@ const likedUsers = ref([
|
||||
{ avatar: '/static/sucai/image-02.png', ellipseX: 40, ellipseY: -40, size: 0.85 },
|
||||
{ avatar: '/static/sucai/image-03.png', ellipseX: -96, ellipseY: 0, size: 0.9 },
|
||||
{ avatar: '/static/sucai/image-04.png', ellipseX: 120, ellipseY: -40, size: 0.9 },
|
||||
{ avatar: '/static/sucai/image-05.png', ellipseX: 64, ellipseY: 32},
|
||||
{ avatar: '/static/sucai/image-06.png', ellipseX: -16, ellipseY: 32 ,size:1.15 }
|
||||
{ avatar: '/static/sucai/image-05.png', ellipseX: 64, ellipseY: 32 },
|
||||
{ avatar: '/static/sucai/image-06.png', ellipseX: -16, ellipseY: 32, size: 1.15 }
|
||||
]);
|
||||
|
||||
// ---- 贴纸相关 ----
|
||||
@ -486,15 +505,48 @@ const showBlockNumber = ref(false);
|
||||
const showTxHash = ref(false);
|
||||
|
||||
// 倒计时
|
||||
const remainSeconds = ref(0);
|
||||
let countdownTimer = null;
|
||||
|
||||
const showCountdown = computed(() => remainSeconds.value > 0);
|
||||
// 倒计时状态
|
||||
const countdowns = ref({});
|
||||
|
||||
// 计算剩余时间
|
||||
const calculateRemainingTime = (item) => {
|
||||
if (item.remainSeconds !== undefined) {
|
||||
if (item.remainSeconds <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(item.remainSeconds / 3600);
|
||||
const minutes = Math.floor((item.remainSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(item.remainSeconds % 60);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
if (item.exhibition_expire_at) {
|
||||
const now = Date.now();
|
||||
const expireTime = new Date(item.exhibition_expire_at).getTime();
|
||||
const remaining = expireTime - now;
|
||||
if (remaining <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(remaining / (60 * 60 * 1000));
|
||||
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
|
||||
const seconds = Math.floor((remaining % (60 * 1000)) / 1000);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
};
|
||||
|
||||
// 更新倒计时
|
||||
const updateCountdowns = () => {
|
||||
countdowns.value['currentAsset'] = calculateRemainingTime({ exhibition_expire_at: assetData.value.exhibition_expire_at });
|
||||
};
|
||||
|
||||
const countdownText = computed(() => {
|
||||
const h = String(Math.floor(remainSeconds.value / 3600)).padStart(2, '0');
|
||||
const m = String(Math.floor((remainSeconds.value % 3600) / 60)).padStart(2, '0');
|
||||
const s = String(Math.floor(remainSeconds.value % 60)).padStart(2, '0');
|
||||
const countdown = countdowns.value['currentAsset'];
|
||||
if (!countdown || countdown.expired) return '';
|
||||
const h = String(countdown.hours).padStart(2, '0');
|
||||
const m = String(countdown.minutes).padStart(2, '0');
|
||||
const s = String(countdown.seconds).padStart(2, '0');
|
||||
return `${h}:${m}:${s}`;
|
||||
});
|
||||
|
||||
@ -512,10 +564,7 @@ const formattedAcquireTime = computed(() => {
|
||||
|
||||
// 截断交易哈希
|
||||
const displayTxHash = computed(() => {
|
||||
const hash = assetData.value.tx_hash;
|
||||
if (!hash) return '未知';
|
||||
if (hash.length <= 20) return hash;
|
||||
return `${hash.substring(0, 10)}...${hash.substring(hash.length - 8)}`;
|
||||
return assetData.value.tx_hash || '未知';
|
||||
});
|
||||
|
||||
// 隐藏交易哈希(保留前6位 + 6个*)
|
||||
@ -578,6 +627,7 @@ const loadData = async () => {
|
||||
isLiked.value = res.data.asset.is_liked || res.data.is_liked || false;
|
||||
likeCount.value = asset.like_count || 0;
|
||||
|
||||
<<<<<<< HEAD
|
||||
// 加载贴纸素材
|
||||
if (asset.material_relations || asset.materials) {
|
||||
loadStickersForAsset(asset.material_relations || asset.materials)
|
||||
@ -585,6 +635,9 @@ const loadData = async () => {
|
||||
|
||||
if (asset.remain_time > 0) {
|
||||
remainSeconds.value = asset.remain_time;
|
||||
=======
|
||||
if (asset.exhibition_expire_at) {
|
||||
>>>>>>> ebe57bc078a35ed4c13ccf966c8f7b5af6327c61
|
||||
startCountdown();
|
||||
}
|
||||
console.log(res.data)
|
||||
@ -627,13 +680,10 @@ const loadData = async () => {
|
||||
|
||||
// 倒计时
|
||||
const startCountdown = () => {
|
||||
updateCountdowns(); // 立即显示,不等待1秒
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
countdownTimer = setInterval(() => {
|
||||
if (remainSeconds.value > 0) {
|
||||
remainSeconds.value--;
|
||||
} else {
|
||||
clearInterval(countdownTimer);
|
||||
}
|
||||
updateCountdowns();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
@ -734,11 +784,15 @@ onLoad((options) => {
|
||||
assetIdParam.value = options?.asset_id || '';
|
||||
orderIdParam.value = options?.order_id || '';
|
||||
fromParam.value = options?.from || '';
|
||||
<<<<<<< HEAD
|
||||
studioKindParam.value = options?.studio_kind || '';
|
||||
craftConfirmMode.value = fromParam.value === 'craft_confirm';
|
||||
if (craftConfirmMode.value) {
|
||||
loadCraftConfirm();
|
||||
}
|
||||
=======
|
||||
loadCurrentUser();
|
||||
>>>>>>> ebe57bc078a35ed4c13ccf966c8f7b5af6327c61
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
@ -965,10 +1019,12 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
min-height: 90%;
|
||||
padding: 104rpx 40rpx 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@ -1069,14 +1125,15 @@ onUnmounted(() => {
|
||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||
padding: 10rpx 28rpx;
|
||||
backdrop-filter: blur(12rpx);
|
||||
}
|
||||
|
||||
.like-area:active {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.like-icon, .heart-icon, .crystal-icon {
|
||||
.like-icon,
|
||||
.heart-icon,
|
||||
.crystal-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
@ -1084,9 +1141,10 @@ onUnmounted(() => {
|
||||
.like-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #e6e6e6;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 收益 */
|
||||
@ -1108,22 +1166,28 @@ onUnmounted(() => {
|
||||
.earnings-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #e6e6e6;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 倒计时 */
|
||||
.countdown-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||||
border-radius: 999rpx;
|
||||
padding: 10rpx 28rpx;
|
||||
}
|
||||
|
||||
.countdown-pill {
|
||||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||||
border-radius: 999rpx;
|
||||
padding: 10rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4);
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.countdown-val {
|
||||
@ -1132,6 +1196,7 @@ onUnmounted(() => {
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 信息行(持有人 + 铸造时间) */
|
||||
@ -1158,8 +1223,8 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
width: 416rpx;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
@ -1176,6 +1241,20 @@ onUnmounted(() => {
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.info-avatar {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.info-nickname {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
/* 点赞用户头像区域 */
|
||||
.liked-users-area {
|
||||
position: relative;
|
||||
@ -1195,7 +1274,7 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.liked-user-image{
|
||||
.liked-user-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@ -1264,7 +1343,6 @@ onUnmounted(() => {
|
||||
padding: 30rpx 16rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
@ -1297,7 +1375,6 @@ onUnmounted(() => {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
font-family: 'yt', sans-serif;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
display: flex
|
||||
@ -1315,13 +1392,70 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 28rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.chain-hash {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
/* 链上哈希遮罩层 */
|
||||
.txhash-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 24rpx;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 64rpx;
|
||||
|
||||
}
|
||||
|
||||
.txhash-popup {
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
|
||||
background-size: 105% 120%;
|
||||
}
|
||||
|
||||
.txhash-popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.txhash-popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
}
|
||||
|
||||
.txhash-popup-close {
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.txhash-popup-content {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
word-break: break-all;
|
||||
line-height: 1.6;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
/* 点赞用户列表 */
|
||||
.like-users-list {
|
||||
display: flex;
|
||||
@ -1334,7 +1468,7 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding:0 16rpx;
|
||||
padding: 0 16rpx;
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
@ -1388,11 +1522,8 @@ onUnmounted(() => {
|
||||
margin-bottom: 8rpx;
|
||||
/* transform: scale(1.2); */
|
||||
}
|
||||
.visit-text{
|
||||
color: #FFFFFF;
|
||||
font-size:16rpx;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
.craft-confirm-scroll {
|
||||
flex: 1;
|
||||
height: calc(100vh - 120rpx);
|
||||
@ -1460,4 +1591,10 @@ onUnmounted(() => {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
=======
|
||||
.visit-text {
|
||||
color: #FFFFFF;
|
||||
font-size: 16rpx;
|
||||
}
|
||||
>>>>>>> ebe57bc078a35ed4c13ccf966c8f7b5af6327c61
|
||||
</style>
|
||||
|
||||
@ -6,13 +6,12 @@
|
||||
<view class="cards-container">
|
||||
<view v-for="(card, index) in cardList" :key="index" class="card-item"
|
||||
:class="{ 'card-selected': selectedIndex === index }" :style="getCardStyle(index)">
|
||||
<view
|
||||
class="card-frame"
|
||||
<view class="card-frame"
|
||||
:class="{ 'no-border': card.comingSoon, 'card-frame--tappable': !card.comingSoon }"
|
||||
@tap.stop="onCardFrameTap(index)"
|
||||
>
|
||||
@tap.stop="onCardFrameTap(index)">
|
||||
<image class="card-image" :src="card.image" mode="aspectFill" />
|
||||
<image v-if="card.comingSoon" class="coming-soon-badge" src="/static/castlove/jinqingqidai.png" mode="aspectFit" />
|
||||
<image v-if="card.comingSoon" class="coming-soon-badge" src="/static/castlove/jinqingqidai.png"
|
||||
mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -21,7 +20,8 @@
|
||||
<view class="text-panel">
|
||||
<!-- 向上按钮 -->
|
||||
<view class="arrow-btn arrow-up" @click="scrollUp">
|
||||
<image class="arrow-icon" src="/static/castlove/jiantou.png" mode="aspectFit" style="transform: rotate(180deg);" />
|
||||
<image class="arrow-icon" src="/static/castlove/jiantou.png" mode="aspectFit"
|
||||
style="transform: rotate(180deg);" />
|
||||
</view>
|
||||
|
||||
<!-- 文字列表 -->
|
||||
@ -65,7 +65,14 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 2, // 初始选中光栅卡
|
||||
selectedIndex: 2,
|
||||
// 工艺名称 → 页面路由映射,方便扩展
|
||||
cardRoutes: {
|
||||
'光栅卡': '/pages/castlove/create',
|
||||
'拍立得': '/pages/castlove/create',
|
||||
'镭射卡': '/pages/castlove/create',
|
||||
'撕拉片': '/pages/castlove/create',
|
||||
},
|
||||
cardList: [
|
||||
{ name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
|
||||
{ name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
|
||||
@ -130,15 +137,24 @@ export default {
|
||||
return;
|
||||
}
|
||||
const pos = this.getCardStackPosition(index);
|
||||
const LENTICULAR_INDEX = 2;
|
||||
const goLenticular =
|
||||
this.selectedIndex === LENTICULAR_INDEX &&
|
||||
(pos === 2 || pos === 1);
|
||||
const targetName = goLenticular ? '光栅卡' : card.name;
|
||||
if (pos === 2 || goLenticular) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/castlove/create?name=${encodeURIComponent(targetName)}`,
|
||||
});
|
||||
// 只有中间位置的卡片点击才会进入创建页
|
||||
// 其他位置点击只是切换选中(把卡片移到中间),再次点击中间卡片才进入
|
||||
if (pos === 2) {
|
||||
if (card.name === '撕拉片') {
|
||||
const route = this.cardRoutes[card.name];
|
||||
if (route) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/castlove/mint/tear-card',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const route = this.cardRoutes[card.name];
|
||||
if (route) {
|
||||
uni.navigateTo({
|
||||
url: `${route}?name=${encodeURIComponent(card.name)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.selectCard(index);
|
||||
@ -164,9 +180,12 @@ export default {
|
||||
uni.showToast({ title: '请选择已开放的工艺', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/castlove/create?name=${encodeURIComponent(card.name)}`
|
||||
})
|
||||
const route = this.cardRoutes[card.name]
|
||||
if (route) {
|
||||
uni.navigateTo({
|
||||
url: `${route}?name=${encodeURIComponent(card.name)}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,6 +236,7 @@ export default {
|
||||
background-image: url('/static/square/cangpinkuang1.png');
|
||||
background-size: cover;
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0.5);
|
||||
|
||||
&.no-border {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-box upload-box--half" @click="chooseLenticularSubject">
|
||||
<image v-if="uploadedSubject" class="uploaded-image" :src="uploadedSubject" mode="aspectFit"></image>
|
||||
<image v-if="uploadedSubject" class="uploaded-image":src="uploadedSubject" mode="aspectFit"></image>
|
||||
<view v-else class="upload-placeholder">
|
||||
<image class="upload-icon" src="/static/icon/add.png" mode="aspectFit"></image>
|
||||
<text class="upload-text">主体图</text>
|
||||
|
||||
@ -50,19 +50,23 @@ const navItems = [
|
||||
name: '星册',
|
||||
icon: '/static/icon/starbook.png',
|
||||
angle: 107, // 右上方
|
||||
path: '/pages/starbook/index'
|
||||
path: '/pages/starbook/index',
|
||||
|
||||
},
|
||||
{
|
||||
name: '铸爱',
|
||||
icon: '/static/icon/castlove.png',
|
||||
angle: 90.01, // 正上方
|
||||
path: '/pages/castlove/mall'
|
||||
path: '/pages/castlove/mall',
|
||||
rotate: '8deg'
|
||||
},
|
||||
{
|
||||
name: '星城',
|
||||
icon: '/static/icon/dressup.png',
|
||||
angle: 73, // 右方
|
||||
path: '/pages/starcity/index'
|
||||
path: '/pages/starcity/index',
|
||||
iconWidth: '120rpx',
|
||||
iconHeight: '120rpx'
|
||||
},
|
||||
{
|
||||
name: '广场',
|
||||
@ -107,7 +111,10 @@ const getNavIconStyle = (index) => {
|
||||
return {
|
||||
'--x': `${x}rpx`,
|
||||
'--y': `${y}rpx`,
|
||||
'--delay': `${delay}s`
|
||||
'--delay': `${delay}s`,
|
||||
'--rotate': item.rotate || '0deg',
|
||||
'--icon-width': item.iconWidth || '100rpx',
|
||||
'--icon-height': item.iconHeight || '100rpx'
|
||||
};
|
||||
};
|
||||
|
||||
@ -231,12 +238,13 @@ const handleNavClick = (index) => {
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
width: var(--icon-width, 100rpx);
|
||||
height: var(--icon-height, 100rpx);
|
||||
margin-bottom: 8rpx;
|
||||
transition: transform 0.3s ease, filter 0.3s ease;
|
||||
filter: drop-shadow(0 6rpx 16rpx rgba(0, 0, 0, 0.4)) drop-shadow(0 2rpx 8rpx rgba(0, 0, 0, 0.2));
|
||||
position: relative;
|
||||
transform: rotate(var(--rotate));
|
||||
}
|
||||
|
||||
/* 底部倒影效果 */
|
||||
|
||||
@ -23,8 +23,7 @@
|
||||
|
||||
<view class="exhibition-grid">
|
||||
<view v-for="(item, index) in exhibitionWorks" :key="item.id" class="exhibition-card"
|
||||
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'"
|
||||
@tap="goToAssetDetail(item.id)">
|
||||
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'" @tap="goToAssetDetail(item.id)">
|
||||
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
|
||||
@ -36,6 +35,13 @@
|
||||
<text class="card-rate-text">{{ item.like_count || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 倒计时背景 -->
|
||||
<view class="countdown-background" :style="getCountdownBackgroundStyle()">
|
||||
<!-- 倒计时文字 -->
|
||||
<text class="countdown-text">
|
||||
{{ formatCountdown(item.id) }}
|
||||
</text>
|
||||
</view>
|
||||
<!-- 图片下方收益 -->
|
||||
<view class="card-income-row"
|
||||
:class="index % 2 === 0 ? 'income-tilt-right' : 'income-tilt-left'">
|
||||
@ -80,13 +86,14 @@
|
||||
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
||||
@tap="goToAssetDetail(item.id)">
|
||||
<!-- 排名图标,绝对定位在卡片左侧 -->
|
||||
<image v-if="index < 3" :src="rankIcons[index]" :class="'rank-icon rank-icon-' + (index + 1)" mode="aspectFit"></image>
|
||||
<image v-if="index < 3" :src="rankIcons[index]" :class="'rank-icon rank-icon-' + (index + 1)"
|
||||
mode="aspectFit"></image>
|
||||
|
||||
<!-- 卡片主体 -->
|
||||
<!-- 卡片主体 -->
|
||||
|
||||
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||
<!-- 作品封面 -->
|
||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''" >
|
||||
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||
<!-- 作品封面 -->
|
||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''">
|
||||
<image class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png"
|
||||
@ -105,7 +112,9 @@
|
||||
|
||||
<!-- 右侧奖励 -->
|
||||
<view class="liked-reward">
|
||||
<image class="reward-token-icon" :src="item.earnings > 10 ? '/static/square/shuijingtubiao.png' : '/static/icon/crystal.png'" mode="aspectFit">
|
||||
<image class="reward-token-icon"
|
||||
:src="item.earnings > 10 ? '/static/square/shuijingtubiao.png' : '/static/icon/crystal.png'"
|
||||
mode="aspectFit">
|
||||
</image>
|
||||
<text class="reward-amount">+{{ item.reward }}</text>
|
||||
</view>
|
||||
@ -123,7 +132,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { getUserGalleriesApi, getUserLikedAssetsApi, getUserProfileApi } from '@/utils/api.js';
|
||||
|
||||
@ -156,6 +165,68 @@ const formatScore = (score) => {
|
||||
return Number(score).toLocaleString();
|
||||
};
|
||||
|
||||
// 计算剩余时间
|
||||
const calculateRemainingTime = (item) => {
|
||||
if (item.remainSeconds !== undefined) {
|
||||
if (item.remainSeconds <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(item.remainSeconds / 3600);
|
||||
const minutes = Math.floor((item.remainSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(item.remainSeconds % 60);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
if (item.expire_at) {
|
||||
const now = Date.now();
|
||||
const expireTime = new Date(item.expire_at).getTime();
|
||||
const remaining = expireTime - now;
|
||||
if (remaining <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(remaining / (60 * 60 * 1000));
|
||||
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
|
||||
const seconds = Math.floor((remaining % (60 * 1000)) / 1000);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
};
|
||||
|
||||
// 更新所有倒计时
|
||||
const updateCountdowns = () => {
|
||||
exhibitionWorks.value.forEach(item => {
|
||||
if (item && item.id) {
|
||||
if (item.remainSeconds !== undefined && item.remainSeconds > 0) {
|
||||
item.remainSeconds--;
|
||||
}
|
||||
countdowns.value[item.id] = calculateRemainingTime(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化倒计时显示
|
||||
const formatCountdown = (itemId) => {
|
||||
const countdown = countdowns.value[itemId];
|
||||
if (!countdown || countdown.expired) return '00:00:00';
|
||||
const h = String(countdown.hours).padStart(2, '0');
|
||||
const m = String(countdown.minutes).padStart(2, '0');
|
||||
const s = String(countdown.seconds).padStart(2, '0');
|
||||
return `${h}:${m}:${s}`;
|
||||
};
|
||||
|
||||
// 获取倒计时背景样式
|
||||
const getCountdownBackgroundStyle = () => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: '20rpx',
|
||||
right: '40%',
|
||||
transform: 'translateX(50%)',
|
||||
width: '140rpx',
|
||||
height: '36rpx',
|
||||
zIndex: 9
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const rankIcons = [
|
||||
'/static/square/icon1.png',
|
||||
'/static/square/icon2.png',
|
||||
@ -165,6 +236,9 @@ const rankIcons = [
|
||||
// 在展作品列表
|
||||
const exhibitionWorks = ref([]);
|
||||
|
||||
// 倒计时状态
|
||||
const countdowns = ref({});
|
||||
|
||||
// 点赞作品列表
|
||||
const likedWorks = ref([]);
|
||||
|
||||
@ -236,6 +310,19 @@ const loadLikedAssets = async () => {
|
||||
onMounted(() => {
|
||||
loadExhibitedAssets();
|
||||
loadLikedAssets();
|
||||
|
||||
// 启动倒计时定时器
|
||||
countdownTimer = setInterval(() => {
|
||||
updateCountdowns();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
let countdownTimer = null;
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -400,8 +487,8 @@ onMounted(() => {
|
||||
|
||||
.card-rate-badge {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
left: 40%;
|
||||
top: 16rpx;
|
||||
left: 25%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -476,6 +563,35 @@ onMounted(() => {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 倒计时背景 */
|
||||
.countdown-background {
|
||||
/* position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 30%;
|
||||
transform: translateX(-50%); */
|
||||
width: 140rpx;
|
||||
height: 36rpx;
|
||||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||||
border-radius: 999rpx;
|
||||
z-index: 9;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
/* 倒计时文字 */
|
||||
.countdown-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8);
|
||||
z-index: 10;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-exhibition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -527,7 +643,7 @@ onMounted(() => {
|
||||
flex-shrink: 0;
|
||||
/* margin-right: 8rpx; */
|
||||
position: relative;
|
||||
left: 32rpx;
|
||||
left: 32rpx;
|
||||
}
|
||||
|
||||
.rank-icon-1 {
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<!-- <text class="nav-title">我的作品</text> -->
|
||||
<view class="nav-placeholder"></view>
|
||||
<view class="nav-settings" @tap="goToSettings">
|
||||
<text class="nav-settings-text">个人设置</text>
|
||||
<image class="nav-settings-icon" src="/static/icon/settings.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -30,6 +30,12 @@
|
||||
@tap="handleExhibitionCardTap(item, index)">
|
||||
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<!-- 领取收益按钮 -->
|
||||
<view class="claim-reward-btn" v-if="isRewardClaimable(item.id)">
|
||||
<image class="claim-crystal-icon" src="/static/square/shuijingtubiao.png" mode="aspectFit">
|
||||
</image>
|
||||
<view @tap.stop="handleClaimReward(item, index)" class="claim-btn-text">领取收益</view>
|
||||
</view>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
|
||||
</image>
|
||||
<!-- 点赞数 -->
|
||||
@ -39,6 +45,14 @@
|
||||
<text class="card-rate-text">{{ item.like_count || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 倒计时背景 -->
|
||||
<view class="countdown-background" v-if="!isRewardClaimable(item.id)"
|
||||
:style="getCountdownBackgroundStyle(index)">
|
||||
<!-- 倒计时文字 -->
|
||||
<text class="countdown-text">
|
||||
{{ formatCountdown(item.id) }}
|
||||
</text>
|
||||
</view>
|
||||
<!-- 图片下方收益 -->
|
||||
<view class="card-income-row"
|
||||
:class="index % 2 === 0 ? 'income-tilt-right' : 'income-tilt-left'">
|
||||
@ -101,13 +115,14 @@
|
||||
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
||||
@tap="goToAssetDetail(item.id)">
|
||||
<!-- 排名图标,绝对定位在卡片左侧 -->
|
||||
<image v-if="index < 3" :src="rankIcons[index]" :class="'rank-icon rank-icon-' + (index + 1)" mode="aspectFit"></image>
|
||||
<image v-if="index < 3" :src="rankIcons[index]" :class="'rank-icon rank-icon-' + (index + 1)"
|
||||
mode="aspectFit"></image>
|
||||
|
||||
<!-- 卡片主体 -->
|
||||
|
||||
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||
<!-- 作品封面 -->
|
||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''" >
|
||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''">
|
||||
<image class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png"
|
||||
@ -126,7 +141,9 @@
|
||||
|
||||
<!-- 右侧奖励 -->
|
||||
<view class="liked-reward">
|
||||
<image class="reward-token-icon" :src="item.reward > 10 ? '/static/square/shuijingtubiao.png' : '/static/icon/crystal.png'" mode="aspectFit">
|
||||
<image class="reward-token-icon"
|
||||
:src="item.reward > 10 ? '/static/square/shuijingtubiao.png' : '/static/icon/crystal.png'"
|
||||
mode="aspectFit">
|
||||
</image>
|
||||
<text class="reward-amount">+{{ item.reward }}</text>
|
||||
</view>
|
||||
@ -258,6 +275,12 @@ const goToAssetDetail = (id) => {
|
||||
// 双击点赞处理
|
||||
const cardTapTimers = {};
|
||||
|
||||
// 领取收益处理
|
||||
const handleClaimReward = (item, _index) => {
|
||||
console.log('领取收益:', item);
|
||||
uni.showToast({ title: '收益已领取', icon: 'success' });
|
||||
};
|
||||
|
||||
const handleExhibitionCardTap = (item, index) => {
|
||||
if (cardTapTimers[item.id]) {
|
||||
// 第二次点击,双击点赞
|
||||
@ -302,9 +325,79 @@ const formatScore = (score) => {
|
||||
return Number(score).toLocaleString();
|
||||
};
|
||||
|
||||
// 计算剩余时间
|
||||
const calculateRemainingTime = (item) => {
|
||||
if (item.remainSeconds !== undefined) {
|
||||
if (item.remainSeconds <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(item.remainSeconds / 3600);
|
||||
const minutes = Math.floor((item.remainSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(item.remainSeconds % 60);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
if (item.expire_at) {
|
||||
const now = Date.now();
|
||||
const expireTime = new Date(item.expire_at).getTime();
|
||||
const remaining = expireTime - now;
|
||||
if (remaining <= 0) {
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const hours = Math.floor(remaining / (60 * 60 * 1000));
|
||||
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
|
||||
const seconds = Math.floor((remaining % (60 * 1000)) / 1000);
|
||||
return { hours, minutes, seconds, expired: false };
|
||||
}
|
||||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
};
|
||||
|
||||
// 更新所有倒计时
|
||||
const updateCountdowns = () => {
|
||||
exhibitionWorks.value.forEach(item => {
|
||||
if (item && item.id) {
|
||||
if (item.remainSeconds !== undefined && item.remainSeconds > 0) {
|
||||
item.remainSeconds--;
|
||||
}
|
||||
countdowns.value[item.id] = calculateRemainingTime(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化倒计时显示
|
||||
const formatCountdown = (itemId) => {
|
||||
const countdown = countdowns.value[itemId];
|
||||
if (!countdown || countdown.expired) return '00:00:00';
|
||||
const h = String(countdown.hours).padStart(2, '0');
|
||||
const m = String(countdown.minutes).padStart(2, '0');
|
||||
const s = String(countdown.seconds).padStart(2, '0');
|
||||
return `${h}:${m}:${s}`;
|
||||
};
|
||||
|
||||
// 判断收益是否可领取(倒计时已到期)
|
||||
const isRewardClaimable = (itemId) => {
|
||||
const countdown = countdowns.value[itemId];
|
||||
return countdown && countdown.expired;
|
||||
};
|
||||
|
||||
// 获取倒计时背景样式
|
||||
const getCountdownBackgroundStyle = () => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: '20rpx',
|
||||
right: '40%',
|
||||
transform: 'translateX(50%)',
|
||||
width: '140rpx',
|
||||
height: '36rpx',
|
||||
zIndex: 9
|
||||
};
|
||||
};
|
||||
|
||||
// 在展作品列表
|
||||
const exhibitionWorks = ref([]);
|
||||
|
||||
// 倒计时状态
|
||||
const countdowns = ref({});
|
||||
|
||||
// 当前点赞作品列表
|
||||
const likedWorks = ref([]);
|
||||
|
||||
@ -380,6 +473,11 @@ onMounted(() => {
|
||||
loadExhibitedAssets();
|
||||
loadLikedAssets();
|
||||
|
||||
// 启动倒计时定时器
|
||||
countdownTimer = setInterval(() => {
|
||||
updateCountdowns();
|
||||
}, 1000);
|
||||
|
||||
// 监听身份切换事件,切换后刷新数据
|
||||
uni.$on('userInfoUpdated', () => {
|
||||
loadExhibitedAssets();
|
||||
@ -387,7 +485,12 @@ onMounted(() => {
|
||||
});
|
||||
});
|
||||
|
||||
let countdownTimer = null;
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
}
|
||||
uni.$off('userInfoUpdated');
|
||||
});
|
||||
|
||||
@ -464,11 +567,9 @@ onShow(() => {
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.nav-settings-text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.8);
|
||||
font-weight: 400;
|
||||
.nav-settings-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
@ -560,13 +661,13 @@ onShow(() => {
|
||||
.card-tilt-left {
|
||||
transform: rotate(-4deg) translateY(10rpx);
|
||||
margin-right: 32rpx;
|
||||
border-radius: 32rpx;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: -16rpx 16rpx 16rpx rgba(229, 76, 93, 0.9);
|
||||
}
|
||||
|
||||
.card-tilt-right {
|
||||
transform: rotate(4deg) translateY(10rpx);
|
||||
border-radius: 32rpx;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 16rpx 16rpx 16rpx rgba(229, 76, 93, 0.9);
|
||||
}
|
||||
|
||||
@ -598,6 +699,50 @@ onShow(() => {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 领取收益按钮 */
|
||||
.claim-reward-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 88%;
|
||||
height: 92%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 16rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.claim-crystal-icon {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%)
|
||||
}
|
||||
|
||||
.claim-btn-text {
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
box-shadow:
|
||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
||||
position: absolute;
|
||||
bottom: 24rpx;
|
||||
}
|
||||
|
||||
.card-user-tag {
|
||||
position: absolute;
|
||||
bottom: 56rpx;
|
||||
@ -617,8 +762,8 @@ onShow(() => {
|
||||
|
||||
.card-rate-badge {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
left: 40%;
|
||||
top: 16rpx;
|
||||
left: 25%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -646,7 +791,7 @@ onShow(() => {
|
||||
z-index: 2;
|
||||
margin-right: -16rpx;
|
||||
left: 20rpx;
|
||||
top: 8rpx
|
||||
/* top: 8rpx */
|
||||
}
|
||||
|
||||
.card-income-text-wrap {
|
||||
@ -697,6 +842,35 @@ onShow(() => {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 倒计时背景 */
|
||||
.countdown-background {
|
||||
/* position: absolute; */
|
||||
/* top: 20rpx;
|
||||
left: 30%; */
|
||||
/* transform: translateX(-50%); */
|
||||
width: 140rpx;
|
||||
height: 36rpx;
|
||||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||||
border-radius: 999rpx;
|
||||
z-index: 9;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
/* 倒计时文字 */
|
||||
.countdown-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-family: 'yt', sans-serif;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8);
|
||||
z-index: 10;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-exhibition {
|
||||
display: flex;
|
||||
@ -931,8 +1105,9 @@ onShow(() => {
|
||||
|
||||
.reward-amount {
|
||||
font-size: 28rpx;
|
||||
color: #c060e0;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
|
||||
@ -7,251 +7,268 @@
|
||||
<!-- 上半部分:用户信息卡片 -->
|
||||
<view class="top-section">
|
||||
|
||||
<!-- 蒙层 -->
|
||||
<view class="background-overlay"></view>
|
||||
<!-- 蒙层 -->
|
||||
<view class="background-overlay"></view>
|
||||
|
||||
<!-- Header组件 -->
|
||||
<image class="close-icon-img" src="/static/starbookcontent/tuichu.png" mode="aspectFit" @tap="goBack"></image>
|
||||
<!-- Header组件 -->
|
||||
<image class="close-icon-img" src="/static/starbookcontent/tuichu.png" mode="aspectFit" @tap="goBack">
|
||||
</image>
|
||||
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-info-card">
|
||||
<!-- 上半部分:头像和用户信息左右布局 -->
|
||||
<view class="user-main-info">
|
||||
<view @tap="handleAvatarClick" class="avatar-container">
|
||||
<Avatar :key="avatarKey" :userId="uid" :size="260" :borderWidth="6" :showLevel="true"
|
||||
:level="fanLevel" :avatarUrl="userAvatarUrl" />
|
||||
</view>
|
||||
<view class="user-text-info">
|
||||
<!-- 昵称 -->
|
||||
<view class="info-row">
|
||||
<text style="font-size: 48rpx;text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);" class="info-value">{{ nickname }}</text>
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-info-card">
|
||||
<!-- 上半部分:头像和用户信息左右布局 -->
|
||||
<view class="user-main-info">
|
||||
<view @tap="handleAvatarClick" class="avatar-container">
|
||||
<Avatar :key="avatarKey" :userId="uid" :size="260" :borderWidth="6" :showLevel="true"
|
||||
:level="fanLevel" :avatarUrl="userAvatarUrl" />
|
||||
</view>
|
||||
<!-- DID -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">DID</text>
|
||||
<text class="info-value uid-value">{{ uid }}</text>
|
||||
<view class="info-btn" @tap="copyUid">查看</view>
|
||||
</view>
|
||||
<!-- 链上地址 -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">链上地址</text>
|
||||
<text class="info-value address-value">{{ displayAddress }}</text>
|
||||
<view class="info-btn" @tap="copyAddress">查看</view>
|
||||
</view>
|
||||
<!-- 手机号 -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">注册手机</text>
|
||||
<text class="info-value" v-if="displayMobile">{{ displayMobile }}</text>
|
||||
<text class="info-value" v-else>未绑定</text>
|
||||
<view class="info-btn" @tap="handleViewMobile">查看</view>
|
||||
<view class="user-text-info">
|
||||
<!-- 昵称 -->
|
||||
<view class="info-row">
|
||||
<text style="font-size: 48rpx;text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);"
|
||||
class="info-value">{{ nickname }}</text>
|
||||
</view>
|
||||
<!-- DID -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">DID</text>
|
||||
<view class="address-row">
|
||||
<text class="info-value uid-value">{{ uid }}</text>
|
||||
<!-- <image class="toggle-icon"
|
||||
:src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'"
|
||||
mode="aspectFit" @tap="copyUid"></image> -->
|
||||
</view>
|
||||
</view>
|
||||
<!-- 链上地址 -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">链上地址</text>
|
||||
<view class="address-row">
|
||||
<text class="info-value address-value" @longpress="copyAddress">{{ displayAddress }}</text>
|
||||
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
||||
<image class="toggle-icon" :src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 手机号 -->
|
||||
<view class="info-row">
|
||||
<text class="info-label">注册手机</text>
|
||||
<view class="address-row">
|
||||
<text class="info-value" v-if="mobile">{{ displayMobile }}</text>
|
||||
<view class="toggle-btn" @tap="toggleMobileDisplay">
|
||||
<image class="toggle-icon" :src="showMobile ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下半部分:白色背景区域 -->
|
||||
<view class="bottom-section">
|
||||
<view class="content-area">
|
||||
<!-- 我的资产 -->
|
||||
<view class="section-bg" style="padding-top: 0;">
|
||||
<view class="section-title">我的资产</view>
|
||||
<view class="assets-grid">
|
||||
<view class="asset-card" @tap="handleAssetClick('myWorks')">
|
||||
<image class="asset-icon" src="/static/icon/dazi.png" mode="aspectFit"></image>
|
||||
<text class="asset-text">个人中心</text>
|
||||
<!-- <text class="arrow">›</text> -->
|
||||
</view>
|
||||
<view class="asset-card" @tap="handleAssetClick('starbook')">
|
||||
<image class="asset-icon" src="/static/icon/starbook.png" mode="aspectFit"></image>
|
||||
<text class="asset-text">我的星册</text>
|
||||
<!-- <text class="arrow">›</text> -->
|
||||
<!-- 下半部分:白色背景区域 -->
|
||||
<view class="bottom-section">
|
||||
<view class="content-area">
|
||||
<!-- 我的资产 -->
|
||||
<view class="section-bg" style="padding-top: 0;">
|
||||
<view class="section-title">我的资产</view>
|
||||
<view class="assets-grid">
|
||||
<view class="asset-card" @tap="handleAssetClick('myWorks')">
|
||||
<image class="asset-icon" src="/static/icon/dazi.png" mode="aspectFit"></image>
|
||||
<text class="asset-text">个人中心</text>
|
||||
<!-- <text class="arrow">›</text> -->
|
||||
</view>
|
||||
<view class="asset-card" @tap="handleAssetClick('starbook')">
|
||||
<image class="asset-icon" src="/static/icon/starbook.png" mode="aspectFit"></image>
|
||||
<text class="asset-text">我的星册</text>
|
||||
<!-- <text class="arrow">›</text> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务与工具 -->
|
||||
<view class="section-bg">
|
||||
<view class="section-title">服务与工具</view>
|
||||
<view class="service-buttons-container">
|
||||
<!-- 新手指引入口 -->
|
||||
<view class="service-button" @tap="handleGuideClick">
|
||||
<image class="service-icon" src="/static/icon/onboarding.png" mode="aspectFit"></image>
|
||||
<text class="service-text">新手指引</text>
|
||||
<text v-if="guideClaimableCount > 0" class="guide-badge">{{ guideClaimableCount }}</text>
|
||||
</view>
|
||||
<view class="service-button" @tap="handleChangeNickname">
|
||||
<image class="service-icon" src="/static/icon/edit-nickname.png" mode="aspectFit"></image>
|
||||
<text class="service-text">修改昵称</text>
|
||||
</view>
|
||||
<view class="service-button" @tap="handleChangePassword">
|
||||
<image class="service-icon" src="/static/icon/edit-password.png" mode="aspectFit"></image>
|
||||
<text class="service-text">修改密码</text>
|
||||
</view>
|
||||
<!-- <view class="service-button" @tap="handleSwitchRole">
|
||||
<!-- 服务与工具 -->
|
||||
<view class="section-bg">
|
||||
<view class="section-title">服务与工具</view>
|
||||
<view class="service-buttons-container">
|
||||
<!-- 新手指引入口 -->
|
||||
<view class="service-button" @tap="handleGuideClick">
|
||||
<image class="service-icon" src="/static/icon/onboarding.png" mode="aspectFit"></image>
|
||||
<text class="service-text">新手指引</text>
|
||||
<text v-if="guideClaimableCount > 0" class="guide-badge">{{ guideClaimableCount
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="service-button" @tap="handleChangeNickname">
|
||||
<image class="service-icon" src="/static/icon/edit-nickname.png" mode="aspectFit">
|
||||
</image>
|
||||
<text class="service-text">修改昵称</text>
|
||||
</view>
|
||||
<view class="service-button" @tap="handleChangePassword">
|
||||
<image class="service-icon" src="/static/icon/edit-password.png" mode="aspectFit">
|
||||
</image>
|
||||
<text class="service-text">修改密码</text>
|
||||
</view>
|
||||
<!-- <view class="service-button" @tap="handleSwitchRole">
|
||||
<image class="service-icon" src="/static/icon/switch-account.png" mode="aspectFit"></image>
|
||||
<text class="service-text">添加身份</text>
|
||||
</view> -->
|
||||
<view class="service-button" @tap="handleDeleteAccount">
|
||||
<image class="service-icon" src="/static/icon/logout.png" mode="aspectFit"></image>
|
||||
<text class="service-text">注销账号</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 修改昵称弹窗 -->
|
||||
<view class="nickname-modal" v-if="showNicknameModal" @tap="closeNicknameModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改昵称</view>
|
||||
<input class="modal-input" v-model="newNickname" placeholder="请输入新昵称" maxlength="20" />
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeNicknameModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmChangeNickname">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 修改密码弹窗 -->
|
||||
<view class="password-modal" v-if="showPasswordModal" @tap="closePasswordModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改密码</view>
|
||||
<!-- 旧密码输入框 -->
|
||||
<view class="modal-password-wrapper">
|
||||
<input class="modal-password-input" :type="showOldPassword ? 'text' : 'password'"
|
||||
v-model="oldPassword" placeholder="请输入旧密码" placeholder-class="input-placeholder" />
|
||||
<view class="modal-eye-icon" @click="showOldPassword = !showOldPassword">
|
||||
<text class="eye-text">{{ showOldPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 新密码输入框 -->
|
||||
<view class="modal-password-wrapper">
|
||||
<input class="modal-password-input" :type="showNewPassword ? 'text' : 'password'"
|
||||
v-model="newPassword" placeholder="请输入新密码" placeholder-class="input-placeholder" />
|
||||
<view class="modal-eye-icon" @click="showNewPassword = !showNewPassword">
|
||||
<text class="eye-text">{{ showNewPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closePasswordModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmChangePassword">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加身份弹窗 -->
|
||||
<view class="add-identity-modal" v-if="showAddIdentityModal" @tap="closeAddIdentityModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">添加身份</view>
|
||||
<view class="modal-subtitle">选择您喜欢的明星,成为TA的粉丝</view>
|
||||
|
||||
<!-- 明星选择列表 -->
|
||||
<view class="star-list">
|
||||
<view v-for="star in fanIdentitiesList" :key="star.star_id" class="star-item"
|
||||
:class="{ 'star-item-selected': selectedStarId === star.star_id }"
|
||||
@tap="selectStar(star.star_id)">
|
||||
<view class="star-info">
|
||||
<text class="star-name">{{ star.name }}</text>
|
||||
<text class="star-tag" v-if="star.tag">{{ star.tag }}</text>
|
||||
</view>
|
||||
<view class="star-radio">
|
||||
<view v-if="selectedStarId === star.star_id" class="star-radio-checked"></view>
|
||||
<view class="service-button" @tap="handleDeleteAccount">
|
||||
<image class="service-icon" src="/static/icon/logout.png" mode="aspectFit"></image>
|
||||
<text class="service-text">注销账号</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称输入框 -->
|
||||
<input class="modal-input" v-model="newIdentityNickname" placeholder="请输入您的新昵称" maxlength="20" />
|
||||
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeAddIdentityModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmAddIdentity">确认</button>
|
||||
<!-- 修改昵称弹窗 -->
|
||||
<view class="nickname-modal" v-if="showNicknameModal" @tap="closeNicknameModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改昵称</view>
|
||||
<input class="modal-input" v-model="newNickname" placeholder="请输入新昵称" maxlength="20" />
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeNicknameModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmChangeNickname">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 身份切换下拉列表 -->
|
||||
<view class="identity-dropdown-modal" v-if="showIdentityDropdown" @tap="closeIdentityDropdown">
|
||||
<view class="identity-dropdown-content" @tap.stop>
|
||||
<view class="identity-dropdown-title">切换粉丝身份</view>
|
||||
<view class="identity-list">
|
||||
<view v-for="identity in myFanIdentities" :key="identity.star_id" class="identity-item" :class="{
|
||||
'identity-item-active': identity.star_id === currentStarId,
|
||||
'identity-item-disabled': identity.star_id === currentStarId
|
||||
}" @tap="handleSwitchIdentity(identity.star_id)">
|
||||
<view class="identity-info">
|
||||
<view class="identity-main">
|
||||
<text class="identity-star-name">{{ identity.star_name }}</text>
|
||||
<text class="identity-star-tag">{{ identity.star_tag }}</text>
|
||||
<!-- 修改密码弹窗 -->
|
||||
<view class="password-modal" v-if="showPasswordModal" @tap="closePasswordModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改密码</view>
|
||||
<!-- 旧密码输入框 -->
|
||||
<view class="modal-password-wrapper">
|
||||
<input class="modal-password-input" :type="showOldPassword ? 'text' : 'password'"
|
||||
v-model="oldPassword" placeholder="请输入旧密码" placeholder-class="input-placeholder" />
|
||||
<view class="modal-eye-icon" @click="showOldPassword = !showOldPassword">
|
||||
<text class="eye-text">{{ showOldPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 新密码输入框 -->
|
||||
<view class="modal-password-wrapper">
|
||||
<input class="modal-password-input" :type="showNewPassword ? 'text' : 'password'"
|
||||
v-model="newPassword" placeholder="请输入新密码" placeholder-class="input-placeholder" />
|
||||
<view class="modal-eye-icon" @click="showNewPassword = !showNewPassword">
|
||||
<text class="eye-text">{{ showNewPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closePasswordModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmChangePassword">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加身份弹窗 -->
|
||||
<view class="add-identity-modal" v-if="showAddIdentityModal" @tap="closeAddIdentityModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">添加身份</view>
|
||||
<view class="modal-subtitle">选择您喜欢的明星,成为TA的粉丝</view>
|
||||
|
||||
<!-- 明星选择列表 -->
|
||||
<view class="star-list">
|
||||
<view v-for="star in fanIdentitiesList" :key="star.star_id" class="star-item"
|
||||
:class="{ 'star-item-selected': selectedStarId === star.star_id }"
|
||||
@tap="selectStar(star.star_id)">
|
||||
<view class="star-info">
|
||||
<text class="star-name">{{ star.name }}</text>
|
||||
<text class="star-tag" v-if="star.tag">{{ star.tag }}</text>
|
||||
</view>
|
||||
<view class="star-radio">
|
||||
<view v-if="selectedStarId === star.star_id" class="star-radio-checked"></view>
|
||||
</view>
|
||||
<text class="identity-nickname">昵称: {{ identity.nickname }}</text>
|
||||
</view>
|
||||
<view v-if="identity.star_id === currentStarId" class="identity-current-badge">
|
||||
<text class="identity-current-text">当前身份</text>
|
||||
</view>
|
||||
|
||||
<!-- 昵称输入框 -->
|
||||
<input class="modal-input" v-model="newIdentityNickname" placeholder="请输入您的新昵称"
|
||||
maxlength="20" />
|
||||
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeAddIdentityModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmAddIdentity">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 身份切换下拉列表 -->
|
||||
<view class="identity-dropdown-modal" v-if="showIdentityDropdown" @tap="closeIdentityDropdown">
|
||||
<view class="identity-dropdown-content" @tap.stop>
|
||||
<view class="identity-dropdown-title">切换粉丝身份</view>
|
||||
<view class="identity-list">
|
||||
<view v-for="identity in myFanIdentities" :key="identity.star_id" class="identity-item"
|
||||
:class="{
|
||||
'identity-item-active': identity.star_id === currentStarId,
|
||||
'identity-item-disabled': identity.star_id === currentStarId
|
||||
}" @tap="handleSwitchIdentity(identity.star_id)">
|
||||
<view class="identity-info">
|
||||
<view class="identity-main">
|
||||
<text class="identity-star-name">{{ identity.star_name }}</text>
|
||||
<text class="identity-star-tag">{{ identity.star_tag }}</text>
|
||||
</view>
|
||||
<text class="identity-nickname">昵称: {{ identity.nickname }}</text>
|
||||
</view>
|
||||
<view v-if="identity.star_id === currentStarId" class="identity-current-badge">
|
||||
<text class="identity-current-text">当前身份</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注销账号确认弹窗 -->
|
||||
<view class="confirm-modal" v-if="showDeleteAccountModal" @tap="closeDeleteAccountModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">注销账号</view>
|
||||
<view class="modal-message">注销账号后,您的所有数据将被永久删除且无法恢复。确定要注销账号吗?</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeDeleteAccountModal">取消</button>
|
||||
<button class="modal-btn-confirm danger" @tap="confirmDeleteAccount">确定注销</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录确认弹窗 -->
|
||||
<view class="confirm-modal" v-if="showLogoutModal" @tap="closeLogoutModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">退出登录</view>
|
||||
<view class="modal-message">确定要退出登录吗?</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeLogoutModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmLogout">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 修改头像弹窗 -->
|
||||
<view class="avatar-modal" v-if="showAvatarModal" @tap="closeAvatarModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改头像</view>
|
||||
|
||||
<!-- 头像预览 -->
|
||||
<view class="avatar-preview">
|
||||
<Avatar :key="avatarKey" :userId="uid" :size="200" :borderWidth="6"
|
||||
:avatarUrl="userAvatarUrl" />
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<button class="upload-avatar-btn" @tap="handleUploadAvatar" :disabled="uploadingAvatar">
|
||||
{{ uploadingAvatar ? '上传中...' : '上传新头像' }}
|
||||
</button>
|
||||
|
||||
<view class="upload-hint">支持JPG、PNG格式,大小不超过10MB</view>
|
||||
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeAvatarModal">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注销账号确认弹窗 -->
|
||||
<view class="confirm-modal" v-if="showDeleteAccountModal" @tap="closeDeleteAccountModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">注销账号</view>
|
||||
<view class="modal-message">注销账号后,您的所有数据将被永久删除且无法恢复。确定要注销账号吗?</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeDeleteAccountModal">取消</button>
|
||||
<button class="modal-btn-confirm danger" @tap="confirmDeleteAccount">确定注销</button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class="logout-section">
|
||||
<button class="logout-button" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录确认弹窗 -->
|
||||
<view class="confirm-modal" v-if="showLogoutModal" @tap="closeLogoutModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">退出登录</view>
|
||||
<view class="modal-message">确定要退出登录吗?</view>
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeLogoutModal">取消</button>
|
||||
<button class="modal-btn-confirm" @tap="confirmLogout">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 修改头像弹窗 -->
|
||||
<view class="avatar-modal" v-if="showAvatarModal" @tap="closeAvatarModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-title">修改头像</view>
|
||||
|
||||
<!-- 头像预览 -->
|
||||
<view class="avatar-preview">
|
||||
<Avatar :key="avatarKey" :userId="uid" :size="200" :borderWidth="6"
|
||||
:avatarUrl="userAvatarUrl" />
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<button class="upload-avatar-btn" @tap="handleUploadAvatar" :disabled="uploadingAvatar">
|
||||
{{ uploadingAvatar ? '上传中...' : '上传新头像' }}
|
||||
</button>
|
||||
|
||||
<view class="upload-hint">支持JPG、PNG格式,大小不超过10MB</view>
|
||||
|
||||
<view class="modal-buttons">
|
||||
<button class="modal-btn-cancel" @tap="closeAvatarModal">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class="logout-section">
|
||||
<button class="logout-button" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 新手引导列表弹窗 -->
|
||||
<GuideListModal :visible="showGuideListModal" @start-guide="handleStartGuide"
|
||||
@claim-success="handleClaimSuccess" @close="showGuideListModal = false" />
|
||||
<!-- 新手引导弹窗 -->
|
||||
<GuideModal :visible="showGuideModal" @close="showGuideModal = false" @updated="handleGuideUpdated" />
|
||||
|
||||
<!-- 全局引导遮罩 -->
|
||||
<GuideOverlay />
|
||||
@ -268,7 +285,7 @@ import Avatar from '../components/Avatar.vue';
|
||||
import { getUserProfileApi, deleteAccountApi, updateNicknameApi, updatePasswordApi, getFanIdentitiesApi, addFanIdentityApi, getMyFanIdentitiesApi, switchFanIdentityApi, getOssSignatureApi, updateAvatarApi } from '@/utils/api';
|
||||
import { validateNickname } from '@/utils/validator.js';
|
||||
import { clearAvatarCache } from '@/utils/avatarCache';
|
||||
import GuideListModal from '@/components/GuideListModal.vue';
|
||||
import GuideModal from '@/pages/tasks/GuideModal.vue';
|
||||
import GuideOverlay from '@/components/GuideOverlay.vue';
|
||||
import {
|
||||
getClaimableRewardCount
|
||||
@ -295,8 +312,13 @@ const newPassword = ref('');
|
||||
const showOldPassword = ref(false);
|
||||
|
||||
// 新手引导
|
||||
const showGuideListModal = ref(false);
|
||||
const showGuideModal = ref(false);
|
||||
const guideClaimableCount = computed(() => getClaimableRewardCount());
|
||||
|
||||
// 引导更新回调
|
||||
const handleGuideUpdated = () => {
|
||||
console.log('[Profile] Guide updated');
|
||||
};
|
||||
const showNewPassword = ref(false);
|
||||
|
||||
// 添加身份弹窗
|
||||
@ -325,18 +347,33 @@ const avatarKey = ref(0); // 用于强制刷新Avatar组件
|
||||
// 手机号
|
||||
const mobile = ref('');
|
||||
|
||||
// 显示脱敏后的手机号(后端已脱敏,直接显示)
|
||||
// 显示手机号/地址
|
||||
const showBlockNumber = ref(false);
|
||||
const showMobile = ref(false);
|
||||
|
||||
// 显示手机号(根据showMobile状态显示脱敏或未脱敏)
|
||||
const displayMobile = computed(() => {
|
||||
return mobile.value || '';
|
||||
console.log('displayMobile computed, mobile:', mobile.value, 'showMobile:', showMobile.value);
|
||||
if (!mobile.value) return '';
|
||||
// showMobile为true时显示完整号码,false时显示脱敏号码
|
||||
if (showMobile.value) {
|
||||
return mobile.value;
|
||||
}
|
||||
// 脱敏显示:显示前3位和后4位
|
||||
const phone = mobile.value;
|
||||
return phone.length === 11 ? phone.slice(0, 3) + '****' + phone.slice(-4) : phone;
|
||||
});
|
||||
|
||||
// 显示截断后的区块链地址
|
||||
const displayAddress = computed(() => {
|
||||
const address = blockchainAddress.value;
|
||||
console.log('displayAddress computed, address:', address, 'showBlockNumber:', showBlockNumber.value);
|
||||
// 如果没有地址,显示默认地址
|
||||
if (!address) return '0xabcd...123c';
|
||||
// showBlockNumber为true时显示完整地址,false时显示截断地址
|
||||
if (showBlockNumber.value) return address;
|
||||
if (address.length <= 20) return address;
|
||||
// 显示前10个字符和后8个字符
|
||||
// 显示前4个字符和后4个字符
|
||||
return address.substring(0, 4) + '...' + address.substring(address.length - 4);
|
||||
});
|
||||
|
||||
@ -392,7 +429,7 @@ const fetchUserInfo = async (forceRefresh = false) => {
|
||||
fanTag.value = userForCache.fan_identity?.name || '';
|
||||
blockchainAddress.value = userForCache.blockchain_address || '';
|
||||
userAvatarUrl.value = userForCache.avatar_url || '';
|
||||
mobile.value = uni.getStorageSync('login_mobile') || '';
|
||||
mobile.value = userForCache.mobile || uni.getStorageSync('login_mobile') || '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
@ -408,7 +445,7 @@ const fetchUserInfo = async (forceRefresh = false) => {
|
||||
fanTag.value = cachedUser.fan_identity?.tag || '';
|
||||
blockchainAddress.value = cachedUser.blockchain_address || '';
|
||||
userAvatarUrl.value = cachedUser.avatar_url || '';
|
||||
mobile.value = uni.getStorageSync('login_mobile') || '';
|
||||
mobile.value = cachedUser.mobile || uni.getStorageSync('login_mobile') || '';
|
||||
} catch (e) {
|
||||
console.error('解析缓存用户信息失败:', e);
|
||||
}
|
||||
@ -423,6 +460,12 @@ const fetchUserInfo = async (forceRefresh = false) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 切换手机号显示
|
||||
const toggleMobileDisplay = () => {
|
||||
console.log('toggleMobileDisplay called, showMobile:', showMobile.value);
|
||||
showMobile.value = !showMobile.value;
|
||||
};
|
||||
|
||||
// 复制UID
|
||||
const copyUid = () => {
|
||||
if (!uid.value) {
|
||||
@ -445,19 +488,6 @@ const handleViewNickname = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 查看手机号
|
||||
const handleViewMobile = () => {
|
||||
if (!displayMobile.value) {
|
||||
uni.showToast({ title: '未绑定手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.setClipboardData({
|
||||
data: displayMobile.value,
|
||||
success: () => uni.showToast({ title: '已复制手机号', icon: 'success' }),
|
||||
fail: () => uni.showToast({ title: '复制失败', icon: 'none' })
|
||||
});
|
||||
};
|
||||
|
||||
// 复制区块链地址
|
||||
const copyAddress = () => {
|
||||
if (!blockchainAddress.value) {
|
||||
@ -917,50 +947,7 @@ const handleDeleteAccount = () => {
|
||||
|
||||
// 点击新手指引
|
||||
const handleGuideClick = () => {
|
||||
showGuideListModal.value = true;
|
||||
};
|
||||
|
||||
// 执行引导
|
||||
const handleStartGuide = (params) => {
|
||||
// 支持传入对象 { key, fromStep, targetPage } 或直接传 key
|
||||
const key = typeof params === 'string' ? params : (params?.key || '');
|
||||
const fromStep = typeof params === 'object' ? (params.fromStep ?? 0) : 0;
|
||||
const targetPage = typeof params === 'object' ? (params.targetPage || '') : '';
|
||||
|
||||
console.log('[Profile] handleStartGuide:', { key, fromStep, targetPage });
|
||||
|
||||
showGuideListModal.value = false;
|
||||
|
||||
// 根据引导类型跳转到对应页面
|
||||
if (key === 'square_home') {
|
||||
// 使用 targetPage 跳转到对应页面
|
||||
const navigateUrl = targetPage || '/pages/square/square';
|
||||
uni.navigateTo({
|
||||
url: navigateUrl,
|
||||
success: () => {
|
||||
// 延迟执行确保页面已加载
|
||||
setTimeout(() => {
|
||||
if (fromStep === 0) {
|
||||
store.dispatch("guide/startGuideFromBeginning", key);
|
||||
} else {
|
||||
store.dispatch("guide/resumeGuide", key);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 其他引导在当前页面触发
|
||||
if (fromStep === 0) {
|
||||
store.dispatch("guide/startGuideFromBeginning", key);
|
||||
} else {
|
||||
store.dispatch("guide/resumeGuide", key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 领取奖励成功
|
||||
const handleClaimSuccess = (reward) => {
|
||||
console.log("[Profile] 领取奖励成功:", reward);
|
||||
showGuideModal.value = true;
|
||||
};
|
||||
|
||||
// 关闭注销账号弹窗
|
||||
@ -1309,12 +1296,12 @@ onShow(() => {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close-icon-img{
|
||||
.close-icon-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
position: relative;
|
||||
top: 32rpx;
|
||||
left: 32rpx;
|
||||
top: 32rpx;
|
||||
left: 32rpx;
|
||||
}
|
||||
|
||||
.user-info-card {
|
||||
@ -1353,8 +1340,8 @@ onShow(() => {
|
||||
gap: 8rpx;
|
||||
margin-left: 8rpx;
|
||||
background-image: url(/static/rank/activity-support-icon/beijingkuang.png);
|
||||
background-size: 100% 110%;
|
||||
background-position: center;
|
||||
background-size: 100% 110%;
|
||||
background-position: center;
|
||||
padding: 24rpx 24rpx 24rpx 240rpx;
|
||||
min-width: 356rpx;
|
||||
}
|
||||
@ -1371,6 +1358,11 @@ onShow(() => {
|
||||
min-width: 112rpx;
|
||||
}
|
||||
|
||||
.address-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
@ -1378,15 +1370,18 @@ onShow(() => {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 200rpx;
|
||||
|
||||
}
|
||||
|
||||
.uid-value {
|
||||
max-width: 200rpx;
|
||||
.uid-value {}
|
||||
|
||||
.toggle-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.address-value {
|
||||
max-width: 160rpx;
|
||||
}
|
||||
.address-value {}
|
||||
|
||||
.info-btn {
|
||||
font-size: 20rpx;
|
||||
@ -1521,7 +1516,7 @@ onShow(() => {
|
||||
|
||||
.asset-card {
|
||||
background-image: url(/static/rank/activity-support-icon/beijingkuang.png);
|
||||
background-size: 100% 100%;
|
||||
background-size: 100% 125%;
|
||||
background-position: center;
|
||||
border-radius: 20rpx;
|
||||
padding: 0 16rpx;
|
||||
@ -1619,8 +1614,10 @@ onShow(() => {
|
||||
|
||||
.logout-section {
|
||||
padding: 10rpx 30rpx;
|
||||
padding-bottom: calc(40rpx + constant(safe-area-inset-bottom)); /* iOS 11.0 */
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
|
||||
padding-bottom: calc(40rpx + constant(safe-area-inset-bottom));
|
||||
/* iOS 11.0 */
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
/* iOS 11.2+ */
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
@ -1686,7 +1683,10 @@ onShow(() => {
|
||||
.modal-content {
|
||||
width: 80%;
|
||||
max-width: 600rpx;
|
||||
background: #ffffff;
|
||||
/* background: #ffffff; */
|
||||
background-image: url('/static/starbookcontent/beijing.png');
|
||||
background-size: cover;
|
||||
background-position: center bottom;
|
||||
border-radius: 30rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<view class="theme-banner">
|
||||
<!-- 背景图 - 使用懒加载 -->
|
||||
<image
|
||||
v-if="bannerImage"
|
||||
:src="bannerImage"
|
||||
class="banner-bg"
|
||||
mode="aspectFill"
|
||||
:lazy-load="true"
|
||||
/>
|
||||
<image v-if="bannerImage" :src="bannerImage" class="banner-bg" mode="aspectFill" :lazy-load="true" />
|
||||
|
||||
<!-- 内容层 -->
|
||||
<view class="banner-content" @tap="handleBannerClick">
|
||||
@ -16,7 +10,7 @@
|
||||
<view class="title-section">
|
||||
|
||||
<view class="subtitle-row">
|
||||
<text class="subtitle-text">{{'「'+ title +'」' }}</text>
|
||||
<text class="subtitle-text">{{ '「' + title + '」' }}</text>
|
||||
|
||||
<!-- 如果确实需要过时数据警告,可以保留,否则可隐藏 -->
|
||||
<view v-if="isStaleData" class="stale-indicator">
|
||||
@ -28,7 +22,7 @@
|
||||
|
||||
<!-- 下半部分:右下角数字 -->
|
||||
<view class="footer-section">
|
||||
<text class="progress-text">{{ formattedCurrent }} / {{ formattedTarget }}</text>
|
||||
<text class="progress-text">当前进度<text class="current-value">{{ formattedCurrent }}</text> / {{ formattedTarget }}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@ -178,13 +172,26 @@ const progressPercent = computed(() => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 8rpx rgba(255, 255, 255, 0.6);
|
||||
font-family: 'yt', sans-serif;
|
||||
background-image: url('@/static/rank/activity-support-icon/beijingkuang.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
padding: 0.8rpx 24rpx;
|
||||
border-radius: 40rpx;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
||||
|
||||
}
|
||||
|
||||
.current-value {
|
||||
color: #FFD700;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<view class="modal-header">
|
||||
<!-- 返回按钮 -->
|
||||
<view class="back-button" @click="handleClose">
|
||||
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
|
||||
<image class="back-icon" src="/static/starbookcontent/tuichu.png" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
|
||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 420 KiB |
BIN
frontend/static/icon/hide.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
frontend/static/icon/settings.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
frontend/static/icon/show.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 1.0 MiB |
@ -96,8 +96,8 @@ const actions = {
|
||||
// 缓存用户信息
|
||||
const user = res.data.user
|
||||
|
||||
// 缓存登录手机号(优先使用后端返回的脱敏手机号)
|
||||
const loginMobile = user.mobile_masked || mobile
|
||||
// 缓存登录手机号
|
||||
const loginMobile = mobile
|
||||
uni.setStorageSync('login_mobile', loginMobile)
|
||||
|
||||
uni.setStorageSync('user', JSON.stringify(user))
|
||||
@ -131,8 +131,8 @@ const actions = {
|
||||
// 缓存用户信息
|
||||
const user = res.data.user
|
||||
|
||||
// 缓存登录手机号(优先使用后端返回的脱敏手机号)
|
||||
const loginMobile = user.mobile_masked || mobile
|
||||
// 缓存登录手机号
|
||||
const loginMobile = mobile
|
||||
uni.setStorageSync('login_mobile', loginMobile)
|
||||
|
||||
uni.setStorageSync('user', JSON.stringify(user))
|
||||
|
||||
@ -3,19 +3,19 @@
|
||||
// 不需要手动注释!
|
||||
|
||||
// #ifdef H5
|
||||
// const baseURL = 'http://localhost:8080' // H5 开发用本机
|
||||
const baseURL = 'http://101.132.250.62:8080' // H5 开发用本机
|
||||
const baseURL = 'http://192.168.110.60:8080' // H5 开发用本机
|
||||
// const baseURL = 'http://101.132.250.62:8080' // H5 开发用本机
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// 开发调试:手机和电脑同一WiFi时用这个(改成你电脑IP)
|
||||
// 上线后:改成实际服务器地址
|
||||
// const baseURL = 'http://192.168.110.60:8080'
|
||||
const baseURL = 'http://192.168.110.60:8080'
|
||||
// #endif
|
||||
|
||||
// 服务器地址(正式上线用)
|
||||
// #ifdef APP-PLUS
|
||||
const baseURL = 'http://101.132.250.62:8080'
|
||||
// const baseURL = 'http://101.132.250.62:8080'
|
||||
// #endif
|
||||
|
||||
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
||||
@ -468,7 +468,9 @@ export function getBatchOssPresignedUrlsApi(files, expires = 3600, type = 'asset
|
||||
* @returns {Promise<{ imageUrl: string, orderId?: string, data: object }>}
|
||||
*/
|
||||
export function uploadLocalFileToOss(filePath, options = {}) {
|
||||
const { type = 'asset', orderId = '', fileName } = options
|
||||
const {
|
||||
type = 'asset', orderId = '', fileName
|
||||
} = options
|
||||
const objectName = fileName || `${Date.now()}.jpg`
|
||||
return new Promise((resolve, reject) => {
|
||||
getOssSignatureApi(type, orderId)
|
||||
|
||||