Compare commits

4 Commits

Author SHA1 Message Date
rjl
0d03fdd782 fix: 优化图片更新逻辑防止闪烁并调整样式
- 在结果详情页和用户分析页引入图片缓存和静默更新机制,通过比较图片大小避免不必要的更新,防止界面闪烁
- 调整用户分析页的布局样式,优化团队名称和评分信息的显示,避免内容溢出
- 禁用折线图动画以提升性能,并更新环境配置中的服务地址
2026-04-02 17:42:19 +08:00
rjl
a76263375a fix: 修复UI样式和轮询逻辑问题
- 修复ActionButton禁用状态颜色错误
- 调整CompetitionLayout布局和z-index层级
- 修正analysis页面评委评分显示位置
- 优化QuestionRenderer字体大小和间距
- 改进cover页面卡片翻转动画效果
- 修复results-detail页面轮询逻辑和时间同步
2026-04-02 15:14:50 +08:00
rjl
9bc58c6270 style(图表): 优化柱状图动画与视觉样式
- 启用平滑动画提升交互体验
- 调整颜色方案增强视觉层次
- 添加阴影效果和间距提升可读性
- 优化标签位置和字体大小
2026-04-01 15:13:23 +08:00
rjl
7b505ec4aa style(admin): 调整侧边栏宽度和文本样式
- 将问题库侧边栏默认宽度从525px调整为535px
- 统一标签页和分类树中的文本字号大小
- 优化文件夹图标的显示尺寸
2026-03-31 10:56:27 +08:00
12 changed files with 184 additions and 67 deletions

View File

@ -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"

View File

@ -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;
} }

View File

@ -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 {

View File

@ -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>

View File

@ -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

View File

@ -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 -->

View File

@ -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() {

View File

@ -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 -->

View File

@ -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;
} }
} }

View File

@ -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', // 动画缓动函数平滑过渡cubicOutlinearquadOutquadIn
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],
}, },
}, },
], ],

View File

@ -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 {

View File

@ -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>