feat: 移除oss预url的鉴权
This commit is contained in:
parent
1b82e4c3bc
commit
75893b1f42
@ -263,15 +263,15 @@ const handleLike = async () => {
|
|||||||
if (liking.value || !assetData.value.asset_id) return;
|
if (liking.value || !assetData.value.asset_id) return;
|
||||||
liking.value = true;
|
liking.value = true;
|
||||||
try {
|
try {
|
||||||
if (isLiked.value) {
|
// if (isLiked.value) {
|
||||||
await unlikeAssetApi(assetData.value.asset_id);
|
// await unlikeAssetApi(assetData.value.asset_id);
|
||||||
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);
|
await likeAssetApi(assetData.value.asset_id);
|
||||||
isLiked.value = true;
|
isLiked.value = true;
|
||||||
likeCount.value += 1;
|
likeCount.value += 1;
|
||||||
}
|
// }
|
||||||
// 通知展馆页面更新点赞数
|
// 通知展馆页面更新点赞数
|
||||||
uni.$emit('assetLikeChanged', {
|
uni.$emit('assetLikeChanged', {
|
||||||
asset_id: assetData.value.asset_id,
|
asset_id: assetData.value.asset_id,
|
||||||
|
|||||||
@ -11,9 +11,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch, onMounted } from 'vue';
|
import { computed, ref, watch, onMounted } from 'vue';
|
||||||
import { getOssPresignedUrlApi } from '@/utils/api.js';
|
|
||||||
import { getCachedAvatarPath, downloadAndCacheAvatar } from '@/utils/avatarCache.js';
|
|
||||||
import { extractFileNameFromUrl } from '@/utils/assetImageHelper.js';
|
|
||||||
|
|
||||||
// 定义 props
|
// 定义 props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -59,67 +56,30 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 本地头像路径或预签名URL
|
// 本地头像路径
|
||||||
const ossPresignedUrl = ref('');
|
const localAvatarUrl = ref('');
|
||||||
|
|
||||||
// 获取用户自己的头像(需要缓存)
|
// 获取用户自己的头像(直接使用后端返回的URL)
|
||||||
const fetchOwnAvatarWithCache = async () => {
|
const fetchOwnAvatar = () => {
|
||||||
if (!props.avatarUrl) {
|
if (!props.avatarUrl) {
|
||||||
ossPresignedUrl.value = '';
|
localAvatarUrl.value = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 直接使用后端返回的 URL
|
||||||
// 1. 先尝试从本地缓存获取文件路径
|
localAvatarUrl.value = props.avatarUrl;
|
||||||
const cachedPath = await getCachedAvatarPath(props.avatarUrl);
|
|
||||||
if (cachedPath) {
|
|
||||||
ossPresignedUrl.value = cachedPath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 本地缓存不存在,从后端获取预签名URL
|
|
||||||
try {
|
|
||||||
// 从avatarUrl中提取真实文件名
|
|
||||||
const fileName = extractFileNameFromUrl(props.avatarUrl) || 'avatar.png';
|
|
||||||
|
|
||||||
const res = await getOssPresignedUrlApi(fileName, 3600, 'avatar');
|
|
||||||
if (res.code === 200 && res.data && res.data.url) {
|
|
||||||
const realUrl = res.data.url;
|
|
||||||
|
|
||||||
// 3. 下载并缓存头像文件到本地
|
|
||||||
const localPath = await downloadAndCacheAvatar(props.avatarUrl, realUrl);
|
|
||||||
|
|
||||||
if (localPath) {
|
|
||||||
// 使用本地文件路径
|
|
||||||
ossPresignedUrl.value = localPath;
|
|
||||||
} else {
|
|
||||||
// 下载失败,临时使用预签名URL
|
|
||||||
console.warn('下载头像文件失败,使用临时URL');
|
|
||||||
ossPresignedUrl.value = realUrl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('获取OSS预签名URL失败:', res.message);
|
|
||||||
ossPresignedUrl.value = '';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取头像异常:', error);
|
|
||||||
ossPresignedUrl.value = '';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取好友头像(不缓存,直接使用传入的URL)
|
// 获取好友头像(直接使用传入的URL)
|
||||||
const fetchFriendAvatarWithoutCache = () => {
|
const fetchFriendAvatar = () => {
|
||||||
// 好友头像已经在FriendsContent中解析完成,直接使用
|
localAvatarUrl.value = props.avatarUrl || '';
|
||||||
ossPresignedUrl.value = props.avatarUrl || '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据enableCache决定使用哪种获取方式
|
// 根据enableCache决定使用哪种获取方式
|
||||||
const fetchAvatar = () => {
|
const fetchAvatar = () => {
|
||||||
if (props.enableCache) {
|
if (props.enableCache) {
|
||||||
// 用户自己的头像:需要缓存
|
fetchOwnAvatar();
|
||||||
fetchOwnAvatarWithCache();
|
|
||||||
} else {
|
} else {
|
||||||
// 好友头像:不缓存,直接使用传入的URL
|
fetchFriendAvatar();
|
||||||
fetchFriendAvatarWithoutCache();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,13 +120,11 @@
|
|||||||
return `/static/avatar/${index}.jpeg`;
|
return `/static/avatar/${index}.jpeg`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 头像图片源(优先使用OSS头像,否则使用默认头像)
|
// 头像图片源(优先使用头像URL,否则使用默认头像)
|
||||||
const avatarImage = computed(() => {
|
const avatarImage = computed(() => {
|
||||||
// 如果有OSS预签名URL,使用它
|
if (localAvatarUrl.value) {
|
||||||
if (ossPresignedUrl.value) {
|
return localAvatarUrl.value;
|
||||||
return ossPresignedUrl.value;
|
|
||||||
}
|
}
|
||||||
// 否则使用默认头像
|
|
||||||
return getDefaultAvatar();
|
return getDefaultAvatar();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,18 +7,13 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { getHotRankingApi, getOssPresignedUrlApi } from '@/utils/api.js';
|
import { getHotRankingApi } from '@/utils/api.js';
|
||||||
|
|
||||||
const emit = defineEmits(['dataLoaded']);
|
const emit = defineEmits(['dataLoaded']);
|
||||||
|
|
||||||
const resolveOssUrl = async (fileName, type) => {
|
const resolveOssUrl = async (fileName, type) => {
|
||||||
if (!fileName) return '';
|
if (!fileName) return '';
|
||||||
try {
|
// 直接使用后端返回的图片URL
|
||||||
const res = await getOssPresignedUrlApi(fileName, 3600, type);
|
|
||||||
if (res?.code === 200 && res.data?.url) return res.data.url;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('[BannerTop3] OSS URL 获取失败', fileName, e?.message);
|
|
||||||
}
|
|
||||||
return fileName;
|
return fileName;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,13 +22,9 @@ const loadTop3 = async () => {
|
|||||||
const res = await getHotRankingApi('total', null, 1, 3);
|
const res = await getHotRankingApi('total', null, 1, 3);
|
||||||
if (res.code === 200 && res.data?.items) {
|
if (res.code === 200 && res.data?.items) {
|
||||||
const items = res.data.items.slice(0, 3);
|
const items = res.data.items.slice(0, 3);
|
||||||
const resolved = await Promise.all(items.map(async (item) => {
|
const resolved = items.map(item => {
|
||||||
const [coverUrl, avatarUrl] = await Promise.all([
|
return { ...item, cover_url: item.cover_url || '', avatar_url: item.avatar_url || '' };
|
||||||
resolveOssUrl(item.cover_url || '', 'asset'),
|
});
|
||||||
resolveOssUrl(item.avatar_url || '', 'avatar'),
|
|
||||||
]);
|
|
||||||
return { ...item, cover_url: coverUrl, avatar_url: avatarUrl };
|
|
||||||
}));
|
|
||||||
emit('dataLoaded', resolved);
|
emit('dataLoaded', resolved);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -137,8 +137,7 @@ import {
|
|||||||
getHotRankingApi,
|
getHotRankingApi,
|
||||||
getOriginalRankingApi,
|
getOriginalRankingApi,
|
||||||
getActivityRankingApi,
|
getActivityRankingApi,
|
||||||
getActivityListApi,
|
getActivityListApi
|
||||||
getOssPresignedUrlApi
|
|
||||||
} from '@/utils/api.js';
|
} from '@/utils/api.js';
|
||||||
import {
|
import {
|
||||||
ACTIVITY_TYPES
|
ACTIVITY_TYPES
|
||||||
@ -367,19 +366,11 @@ const fetchActivityList = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取OSS图片的预签名URL
|
// 获取OSS图片URL - 直接使用后端返回的URL
|
||||||
const getOssImageUrl = async (fileName, type = 'avatar') => {
|
const getOssImageUrl = async (fileName, type = 'avatar') => {
|
||||||
if (!fileName || fileName === '') return;
|
if (!fileName || fileName === '') return;
|
||||||
try {
|
// 直接使用后端返回的图片URL
|
||||||
const response = await getOssPresignedUrlApi(fileName, 3600, type);
|
return fileName;
|
||||||
if (response && response.code === 200 && response.data && response.data.url) {
|
|
||||||
return response.data.url;
|
|
||||||
}
|
|
||||||
return fileName; // 如果获取失败,返回原始文件名
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get OSS presigned URL:', error);
|
|
||||||
return fileName; // 如果出错,返回原始文件名
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 转换活动排名数据
|
// 转换活动排名数据
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<image class="bg-image" src="/static/square/beijingban.png" mode="aspectFill"></image>
|
<image class="bg-image" src="/static/square/beijingban.png" mode="aspectFill"></image>
|
||||||
|
|
||||||
<!-- 顶部导航 -->
|
<!-- 顶部导航 -->
|
||||||
<view class="nav-bar ">
|
<view class="nav-bar">
|
||||||
<view class="nav-back" @tap="goBack">
|
<view class="nav-back" @tap="goBack">
|
||||||
<image class="nav-back-icon" src="/static/icon/back.png" mode="aspectFit"></image>
|
<image class="nav-back-icon" src="/static/icon/back.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
@ -51,7 +51,9 @@
|
|||||||
|
|
||||||
<!-- 空状态占位:显示剩余空展位卡片 -->
|
<!-- 空状态占位:显示剩余空展位卡片 -->
|
||||||
<view v-if="exhibitionWorks.length < 2" class="empty-exhibition">
|
<view v-if="exhibitionWorks.length < 2" class="empty-exhibition">
|
||||||
<view class="empty-card empty-card-left" @tap="openAssetSelector(0)">
|
<!-- 根据已展出数量决定显示几个空卡片 -->
|
||||||
|
<view v-if="exhibitionWorks.length === 0" class="empty-card empty-card-left"
|
||||||
|
@tap="openAssetSelector(0)">
|
||||||
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
|
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
|
||||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png"
|
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png"
|
||||||
mode="aspectFill"></image>
|
mode="aspectFill"></image>
|
||||||
@ -83,7 +85,7 @@
|
|||||||
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
||||||
@tap="goToAssetDetail(item.id)">
|
@tap="goToAssetDetail(item.id)">
|
||||||
<!-- 排名图标,绝对定位在卡片左侧 -->
|
<!-- 排名图标,绝对定位在卡片左侧 -->
|
||||||
<image v-if="index < 3" :src="rankIcons[index]" class="rank-icon-img" mode="aspectFit"></image>
|
<!-- <image v-if="index < 3" :src="rankIcons[index]" class="rank-icon-img" mode="aspectFit"></image> -->
|
||||||
|
|
||||||
<!-- 卡片主体 -->
|
<!-- 卡片主体 -->
|
||||||
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||||
@ -424,7 +426,7 @@ onShow(() => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-block {
|
.section-block{
|
||||||
background: rgb(249 159 192 / 45%);
|
background: rgb(249 159 192 / 45%);
|
||||||
border-radius: 48rpx;
|
border-radius: 48rpx;
|
||||||
padding: 16rpx;
|
padding: 16rpx;
|
||||||
|
|||||||
@ -263,9 +263,9 @@ import { useStore } from 'vuex';
|
|||||||
import { onReady } from "@dcloudio/uni-app";
|
import { onReady } from "@dcloudio/uni-app";
|
||||||
import Header from '../components/Header.vue';
|
import Header from '../components/Header.vue';
|
||||||
import Avatar from '../components/Avatar.vue';
|
import Avatar from '../components/Avatar.vue';
|
||||||
import { getUserProfileApi, deleteAccountApi, updateNicknameApi, updatePasswordApi, getFanIdentitiesApi, addFanIdentityApi, getMyFanIdentitiesApi, switchFanIdentityApi, getOssSignatureApi, updateAvatarApi, getOssPresignedUrlApi } from '@/utils/api';
|
import { getUserProfileApi, deleteAccountApi, updateNicknameApi, updatePasswordApi, getFanIdentitiesApi, addFanIdentityApi, getMyFanIdentitiesApi, switchFanIdentityApi, getOssSignatureApi, updateAvatarApi } from '@/utils/api';
|
||||||
import { validateNickname } from '@/utils/validator.js';
|
import { validateNickname } from '@/utils/validator.js';
|
||||||
import { clearAvatarCache, downloadAndCacheAvatar } from '@/utils/avatarCache';
|
import { clearAvatarCache } from '@/utils/avatarCache';
|
||||||
import GuideListModal from '@/components/GuideListModal.vue';
|
import GuideListModal from '@/components/GuideListModal.vue';
|
||||||
import GuideOverlay from '@/components/GuideOverlay.vue';
|
import GuideOverlay from '@/components/GuideOverlay.vue';
|
||||||
import {
|
import {
|
||||||
@ -1032,22 +1032,9 @@ const closeAvatarModal = () => {
|
|||||||
showAvatarModal.value = false;
|
showAvatarModal.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理头像更新后的操作(下载缓存、更新本地存储、刷新UI)
|
// 处理头像更新后的操作(更新本地存储、刷新UI)
|
||||||
const handleAvatarUpdateSuccess = async (newAvatarUrl) => {
|
const handleAvatarUpdateSuccess = async (newAvatarUrl) => {
|
||||||
try {
|
// 1. 更新本地缓存
|
||||||
// 1. 获取新头像的真实URL并下载缓存
|
|
||||||
const fileName = 'avatar.png';
|
|
||||||
const urlRes = await getOssPresignedUrlApi(fileName, 3600, 'avatar');
|
|
||||||
if (urlRes.code === 200 && urlRes.data && urlRes.data.url) {
|
|
||||||
// 立即下载并缓存新头像
|
|
||||||
await downloadAndCacheAvatar(newAvatarUrl, urlRes.data.url);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('预下载头像失败:', error);
|
|
||||||
// 不影响主流程,继续
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 更新本地缓存
|
|
||||||
userAvatarUrl.value = newAvatarUrl;
|
userAvatarUrl.value = newAvatarUrl;
|
||||||
const userStr = uni.getStorageSync('user');
|
const userStr = uni.getStorageSync('user');
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
|
|||||||
@ -33,16 +33,17 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
defineProps({
|
||||||
modelValue: { type: String, default: 'hot' }
|
modelValue: { type: String, default: 'new' }
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['update:modelValue'])
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'hot', label: '人气王者', emoji: null, icon: '/static/square/rementubiao.png', iconWidth: 104, iconHeight: 104 },
|
{ key: 'hot', label: '人气王者', emoji: null, icon: '/static/square/rementubiao.png', iconWidth: 104, iconHeight: 104 },
|
||||||
{ key: 'xingka', label: '潜力之星', emoji: null, icon: '/static/square/xingka.png', iconWidth: 80, iconHeight: 80 },
|
{ key: 'new', label: '新鲜上架', emoji: null, icon: '/static/square/baji.png', iconWidth: 80, iconHeight: 80 },
|
||||||
{ key: 'baji', label: '新鲜上架', emoji: null, icon: '/static/square/baji.png', iconWidth: 80, iconHeight: 80 },
|
{ key: 'potential', label: '潜力之星', emoji: null, icon: '/static/square/xingka.png', iconWidth: 80, iconHeight: 80 },
|
||||||
{ key: 'haibao', label: '随机寻宝', emoji: null, icon: '/static/square/haibao.png', iconWidth: 96, iconHeight: 96 },]
|
{ key: 'random', label: '随机寻宝', emoji: null, icon: '/static/square/haibao.png', iconWidth: 96, iconHeight: 96 },
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -66,7 +66,8 @@ const props = defineProps({
|
|||||||
screenHeight: { type: Number, default: 812 },
|
screenHeight: { type: Number, default: 812 },
|
||||||
bannerBottom: { type: Number, default: 200 },
|
bannerBottom: { type: Number, default: 200 },
|
||||||
useMockData: { type: Boolean, default: false }, // 是否使用模拟数据
|
useMockData: { type: Boolean, default: false }, // 是否使用模拟数据
|
||||||
category: { type: String, default: 'hot' }, // 当前分类
|
category: { type: String, default: '' }, // 当前分类
|
||||||
|
isActive: { type: Boolean, default: true }, // 是否处于激活状态
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['cardClick'])
|
const emit = defineEmits(['cardClick'])
|
||||||
@ -91,6 +92,8 @@ let currentScrollLeft = 0
|
|||||||
let idCounter = 0
|
let idCounter = 0
|
||||||
let isComponentMounted = false // 标记组件是否已卸载
|
let isComponentMounted = false // 标记组件是否已卸载
|
||||||
let mockDataOffset = 0 // 模拟数据循环偏移量
|
let mockDataOffset = 0 // 模拟数据循环偏移量
|
||||||
|
let appendFailed = false // 标记追加是否已失败
|
||||||
|
let isInitialLoading = true // 标记是否在初始加载中
|
||||||
|
|
||||||
// ========== RAF 兼容 ==========
|
// ========== RAF 兼容 ==========
|
||||||
const rafFn = (cb) => {
|
const rafFn = (cb) => {
|
||||||
@ -112,17 +115,26 @@ let appendTimer = null // 防抖定时器
|
|||||||
|
|
||||||
const startAutoScroll = () => {
|
const startAutoScroll = () => {
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (rafId) cancelAnimationFrame(rafId)
|
// 确保没有重复的 RAF 在运行
|
||||||
|
if (rafId) {
|
||||||
|
cafFn(rafId)
|
||||||
|
rafId = null
|
||||||
|
}
|
||||||
|
// 重置滚动状态
|
||||||
|
userInteracting = false
|
||||||
|
|
||||||
let lastScrollLeft = 0 // 用于检测手动滚动
|
let lastScrollLeft = 0 // 用于检测手动滚动
|
||||||
|
|
||||||
const step = () => {
|
const step = () => {
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
rafId = null
|
rafId = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 每次 RAF 循环开始时也检查一次
|
||||||
|
if (!isComponentMounted) {
|
||||||
|
rafId = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +155,8 @@ const startAutoScroll = () => {
|
|||||||
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
||||||
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
||||||
// 直接调用 appendMore(不需要防抖)
|
// 直接调用 appendMore(不需要防抖)
|
||||||
if (!isLoadingMore) {
|
// 注意:真实接口模式下不应该自动追加数据,由 has_more 控制
|
||||||
|
if (!isLoadingMore && !appendFailed && !isInitialLoading && props.useMockData) {
|
||||||
appendMore()
|
appendMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,14 +191,14 @@ const pauseForUser = () => {
|
|||||||
|
|
||||||
// 防抖:延迟 500ms 后再执行追加,避免频繁调用
|
// 防抖:延迟 500ms 后再执行追加,避免频繁调用
|
||||||
const scheduleAppend = () => {
|
const scheduleAppend = () => {
|
||||||
if (!isComponentMounted) return
|
if (!isComponentMounted || appendFailed) return
|
||||||
if (appendTimer) clearTimeout(appendTimer)
|
if (appendTimer) clearTimeout(appendTimer)
|
||||||
appendTimer = setTimeout(() => {
|
appendTimer = setTimeout(() => {
|
||||||
appendTimer = null
|
appendTimer = null
|
||||||
if (isComponentMounted) {
|
if (isComponentMounted && !appendFailed) {
|
||||||
appendMore()
|
appendMore()
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 800)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 触摸 / 滚动事件 ==========
|
// ========== 触摸 / 滚动事件 ==========
|
||||||
@ -198,7 +211,10 @@ const onScroll = (e) => {
|
|||||||
// 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加
|
// 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加
|
||||||
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
||||||
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
||||||
scheduleAppend()
|
// 注意:真实接口模式下不应该自动追加数据,由 has_more 控制
|
||||||
|
if (!isLoadingMore && !appendFailed && !isInitialLoading && props.useMockData) {
|
||||||
|
scheduleAppend()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +236,7 @@ const scrollStyle = computed(() => ({
|
|||||||
// 1. 后端传 span:直接按 span 分组成列
|
// 1. 后端传 span:直接按 span 分组成列
|
||||||
// 2. 后端不传 span:使用分类特定的 span 阈值计算
|
// 2. 后端不传 span:使用分类特定的 span 阈值计算
|
||||||
class WaterfallLayout {
|
class WaterfallLayout {
|
||||||
constructor(containerH, category = 'hot', gap = GAP) {
|
constructor(containerH, category = 'random', gap = GAP) {
|
||||||
this.containerH = containerH
|
this.containerH = containerH
|
||||||
this.category = category
|
this.category = category
|
||||||
this.gap = gap
|
this.gap = gap
|
||||||
@ -408,15 +424,9 @@ const randomLikes = () => {
|
|||||||
// 本地素材图,循环使用
|
// 本地素材图,循环使用
|
||||||
const MOCK_IMAGES = Array.from({ length: 16 }, (_, i) => `/static/sucai/image-${String(i + 1).padStart(2, '0')}.png`)
|
const MOCK_IMAGES = Array.from({ length: 16 }, (_, i) => `/static/sucai/image-${String(i + 1).padStart(2, '0')}.png`)
|
||||||
|
|
||||||
const mapUser = async (u) => {
|
const mapUser = (u) => {
|
||||||
// 优先用后端图,没有则用本地素材循环
|
// 直接使用后端返回的图片URL
|
||||||
let coverUrl = MOCK_IMAGES[idCounter % MOCK_IMAGES.length]
|
let coverUrl = u.cover_url || MOCK_IMAGES[idCounter % MOCK_IMAGES.length]
|
||||||
if (u.cover_url) {
|
|
||||||
try {
|
|
||||||
const r = await getOssPresignedUrlApi(u.cover_url, 3600, 'asset')
|
|
||||||
if (r?.code === 200 && r.data?.url) coverUrl = r.data.url
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
// 后端可以返回 span 字段(1~4)来控制卡片大小,不传则前端随机模板决定
|
// 后端可以返回 span 字段(1~4)来控制卡片大小,不传则前端随机模板决定
|
||||||
return {
|
return {
|
||||||
id: idCounter++,
|
id: idCounter++,
|
||||||
@ -451,6 +461,7 @@ const loadUsers = async () => {
|
|||||||
|
|
||||||
// 切换分类时重置偏移量
|
// 切换分类时重置偏移量
|
||||||
mockDataOffset = 0
|
mockDataOffset = 0
|
||||||
|
isLoadingMore = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let items
|
let items
|
||||||
@ -465,7 +476,7 @@ const loadUsers = async () => {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 使用真实API
|
// 使用真实API
|
||||||
const res = await getInspirationFlowApi({ limit: 40, type: props.category })
|
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
||||||
|
|
||||||
if (!isComponentMounted) return
|
if (!isComponentMounted) return
|
||||||
if (res.code === 200 && res.data?.items) {
|
if (res.code === 200 && res.data?.items) {
|
||||||
@ -498,18 +509,9 @@ const loadUsers = async () => {
|
|||||||
|
|
||||||
const appendMore = async () => {
|
const appendMore = async () => {
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isLoadingMore) {
|
if (isLoadingMore) {
|
||||||
|
|
||||||
// 等待一下再试
|
|
||||||
setTimeout(() => {
|
|
||||||
if (isComponentMounted && !isLoadingMore) {
|
|
||||||
|
|
||||||
appendMore()
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isLoadingMore = true
|
isLoadingMore = true
|
||||||
@ -522,8 +524,6 @@ const appendMore = async () => {
|
|||||||
const allItems = mockData.items
|
const allItems = mockData.items
|
||||||
const batchSize = 20
|
const batchSize = 20
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 从 mockDataOffset 位置开始取 batchSize 个
|
// 从 mockDataOffset 位置开始取 batchSize 个
|
||||||
const itemsToAdd = []
|
const itemsToAdd = []
|
||||||
for (let i = 0; i < batchSize; i++) {
|
for (let i = 0; i < batchSize; i++) {
|
||||||
@ -542,18 +542,22 @@ const appendMore = async () => {
|
|||||||
mockDataOffset = mockDataOffset + batchSize
|
mockDataOffset = mockDataOffset + batchSize
|
||||||
|
|
||||||
items = itemsToAdd
|
items = itemsToAdd
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 使用真实API
|
// 使用真实API
|
||||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
||||||
if (!isComponentMounted) return
|
if (!isComponentMounted) return
|
||||||
if (res.code === 200 && res.data?.items) {
|
if (res.code === 200 && res.data?.items) {
|
||||||
items = res.data.items
|
items = res.data.items
|
||||||
|
// 真实接口根据 has_more 决定是否继续加载
|
||||||
|
if (!res.data.has_more) {
|
||||||
|
appendFailed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
|
|
||||||
const withData = items.map((item) => {
|
const withData = items.map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.asset_id, // 直接使用 asset_id(已经在上面保证唯一)
|
id: item.asset_id, // 直接使用 asset_id(已经在上面保证唯一)
|
||||||
@ -564,17 +568,20 @@ const appendMore = async () => {
|
|||||||
span: item.span ?? null,
|
span: item.span ?? null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const placed = layout.addCards(withData)
|
const placed = layout.addCards(withData)
|
||||||
|
|
||||||
cards.value = [...cards.value, ...placed]
|
cards.value = [...cards.value, ...placed]
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 没有可追加的数据,标记失败停止
|
||||||
|
appendFailed = true
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
||||||
|
appendFailed = true
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
isLoadingMore = false
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -615,20 +622,57 @@ const handleCardClick = (card) => {
|
|||||||
// ========== 初始化 ==========
|
// ========== 初始化 ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isComponentMounted = true
|
isComponentMounted = true
|
||||||
const containerH = props.screenHeight - props.bannerBottom
|
isInitialLoading = true
|
||||||
layout = new WaterfallLayout(containerH, props.category)
|
appendFailed = false
|
||||||
|
userInteracting = false
|
||||||
currentScrollLeft = 0
|
currentScrollLeft = 0
|
||||||
scrollLeft.value = 0
|
scrollLeft.value = 0
|
||||||
|
const containerH = props.screenHeight - props.bannerBottom
|
||||||
|
layout = new WaterfallLayout(containerH, props.category)
|
||||||
loadUsers().then(() => {
|
loadUsers().then(() => {
|
||||||
|
isInitialLoading = false
|
||||||
nextTick(() => startAutoScroll())
|
nextTick(() => startAutoScroll())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听页面可见性变化
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (!isComponentMounted) return
|
||||||
|
if (document.hidden) {
|
||||||
|
userInteracting = true
|
||||||
|
} else {
|
||||||
|
userInteracting = false
|
||||||
|
if (!rafId) {
|
||||||
|
startAutoScroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 isActive 属性变化(父组件控制)
|
||||||
|
watch(() => props.isActive, (active) => {
|
||||||
|
if (!isComponentMounted) return
|
||||||
|
if (active) {
|
||||||
|
userInteracting = false
|
||||||
|
if (!rafId) {
|
||||||
|
startAutoScroll()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userInteracting = true
|
||||||
|
}
|
||||||
|
}, { immediate: false })
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
isComponentMounted = false
|
isComponentMounted = false
|
||||||
stopAutoScroll()
|
stopAutoScroll()
|
||||||
clearTimeout(resumeTimer)
|
clearTimeout(resumeTimer)
|
||||||
clearTimeout(appendTimer)
|
clearTimeout(appendTimer)
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => [props.screenHeight, props.bannerBottom], () => {
|
watch(() => [props.screenHeight, props.bannerBottom], () => {
|
||||||
@ -662,6 +706,8 @@ watch(() => props.category, (newCategory) => {
|
|||||||
allUsers.value = []
|
allUsers.value = []
|
||||||
totalWidth.value = 0
|
totalWidth.value = 0
|
||||||
mockDataOffset = 0
|
mockDataOffset = 0
|
||||||
|
appendFailed = false
|
||||||
|
isInitialLoading = false
|
||||||
|
|
||||||
// 先停止 RAF,确保在 loadUsers 完成前不会有滚动逻辑
|
// 先停止 RAF,确保在 loadUsers 完成前不会有滚动逻辑
|
||||||
if (rafId) {
|
if (rafId) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { getActivityListApi, getOssPresignedUrlApi } from '@/utils/api.js'
|
import { getActivityListApi } from '@/utils/api.js'
|
||||||
|
|
||||||
export function useBanner() {
|
export function useBanner() {
|
||||||
const bannerActivities = ref([])
|
const bannerActivities = ref([])
|
||||||
@ -8,24 +8,11 @@ export function useBanner() {
|
|||||||
try {
|
try {
|
||||||
const starId = uni.getStorageSync('star_id') || null
|
const starId = uni.getStorageSync('star_id') || null
|
||||||
const res = await getActivityListApi(starId, 1, 10)
|
const res = await getActivityListApi(starId, 1, 10)
|
||||||
|
|
||||||
if (res.code === 200 && res.data?.activities) {
|
if (res.code === 200 && res.data?.activities) {
|
||||||
const activities = res.data.activities
|
const activities = res.data.activities
|
||||||
// 并行获取所有 OSS URL
|
// 直接使用后端返回的图片URL
|
||||||
const urlPromises = activities.map(async (item) => {
|
bannerActivities.value = activities
|
||||||
if (!item.cover_image) return { item, url: null }
|
|
||||||
try {
|
|
||||||
const r = await getOssPresignedUrlApi(item.cover_image, 3600, 'avatar')
|
|
||||||
return { item, url: r?.code === 200 && r.data?.url ? r.data.url : null }
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[useBanner] 获取 OSS URL 失败', e)
|
|
||||||
return { item, url: null }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const results = await Promise.all(urlPromises)
|
|
||||||
bannerActivities.value = results.map(({ item, url }) =>
|
|
||||||
url ? { ...item, cover_image: url } : item
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)
|
console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// ========== 模拟数据配置 ==========
|
// ========== 模拟数据配置 ==========
|
||||||
|
|
||||||
// 是否使用模拟数据(开发调试时设为 true,上线后改为 false)
|
// 是否使用模拟数据(开发调试时设为 true,上线后改为 false)
|
||||||
export const USE_MOCK_DATA = true
|
export const USE_MOCK_DATA = false
|
||||||
|
|
||||||
// 模拟图片列表
|
// 模拟图片列表
|
||||||
const MOCK_IMAGES = [
|
const MOCK_IMAGES = [
|
||||||
@ -43,8 +43,16 @@ export const SPAN_CONFIG = {
|
|||||||
{ max: Infinity, span: 4 }, // 10w+ → span 4
|
{ max: Infinity, span: 4 }, // 10w+ → span 4
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// 新鲜上架:低点赞,小卡片为主
|
||||||
|
new: {
|
||||||
|
thresholds: [
|
||||||
|
{ max: 200, span: 1 }, // 200 以下 → span 1
|
||||||
|
{ max: 500, span: 2 }, // 200-500 → span 2
|
||||||
|
{ max: Infinity, span: 3 },// 500+ → span 3
|
||||||
|
]
|
||||||
|
},
|
||||||
// 潜力之星:中等点赞,中小卡片
|
// 潜力之星:中等点赞,中小卡片
|
||||||
xingka: {
|
potential: {
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{ max: 5000, span: 1 }, // 5k 以下 → span 1
|
{ max: 5000, span: 1 }, // 5k 以下 → span 1
|
||||||
{ max: 10000, span: 2 }, // 5k-1w → span 2
|
{ max: 10000, span: 2 }, // 5k-1w → span 2
|
||||||
@ -52,16 +60,8 @@ export const SPAN_CONFIG = {
|
|||||||
{ max: Infinity, span: 4 },// 1.5w+ → span 4
|
{ max: Infinity, span: 4 },// 1.5w+ → span 4
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 新鲜上架:低点赞,小卡片为主
|
|
||||||
baji: {
|
|
||||||
thresholds: [
|
|
||||||
{ max: 200, span: 1 }, // 200 以下 → span 1
|
|
||||||
{ max: 500, span: 2 }, // 200-500 → span 2
|
|
||||||
{ max: Infinity, span: 3 },// 500+ → span 3
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 随机寻宝:混合
|
// 随机寻宝:混合
|
||||||
haibao: {
|
random: {
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{ max: 10000, span: 1 }, // 1w 以下 → span 1
|
{ max: 10000, span: 1 }, // 1w 以下 → span 1
|
||||||
{ max: 30000, span: 2 }, // 1w-3w → span 2
|
{ max: 30000, span: 2 }, // 1w-3w → span 2
|
||||||
@ -95,42 +95,12 @@ export const MOCK_RENQIWANG = {
|
|||||||
{ asset_id: 10009, name: '粉红泡泡', cover_url: MOCK_IMAGES[8], like_count: 45600, owner_nickname: '小迷糊' },
|
{ asset_id: 10009, name: '粉红泡泡', cover_url: MOCK_IMAGES[8], like_count: 45600, owner_nickname: '小迷糊' },
|
||||||
{ asset_id: 10010, name: '爱的力量', cover_url: MOCK_IMAGES[9], like_count: 42300, owner_nickname: '小幸运' },
|
{ asset_id: 10010, name: '爱的力量', cover_url: MOCK_IMAGES[9], like_count: 42300, owner_nickname: '小幸运' },
|
||||||
{ asset_id: 10011, name: '璀璨星河', cover_url: MOCK_IMAGES[10], like_count: 39800, owner_nickname: '小浪漫' },
|
{ asset_id: 10011, name: '璀璨星河', cover_url: MOCK_IMAGES[10], like_count: 39800, owner_nickname: '小浪漫' },
|
||||||
// { asset_id: 10012, name: '甜蜜日常', cover_url: MOCK_IMAGES[11], like_count: 37500, owner_nickname: '小清新' },
|
|
||||||
// { asset_id: 10013, name: '爱心发射', cover_url: MOCK_IMAGES[12], like_count: 35200, owner_nickname: '小活力' },
|
|
||||||
// { asset_id: 10014, name: '超级喜欢', cover_url: MOCK_IMAGES[13], like_count: 32900, owner_nickname: '小呆萌' },
|
|
||||||
// { asset_id: 10015, name: '温暖拥抱', cover_url: MOCK_IMAGES[14], like_count: 30600, owner_nickname: '小棉花' },
|
|
||||||
// { asset_id: 10016, name: '今日份心动', cover_url: MOCK_IMAGES[15], like_count: 28500, owner_nickname: '小牛奶' },
|
|
||||||
],
|
],
|
||||||
cursor: 'renqiwang_cursor_001',
|
cursor: 'renqiwang_cursor_001',
|
||||||
has_more: true,
|
has_more: true,
|
||||||
session_id: 'renqiwang_session',
|
session_id: 'renqiwang_session',
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 潜力之星 - 中等点赞有潜力的作品 ==========
|
|
||||||
export const MOCK_QIANLIXING = {
|
|
||||||
items: [
|
|
||||||
{ asset_id: 20001, name: '初露锋芒', cover_url: MOCK_IMAGES[0], like_count: 12800, owner_nickname: '小新芽' },
|
|
||||||
{ asset_id: 20002, name: '蓄势待发', cover_url: MOCK_IMAGES[1], like_count: 11500, owner_nickname: '小嫩草' },
|
|
||||||
{ asset_id: 20003, name: '冉冉升起', cover_url: MOCK_IMAGES[2], like_count: 10200, owner_nickname: '小泡泡' },
|
|
||||||
{ asset_id: 20004, name: '明日之星', cover_url: MOCK_IMAGES[3], like_count: 9800, owner_nickname: '小火苗' },
|
|
||||||
{ asset_id: 20005, name: '潜力无限', cover_url: MOCK_IMAGES[4], like_count: 8900, owner_nickname: '小萌芽' },
|
|
||||||
{ asset_id: 20006, name: '闪耀新星', cover_url: MOCK_IMAGES[5], like_count: 8200, owner_nickname: '小水滴' },
|
|
||||||
{ asset_id: 20007, name: '小荷才露', cover_url: MOCK_IMAGES[6], like_count: 7600, owner_nickname: '小竹笋' },
|
|
||||||
{ asset_id: 20008, name: '锋芒初现', cover_url: MOCK_IMAGES[7], like_count: 7100, owner_nickname: '小鸽子' },
|
|
||||||
{ asset_id: 20009, name: '闪闪发光', cover_url: MOCK_IMAGES[8], like_count: 6500, owner_nickname: '小萤火' },
|
|
||||||
{ asset_id: 20010, name: '未来可期', cover_url: MOCK_IMAGES[9], like_count: 5900, owner_nickname: '小芽芽' },
|
|
||||||
{ asset_id: 20011, name: '新秀登场', cover_url: MOCK_IMAGES[10], like_count: 5400, owner_nickname: '小藤蔓' },
|
|
||||||
// { asset_id: 20012, name: '蒸蒸日上', cover_url: MOCK_IMAGES[11], like_count: 4900, owner_nickname: '小葵花' },
|
|
||||||
// { asset_id: 20013, name: '茁壮成长', cover_url: MOCK_IMAGES[12], like_count: 4500, owner_nickname: '小苗苗' },
|
|
||||||
// { asset_id: 20014, name: '初绽光芒', cover_url: MOCK_IMAGES[13], like_count: 4100, owner_nickname: '小花花' },
|
|
||||||
// { asset_id: 20015, name: '星火燎原', cover_url: MOCK_IMAGES[14], like_count: 3800, owner_nickname: '小豆芽' },
|
|
||||||
// { asset_id: 20016, name: '蓄力中...', cover_url: MOCK_IMAGES[15], like_count: 3500, owner_nickname: '小冰晶' },
|
|
||||||
],
|
|
||||||
cursor: 'qianlixing_cursor_001',
|
|
||||||
has_more: true,
|
|
||||||
session_id: 'qianlixing_session',
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 新鲜上架 - 新发布作品,点赞较低 ==========
|
// ========== 新鲜上架 - 新发布作品,点赞较低 ==========
|
||||||
export const MOCK_XINXIANSHANG = {
|
export const MOCK_XINXIANSHANG = {
|
||||||
items: [
|
items: [
|
||||||
@ -145,17 +115,32 @@ export const MOCK_XINXIANSHANG = {
|
|||||||
{ asset_id: 30009, name: '新鲜出炉', cover_url: MOCK_IMAGES[8], like_count: 98, owner_nickname: '小创作者' },
|
{ asset_id: 30009, name: '新鲜出炉', cover_url: MOCK_IMAGES[8], like_count: 98, owner_nickname: '小创作者' },
|
||||||
{ asset_id: 30010, name: '首发作品', cover_url: MOCK_IMAGES[9], like_count: 267, owner_nickname: '小练手' },
|
{ asset_id: 30010, name: '首发作品', cover_url: MOCK_IMAGES[9], like_count: 267, owner_nickname: '小练手' },
|
||||||
{ asset_id: 30011, name: '全新上线', cover_url: MOCK_IMAGES[10], like_count: 189, owner_nickname: '新起步' },
|
{ asset_id: 30011, name: '全新上线', cover_url: MOCK_IMAGES[10], like_count: 189, owner_nickname: '新起步' },
|
||||||
// { asset_id: 30012, name: '今日份新', cover_url: MOCK_IMAGES[11], like_count: 156, owner_nickname: '小萌娃' },
|
|
||||||
// { asset_id: 30013, name: '新新人类', cover_url: MOCK_IMAGES[12], like_count: 223, owner_nickname: '小新潮' },
|
|
||||||
// { asset_id: 30014, name: '新鲜血液', cover_url: MOCK_IMAGES[13], like_count: 134, owner_nickname: '小白白' },
|
|
||||||
// { asset_id: 30015, name: '新晋选手', cover_url: MOCK_IMAGES[14], like_count: 278, owner_nickname: '小小新' },
|
|
||||||
// { asset_id: 30016, name: '首发新动态', cover_url: MOCK_IMAGES[15], like_count: 201, owner_nickname: '小清新' },
|
|
||||||
],
|
],
|
||||||
cursor: 'xinxianshang_cursor_001',
|
cursor: 'xinxianshang_cursor_001',
|
||||||
has_more: true,
|
has_more: true,
|
||||||
session_id: 'xinxianshang_session',
|
session_id: 'xinxianshang_session',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 潜力之星 - 中等点赞有潜力的作品 ==========
|
||||||
|
export const MOCK_QIANLIXING = {
|
||||||
|
items: [
|
||||||
|
{ asset_id: 20001, name: '初露锋芒', cover_url: MOCK_IMAGES[0], like_count: 12800, owner_nickname: '小新芽' },
|
||||||
|
{ asset_id: 20002, name: '蓄势待发', cover_url: MOCK_IMAGES[1], like_count: 11500, owner_nickname: '小嫩草' },
|
||||||
|
{ asset_id: 20003, name: '冉冉升起', cover_url: MOCK_IMAGES[2], like_count: 10200, owner_nickname: '小泡泡' },
|
||||||
|
{ asset_id: 20004, name: '明日之星', cover_url: MOCK_IMAGES[3], like_count: 9800, owner_nickname: '小火苗' },
|
||||||
|
{ asset_id: 20005, name: '潜力无限', cover_url: MOCK_IMAGES[4], like_count: 8900, owner_nickname: '小萌芽' },
|
||||||
|
{ asset_id: 20006, name: '闪耀新星', cover_url: MOCK_IMAGES[5], like_count: 8200, owner_nickname: '小水滴' },
|
||||||
|
{ asset_id: 20007, name: '小荷才露', cover_url: MOCK_IMAGES[6], like_count: 7600, owner_nickname: '小竹笋' },
|
||||||
|
{ asset_id: 20008, name: '锋芒初现', cover_url: MOCK_IMAGES[7], like_count: 7100, owner_nickname: '小鸽子' },
|
||||||
|
{ asset_id: 20009, name: '闪闪发光', cover_url: MOCK_IMAGES[8], like_count: 6500, owner_nickname: '小萤火' },
|
||||||
|
{ asset_id: 20010, name: '未来可期', cover_url: MOCK_IMAGES[9], like_count: 5900, owner_nickname: '小芽芽' },
|
||||||
|
{ asset_id: 20011, name: '新秀登场', cover_url: MOCK_IMAGES[10], like_count: 5400, owner_nickname: '小藤蔓' },
|
||||||
|
],
|
||||||
|
cursor: 'qianlixing_cursor_001',
|
||||||
|
has_more: true,
|
||||||
|
session_id: 'qianlixing_session',
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 随机寻宝 - 随机混合数据 ==========
|
// ========== 随机寻宝 - 随机混合数据 ==========
|
||||||
export const MOCK_SUIJIXUNBAO = {
|
export const MOCK_SUIJIXUNBAO = {
|
||||||
items: [
|
items: [
|
||||||
@ -170,11 +155,6 @@ export const MOCK_SUIJIXUNBAO = {
|
|||||||
{ asset_id: 40009, name: '神秘宝藏9', cover_url: MOCK_IMAGES[8], like_count: 62000, owner_nickname: '寻宝奇兵' },
|
{ asset_id: 40009, name: '神秘宝藏9', cover_url: MOCK_IMAGES[8], like_count: 62000, owner_nickname: '寻宝奇兵' },
|
||||||
{ asset_id: 40010, name: '神秘宝藏10', cover_url: MOCK_IMAGES[9], like_count: 800, owner_nickname: '淘宝猎人' },
|
{ asset_id: 40010, name: '神秘宝藏10', cover_url: MOCK_IMAGES[9], like_count: 800, owner_nickname: '淘宝猎人' },
|
||||||
{ asset_id: 40011, name: '神秘宝藏11', cover_url: MOCK_IMAGES[10], like_count: 9500, owner_nickname: '挖宝小分队' },
|
{ asset_id: 40011, name: '神秘宝藏11', cover_url: MOCK_IMAGES[10], like_count: 9500, owner_nickname: '挖宝小分队' },
|
||||||
// { asset_id: 40012, name: '神秘宝藏12', cover_url: MOCK_IMAGES[11], like_count: 72000, owner_nickname: '淘宝小能手' },
|
|
||||||
// { asset_id: 40013, name: '神秘宝藏13', cover_url: MOCK_IMAGES[12], like_count: 350, owner_nickname: '宝藏猎人' },
|
|
||||||
// { asset_id: 40014, name: '神秘宝藏14', cover_url: MOCK_IMAGES[13], like_count: 48000, owner_nickname: '寻宝小天才' },
|
|
||||||
// { asset_id: 40015, name: '神秘宝藏15', cover_url: MOCK_IMAGES[14], like_count: 11000, owner_nickname: '淘宝小精灵' },
|
|
||||||
// { asset_id: 40016, name: '神秘宝藏16', cover_url: MOCK_IMAGES[15], like_count: 600, owner_nickname: '挖宝小专家' },
|
|
||||||
],
|
],
|
||||||
cursor: 'suijixunbao_cursor_001',
|
cursor: 'suijixunbao_cursor_001',
|
||||||
has_more: true,
|
has_more: true,
|
||||||
@ -184,9 +164,9 @@ export const MOCK_SUIJIXUNBAO = {
|
|||||||
// ========== 分类映射 ==========
|
// ========== 分类映射 ==========
|
||||||
export const MOCK_DATA_MAP = {
|
export const MOCK_DATA_MAP = {
|
||||||
hot: MOCK_RENQIWANG,
|
hot: MOCK_RENQIWANG,
|
||||||
xingka: MOCK_QIANLIXING,
|
new: MOCK_XINXIANSHANG,
|
||||||
baji: MOCK_XINXIANSHANG,
|
potential: MOCK_QIANLIXING,
|
||||||
haibao: MOCK_SUIJIXUNBAO,
|
random: MOCK_SUIJIXUNBAO,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据分类获取模拟数据
|
// 根据分类获取模拟数据
|
||||||
@ -207,13 +187,13 @@ export function generateMockItems(category = 'hot', count = 20, startId = 50000)
|
|||||||
case 'hot':
|
case 'hot':
|
||||||
like_count = 20000 + Math.floor(Math.random() * 100000) // 2w-12w
|
like_count = 20000 + Math.floor(Math.random() * 100000) // 2w-12w
|
||||||
break
|
break
|
||||||
case 'xingka':
|
case 'new':
|
||||||
like_count = 3000 + Math.floor(Math.random() * 15000) // 3k-18k
|
|
||||||
break
|
|
||||||
case 'baji':
|
|
||||||
like_count = 50 + Math.floor(Math.random() * 500) // 50-550
|
like_count = 50 + Math.floor(Math.random() * 500) // 50-550
|
||||||
break
|
break
|
||||||
case 'haibao':
|
case 'potential':
|
||||||
|
like_count = 3000 + Math.floor(Math.random() * 15000) // 3k-18k
|
||||||
|
break
|
||||||
|
case 'random':
|
||||||
like_count = Math.floor(Math.random() * 80000) // 0-8w
|
like_count = Math.floor(Math.random() * 80000) // 0-8w
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
:bannerBottom="bannerBottomPx"
|
:bannerBottom="bannerBottomPx"
|
||||||
:useMockData="USE_MOCK_DATA"
|
:useMockData="USE_MOCK_DATA"
|
||||||
:category="activeContentTab"
|
:category="activeContentTab"
|
||||||
|
:isActive="isActive"
|
||||||
@cardClick="handleCardClick"
|
@cardClick="handleCardClick"
|
||||||
class="fall-bg"
|
class="fall-bg"
|
||||||
/>
|
/>
|
||||||
@ -59,7 +60,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import Header from '../components/Header.vue'
|
import Header from '../components/Header.vue'
|
||||||
import BottomNav from '../components/BottomNav.vue'
|
import BottomNav from '../components/BottomNav.vue'
|
||||||
@ -78,9 +79,10 @@ const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname
|
|||||||
const currentStarId = ref(uni.getStorageSync('star_id') || null)
|
const currentStarId = ref(uni.getStorageSync('star_id') || null)
|
||||||
|
|
||||||
// ========== UI State ==========
|
// ========== UI State ==========
|
||||||
const activeContentTab = ref('hot')
|
const activeContentTab = ref('random')
|
||||||
const navExpanded = ref(false)
|
const navExpanded = ref(false)
|
||||||
const showRankingModal = ref(false)
|
const showRankingModal = ref(false)
|
||||||
|
const isActive = ref(true)
|
||||||
|
|
||||||
// ========== Screen Info ==========
|
// ========== Screen Info ==========
|
||||||
const screenWidth = ref(375)
|
const screenWidth = ref(375)
|
||||||
@ -163,6 +165,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
isActive.value = true
|
||||||
// 检查是否需要显示引导,如果需要则跳转到引导页面
|
// 检查是否需要显示引导,如果需要则跳转到引导页面
|
||||||
if (shouldShowGuideStartModal()) {
|
if (shouldShowGuideStartModal()) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@ -171,6 +174,10 @@ onShow(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
isActive.value = false
|
||||||
|
})
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
// 调试模式:读取 guide_debug 参数并设置存储
|
// 调试模式:读取 guide_debug 参数并设置存储
|
||||||
if (options && 'guide_debug' in options) {
|
if (options && 'guide_debug' in options) {
|
||||||
|
|||||||
@ -83,11 +83,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { onLoad, onUnload, onShow, onHide } from '@dcloudio/uni-app'
|
import { onLoad, onUnload, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
import {
|
import {
|
||||||
fetchActivityDetail,
|
fetchActivityDetail,
|
||||||
fetchActivityProgress,
|
fetchActivityProgress,
|
||||||
} from '@/utils/activity-config'
|
} from '@/utils/activity-config'
|
||||||
import { getOssPresignedUrlApi } from '@/utils/api'
|
|
||||||
import { getProgressManager, releaseProgressManager, cleanupProgressManager } from '@/utils/progress-manager'
|
import { getProgressManager, releaseProgressManager, cleanupProgressManager } from '@/utils/progress-manager'
|
||||||
import screenCache from '@/utils/screen-cache'
|
import screenCache from '@/utils/screen-cache'
|
||||||
import performanceMonitor from '@/utils/performance-monitor'
|
import performanceMonitor from '@/utils/performance-monitor'
|
||||||
@ -222,52 +221,23 @@ async function handleContribute(itemType) {
|
|||||||
function onAnimationComplete() {
|
function onAnimationComplete() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预签名 URL 缓存,同一路径只请求一次
|
// 批量转换图片路径 - 直接使用后端返回的URL
|
||||||
const presignedUrlCache = new Map()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换图片路径为OSS预签名URL
|
|
||||||
*/
|
|
||||||
async function convertImageToPresignedUrl(imagePath) {
|
|
||||||
if (!imagePath) return null
|
|
||||||
if (imagePath === '') return ''
|
|
||||||
|
|
||||||
if (presignedUrlCache.has(imagePath)) {
|
|
||||||
return presignedUrlCache.get(imagePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await getOssPresignedUrlApi(imagePath, 3600, 'avatar')
|
|
||||||
const url = response.data?.url || imagePath
|
|
||||||
presignedUrlCache.set(imagePath, url)
|
|
||||||
return url
|
|
||||||
} catch (error) {
|
|
||||||
console.error('图片URL转换失败:', imagePath, error)
|
|
||||||
return imagePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量转换图片路径
|
|
||||||
*/
|
|
||||||
async function convertActivityImages(activityData) {
|
async function convertActivityImages(activityData) {
|
||||||
try {
|
try {
|
||||||
// 并发转换所有图片
|
// 并发转换所有图片
|
||||||
const [bannerImage, coverImage, stageBackground] = await Promise.all([
|
const [bannerImage, coverImage, stageBackground] = await Promise.all([
|
||||||
convertImageToPresignedUrl(activityData.banner_image),
|
Promise.resolve(activityData.banner_image),
|
||||||
convertImageToPresignedUrl(activityData.cover_image),
|
Promise.resolve(activityData.cover_image),
|
||||||
convertImageToPresignedUrl(activityData.current_stage_background)
|
Promise.resolve(activityData.current_stage_background)
|
||||||
])
|
])
|
||||||
|
|
||||||
// 转换道具图标
|
// 转换道具图标
|
||||||
const actionItems = activityData.items || []
|
const actionItems = activityData.items || []
|
||||||
const convertedItems = await Promise.all(
|
const convertedItems = actionItems.map(item => ({
|
||||||
actionItems.map(async (item) => ({
|
...item,
|
||||||
...item,
|
icon: item.icon
|
||||||
icon: await convertImageToPresignedUrl(item.icon)
|
}))
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bannerImage,
|
bannerImage,
|
||||||
coverImage,
|
coverImage,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { loginApi, registerApi, getOssPresignedUrlApi } from '@/utils/api'
|
import { loginApi, registerApi } from '@/utils/api'
|
||||||
import { downloadAndCacheAvatar } from '@/utils/avatarCache'
|
|
||||||
import { resetAllGuides } from '@/utils/guideConfig'
|
import { resetAllGuides } from '@/utils/guideConfig'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
@ -103,31 +102,7 @@ const actions = {
|
|||||||
|
|
||||||
uni.setStorageSync('user', JSON.stringify(user))
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
commit('SET_USER_INFO', user)
|
commit('SET_USER_INFO', user)
|
||||||
|
|
||||||
// 处理头像缓存
|
|
||||||
if (user.avatar_url) {
|
|
||||||
try {
|
|
||||||
// 获取真实的预签名URL
|
|
||||||
const urlRes = await getOssPresignedUrlApi(user.avatar_url, 3600, 'avatar')
|
|
||||||
if (urlRes.code === 200 && urlRes.data && urlRes.data.url) {
|
|
||||||
// 下载并缓存头像
|
|
||||||
const localPath = await downloadAndCacheAvatar(user.avatar_url, urlRes.data.url)
|
|
||||||
|
|
||||||
// 触发头像更新事件,通知Header刷新
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.$emit('avatarUpdated', {
|
|
||||||
uid: user.uid,
|
|
||||||
avatarUrl: user.avatar_url,
|
|
||||||
localPath: localPath
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
} catch (avatarError) {
|
|
||||||
console.error('头像缓存失败,但不影响登录:', avatarError)
|
|
||||||
// 头像缓存失败不影响登录流程
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(res)
|
return Promise.resolve(res)
|
||||||
} else {
|
} else {
|
||||||
// 返回错误信息,包含 message
|
// 返回错误信息,包含 message
|
||||||
@ -162,31 +137,7 @@ const actions = {
|
|||||||
|
|
||||||
uni.setStorageSync('user', JSON.stringify(user))
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
commit('SET_USER_INFO', user)
|
commit('SET_USER_INFO', user)
|
||||||
|
|
||||||
// 处理头像缓存
|
|
||||||
if (user.avatar_url) {
|
|
||||||
try {
|
|
||||||
// 获取真实的预签名URL
|
|
||||||
const urlRes = await getOssPresignedUrlApi(user.avatar_url, 3600, 'avatar')
|
|
||||||
if (urlRes.code === 200 && urlRes.data && urlRes.data.url) {
|
|
||||||
// 下载并缓存头像
|
|
||||||
const localPath = await downloadAndCacheAvatar(user.avatar_url, urlRes.data.url)
|
|
||||||
|
|
||||||
// 触发头像更新事件,通知Header刷新
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.$emit('avatarUpdated', {
|
|
||||||
uid: user.uid,
|
|
||||||
avatarUrl: user.avatar_url,
|
|
||||||
localPath: localPath
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
} catch (avatarError) {
|
|
||||||
console.error('头像缓存失败,但不影响注册:', avatarError)
|
|
||||||
// 头像缓存失败不影响注册流程
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(res)
|
return Promise.resolve(res)
|
||||||
} else {
|
} else {
|
||||||
// 处理昵称已存在的情况
|
// 处理昵称已存在的情况
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// API 基础配置
|
// API 基础配置
|
||||||
const baseURL = 'http://101.132.250.62:8080'
|
// const baseURL = 'http://101.132.250.62:8080'
|
||||||
// const baseURL = 'http://192.168.110.60:8080'
|
// const baseURL = 'http://192.168.110.60:8080'
|
||||||
// const baseURL = 'http://localhost:8080'
|
const baseURL = 'http://localhost:8080'
|
||||||
|
|
||||||
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
||||||
const USE_MOCK_API = false
|
const USE_MOCK_API = false
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { getOssPresignedUrlApi } from './api.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从URL提取文件名
|
* 从URL提取文件名
|
||||||
* @param {String} url - URL字符串
|
* @param {String} url - URL字符串
|
||||||
@ -9,10 +7,10 @@ export function extractFileNameFromUrl(url) {
|
|||||||
if (!url || url.startsWith('/static/')) {
|
if (!url || url.startsWith('/static/')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let urlPath = url;
|
let urlPath = url;
|
||||||
|
|
||||||
// 如果是完整URL,提取路径部分
|
// 如果是完整URL,提取路径部分
|
||||||
if (url.includes('://')) {
|
if (url.includes('://')) {
|
||||||
// 找到协议后的第一个 / 开始的路径
|
// 找到协议后的第一个 / 开始的路径
|
||||||
@ -22,7 +20,7 @@ export function extractFileNameFromUrl(url) {
|
|||||||
urlPath = url.substring(pathStartIndex);
|
urlPath = url.substring(pathStartIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取最后一个 / 之后的内容作为文件名
|
// 提取最后一个 / 之后的内容作为文件名
|
||||||
const fileName = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
const fileName = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
||||||
return fileName || null;
|
return fileName || null;
|
||||||
@ -66,7 +64,7 @@ export function extractOssObjectPath(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取藏品封面的真实URL
|
* 获取藏品封面的真实URL - 直接使用后端返回的URL
|
||||||
* @param {String} coverUrl - 后端返回的cover_url
|
* @param {String} coverUrl - 后端返回的cover_url
|
||||||
* @returns {Promise<String>} 真实可访问的URL
|
* @returns {Promise<String>} 真实可访问的URL
|
||||||
*/
|
*/
|
||||||
@ -79,63 +77,21 @@ export async function getAssetCoverRealUrl(coverUrl) {
|
|||||||
return coverUrl || DEFAULT_IMAGE;
|
return coverUrl || DEFAULT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// 直接使用后端返回的 URL
|
||||||
// 提取完整OSS对象路径(保留 asset/13/87/filename.jpg 这样的前缀)
|
return coverUrl;
|
||||||
let objectPath = extractOssObjectPath(coverUrl);
|
|
||||||
if (!objectPath) {
|
|
||||||
console.warn('无法提取OSS对象路径,使用默认图片');
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL解码(处理 %2F -> / 等编码)
|
|
||||||
let decodedPath = decodeURIComponent(objectPath);
|
|
||||||
|
|
||||||
// 去掉 query string(?及其后面的内容)
|
|
||||||
const queryIndex = decodedPath.indexOf('?');
|
|
||||||
if (queryIndex !== -1) {
|
|
||||||
decodedPath = decodedPath.substring(0, queryIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 调用API获取预签名URL(type='asset')
|
|
||||||
const res = await getOssPresignedUrlApi(decodedPath, 3600, 'asset');
|
|
||||||
|
|
||||||
if (res.code === 200 && res.data && res.data.url) {
|
|
||||||
return res.data.url;
|
|
||||||
} else {
|
|
||||||
console.error('获取OSS预签名URL失败:', res.message);
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取藏品封面URL失败:', error);
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取好友头像的真实URL
|
* 获取好友头像的真实URL - 直接使用后端返回的URL
|
||||||
* @param {String} avatarUrl - 后端返回的avatar_url(完整URL)
|
* @param {String} avatarUrl - 后端返回的avatar_url(完整URL)
|
||||||
* @returns {Promise<String>} 真实可访问的URL,失败返回空字符串(让Avatar组件使用默认头像)
|
* @returns {Promise<String>} 真实可访问的URL,失败返回空字符串
|
||||||
*/
|
*/
|
||||||
export async function getFriendAvatarRealUrl(avatarUrl) {
|
export async function getFriendAvatarRealUrl(avatarUrl) {
|
||||||
|
|
||||||
// 如果是本地静态资源或为空,直接返回
|
// 如果是本地静态资源或为空,直接返回
|
||||||
if (!avatarUrl || avatarUrl.startsWith('/static/')) {
|
if (!avatarUrl || avatarUrl.startsWith('/static/')) {
|
||||||
return avatarUrl || '';
|
return avatarUrl || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// 直接使用后端返回的 URL
|
||||||
// 直接将完整URL作为filename传递给预签名API
|
return avatarUrl;
|
||||||
const res = await getOssPresignedUrlApi(avatarUrl, 3600, 'avatar');
|
|
||||||
|
|
||||||
if (res.code === 200 && res.data && res.data.url) {
|
|
||||||
return res.data.url;
|
|
||||||
} else {
|
|
||||||
console.error('获取头像预签名URL失败:', res.message);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取好友头像URL失败:', error);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user