feat(admin): 更新用户界面组件并优化样式
- 移除 uno.config.ts 中的注释以简化字体配置 - 更新 .env.dev 文件中的服务基础 URL - 新增用户排名标题背景图片并优化相关组件样式 - 调整 ActionButton 组件的最小宽度 - 修改 CompetitionLayout 组件中的图像和样式以提升视觉效果 - 更新全局 CSS 文件以增加基础字体大小 - 优化 results-detail 视图中的发布逻辑延迟 - 调整用户分析和封面页面的文本样式 - 更新团队页面的团队名称样式以适应设计规范
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
|
||||
# VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com
|
||||
|
||||
VITE_SERVICE_BASE_URL=http://172.16.10.130:5000
|
||||
VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com/
|
||||
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
"demo": "http://localhost:9528"
|
||||
|
||||
BIN
apps/admin/src/assets/imgs/user/rank-title.png
Normal file
BIN
apps/admin/src/assets/imgs/user/rank-title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 177 KiB |
@ -135,7 +135,7 @@ function handleClick() {
|
||||
.custom-btn {
|
||||
background: v-bind(customBg);
|
||||
background-color: transparent;
|
||||
min-width: 210px;
|
||||
min-width: 220px;
|
||||
width: auto;
|
||||
height: 60px;
|
||||
padding: 0 20px;
|
||||
|
||||
@ -230,7 +230,7 @@ watch(titleBgUrl, (newUrl) => {
|
||||
<header class="page-header flex items-center px-8 text-[30px] text-[#333333] font-bold">
|
||||
<!-- 左侧logo -->
|
||||
<div class="flex cursor-pointer items-center" @click="handleBackHome">
|
||||
<img :src="cqLogo" alt="重庆logo" class="h-20 w-auto">
|
||||
<img :src="cqLogo" alt="重庆logo" class="h-18 w-auto">
|
||||
</div>
|
||||
|
||||
<!-- 中间文字 -->
|
||||
@ -249,17 +249,23 @@ watch(titleBgUrl, (newUrl) => {
|
||||
<section v-if="showTitle" class="title-section">
|
||||
<Transition name="scroll-unfold" appear>
|
||||
<div v-if="showContent" class="title-wrapper">
|
||||
<div class="scroll-bg">
|
||||
<div
|
||||
class="scroll-bg"
|
||||
:style="{ width: props.titleBgType === 3 ? '400px' : props.titleBgType === 4 ? '420px' : '480px' }"
|
||||
>
|
||||
<!-- 预渲染所有背景图,用 opacity 切换避免加载抖动 -->
|
||||
<img
|
||||
v-for="(url, _key) in titleBgUrlMapping" :key="_key" :src="url" class="scroll-bg-img" :class="{
|
||||
active: titleBgUrl === url && imageLoadStatus[url] !== false,
|
||||
loading: imageLoadStatus[url] === undefined,
|
||||
}" :style="{ visibility: imageLoadStatus[url] === false ? 'hidden' : 'visible' }" aria-hidden="true"
|
||||
@load="imageLoadStatus[url] = true" @error="imageLoadStatus[url] = false"
|
||||
}" :style="{ visibility: imageLoadStatus[url] === false ? 'hidden' : 'visible' }"
|
||||
aria-hidden="true" @load="imageLoadStatus[url] = true" @error="imageLoadStatus[url] = false"
|
||||
>
|
||||
<Transition name="fade-in-delay" appear>
|
||||
<h1 v-if="showContent" class="main-title" :style="{ color: titleTextColor }">
|
||||
<h1
|
||||
v-if="showContent" class="main-title"
|
||||
:style="{ color: titleTextColor, letterSpacing: props.titleBgType === 2 ? '6px' : props.titleBgType === 4 ? '1px' : '4px', fontSize: props.titleBgType === 4 ? '38px' : '' }"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
</Transition>
|
||||
@ -285,7 +291,7 @@ watch(titleBgUrl, (newUrl) => {
|
||||
class="back-btn" :style="{ visibility: showBack ? 'visible' : 'hidden' }" type="custom"
|
||||
:text="backBtnText || ''" :theme="btnTheme" :font-size="fontSize" @click="handleBack"
|
||||
>
|
||||
<SvgIcon v-if="!backBtnText" icon="mdi:arrow-left" size="40" class="arrow-icon" />
|
||||
<SvgIcon v-if="!backBtnText" icon="mdi:arrow-left" size="42" class="arrow-icon" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
@ -294,14 +300,14 @@ watch(titleBgUrl, (newUrl) => {
|
||||
v-if="showNext" type="custom" :text="nextBtnText || '下一步'" :theme="btnTheme"
|
||||
:disabled="nextDisabled" :font-size="fontSize" @click="handleNext"
|
||||
>
|
||||
<SvgIcon v-if="!nextBtnText" icon="mdi:arrow-right" class="arrow-icon" size="40" />
|
||||
<SvgIcon v-if="!nextBtnText" icon="mdi:arrow-right" class="arrow-icon" size="42" />
|
||||
</ActionButton>
|
||||
|
||||
<ActionButton
|
||||
v-if="showNextBtn2" :type="nextBtnText2 ? 'text' : 'button'" :text="nextBtnText2 || '下一步'"
|
||||
:theme="btnTheme" :disabled="nextDisabled" @click="handleNext2"
|
||||
>
|
||||
<SvgIcon v-if="!nextBtnText2" icon="mdi:arrow-right" class="arrow-icon" size="40" />
|
||||
<SvgIcon v-if="!nextBtnText2" icon="mdi:arrow-right" class="arrow-icon" size="42" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</footer>
|
||||
@ -330,9 +336,9 @@ watch(titleBgUrl, (newUrl) => {
|
||||
}
|
||||
|
||||
.scroll-bg {
|
||||
width: 400px !important;
|
||||
width: 400px;
|
||||
/* Fixed width to prevent squishing during animation */
|
||||
height: 155px !important;
|
||||
height: 180px !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -363,10 +369,10 @@ watch(titleBgUrl, (newUrl) => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 2.6rem;
|
||||
font-size: 2.66rem;
|
||||
color: $text-color;
|
||||
margin: 0;
|
||||
letter-spacing: 3px;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
/* Prevent text wrapping */
|
||||
@ -444,12 +450,13 @@ watch(titleBgUrl, (newUrl) => {
|
||||
|
||||
.page-header {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
top: -50px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20px 40px;
|
||||
padding: 20px 44px;
|
||||
font-weight: 700;
|
||||
color: $secondary-color;
|
||||
z-index: 10;
|
||||
|
||||
@ -7,6 +7,7 @@ body,
|
||||
#app {
|
||||
height: 100%;
|
||||
text-autospace: normal;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
html {
|
||||
|
||||
@ -473,7 +473,7 @@ async function handleNexCurrentQuestion() {
|
||||
|
||||
function handlePublish() {
|
||||
const endTimeStr = (currentQuestion.value as any)?.EndTime
|
||||
if (endTimeStr && Date.now() < new Date(endTimeStr).getTime() + 5000) {
|
||||
if (endTimeStr && Date.now() < new Date(endTimeStr).getTime() + 1000) {
|
||||
window.$message?.warning('倒计时未结束(需等待5秒延迟),暂不能发布')
|
||||
return
|
||||
}
|
||||
@ -589,7 +589,7 @@ const canPublish = computed(() => {
|
||||
if (!endTime)
|
||||
return true
|
||||
// 结束时间 + 5秒延迟才能发布
|
||||
const end = new Date(endTime).getTime() + 5000
|
||||
const end = new Date(endTime).getTime() + 2000
|
||||
return nowTs.value >= end
|
||||
})
|
||||
|
||||
@ -599,8 +599,8 @@ function startPolling() {
|
||||
// 检查是否已经结束
|
||||
const endTime = (currentQuestion.value as any)?.EndTime
|
||||
if (endTime) {
|
||||
// 增加 12s 缓冲时间
|
||||
const end = new Date(endTime).getTime() + 12000
|
||||
// 增加 5s 缓冲时间
|
||||
const end = new Date(endTime).getTime() + 3000
|
||||
const now = new Date().getTime()
|
||||
if (now > end) {
|
||||
console.log('当前题目已结束(含12s缓冲),仅获取一次最终结果,不启动轮询', endTime)
|
||||
@ -622,10 +622,10 @@ function startPolling() {
|
||||
// 每次轮询前再次检查是否过期
|
||||
const currentEndTime = (currentQuestion.value as any)?.EndTime
|
||||
if (currentEndTime) {
|
||||
// 增加 12s 缓冲时间
|
||||
const endTs = new Date(currentEndTime).getTime() + 12000
|
||||
// 增加 5s 缓冲时间
|
||||
const endTs = new Date(currentEndTime).getTime() + 3000
|
||||
if (Date.now() > endTs) {
|
||||
console.log('题目时间已到(含12s缓冲),停止轮询', currentEndTime)
|
||||
console.log('题目时间已到(含3s缓冲),停止轮询', currentEndTime)
|
||||
stopPolling()
|
||||
return
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-title="true" :show-back="false" :show-next="showNextBtn" :title-bg-type="3" title-text-color="#ffffff" title="答题图解" action-position="top"
|
||||
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"
|
||||
>
|
||||
<div class="relative h-full w-full flex flex-col items-center px-12 pt-10 font-sans">
|
||||
|
||||
@ -240,7 +240,7 @@ onMounted(async () => {
|
||||
<!-- 文字内容 -->
|
||||
<div class="relative z-10 transform text-center">
|
||||
<div
|
||||
class="card-text text-2xl text-[#333333] font-bold leading-tight font-serif drop-shadow-sm"
|
||||
class="card-text text-26px text-[#333333] font-bold leading-tight font-serif drop-shadow-sm"
|
||||
>
|
||||
<span class="block">{{ node.moduleName.slice(0, 2) }}</span>
|
||||
<span class="block">{{ node.moduleName.slice(2) }}</span>
|
||||
|
||||
@ -285,7 +285,7 @@ const titleBgTypeNum = computed(() => RoundType.value === 0 ? 3 : 4) || 3
|
||||
>
|
||||
<!-- 图表标题栏 -->
|
||||
<div v-if="isStarted" class="z-index-999 relative mb-4">
|
||||
<img src="@/assets/imgs/user/rank-bg-kuang.png" alt="答题进度" class="z-index-999 absolute left--26px top--22px h-auto max-h-16 w-auto">
|
||||
<img src="@/assets/imgs/user/rank-bg-kuang.png" alt="答题进度" class="z-index-999 absolute left--30px top--25px h-auto max-h-16 w-auto">
|
||||
</div>
|
||||
|
||||
<!-- 柱状图(ECharts) -->
|
||||
|
||||
@ -126,7 +126,7 @@ watch(
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="mb-1 text-gray-800 font-bold leading-relaxed">
|
||||
<div class="rich-content content-with-bg pinyin-text ml-3 mt-4 flex text-7xl !max-w-100% !px-16 !py-8 !tracking-[0.08em]">
|
||||
<div class="rich-content content-with-bg pinyin-text ml-3 mt-4 flex text-7xl !max-w-100% !px-16 !py-4 !tracking-[0.08em]">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
@ -135,8 +135,8 @@ watch(
|
||||
<!-- 模板C: 汉字加一加 (提示+部件) -->
|
||||
<div v-else-if="template === QuestionCategoryEnum.CharacterRadicalAddition" class="text-center">
|
||||
<div class="mb-6 inline-block text-5xl font-bold leading-none">
|
||||
<div class="rich-content ml-3 mt-4 flex gap-1 text-6xl tracking-[0.06em]">
|
||||
请写出含有“ <span class="text-color-red !text-6xl">{{ content }}</span>”的汉字,书写时间为60秒。
|
||||
<div class="rich-content ml-3 mt-4 flex gap-1 text-5xl tracking-[0.06em]">
|
||||
请写出含有“ <span class="text-color-red !text-5xl">{{ content }}</span>”的汉字,书写时间为60秒。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -153,7 +153,7 @@ watch(
|
||||
<!-- 模板E: 成语-文字要求 -->
|
||||
<div v-else-if="template === QuestionCategoryEnum.IdiomWriting2" class="text-center">
|
||||
<div class="mb-2">
|
||||
<img :src="ImageUrl" preview-disabled class="max-h-380px max-w-700px" object-fit="cover">
|
||||
<img :src="ImageUrl" preview-disabled class="max-h-368px max-w-700px" object-fit="cover">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -173,8 +173,8 @@ watch(
|
||||
|
||||
<!-- 模板: 场景猜诗句 -->
|
||||
<div v-else-if="template === QuestionCategoryEnum.SceneBasedPoemGuessing" 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="rich-content" v-html="content" />
|
||||
<div class="mb-1px p-16 text-3xl text-gray-800 font-bold leading-relaxed line-height-16">
|
||||
<div class="rich-content indent-[2em]" v-html="content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import type { DataTableColumns } from 'naive-ui'
|
||||
import { NDataTable, NImage, NTag, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import title from '@/assets/imgs/user/rank-title.svg'
|
||||
import title from '@/assets/imgs/user/rank-title.png'
|
||||
import ActionButton from '@/components/custom/user/ActionButton.vue'
|
||||
import { fetchUserRankList } from '@/service/api/rank'
|
||||
import ExtraTimeModal from './components/ExtraTimeModal.vue'
|
||||
@ -57,6 +57,23 @@ const columns = ref<DataTableColumns<TeamData>>([
|
||||
|
||||
const tableData = ref<TeamData[]>([])
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
const mockUserRankList: Api.Rank.ActivityRankResList = Array.from({ length: 13 }, (_, teamIndex) => {
|
||||
// eslint-disable-next-line ts/no-shadow
|
||||
const questions = Array.from({ length: 10 }, (_, questionIndex) => {
|
||||
const QuestionNO = questionIndex + 1
|
||||
const ResultPotin = Math.max(0, 10 - teamIndex - (questionIndex % 3))
|
||||
return { QuestionNO, ResultPotin }
|
||||
})
|
||||
|
||||
return {
|
||||
TeamID: 1001 + teamIndex,
|
||||
Name: `测试队伍${teamIndex + 1}`,
|
||||
TotalPoint: questions.reduce((sum, item) => sum + item.ResultPotin, 0),
|
||||
Questions: questions,
|
||||
}
|
||||
})
|
||||
|
||||
async function getUserRankList() {
|
||||
const { data, error } = await fetchUserRankList(MainID, RoundType)
|
||||
if (error) {
|
||||
@ -64,9 +81,11 @@ async function getUserRankList() {
|
||||
return
|
||||
}
|
||||
|
||||
if (data?.data && data.data.length > 0) {
|
||||
const rankSource = data?.data || []
|
||||
|
||||
if (rankSource.length > 0) {
|
||||
// 动态生成题目列
|
||||
const firstTeam = data.data[0]
|
||||
const firstTeam = rankSource[0]
|
||||
const questionColumns = firstTeam.Questions.map(q => ({
|
||||
title: `第${q.QuestionNO}题`,
|
||||
key: `q${q.QuestionNO}`,
|
||||
@ -94,12 +113,12 @@ async function getUserRankList() {
|
||||
return <span>{rank}</span>
|
||||
},
|
||||
},
|
||||
{ title: '队伍名称', key: 'teamName', align: 'left', width: 150, className: 'team-name' },
|
||||
{ title: '队伍名称', key: 'teamName', align: 'left', width: 190, className: 'team-name' },
|
||||
...questionColumns,
|
||||
{ title: '总积分', key: 'total', align: 'center', className: 'total-score' },
|
||||
]
|
||||
|
||||
tableData.value = data.data.map((item, index) => {
|
||||
tableData.value = rankSource.map((item, index) => {
|
||||
const questions: Record<string, number> = {}
|
||||
item.Questions.forEach((q) => {
|
||||
questions[`q${q.QuestionNO}`] = q.ResultPotin
|
||||
@ -114,7 +133,7 @@ async function getUserRankList() {
|
||||
} as TeamData
|
||||
})
|
||||
|
||||
teamOptions.value = data.data.map(item => ({ label: item.Name, value: item.TeamID }))
|
||||
teamOptions.value = rankSource.map(item => ({ label: item.Name, value: item.TeamID }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,21 +156,21 @@ function rowClassName(row: TeamData) {
|
||||
|
||||
<template>
|
||||
<CompetitionLayout :show-back="false" :show-next="false" :show-title="false">
|
||||
<div class="leaderboard-container">
|
||||
<div class="leaderboard-container h-100%">
|
||||
<div class="leaderboard-header">
|
||||
<NImage :src="title" alt="logo" preview-disabled class="mt--60px" object-fit="contain" />
|
||||
<p class="mt--20px text-center text-lg text-#6b778d">
|
||||
<NImage :src="title" alt="logo" preview-disabled class="mt--42px" object-fit="contain" />
|
||||
<!-- <p class="mt--16px text-center text-18px text-#6b778d">
|
||||
"勤学如春起之苗,不见其增,日有所长"
|
||||
</p>
|
||||
</p> -->
|
||||
<ActionButton
|
||||
v-if="hasExtraTimeBtn" class="position-absolute bottom-20px right-20px" type="text" text="加时赛"
|
||||
@click="handleAddTime"
|
||||
v-if="hasExtraTimeBtn" class="z-index-99 position-absolute right-20px top-20px" type="text"
|
||||
text="加时赛" @click="handleAddTime"
|
||||
/>
|
||||
</div>
|
||||
<div class="leaderboard-content">
|
||||
<NDataTable
|
||||
:columns="columns" :data="tableData" :bordered="false" :bottom-bordered="false" size="large"
|
||||
:row-class-name="rowClassName" class="rank-table"
|
||||
:columns="columns" :data="tableData" :bordered="false" max-height="100%" :bottom-bordered="false"
|
||||
size="large" :row-class-name="rowClassName" class="rank-table !h-100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,7 +182,7 @@ function rowClassName(row: TeamData) {
|
||||
/* 1. 整体容器和背景 */
|
||||
.leaderboard-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
background-image: url('https://raw.githubusercontent.com/0x3f3f3f3f/figure-bed/main/img/20240524150352.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
@ -193,19 +212,12 @@ function rowClassName(row: TeamData) {
|
||||
}
|
||||
|
||||
.leaderboard-header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: #1a2a44;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* .leaderboard-header p {
|
||||
font-size: 1rem;
|
||||
color: #6b778d;
|
||||
margin-top: 8px;
|
||||
} */
|
||||
|
||||
/* 3. 表格内容区 */
|
||||
.leaderboard-content {
|
||||
width: 100%;
|
||||
/* max-width: 1200px; */
|
||||
@ -213,6 +225,7 @@ function rowClassName(row: TeamData) {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(26, 42, 68, 0.05);
|
||||
overflow: hidden;
|
||||
height: calc(100% - 220px);
|
||||
}
|
||||
|
||||
/* 4. Naive UI 表格深度定制 */
|
||||
@ -221,7 +234,7 @@ function rowClassName(row: TeamData) {
|
||||
}
|
||||
|
||||
:deep(.rank-table .n-data-table-th) {
|
||||
font-size: 14px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
border-bottom: 2px solid #dee2e6 !important;
|
||||
@ -236,11 +249,11 @@ function rowClassName(row: TeamData) {
|
||||
}
|
||||
|
||||
:deep(.rank-table .n-data-table-td) {
|
||||
font-size: 15px;
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e9ecef !important;
|
||||
padding-top: 16px !important;
|
||||
padding-bottom: 16px !important;
|
||||
/* padding-top: 16px !important; */
|
||||
/* padding-bottom: 16px !important; */
|
||||
}
|
||||
|
||||
/* 奇偶行交替色 */
|
||||
@ -256,7 +269,7 @@ function rowClassName(row: TeamData) {
|
||||
|
||||
:deep(.total-score) {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
font-size: 2.1rem;
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
@ -284,4 +297,11 @@ function rowClassName(row: TeamData) {
|
||||
:deep(.rank-top-3 .total-score) {
|
||||
color: #cd7f32;
|
||||
}
|
||||
|
||||
/* 强制表格容器占满父级 */
|
||||
.leaderboard-content :deep(.n-data-table) {
|
||||
height: 100% !important;
|
||||
max-height: 100% !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -116,7 +116,7 @@ onMounted(() => {
|
||||
<div class="icon-wrapper">
|
||||
<img src="@/assets/imgs/user/team-title-icon.webp" alt="team-icon" class="h-full w-full object-cover">
|
||||
</div>
|
||||
<div class="team-name text-3xl">
|
||||
<div class="team-name text-2xl">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user