feat:修改已知bug
This commit is contained in:
parent
542b1bd4f0
commit
a1bb302be8
@ -1,7 +1,7 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
||||||
# VITE_API_BASE_URL=http://192.168.110.60:8080
|
VITE_API_BASE_URL=http://192.168.110.60:8080
|
||||||
VITE_API_BASE_URL=https://api.topfans.online
|
# VITE_API_BASE_URL=https://api.topfans.online
|
||||||
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
||||||
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
||||||
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
||||||
|
|||||||
@ -215,7 +215,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onUnmounted } from 'vue';
|
import { ref, computed, onUnmounted } from 'vue';
|
||||||
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
|
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app';
|
||||||
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi, getAssetMaterialsApi, getAssetLikersApi } from '@/utils/api.js';
|
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi, getAssetMaterialsApi, getAssetLikersApi } from '@/utils/api.js';
|
||||||
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
||||||
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
|
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
|
||||||
@ -295,17 +295,20 @@ const isLiked = ref(false);
|
|||||||
const likeCount = ref(0);
|
const likeCount = ref(0);
|
||||||
const liking = ref(false);
|
const liking = ref(false);
|
||||||
|
|
||||||
// 当前用户头像
|
// 当前用户信息
|
||||||
const userAvatarUrl = ref('');
|
const userAvatarUrl = ref('');
|
||||||
|
const currentUserId = ref('');
|
||||||
|
const currentUserNickname = ref('');
|
||||||
|
|
||||||
// 加载当前用户信息
|
// 加载当前用户信息
|
||||||
const loadCurrentUser = () => {
|
const loadCurrentUser = () => {
|
||||||
try {
|
try {
|
||||||
const userStr = uni.getStorageSync('user');
|
const userStr = uni.getStorageSync('user');
|
||||||
if (userStr) {
|
if (!userStr) return;
|
||||||
const userInfo = JSON.parse(userStr);
|
const userInfo = typeof userStr === 'string' ? JSON.parse(userStr) : userStr;
|
||||||
userAvatarUrl.value = userInfo?.avatar_url || '';
|
userAvatarUrl.value = userInfo?.avatar_url || '';
|
||||||
}
|
currentUserId.value = String(userInfo?.uid || userInfo?.user_id || '');
|
||||||
|
currentUserNickname.value = userInfo?.nickname || '';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析用户信息失败:', e);
|
console.error('解析用户信息失败:', e);
|
||||||
}
|
}
|
||||||
@ -325,30 +328,41 @@ const ellipseConfig = [
|
|||||||
// 点赞用户数据(从API加载)
|
// 点赞用户数据(从API加载)
|
||||||
const likedUsers = ref([]);
|
const likedUsers = ref([]);
|
||||||
|
|
||||||
|
// 把 API 返回的原始点赞用户映射成展示用的头像数组(最多 6 个,按椭圆排列)
|
||||||
|
const mapLikersToDisplay = (users) => {
|
||||||
|
if (!Array.isArray(users) || !users.length) return [];
|
||||||
|
return users.slice(0, 6).map((user, index) => {
|
||||||
|
const config = ellipseConfig[index] || { ellipseX: 0, ellipseY: 0, size: 1 };
|
||||||
|
return {
|
||||||
|
avatar: user.avatar || '/static/sucai/default-avatar.png',
|
||||||
|
ellipseX: config.ellipseX,
|
||||||
|
ellipseY: config.ellipseY,
|
||||||
|
size: config.size
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 把 API 返回的原始点赞用户映射成弹窗用的完整列表
|
||||||
|
const mapLikersToModal = (users) => {
|
||||||
|
if (!Array.isArray(users)) return [];
|
||||||
|
return users.map(user => ({
|
||||||
|
userId: user.user_id,
|
||||||
|
nickname: user.nickname || '匿名用户',
|
||||||
|
avatar: user.avatar || '/static/sucai/default-avatar.png',
|
||||||
|
liked_at: user.liked_at || 0
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
// 加载点赞用户头像
|
// 加载点赞用户头像
|
||||||
const loadLikedUsers = async (assetId) => {
|
const loadLikedUsers = async (assetId) => {
|
||||||
|
if (!assetId) return;
|
||||||
try {
|
try {
|
||||||
likedUsersLoading.value = true;
|
likedUsersLoading.value = true;
|
||||||
// 加载点赞用户
|
// 加载点赞用户
|
||||||
const res = await getAssetLikersApi(assetId, 20, 0);
|
const res = await getAssetLikersApi(assetId, 20, 0);
|
||||||
if (res.code === 200 && res.data?.users) {
|
if (res.code === 200 && res.data?.users) {
|
||||||
// 映射到显示格式(头像展示用)
|
likedUsers.value = mapLikersToDisplay(res.data.users);
|
||||||
likedUsers.value = res.data?.users.slice(0, 6).map((user, index) => {
|
allLikedUsers.value = mapLikersToModal(res.data.users);
|
||||||
const config = ellipseConfig[index] || { ellipseX: 0, ellipseY: 0, size: 1 };
|
|
||||||
return {
|
|
||||||
avatar: user.avatar || '/static/sucai/default-avatar.png',
|
|
||||||
ellipseX: config.ellipseX,
|
|
||||||
ellipseY: config.ellipseY,
|
|
||||||
size: config.size
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// 设置全部点赞用户用于弹窗展示
|
|
||||||
allLikedUsers.value = res.data?.users.map(user => ({
|
|
||||||
userId: user.user_id,
|
|
||||||
nickname: user.nickname || '匿名用户',
|
|
||||||
avatar: user.avatar || '/static/sucai/default-avatar.png',
|
|
||||||
liked_at: user.liked_at || 0
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载点赞用户失败:', e);
|
console.error('加载点赞用户失败:', e);
|
||||||
@ -540,21 +554,16 @@ const copyHash = () => {
|
|||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
console.log('loadData 开始执行');
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
loadError.value = '';
|
loadError.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let assetId = assetIdParam.value;
|
let assetId = assetIdParam.value;
|
||||||
console.log('assetIdParam:', assetIdParam.value, 'orderIdParam:', orderIdParam.value);
|
|
||||||
|
|
||||||
if (!assetId && orderIdParam.value) {
|
if (!assetId && orderIdParam.value) {
|
||||||
console.log('通过 order_id 获取资产信息');
|
|
||||||
const mintRes = await getMintOrderDetailApi(orderIdParam.value);
|
const mintRes = await getMintOrderDetailApi(orderIdParam.value);
|
||||||
console.log('getMintOrderDetailApi 响应:', mintRes);
|
|
||||||
if (mintRes.code === 200 && mintRes.data?.asset?.asset_id) {
|
if (mintRes.code === 200 && mintRes.data?.asset?.asset_id) {
|
||||||
assetId = mintRes.data.asset.asset_id;
|
assetId = mintRes.data.asset.asset_id;
|
||||||
console.log('获取到 assetId:', assetId);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('获取铸造订单详情失败');
|
throw new Error('获取铸造订单详情失败');
|
||||||
}
|
}
|
||||||
@ -562,9 +571,7 @@ const loadData = async () => {
|
|||||||
|
|
||||||
if (!assetId) throw new Error('藏品信息不完整');
|
if (!assetId) throw new Error('藏品信息不完整');
|
||||||
|
|
||||||
console.log('调用 getAssetDetailApi,assetId:', assetId);
|
|
||||||
const res = await getAssetDetailApi(assetId);
|
const res = await getAssetDetailApi(assetId);
|
||||||
console.log('getAssetDetailApi 响应:', res);
|
|
||||||
if (res.code === 200 && res.data?.asset) {
|
if (res.code === 200 && res.data?.asset) {
|
||||||
const asset = res.data.asset;
|
const asset = res.data.asset;
|
||||||
assetData.value = asset;
|
assetData.value = asset;
|
||||||
@ -579,12 +586,9 @@ const loadData = async () => {
|
|||||||
if (asset.exhibition_expire_at) {
|
if (asset.exhibition_expire_at) {
|
||||||
startCountdown();
|
startCountdown();
|
||||||
}
|
}
|
||||||
console.log(res.data)
|
|
||||||
|
|
||||||
// 异步加载封面图片,不阻塞页面渲染
|
// 异步加载封面图片,不阻塞页面渲染
|
||||||
console.log('开始加载封面图片:', asset.cover_url);
|
|
||||||
getAssetCoverRealUrl(asset.cover_url).then(async (url) => {
|
getAssetCoverRealUrl(asset.cover_url).then(async (url) => {
|
||||||
console.log('封面图片加载成功:', url);
|
|
||||||
coverUrl.value = url;
|
coverUrl.value = url;
|
||||||
if (isLenticularAsset.value) {
|
if (isLenticularAsset.value) {
|
||||||
// 参考 success.vue 逻辑:通过 bindAssetMaterialsApi 获取素材列表
|
// 参考 success.vue 逻辑:通过 bindAssetMaterialsApi 获取素材列表
|
||||||
@ -593,7 +597,6 @@ const loadData = async () => {
|
|||||||
// 调用素材接口获取完整的素材列表
|
// 调用素材接口获取完整的素材列表
|
||||||
try {
|
try {
|
||||||
const materialsRes = await getAssetMaterialsApi(assetId)
|
const materialsRes = await getAssetMaterialsApi(assetId)
|
||||||
console.log('getAssetMaterialsApi 响应:', materialsRes)
|
|
||||||
if (materialsRes.code === 200 && materialsRes.data) {
|
if (materialsRes.code === 200 && materialsRes.data) {
|
||||||
const materialsList = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || []
|
const materialsList = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || []
|
||||||
// 找到 bg 和 main 素材
|
// 找到 bg 和 main 素材
|
||||||
@ -619,7 +622,6 @@ const loadData = async () => {
|
|||||||
console.error('加载封面图片失败:', err);
|
console.error('加载封面图片失败:', err);
|
||||||
coverUrl.value = ''; // 失败时不显示默认图片
|
coverUrl.value = ''; // 失败时不显示默认图片
|
||||||
});
|
});
|
||||||
console.log('loadData 执行成功');
|
|
||||||
// 异步加载点赞用户头像
|
// 异步加载点赞用户头像
|
||||||
loadLikedUsers(assetId);
|
loadLikedUsers(assetId);
|
||||||
} else {
|
} else {
|
||||||
@ -630,7 +632,6 @@ const loadData = async () => {
|
|||||||
loadError.value = err.message || '加载失败,请重试';
|
loadError.value = err.message || '加载失败,请重试';
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
console.log('loadData 执行完成,loading:', loading.value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -643,6 +644,29 @@ const startCountdown = () => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 把当前用户乐观插入到点赞用户列表(让「创作者信息模块」立刻反映本次点赞)
|
||||||
|
const prependCurrentUserToLikers = () => {
|
||||||
|
if (!userAvatarUrl.value) return;
|
||||||
|
const meAvatar = userAvatarUrl.value;
|
||||||
|
// 1) 顶部头像展示列表:去重后插到最前,重新应用椭圆配置
|
||||||
|
const nextDisplay = [
|
||||||
|
{ avatar: meAvatar },
|
||||||
|
...likedUsers.value.filter(u => u.avatar !== meAvatar)
|
||||||
|
];
|
||||||
|
likedUsers.value = mapLikersToDisplay(nextDisplay);
|
||||||
|
// 2) 弹窗用完整列表:去重后插到最前
|
||||||
|
const meRow = {
|
||||||
|
userId: currentUserId.value,
|
||||||
|
nickname: currentUserNickname.value || '我',
|
||||||
|
avatar: meAvatar,
|
||||||
|
liked_at: Date.now()
|
||||||
|
};
|
||||||
|
allLikedUsers.value = [
|
||||||
|
meRow,
|
||||||
|
...allLikedUsers.value.filter(u => u.avatar !== meAvatar)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
// 点赞
|
// 点赞
|
||||||
const handleLike = async () => {
|
const handleLike = async () => {
|
||||||
if (liking.value || !assetData.value.asset_id) return;
|
if (liking.value || !assetData.value.asset_id) return;
|
||||||
@ -653,9 +677,15 @@ const handleLike = async () => {
|
|||||||
// isLiked.value = false;
|
// isLiked.value = false;
|
||||||
// likeCount.value = Math.max(0, likeCount.value - 1);
|
// likeCount.value = Math.max(0, likeCount.value - 1);
|
||||||
// } else {
|
// } else {
|
||||||
await likeAssetApi(assetData.value.asset_id);
|
const res = await likeAssetApi(assetData.value.asset_id);
|
||||||
isLiked.value = true;
|
isLiked.value = true;
|
||||||
likeCount.value += 1;
|
// 优先用后端返回的最新 like_count;没有则本地 +1
|
||||||
|
const serverCount = res?.data?.like_count;
|
||||||
|
likeCount.value = typeof serverCount === 'number' ? serverCount : likeCount.value + 1;
|
||||||
|
// 乐观更新「创作者信息模块」——立刻把当前用户头像插到第一位
|
||||||
|
prependCurrentUserToLikers();
|
||||||
|
// 后台静默拉取一次真实列表,保证后续顺序、昵称、avatar URL 与服务端一致
|
||||||
|
loadLikedUsers(assetData.value.asset_id);
|
||||||
// }
|
// }
|
||||||
// 通知展馆页面更新点赞数
|
// 通知展馆页面更新点赞数
|
||||||
uni.$emit('assetLikeChanged', {
|
uni.$emit('assetLikeChanged', {
|
||||||
@ -669,7 +699,7 @@ const handleLike = async () => {
|
|||||||
// 显示后端返回的实际错误信息
|
// 显示后端返回的实际错误信息
|
||||||
const showMsg = errData || errMsg || '点赞失败';
|
const showMsg = errData || errMsg || '点赞失败';
|
||||||
uni.showToast({ title: showMsg, icon: 'none', duration: 2000 });
|
uni.showToast({ title: showMsg, icon: 'none', duration: 2000 });
|
||||||
console.log('点赞错误:', err);
|
console.error('点赞失败:', err);
|
||||||
} finally {
|
} finally {
|
||||||
liking.value = false;
|
liking.value = false;
|
||||||
}
|
}
|
||||||
@ -712,12 +742,6 @@ const handleCraftMint = async () => {
|
|||||||
const bgImagePath = isCraftLenticular.value
|
const bgImagePath = isCraftLenticular.value
|
||||||
? lenticularLayers.value.find((l) => l.id === 'base')?.src || ''
|
? lenticularLayers.value.find((l) => l.id === 'base')?.src || ''
|
||||||
: undefined;
|
: undefined;
|
||||||
console.log('[asset-detail] handleCraftMint', {
|
|
||||||
isCraftLenticular: isCraftLenticular.value,
|
|
||||||
bgImagePath: bgImagePath,
|
|
||||||
lenticularLayers: lenticularLayers.value.map(l => ({ id: l.id, src: l.src ? l.src.substring(0, 30) : '' })),
|
|
||||||
craftFormDataKeys: Object.keys(craftFormData.value || {})
|
|
||||||
})
|
|
||||||
craftMinting.value = true;
|
craftMinting.value = true;
|
||||||
uni.showLoading({ title: '铸造中…', mask: true });
|
uni.showLoading({ title: '铸造中…', mask: true });
|
||||||
try {
|
try {
|
||||||
@ -749,32 +773,71 @@ const handleBack = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 点赞轮询定时器:覆盖跨用户/跨设备「实时」更新(后端暂无点赞 WS)
|
||||||
|
let likePollingTimer = null;
|
||||||
|
const LIKE_POLLING_INTERVAL = 30000; // 20s
|
||||||
|
|
||||||
|
// 启动点赞轮询
|
||||||
|
const startLikePolling = () => {
|
||||||
|
stopLikePolling();
|
||||||
|
if (!assetData.value?.asset_id) return;
|
||||||
|
likePollingTimer = setInterval(() => {
|
||||||
|
// 页面被切到后台时轮询其实也会跑,但 uni-app 在 H5/App 下 setInterval 仍触发;
|
||||||
|
// 定时器在 onHide 清理,onShow 重新启动,天然只在可见时拉取
|
||||||
|
if (assetData.value?.asset_id) {
|
||||||
|
loadLikedUsers(assetData.value.asset_id);
|
||||||
|
}
|
||||||
|
}, LIKE_POLLING_INTERVAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopLikePolling = () => {
|
||||||
|
if (likePollingTimer) {
|
||||||
|
clearInterval(likePollingTimer);
|
||||||
|
likePollingTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其他页面触发的点赞变更事件:保持本页面与全局一致
|
||||||
|
const handleAssetLikeChangedExternal = (data) => {
|
||||||
|
if (!data || String(data.asset_id) !== String(assetData.value?.asset_id)) return;
|
||||||
|
if (typeof data.like_count === 'number') likeCount.value = data.like_count;
|
||||||
|
if (typeof data.is_liked === 'boolean') isLiked.value = data.is_liked;
|
||||||
|
// 拉取最新点赞用户列表,更新「创作者信息模块」
|
||||||
|
if (assetData.value?.asset_id) {
|
||||||
|
loadLikedUsers(assetData.value.asset_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
console.log('onLoad 触发,参数:', options);
|
|
||||||
assetIdParam.value = options?.asset_id || '';
|
assetIdParam.value = options?.asset_id || '';
|
||||||
orderIdParam.value = options?.order_id || '';
|
orderIdParam.value = options?.order_id || '';
|
||||||
fromParam.value = options?.from || '';
|
fromParam.value = options?.from || '';
|
||||||
studioKindParam.value = options?.studio_kind || '';
|
studioKindParam.value = options?.studio_kind || '';
|
||||||
loadCurrentUser();
|
loadCurrentUser();
|
||||||
|
// 订阅跨页面点赞事件
|
||||||
|
uni.$on('assetLikeChanged', handleAssetLikeChangedExternal);
|
||||||
|
|
||||||
// craft_confirm 模式:不需要调用 loadData,而是加载本地表单数据
|
// craft_confirm 模式:不需要调用 loadData,而是加载本地表单数据
|
||||||
if (fromParam.value === 'craft_confirm') {
|
if (fromParam.value === 'craft_confirm') {
|
||||||
console.log('[asset-detail] craft_confirm 模式,加载本地数据');
|
|
||||||
craftConfirmMode.value = true;
|
craftConfirmMode.value = true;
|
||||||
loadCraftConfirm();
|
loadCraftConfirm();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
console.log('onShow 触发');
|
|
||||||
const currentKey = `${assetIdParam.value}_${orderIdParam.value}`;
|
const currentKey = `${assetIdParam.value}_${orderIdParam.value}`;
|
||||||
console.log('当前key:', currentKey, '上次key:', lastLoadKey.value);
|
|
||||||
|
|
||||||
// 如果参数变化了,或者是首次加载,则重新加载数据
|
// 如果参数变化了,或者是首次加载,则重新加载数据
|
||||||
if ((assetIdParam.value || orderIdParam.value) && currentKey !== lastLoadKey.value) {
|
if ((assetIdParam.value || orderIdParam.value) && currentKey !== lastLoadKey.value) {
|
||||||
lastLoadKey.value = currentKey;
|
lastLoadKey.value = currentKey;
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 页面每次回到前台都重新拉一次点赞列表 —— 兜底「创作者信息模块」时效
|
||||||
|
if (assetData.value?.asset_id && !craftConfirmMode.value) {
|
||||||
|
loadLikedUsers(assetData.value.asset_id);
|
||||||
|
// startLikePolling();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onHide(() => {
|
onHide(() => {
|
||||||
@ -784,11 +847,20 @@ onHide(() => {
|
|||||||
if (isLenticularDetail.value) {
|
if (isLenticularDetail.value) {
|
||||||
stopTiltPreview()
|
stopTiltPreview()
|
||||||
}
|
}
|
||||||
|
stopLikePolling();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
// 页面被关闭/销毁:清理轮询和事件订阅,避免泄漏到下一页或后台
|
||||||
|
stopLikePolling();
|
||||||
|
uni.$off('assetLikeChanged', handleAssetLikeChangedExternal);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (countdownTimer) clearInterval(countdownTimer);
|
if (countdownTimer) clearInterval(countdownTimer);
|
||||||
stopTiltPreview();
|
stopTiltPreview();
|
||||||
|
stopLikePolling();
|
||||||
|
uni.$off('assetLikeChanged', handleAssetLikeChangedExternal);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -455,6 +455,7 @@ onUnmounted(() => {
|
|||||||
.content-scroll {
|
.content-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user