fix: 优化图片更新逻辑防止闪烁并调整样式
- 在结果详情页和用户分析页引入图片缓存和静默更新机制,通过比较图片大小避免不必要的更新,防止界面闪烁 - 调整用户分析页的布局样式,优化团队名称和评分信息的显示,避免内容溢出 - 禁用折线图动画以提升性能,并更新环境配置中的服务地址
This commit is contained in:
@ -5,7 +5,8 @@
|
||||
|
||||
# VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com
|
||||
|
||||
VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com/
|
||||
# VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com/
|
||||
VITE_SERVICE_BASE_URL=http://172.16.10.130:5000
|
||||
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
"demo": "http://localhost:9528"
|
||||
|
||||
@ -270,6 +270,42 @@ async function fetchCurrentQuestion(shouldSync = true) {
|
||||
}
|
||||
}
|
||||
|
||||
const blobSizeCache = new Map<string, number>()
|
||||
const objectUrlCache = new Map<string, string>()
|
||||
|
||||
async function checkAndUpdateImage(uniqueId: string, originalUrl: string, item: any) {
|
||||
try {
|
||||
const timestamp = Date.now()
|
||||
const separator = originalUrl.includes('?') ? '&' : '?'
|
||||
const fetchUrl = `${originalUrl}${separator}_t=${timestamp}`
|
||||
|
||||
const response = await fetch(fetchUrl)
|
||||
if (!response.ok)
|
||||
return
|
||||
const blob = await response.blob()
|
||||
|
||||
const prevSize = blobSizeCache.get(uniqueId)
|
||||
if (prevSize === blob.size) {
|
||||
return // 内容大小未变,认为图片没变,不更新 src,避免闪烁
|
||||
}
|
||||
|
||||
blobSizeCache.set(uniqueId, blob.size)
|
||||
const objectUrl = URL.createObjectURL(blob)
|
||||
|
||||
const oldUrl = objectUrlCache.get(uniqueId)
|
||||
if (oldUrl) {
|
||||
URL.revokeObjectURL(oldUrl)
|
||||
}
|
||||
objectUrlCache.set(uniqueId, objectUrl)
|
||||
|
||||
// 替换为本地 Blob URL,由于数据已在内存中,更新时不会闪烁
|
||||
item.AnswerValuePicture = objectUrl
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Silent image update failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取答题卡数据
|
||||
*/
|
||||
@ -371,13 +407,22 @@ async function fetchAnswerData() {
|
||||
// 强制更新图片 URL,防止缓存
|
||||
// 优先使用 ans 中的图片,如果没有则回退到 answerData 中的图片
|
||||
const imageUrl = ans.AnswerValuePicture || (parsedAnswers.length === 1 && parsedAnswers[0] === ans ? answerData.UserAnswerPicture : '')
|
||||
|
||||
const uniqueId = `${answerData.TeamId}-${ans.Guid}`
|
||||
let finalImageUrl = imageUrl
|
||||
|
||||
if (imageUrl) {
|
||||
if (oldAns && oldAns._originalUrl === imageUrl) {
|
||||
finalImageUrl = oldAns.AnswerValuePicture
|
||||
}
|
||||
else {
|
||||
finalImageUrl = imageUrl
|
||||
}
|
||||
}
|
||||
|
||||
const cleanBaseUrl = ''
|
||||
const timestamp = new Date().getTime()
|
||||
|
||||
finalImageUrl = `${finalImageUrl}?_t=${timestamp}`
|
||||
console.log(finalImageUrl, 'finalImageUrl')
|
||||
|
||||
// 判断是否手动干预:
|
||||
// 1. 本地存储有状态 (hasStoredStatus) 或有分数 (hasStoredScore)
|
||||
// 2. 内存中有手动标记 (oldAns._isManual)
|
||||
@ -407,6 +452,7 @@ async function fetchAnswerData() {
|
||||
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
||||
AnswerText: ans.AnswerText,
|
||||
AnswerValuePicture: finalImageUrl,
|
||||
_originalUrl: imageUrl,
|
||||
_baseImgUrl: cleanBaseUrl,
|
||||
_t: timestamp, // 保存时间戳
|
||||
Score: finalScore,
|
||||
@ -415,17 +461,21 @@ async function fetchAnswerData() {
|
||||
_aiStatus: aiStatus,
|
||||
_aiScore: aiScore,
|
||||
}
|
||||
if (imageUrl) {
|
||||
checkAndUpdateImage(uniqueId, imageUrl, resultItem)
|
||||
}
|
||||
return resultItem
|
||||
}
|
||||
|
||||
// 如果没有手动编辑
|
||||
return {
|
||||
const resultItem = {
|
||||
...ans,
|
||||
TeamId: answerData.TeamId, // [新增] 注入 TeamId
|
||||
QuestionId: answerData.QuestionId, // [新增]
|
||||
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
||||
AnswerText: ans.AnswerText,
|
||||
AnswerValuePicture: finalImageUrl,
|
||||
_originalUrl: imageUrl,
|
||||
_baseImgUrl: cleanBaseUrl,
|
||||
_t: timestamp, // 保存时间戳
|
||||
Status: ans.Status,
|
||||
@ -434,6 +484,10 @@ async function fetchAnswerData() {
|
||||
Score: null, // 默认 null
|
||||
_isManual: false,
|
||||
}
|
||||
if (imageUrl) {
|
||||
checkAndUpdateImage(uniqueId, imageUrl, resultItem)
|
||||
}
|
||||
return resultItem
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@ -117,6 +117,42 @@ async function checkNextStep() {
|
||||
}
|
||||
}
|
||||
|
||||
const blobSizeCache = new Map<string, number>()
|
||||
const objectUrlCache = new Map<string, string>()
|
||||
|
||||
async function checkAndUpdateImage(uniqueId: string, originalUrl: string, team: any) {
|
||||
try {
|
||||
const timestamp = Date.now()
|
||||
const separator = originalUrl.includes('?') ? '&' : '?'
|
||||
const fetchUrl = `${originalUrl}${separator}_t=${timestamp}`
|
||||
|
||||
const response = await fetch(fetchUrl)
|
||||
if (!response.ok)
|
||||
return
|
||||
const blob = await response.blob()
|
||||
|
||||
const prevSize = blobSizeCache.get(uniqueId)
|
||||
if (prevSize === blob.size) {
|
||||
return // 内容大小未变,认为图片没变,不更新 src,避免闪烁
|
||||
}
|
||||
|
||||
blobSizeCache.set(uniqueId, blob.size)
|
||||
const objectUrl = URL.createObjectURL(blob)
|
||||
|
||||
const oldUrl = objectUrlCache.get(uniqueId)
|
||||
if (oldUrl) {
|
||||
URL.revokeObjectURL(oldUrl)
|
||||
}
|
||||
objectUrlCache.set(uniqueId, objectUrl)
|
||||
|
||||
// 替换为本地 Blob URL,由于数据已在内存中,更新时不会闪烁
|
||||
team.UserAnswerPicture = objectUrl
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Silent image update failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取游戏现场统计结果 */
|
||||
async function fetchGetGameStatisticsData() {
|
||||
if (store.currentQuestionInfo && store.currentQuestionDetail) {
|
||||
@ -129,12 +165,25 @@ async function fetchGetGameStatisticsData() {
|
||||
const { data } = await fetchGetGameStatistics(_params)
|
||||
const rawTeams = (data?.data as any[]) || []
|
||||
|
||||
// 为每个队伍的图片 URL 添加时间戳,防止缓存
|
||||
teams.value = rawTeams.map((team) => {
|
||||
const uniqueId = team.TeamID || team.TeamName || Math.random().toString()
|
||||
team._uniqueId = uniqueId
|
||||
|
||||
if (team.UserAnswerPicture) {
|
||||
const timestamp = Date.now()
|
||||
const separator = team.UserAnswerPicture.includes('?') ? '&' : '?'
|
||||
team.UserAnswerPicture = `${team.UserAnswerPicture}${separator}_t=${timestamp}`
|
||||
const originalUrl = team.UserAnswerPicture
|
||||
const oldTeam = teams.value.find(t => t._uniqueId === uniqueId)
|
||||
|
||||
if (oldTeam && oldTeam.UserAnswerPicture && oldTeam._originalUrl === originalUrl) {
|
||||
// 保持原有的 URL,避免每次轮询都刷新
|
||||
team.UserAnswerPicture = oldTeam.UserAnswerPicture
|
||||
}
|
||||
else {
|
||||
team.UserAnswerPicture = originalUrl
|
||||
}
|
||||
team._originalUrl = originalUrl
|
||||
|
||||
// 后台静默获取最新图片并对比
|
||||
checkAndUpdateImage(uniqueId, originalUrl, team)
|
||||
}
|
||||
return team
|
||||
})
|
||||
@ -164,7 +213,7 @@ onBeforeUnmount(() => {
|
||||
back-btn-text="返回" :next-btn-text="isCompleted ? '已完成,返选组' : '下一题'" btn-theme="light" @back="handleBack"
|
||||
@next="handleNext"
|
||||
>
|
||||
<div class="relative h-full w-full flex flex-col items-center px-12 pt-10 font-sans">
|
||||
<div class="relative h-full w-full flex flex-col items-center px-10 pt-10px font-sans">
|
||||
<!-- Content -->
|
||||
<div class="w-full flex flex-col flex-1 overflow-hidden">
|
||||
<div class="mb-2 text-xl text-red-600 font-bold">
|
||||
@ -177,15 +226,17 @@ onBeforeUnmount(() => {
|
||||
class="flex flex-col border-2 border-gray-200 rounded-xl bg-white/90 p-4 shadow-sm"
|
||||
>
|
||||
<div
|
||||
class="mb-3 flex items-center gap-2 rounded-full from-sky-300 to-cyan-200 bg-gradient-to-r px-4 py-2 shadow-sm"
|
||||
class="mb-3 flex items-center justify-between gap-2 rounded-full from-sky-300 to-cyan-200 bg-gradient-to-r px-4 py-2 shadow-sm"
|
||||
>
|
||||
<span class="text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
|
||||
<span class="rounded-full bg-green-100/90 px-5 py-2 text-sm">
|
||||
评委评分 <span class="text-lg text-orange-600 font-bold">{{ team.ResultPotins }}</span>
|
||||
</span>
|
||||
<span class="rounded-full bg-orange-100/60 px-5 py-2 text-sm">
|
||||
AI评分 <span class="text-md text-orange-600 font-bold">{{ team.Points }}</span>
|
||||
</span>
|
||||
<span class="overflow-hidden text-ellipsis whitespace-nowrap text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
|
||||
<div class="flex shrink-0 gap-2">
|
||||
<span class="whitespace-nowrap rounded-full bg-green-100/90 px-3 py-1.5 text-sm">
|
||||
评委评分 <span class="text-lg text-orange-600 font-bold">{{ team.ResultPotins }}</span>
|
||||
</span>
|
||||
<span class="whitespace-nowrap rounded-full bg-orange-100/60 px-3 py-1.5 text-sm">
|
||||
AI评分 <span class="text-md text-orange-600 font-bold">{{ team.Points }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Answer Image Area -->
|
||||
|
||||
@ -41,9 +41,9 @@ function buildOption(list: BarDatum[]): ECOption {
|
||||
|
||||
return {
|
||||
// 启用动画,让柱子变化更平滑
|
||||
animation: true,
|
||||
animation: false,
|
||||
animationDuration: 800,
|
||||
animationEasing: 'cubicOut',
|
||||
animationEasing: 'linear', // 动画缓动函数,平滑过渡,cubicOut|linear|quadOut|quadIn
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
|
||||
Reference in New Issue
Block a user