Compare commits
4 Commits
a7cf35e6bf
...
0d03fdd782
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d03fdd782 | |||
| a76263375a | |||
| 9bc58c6270 | |||
| 7b505ec4aa |
@ -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://api.ydzx.qyzhjy.com/
|
||||||
|
VITE_SERVICE_BASE_URL=http://172.16.10.130:5000
|
||||||
|
|
||||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||||
"demo": "http://localhost:9528"
|
"demo": "http://localhost:9528"
|
||||||
|
|||||||
@ -151,8 +151,8 @@ function handleClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-disabled {
|
&.is-disabled {
|
||||||
color: #eb5e55;
|
color: red;
|
||||||
filter: grayscale(0.5);
|
// filter: grayscale(0.5);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -229,12 +229,12 @@ watch(titleBgUrl, (newUrl) => {
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="page-header flex items-center px-8 text-[30px] text-[#333333] font-bold">
|
<header class="page-header flex items-center px-8 text-[30px] text-[#333333] font-bold">
|
||||||
<!-- 左侧logo -->
|
<!-- 左侧logo -->
|
||||||
<div class="flex cursor-pointer items-center" @click="handleBackHome">
|
<div class="!z-index-99 flex cursor-pointer items-center" @click="handleBackHome">
|
||||||
<img :src="cqLogo" alt="重庆logo" class="h-18 w-auto">
|
<img :src="cqLogo" alt="重庆logo" class="h-18 w-auto">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间文字 -->
|
<!-- 中间文字 -->
|
||||||
<div class="flex flex-1 justify-center">
|
<div class="!z-index-99 flex flex-1 justify-center">
|
||||||
<img :src="qwLogo" alt="重庆logo" class="h-14 w-auto">
|
<img :src="qwLogo" alt="重庆logo" class="h-14 w-auto">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -450,16 +450,15 @@ watch(titleBgUrl, (newUrl) => {
|
|||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -50px;
|
top: -58px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 180px;
|
height: 196px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20px 44px;
|
padding: 20px 44px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $secondary-color;
|
color: $secondary-color;
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
|
|||||||
@ -214,7 +214,7 @@ init()
|
|||||||
<template #prefix>
|
<template #prefix>
|
||||||
<SvgIcon :icon="tab.icon" :local-icon="tab.localIcon" class="inline-block align-text-bottom text-16px" />
|
<SvgIcon :icon="tab.icon" :local-icon="tab.localIcon" class="inline-block align-text-bottom text-16px" />
|
||||||
</template>
|
</template>
|
||||||
<div class="max-w-240px ellipsis-text">
|
<div class="max-w-240px ellipsis-text text-14px">
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</div>
|
</div>
|
||||||
</PageTab>
|
</PageTab>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ defineOptions({ name: 'QuestionStore' })
|
|||||||
const currentCategory = ref<any>(null)
|
const currentCategory = ref<any>(null)
|
||||||
|
|
||||||
// Resize logic
|
// Resize logic
|
||||||
const siderWidth = ref(525)
|
const siderWidth = ref(535)
|
||||||
const LOCAL_STORAGE_KEY = 'question-store-sider-width'
|
const LOCAL_STORAGE_KEY = 'question-store-sider-width'
|
||||||
const minWidth = 300
|
const minWidth = 300
|
||||||
const maxWidth = 800
|
const maxWidth = 800
|
||||||
|
|||||||
@ -231,10 +231,10 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 overflow-hidden">
|
<div class="flex items-center gap-2 overflow-hidden">
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
icon="carbon:folder" class="flex-shrink-0 text-lg"
|
icon="carbon:folder" class="flex-shrink-0 text-sm"
|
||||||
:class="selectedKey === item.Id ? 'text-primary' : 'text-gray-400'"
|
:class="selectedKey === item.Id ? 'text-primary' : 'text-gray-400'"
|
||||||
/>
|
/>
|
||||||
<span class="truncate font-medium">{{ item.Name }} - {{ item.QuestionContent }}</span>
|
<span class="truncate text-16px font-medium">{{ item.Name }} - {{ item.QuestionContent }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!-- eslint-disable no-console -->
|
<!-- eslint-disable no-console -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useIntervalFn, useStorage } from '@vueuse/core'
|
||||||
import { NButton, NCard, NImage, NInputNumber, NTag, useMessage } from 'naive-ui'
|
import { NButton, NCard, NImage, NInputNumber, NTag, useMessage } from 'naive-ui'
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
@ -33,6 +33,9 @@ const questionInfo = ref<Api.Competition.QuestionListDetailRound>() // 题目详
|
|||||||
const currentQuestionDetailID = ref<number | string>('') // 当前选中的题目详情 ID
|
const currentQuestionDetailID = ref<number | string>('') // 当前选中的题目详情 ID
|
||||||
|
|
||||||
const nowTs = ref(Date.now())
|
const nowTs = ref(Date.now())
|
||||||
|
useIntervalFn(() => {
|
||||||
|
nowTs.value = Date.now()
|
||||||
|
}, 1000)
|
||||||
// 判断当前activityId是正在比赛的活动
|
// 判断当前activityId是正在比赛的活动
|
||||||
const isActiveActivityId = computed(() => {
|
const isActiveActivityId = computed(() => {
|
||||||
if (!currentQuestion.value)
|
if (!currentQuestion.value)
|
||||||
@ -254,7 +257,9 @@ async function fetchCurrentQuestion(shouldSync = true) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
currentQuestion.value = data?.data || {}
|
currentQuestion.value = data?.data || {}
|
||||||
|
const endTime = new Date(currentQuestion.value.EndTime)
|
||||||
console.warn('current question', currentQuestion.value)
|
console.warn('current question', currentQuestion.value)
|
||||||
|
console.log('当前题目结束时间是:', endTime.toLocaleString())
|
||||||
// 只在初始化时同步选中题,轮询时不同步
|
// 只在初始化时同步选中题,轮询时不同步
|
||||||
if (shouldSync) {
|
if (shouldSync) {
|
||||||
syncCurrentQuestionSelection()
|
syncCurrentQuestionSelection()
|
||||||
@ -265,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取答题卡数据
|
* 获取答题卡数据
|
||||||
*/
|
*/
|
||||||
@ -366,13 +407,22 @@ async function fetchAnswerData() {
|
|||||||
// 强制更新图片 URL,防止缓存
|
// 强制更新图片 URL,防止缓存
|
||||||
// 优先使用 ans 中的图片,如果没有则回退到 answerData 中的图片
|
// 优先使用 ans 中的图片,如果没有则回退到 answerData 中的图片
|
||||||
const imageUrl = ans.AnswerValuePicture || (parsedAnswers.length === 1 && parsedAnswers[0] === ans ? answerData.UserAnswerPicture : '')
|
const imageUrl = ans.AnswerValuePicture || (parsedAnswers.length === 1 && parsedAnswers[0] === ans ? answerData.UserAnswerPicture : '')
|
||||||
|
|
||||||
|
const uniqueId = `${answerData.TeamId}-${ans.Guid}`
|
||||||
let finalImageUrl = imageUrl
|
let finalImageUrl = imageUrl
|
||||||
|
|
||||||
|
if (imageUrl) {
|
||||||
|
if (oldAns && oldAns._originalUrl === imageUrl) {
|
||||||
|
finalImageUrl = oldAns.AnswerValuePicture
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
finalImageUrl = imageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const cleanBaseUrl = ''
|
const cleanBaseUrl = ''
|
||||||
const timestamp = new Date().getTime()
|
const timestamp = new Date().getTime()
|
||||||
|
|
||||||
finalImageUrl = `${finalImageUrl}?_t=${timestamp}`
|
|
||||||
console.log(finalImageUrl, 'finalImageUrl')
|
|
||||||
|
|
||||||
// 判断是否手动干预:
|
// 判断是否手动干预:
|
||||||
// 1. 本地存储有状态 (hasStoredStatus) 或有分数 (hasStoredScore)
|
// 1. 本地存储有状态 (hasStoredStatus) 或有分数 (hasStoredScore)
|
||||||
// 2. 内存中有手动标记 (oldAns._isManual)
|
// 2. 内存中有手动标记 (oldAns._isManual)
|
||||||
@ -402,6 +452,7 @@ async function fetchAnswerData() {
|
|||||||
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
||||||
AnswerText: ans.AnswerText,
|
AnswerText: ans.AnswerText,
|
||||||
AnswerValuePicture: finalImageUrl,
|
AnswerValuePicture: finalImageUrl,
|
||||||
|
_originalUrl: imageUrl,
|
||||||
_baseImgUrl: cleanBaseUrl,
|
_baseImgUrl: cleanBaseUrl,
|
||||||
_t: timestamp, // 保存时间戳
|
_t: timestamp, // 保存时间戳
|
||||||
Score: finalScore,
|
Score: finalScore,
|
||||||
@ -410,17 +461,21 @@ async function fetchAnswerData() {
|
|||||||
_aiStatus: aiStatus,
|
_aiStatus: aiStatus,
|
||||||
_aiScore: aiScore,
|
_aiScore: aiScore,
|
||||||
}
|
}
|
||||||
|
if (imageUrl) {
|
||||||
|
checkAndUpdateImage(uniqueId, imageUrl, resultItem)
|
||||||
|
}
|
||||||
return resultItem
|
return resultItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有手动编辑
|
// 如果没有手动编辑
|
||||||
return {
|
const resultItem = {
|
||||||
...ans,
|
...ans,
|
||||||
TeamId: answerData.TeamId, // [新增] 注入 TeamId
|
TeamId: answerData.TeamId, // [新增] 注入 TeamId
|
||||||
QuestionId: answerData.QuestionId, // [新增]
|
QuestionId: answerData.QuestionId, // [新增]
|
||||||
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
QuestionDetailID: answerData.QuestionDetaiID, // [新增] 注意后端字段拼写
|
||||||
AnswerText: ans.AnswerText,
|
AnswerText: ans.AnswerText,
|
||||||
AnswerValuePicture: finalImageUrl,
|
AnswerValuePicture: finalImageUrl,
|
||||||
|
_originalUrl: imageUrl,
|
||||||
_baseImgUrl: cleanBaseUrl,
|
_baseImgUrl: cleanBaseUrl,
|
||||||
_t: timestamp, // 保存时间戳
|
_t: timestamp, // 保存时间戳
|
||||||
Status: ans.Status,
|
Status: ans.Status,
|
||||||
@ -429,6 +484,10 @@ async function fetchAnswerData() {
|
|||||||
Score: null, // 默认 null
|
Score: null, // 默认 null
|
||||||
_isManual: false,
|
_isManual: false,
|
||||||
}
|
}
|
||||||
|
if (imageUrl) {
|
||||||
|
checkAndUpdateImage(uniqueId, imageUrl, resultItem)
|
||||||
|
}
|
||||||
|
return resultItem
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -466,9 +525,6 @@ async function handleNexCurrentQuestion() {
|
|||||||
currentQuestionId.value = currentQuestion.value?.QuestionID || currentQuestionId.value
|
currentQuestionId.value = currentQuestion.value?.QuestionID || currentQuestionId.value
|
||||||
currentGroupId.value = currentQuestion.value?.TeamGroupID || currentGroupId.value
|
currentGroupId.value = currentQuestion.value?.TeamGroupID || currentGroupId.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前时间戳,以便重新计算 canPublish
|
|
||||||
nowTs.value = Date.now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePublish() {
|
function handlePublish() {
|
||||||
@ -617,25 +673,23 @@ function startPolling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchAnswerData() // 立即获取
|
fetchAnswerData() // 立即获取
|
||||||
nowTs.value = Date.now()
|
|
||||||
pollingTimer.value = setInterval(() => {
|
pollingTimer.value = setInterval(() => {
|
||||||
// 每次轮询前再次检查是否过期
|
// 每次轮询前再次检查是否过期
|
||||||
const currentEndTime = (currentQuestion.value as any)?.EndTime
|
const currentEndTime = (currentQuestion.value as any)?.EndTime
|
||||||
if (currentEndTime) {
|
if (currentEndTime) {
|
||||||
// 增加 5s 缓冲时间
|
// 增加 1s 缓冲时间
|
||||||
const endTs = new Date(currentEndTime).getTime() + 3000
|
const endTs = new Date(currentEndTime).getTime() + 1000
|
||||||
if (Date.now() > endTs) {
|
if (Date.now() > endTs) {
|
||||||
console.log('题目时间已到(含3s缓冲),停止轮询', currentEndTime)
|
console.log('题目时间已到(含1s缓冲),停止轮询', currentEndTime)
|
||||||
stopPolling()
|
stopPolling()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nowTs.value = Date.now()
|
|
||||||
|
|
||||||
// 轮询时同时更新当前题目信息(获取最新的 EndTime),但不触发同步
|
// 轮询时同时更新当前题目信息(获取最新的 EndTime),但不触发同步
|
||||||
fetchCurrentQuestion(false)
|
fetchCurrentQuestion(false)
|
||||||
fetchAnswerData()
|
fetchAnswerData()
|
||||||
}, 3000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPolling() {
|
function stopPolling() {
|
||||||
|
|||||||
@ -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() {
|
async function fetchGetGameStatisticsData() {
|
||||||
if (store.currentQuestionInfo && store.currentQuestionDetail) {
|
if (store.currentQuestionInfo && store.currentQuestionDetail) {
|
||||||
@ -129,12 +165,25 @@ async function fetchGetGameStatisticsData() {
|
|||||||
const { data } = await fetchGetGameStatistics(_params)
|
const { data } = await fetchGetGameStatistics(_params)
|
||||||
const rawTeams = (data?.data as any[]) || []
|
const rawTeams = (data?.data as any[]) || []
|
||||||
|
|
||||||
// 为每个队伍的图片 URL 添加时间戳,防止缓存
|
|
||||||
teams.value = rawTeams.map((team) => {
|
teams.value = rawTeams.map((team) => {
|
||||||
|
const uniqueId = team.TeamID || team.TeamName || Math.random().toString()
|
||||||
|
team._uniqueId = uniqueId
|
||||||
|
|
||||||
if (team.UserAnswerPicture) {
|
if (team.UserAnswerPicture) {
|
||||||
const timestamp = Date.now()
|
const originalUrl = team.UserAnswerPicture
|
||||||
const separator = team.UserAnswerPicture.includes('?') ? '&' : '?'
|
const oldTeam = teams.value.find(t => t._uniqueId === uniqueId)
|
||||||
team.UserAnswerPicture = `${team.UserAnswerPicture}${separator}_t=${timestamp}`
|
|
||||||
|
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
|
return team
|
||||||
})
|
})
|
||||||
@ -164,11 +213,11 @@ onBeforeUnmount(() => {
|
|||||||
back-btn-text="返回" :next-btn-text="isCompleted ? '已完成,返选组' : '下一题'" btn-theme="light" @back="handleBack"
|
back-btn-text="返回" :next-btn-text="isCompleted ? '已完成,返选组' : '下一题'" btn-theme="light" @back="handleBack"
|
||||||
@next="handleNext"
|
@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 -->
|
<!-- Content -->
|
||||||
<div class="w-full flex flex-col flex-1 overflow-hidden">
|
<div class="w-full flex flex-col flex-1 overflow-hidden">
|
||||||
<div class="mb-2 text-xl text-red-600 font-bold">
|
<div class="mb-2 text-xl text-red-600 font-bold">
|
||||||
备注:评委正在评分中,请等待。。。
|
备注:评委正在评分中,请等待...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="teams && teams.length > 0" class="grid grid-cols-4 h-full gap-6 pb-8">
|
<div v-if="teams && teams.length > 0" class="grid grid-cols-4 h-full gap-6 pb-8">
|
||||||
@ -177,15 +226,17 @@ onBeforeUnmount(() => {
|
|||||||
class="flex flex-col border-2 border-gray-200 rounded-xl bg-white/90 p-4 shadow-sm"
|
class="flex flex-col border-2 border-gray-200 rounded-xl bg-white/90 p-4 shadow-sm"
|
||||||
>
|
>
|
||||||
<div
|
<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="overflow-hidden text-ellipsis whitespace-nowrap text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
|
||||||
<span class="rounded-full bg-orange-100/60 px-5 py-2 text-sm">
|
<div class="flex shrink-0 gap-2">
|
||||||
AI评分 <span class="text-md text-orange-600 font-bold">{{ team.Points }}</span>
|
<span class="whitespace-nowrap rounded-full bg-green-100/90 px-3 py-1.5 text-sm">
|
||||||
</span>
|
评委评分 <span class="text-lg text-orange-600 font-bold">{{ team.ResultPotins }}</span>
|
||||||
<span class="rounded-full bg-green-100/60 px-5 py-2 text-sm">
|
</span>
|
||||||
评委评分 <span class="text-md text-green-600 font-bold">{{ team.ResultPotins }}</span>
|
<span class="whitespace-nowrap rounded-full bg-orange-100/60 px-3 py-1.5 text-sm">
|
||||||
</span>
|
AI评分 <span class="text-md text-orange-600 font-bold">{{ team.Points }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Answer Image Area -->
|
<!-- Answer Image Area -->
|
||||||
|
|||||||
@ -288,7 +288,7 @@ onMounted(async () => {
|
|||||||
// [过冲状态]
|
// [过冲状态]
|
||||||
// 飞到眼前一点点 (Z=50px)
|
// 飞到眼前一点点 (Z=50px)
|
||||||
// 角度稍微回弹 (rotateY -15deg)
|
// 角度稍微回弹 (rotateY -15deg)
|
||||||
transform: translate3d(0, -20px, 50px) rotateY(-15deg) rotateX(-10deg) scale(1.05);
|
transform: translate3d(0, -20px, 50px) rotateY(-15deg) rotateX(-10deg) scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
@ -328,8 +328,8 @@ onMounted(async () => {
|
|||||||
// [翻转状态]
|
// [翻转状态]
|
||||||
// rotateY(180deg): 绕Y轴旋转180度,实现翻面
|
// rotateY(180deg): 绕Y轴旋转180度,实现翻面
|
||||||
// scale(1.05): 翻转同时放大1.05倍,产生"向观众逼近"的视觉冲击力
|
// scale(1.05): 翻转同时放大1.05倍,产生"向观众逼近"的视觉冲击力
|
||||||
transform: rotateY(180deg) scale(1.06);
|
transform: rotateY(180deg) scale(1.08);
|
||||||
box-shadow: 0 20px 40px -5px rgba(0, 0, 0, 0.3); // [动态阴影] 翻转浮起时阴影更深、更扩散
|
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.3); // [动态阴影] 翻转浮起时阴影更深、更扩散
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// border: 1px solid rgba(255, 255, 255, 0.5); // 增加微妙的边框
|
// border: 1px solid rgba(231, 215, 144, 0.5); // 增加微妙的边框
|
||||||
}
|
}
|
||||||
|
|
||||||
// 正面(实际是卡背图案)
|
// 正面(实际是卡背图案)
|
||||||
@ -367,7 +367,7 @@ onMounted(async () => {
|
|||||||
// 背面(实际是内容面)
|
// 背面(实际是内容面)
|
||||||
.flip-card-back {
|
.flip-card-back {
|
||||||
// background-color: #fff;
|
// background-color: #fff;
|
||||||
transform: rotateY(180deg);
|
transform: rotateY(200deg);
|
||||||
// border: 4px solid #fff;
|
// border: 4px solid #fff;
|
||||||
|
|
||||||
.card-content-design {
|
.card-content-design {
|
||||||
@ -409,7 +409,7 @@ onMounted(async () => {
|
|||||||
// animation-delay: 0.2s; 确保卡片翻转到一半(正对屏幕)时才开始闪光,
|
// animation-delay: 0.2s; 确保卡片翻转到一半(正对屏幕)时才开始闪光,
|
||||||
// 模拟光线随着角度变化掠过卡片表面的物理效果
|
// 模拟光线随着角度变化掠过卡片表面的物理效果
|
||||||
animation: shine-sweep 3s ease-out forwards;
|
animation: shine-sweep 3s ease-out forwards;
|
||||||
animation-delay: 0.2s;
|
animation-delay: 1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,14 +455,14 @@ onMounted(async () => {
|
|||||||
|
|
||||||
10% {
|
10% {
|
||||||
// 刚进入时瞬间变亮
|
// 刚进入时瞬间变亮
|
||||||
opacity: 0.8;
|
opacity: 0.8; // 0.8 是一个半透明值,模拟光束的透明度,值越大越透明
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
// 扫出视野
|
// 扫出视野
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 150%;
|
left: 150%;
|
||||||
opacity: 0;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,11 +40,17 @@ function buildOption(list: BarDatum[]): ECOption {
|
|||||||
const correct = list.map(i => i.correct)
|
const correct = list.map(i => i.correct)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 禁用动画,避免频繁刷新时的抖动
|
// 启用动画,让柱子变化更平滑
|
||||||
animation: false,
|
animation: false,
|
||||||
|
animationDuration: 800,
|
||||||
|
animationEasing: 'linear', // 动画缓动函数,平滑过渡,cubicOut|linear|quadOut|quadIn
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: { type: 'shadow' },
|
axisPointer: { type: 'shadow' },
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
borderWidth: 1,
|
||||||
|
textStyle: { color: '#1e3a8a' },
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ['答题数量', '预估得分'],
|
data: ['答题数量', '预估得分'],
|
||||||
@ -52,17 +58,18 @@ function buildOption(list: BarDatum[]): ECOption {
|
|||||||
right: 0,
|
right: 0,
|
||||||
icon: 'circle',
|
icon: 'circle',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#666',
|
color: '#64748b',
|
||||||
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: { left: 16, right: 16, top: 40, bottom: 0, containLabel: true },
|
grid: { left: 16, right: 16, top: 70, bottom: 0, containLabel: true },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: categories,
|
data: categories,
|
||||||
axisTick: { show: false }, // 隐藏 x 轴刻度线
|
axisTick: { show: false }, // 隐藏 x 轴刻度线
|
||||||
axisLine: { show: false }, // 隐藏 x 轴线
|
axisLine: { show: false }, // 隐藏 x 轴线
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: '#003366', // 深蓝色字体
|
color: '#1e3a8a', // 深蓝色字体
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
margin: 16,
|
margin: 16,
|
||||||
@ -82,6 +89,7 @@ function buildOption(list: BarDatum[]): ECOption {
|
|||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: total,
|
data: total,
|
||||||
barWidth: 24,
|
barWidth: 24,
|
||||||
|
barGap: '40%', // 柱子间的间隙
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
// 从上到下:浅蓝 -> 深蓝
|
// 从上到下:浅蓝 -> 深蓝
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
@ -89,14 +97,17 @@ function buildOption(list: BarDatum[]): ECOption {
|
|||||||
{ offset: 1, color: '#3b82f6' }, // blue-500 (底部深色)
|
{ offset: 1, color: '#3b82f6' }, // blue-500 (底部深色)
|
||||||
]),
|
]),
|
||||||
borderRadius: [4, 4, 0, 0],
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowColor: 'rgba(59, 130, 246, 0.2)',
|
||||||
|
shadowOffsetY: 4,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
color: '#003366', // 深蓝色
|
color: '#1e3a8a', // 深蓝色
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
offset: [0, 2],
|
offset: [0, -4],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -111,14 +122,17 @@ function buildOption(list: BarDatum[]): ECOption {
|
|||||||
{ offset: 1, color: '#f97316' }, // orange-500 (底部深色)
|
{ offset: 1, color: '#f97316' }, // orange-500 (底部深色)
|
||||||
]),
|
]),
|
||||||
borderRadius: [4, 4, 0, 0],
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowColor: 'rgba(249, 115, 22, 0.2)',
|
||||||
|
shadowOffsetY: 4,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
color: '#ea580c', // orange-600
|
color: '#ea580c', // orange-600
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
offset: [0, 2],
|
offset: [0, -4],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -185,7 +185,7 @@ watch(
|
|||||||
>
|
>
|
||||||
<!-- 上方:词组 -->
|
<!-- 上方:词组 -->
|
||||||
<div class="flex flex-wrap justify-center gap-4">
|
<div class="flex flex-wrap justify-center gap-4">
|
||||||
<div v-for="(word, index) in associationWords" :key="`word-${index}`" class="association-word text-bold">
|
<div v-for="(word, index) in associationWords" :key="`word-${index}`" class="association-word text-bold text-center text-3xl">
|
||||||
{{ word }}
|
{{ word }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -215,9 +215,9 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模板: 诗词常识 -->
|
<!-- 模板: 加时赛诗词常识 -->
|
||||||
<div v-else-if="template === QuestionCategoryEnum.PoetryCommonKnowledge" class="w-full flex flex-col items-center">
|
<div v-else-if="template === QuestionCategoryEnum.PoetryCommonKnowledge" class="w-full flex flex-col items-center">
|
||||||
<div class="mb-1 p-16 text-3xl text-gray-800 font-bold leading-relaxed line-height-20">
|
<div class="line-height mb-1 p-16 text-3xl text-gray-800 font-bold leading-relaxed">
|
||||||
<div class="rich-content" v-html="content" />
|
<div class="rich-content" v-html="content" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -445,8 +445,7 @@ watch(
|
|||||||
:deep(p),
|
:deep(p),
|
||||||
:deep(span),
|
:deep(span),
|
||||||
:deep(div) {
|
:deep(div) {
|
||||||
font-size: 2.2rem;
|
font-size: 2rem;
|
||||||
/* text-5xl */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,15 +493,14 @@ watch(
|
|||||||
.association-word {
|
.association-word {
|
||||||
background: url('@/assets/imgs/user/dui-kuang.png') no-repeat center;
|
background: url('@/assets/imgs/user/dui-kuang.png') no-repeat center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
min-width: 160px;
|
min-width: 170px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0 20px;
|
letter-spacing: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.association-char {
|
.association-char {
|
||||||
|
|||||||
@ -157,13 +157,13 @@ function rowClassName(row: TeamData) {
|
|||||||
<template>
|
<template>
|
||||||
<CompetitionLayout :show-back="false" :show-next="false" :show-title="false">
|
<CompetitionLayout :show-back="false" :show-next="false" :show-title="false">
|
||||||
<div class="leaderboard-container h-100%">
|
<div class="leaderboard-container h-100%">
|
||||||
<div class="leaderboard-header">
|
<div class="leaderboard-header !z-index-99">
|
||||||
<NImage :src="title" alt="logo" preview-disabled class="mt--42px" object-fit="contain" />
|
<NImage :src="title" alt="logo" preview-disabled class="mt--42px" object-fit="contain" />
|
||||||
<!-- <p class="mt--16px text-center text-18px text-#6b778d">
|
<!-- <p class="mt--16px text-center text-18px text-#6b778d">
|
||||||
"勤学如春起之苗,不见其增,日有所长"
|
"勤学如春起之苗,不见其增,日有所长"
|
||||||
</p> -->
|
</p> -->
|
||||||
<ActionButton
|
<ActionButton
|
||||||
v-if="hasExtraTimeBtn" class="z-index-99 position-absolute right-20px top-20px" type="text"
|
v-if="hasExtraTimeBtn" class="!z-index-100 position-absolute right-20px top-20px" type="text"
|
||||||
text="加时赛" @click="handleAddTime"
|
text="加时赛" @click="handleAddTime"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user