Files
reader-star/apps/admin/src/views/user/rank-list/index.vue
rjl 2fad025496 feat(admin): 新增品牌logo资源并优化用户界面组件
- 新增重庆、树人、文字logo资源(PNG和WebP格式)
- 新增用户界面背景图片资源(内容背景、对框、主页背景等)
- 优化ActionButton组件样式(自适应宽度、禁用状态样式)
- 更新CompetitionLayout组件header布局,集成新logo资源
- 优化TianZiGe组件功能和交互体验
- 更新竞赛相关页面组件(QuestionConfig、draw、QuestionRenderer等)
- 更新排名列表页面样式和功能
- 更新诗词常识题库数据
- 完善Competition类型定义
2026-03-18 16:58:13 +08:00

288 lines
7.4 KiB
Vue

<script setup lang="tsx">
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 ActionButton from '@/components/custom/user/ActionButton.vue'
import { fetchUserRankList } from '@/service/api/rank'
import ExtraTimeModal from './components/ExtraTimeModal.vue'
const route = useRoute()
const message = useMessage()
const MainID = Number(route.query.activityId)
const RoundType = Number(route.query.RoundType) || 0
const hasExtraTimeBtn = RoundType === 0
const showExtraTimeModal = ref(false)
const teamOptions = ref<{ label: string, value: number }[]>([])
function handleAddTime() {
showExtraTimeModal.value = true
}
// 定义数据结构
interface TeamData {
id: string
rank: number
teamName: string
[key: string]: number | string // 允许任意数量的题目分数
total: number
}
const columns = ref<DataTableColumns<TeamData>>([
{
title: '排名',
key: 'rank',
align: 'center',
width: 80,
render(row: TeamData) {
const rank = row.rank
if (rank === 1) {
return <NTag type="warning" size="large" round>🏆</NTag>
}
if (rank === 2) {
return <NTag type="info" size="large" round>🥈</NTag>
}
if (rank === 3) {
return <NTag type="success" size="large" round>🥉</NTag>
}
return <span>{rank}</span>
},
},
{ title: '队伍名称', key: 'teamName', align: 'left', width: 150, className: 'team-name' },
{ title: '总积分', key: 'total', align: 'center', className: 'total-score' },
])
const tableData = ref<TeamData[]>([])
async function getUserRankList() {
const { data, error } = await fetchUserRankList(MainID, RoundType)
if (error) {
message.error(error.message || '获取排行榜失败')
return
}
if (data?.data && data.data.length > 0) {
// 动态生成题目列
const firstTeam = data.data[0]
const questionColumns = firstTeam.Questions.map(q => ({
title: `${q.QuestionNO}`,
key: `q${q.QuestionNO}`,
align: 'center' as const,
}))
// 更新表头
columns.value = [
{
title: '排名',
key: 'rank',
align: 'center',
width: 80,
render(row: TeamData) {
const rank = row.rank
if (rank === 1) {
return <NTag type="warning" size="large" class="!py-6 !text-3xl" round>🏆</NTag>
}
if (rank === 2) {
return <NTag type="info" size="large" class="!py-6 !text-3xl" round>🥈</NTag>
}
if (rank === 3) {
return <NTag type="success" size="large" class="!py-6 !text-3xl" round>🥉</NTag>
}
return <span>{rank}</span>
},
},
{ title: '队伍名称', key: 'teamName', align: 'left', width: 150, className: 'team-name' },
...questionColumns,
{ title: '总积分', key: 'total', align: 'center', className: 'total-score' },
]
tableData.value = data.data.map((item, index) => {
const questions: Record<string, number> = {}
item.Questions.forEach((q) => {
questions[`q${q.QuestionNO}`] = q.ResultPotin
})
return {
id: String(item.TeamID),
rank: index + 1,
teamName: item.Name,
total: item.TotalPoint,
...questions,
} as TeamData
})
teamOptions.value = data.data.map(item => ({ label: item.Name, value: item.TeamID }))
}
}
onMounted(() => {
if (MainID) {
getUserRankList()
}
})
// 动态赋予行样式:根据排名和奇偶数设置 class
function rowClassName(row: TeamData) {
const rank = row.rank
const classes = ['rank-row']
if (rank >= 1 && rank <= 3) {
classes.push(`rank-top-${rank}`)
}
return classes.join(' ')
}
</script>
<template>
<CompetitionLayout :show-back="false" :show-next="false" :show-title="false">
<div class="leaderboard-container">
<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">
"勤学如春起之苗,不见其增,日有所长"
</p>
<ActionButton
v-if="hasExtraTimeBtn" class="position-absolute bottom-20px right-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"
/>
</div>
</div>
<ExtraTimeModal v-model:show="showExtraTimeModal" :activity-id="MainID" :team-options="teamOptions" />
</CompetitionLayout>
</template>
<style scoped>
/* 1. 整体容器和背景 */
.leaderboard-container {
width: 100%;
min-height: 100vh;
background-image: url('https://raw.githubusercontent.com/0x3f3f3f3f/figure-bed/main/img/20240524150352.png');
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
/* width: 100%; */
/* height: 781px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: url('@/assets/imgs/user/team-bg.webp') no-repeat center center;
background-size: 100% 100%;
transform-origin: center;
padding: 40px 20px;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; */
}
/* 2. 页面标题 */
.leaderboard-header {
text-align: center;
}
.leaderboard-header h1 {
font-size: 2.5rem;
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; */
background-color: #fff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(26, 42, 68, 0.05);
overflow: hidden;
}
/* 4. Naive UI 表格深度定制 */
:deep(.rank-table .n-data-table-thead) {
background-color: #f8f9fa;
}
:deep(.rank-table .n-data-table-th) {
font-size: 14px;
font-weight: 600;
color: #495057;
border-bottom: 2px solid #dee2e6 !important;
}
:deep(.rank-table .n-data-table-tr) {
transition: background-color 0.2s;
}
:deep(.rank-table .n-data-table-tr:hover) {
background-color: #f1f3f5 !important;
}
:deep(.rank-table .n-data-table-td) {
font-size: 15px;
color: #333;
border-bottom: 1px solid #e9ecef !important;
padding-top: 16px !important;
padding-bottom: 16px !important;
}
/* 奇偶行交替色 */
:deep(.rank-table .n-data-table-tr:nth-of-type(odd)) {
background-color: #fdfdfd;
}
/* 队伍名称和总分加粗 */
:deep(.team-name) {
font-weight: 600;
color: #2c3e50;
}
:deep(.total-score) {
font-weight: 700;
font-size: 1.1rem;
color: #e67e22;
}
/* 前三名高亮 */
:deep(.rank-top-1 .n-data-table-td) {
background-color: #fff9e6 !important;
}
:deep(.rank-top-2 .n-data-table-td) {
background-color: #f0f4ff !important;
}
:deep(.rank-top-3 .n-data-table-td) {
background-color: #e6f4ea !important;
}
:deep(.rank-top-1 .total-score) {
color: #d4af37;
}
:deep(.rank-top-2 .total-score) {
color: #a0a0a0;
}
:deep(.rank-top-3 .total-score) {
color: #cd7f32;
}
</style>