2026-05-01 12:24:23 +09:00

422 lines
8.7 KiB
Vue

<template>
<div class="ranking-list">
<SubPageHeader>
<template #center>
<nav class="top-tabs">
<button
v-for="tab in topTabs"
:key="tab"
class="top-tab"
:class="{ active: tab === activeTab }"
type="button"
@click="activeTab = tab"
>
{{ tab }}
</button>
</nav>
</template>
</SubPageHeader>
<main class="ranking-body">
<section class="ranking-panel">
<div class="ranking-toolbar">
<LmFilterTabs v-model="metricType" :options="metricOptions" mode="compact" />
<LmFilterTabs v-model="scopeType" :options="scopeOptions" mode="compact" />
</div>
<div class="ranking-table-card">
<div class="ranking-scroll">
<table class="ranking-table">
<thead>
<tr>
<th class="rank-col">排名</th>
<th>姓名</th>
<th class="metric-col">{{ metricLabel }}</th>
<th class="check-col"></th>
</tr>
</thead>
<tbody>
<tr v-for="student in rankingRows" :key="student.rank" :class="`rank-${student.rank}`">
<td class="rank-col">
<span v-if="student.rank <= 3" class="rank-medal" :class="`medal-${student.rank}`">
{{ student.rank }}
</span>
<span v-else class="rank-number">{{ student.rank }}</span>
</td>
<td>
<div class="student-info">
<span class="animal-avatar">{{ student.animal }}</span>
<span class="name-card">
<span class="student-name">{{ student.name }}</span>
</span>
<span class="gender-icon"></span>
</div>
</td>
<td>{{ student.value }}</td>
<td class="check-col">
<span class="fake-checkbox"></span>
</td>
</tr>
</tbody>
</table>
</div>
<table class="ranking-table current-table">
<tbody>
<tr>
<td class="rank-col">100</td>
<td>
<div class="student-info">
<span class="animal-avatar">{{ currentStudent.animal }}</span>
<span class="name-card">
<span class="student-name">{{ currentStudent.name }}</span>
</span>
<span class="gender-icon"></span>
</div>
</td>
<td class="metric-col">{{ currentStudent.value }}</td>
<td class="check-col"></td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import SubPageHeader from '@/components/layout/SubPageHeader.vue'
import LmFilterTabs, { type FilterTabOption } from '@/components/lm/LmFilterTabs.vue'
type RankingRow = {
rank: number
name: string
animal: string
value: number
}
const topTabs = ['综合', '语感', '单词', '乐贝']
const activeTab = ref('语感')
const metricType = ref('sentence')
const scopeType = ref('global')
const metricOptions: FilterTabOption[] = [
{ key: 'sentence', label: '句子数' },
{ key: 'vocabulary', label: '阅读词汇量' },
]
const scopeOptions: FilterTabOption[] = [
{ key: 'global', label: '全服' },
{ key: 'region', label: '地区' },
{ key: 'school', label: '本校' },
]
const metricLabel = computed(() => {
return metricOptions.find(option => option.key === metricType.value)?.label ?? '句子数'
})
const animalIcons = ['🐵', '🐨', '🐻', '🦁', '🐯', '🦊', '🐒']
const rankingRows: RankingRow[] = animalIcons.map((animal, index) => ({
rank: index + 1,
name: '张小小',
animal,
value: 581,
}))
const currentStudent: RankingRow = {
rank: 100,
name: '张小小',
animal: '🐶',
value: 581,
}
</script>
<style scoped lang="scss">
.ranking-list {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.top-tabs {
display: flex;
align-items: center;
gap: 56px;
}
.top-tab {
position: relative;
border: none;
background: transparent;
color: #111827;
cursor: pointer;
font-size: 36px;
font-weight: 600;
line-height: 1;
padding: 8px 0 12px;
transition: color 0.2s;
&.active {
color: #2d6fe0;
&::after {
content: "";
position: absolute;
left: 50%;
bottom: 0;
width: 34px;
height: 6px;
border-radius: 999px;
background: #3d78f2;
transform: translateX(-50%);
}
}
}
.ranking-body {
flex: 1;
min-height: 0;
padding: 0 64px 36px;
}
.ranking-panel {
width: 100%;
max-width: 1792px;
height: 100%;
margin: 0 auto;
overflow: hidden;
border: 2px solid rgba(255, 255, 255, 0.62);
border-radius: 20px;
background: rgba(255, 255, 255, 0.28);
}
.ranking-toolbar {
display: flex;
align-items: center;
gap: 52px;
height: 98px;
padding: 0 34px;
:deep(.lm-filter-tabs) {
background: rgba(230, 235, 245, 0.78);
}
:deep(.filter-btn) {
font-size: 20px;
padding: 5px 16px;
}
}
.ranking-table-card {
display: flex;
flex-direction: column;
height: calc(100% - 98px);
overflow: hidden;
border-radius: 18px;
background: rgba(255, 255, 255, 0.88);
}
.ranking-scroll {
flex: 1;
min-height: 0;
overflow: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.ranking-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
th,
td {
height: 104px;
border-bottom: 1px solid rgba(225, 230, 239, 0.52);
color: #1f2937;
font-size: 26px;
font-weight: 400;
text-align: left;
vertical-align: middle;
}
th {
position: sticky;
top: 0;
z-index: 2;
height: 58px;
color: #7d8796;
font-size: 22px;
background: rgba(247, 249, 253, 0.86);
}
th:nth-child(2),
td:nth-child(2) {
width: 48%;
}
th:nth-child(3),
td:nth-child(3) {
text-align: center;
}
}
.metric-col{
width: 820px;
}
.rank-col {
width: 410px;
padding-left: 58px;
white-space: nowrap;
}
th.rank-col {
padding-left: 62px;
}
.check-col {
width: 104px;
text-align: center;
}
.rank-1 {
background: linear-gradient(90deg, rgba(255, 246, 166, 0.5), rgba(255, 255, 255, 0.8) 62%);
}
.rank-2 {
background: linear-gradient(90deg, rgba(224, 231, 255, 0.6), rgba(255, 255, 255, 0.8) 62%);
}
.rank-3 {
background: linear-gradient(90deg, rgba(255, 229, 203, 0.5), rgba(255, 255, 255, 0.8) 62%);
}
.rank-number {
color: #333;
font-size: 26px;
}
.rank-medal {
display: inline-flex;
width: 34px;
height: 34px;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #8a6d13;
font-size: 18px;
font-weight: 600;
box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.55);
}
.medal-1 {
background: linear-gradient(180deg, #ffef7d, #f6c844);
}
.medal-2 {
background: linear-gradient(180deg, #e8ebf8, #bdc5df);
}
.medal-3 {
background: linear-gradient(180deg, #f5dbad, #d3aa67);
}
.student-info {
display: flex;
align-items: center;
gap: 24px;
}
.animal-avatar {
display: inline-flex;
width: 54px;
height: 54px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #fff;
font-size: 36px;
line-height: 1;
}
.name-card {
position: relative;
display: inline-flex;
width: 250px;
height: 58px;
align-items: center;
justify-content: center;
border: 3px solid rgba(246, 151, 196, 0.78);
border-radius: 999px;
background: #fff8fb;
box-shadow:
inset 0 0 0 2px rgba(255, 255, 255, 0.95),
0 1px 0 rgba(255, 255, 255, 0.9);
&::before,
&::after {
content: "";
position: absolute;
top: 50%;
width: 10px;
height: 10px;
border-radius: 50%;
transform: translateY(-50%);
}
&::before {
left: 16px;
background: #9be7ff;
}
&::after {
right: 16px;
background: #ffe68a;
}
}
.student-name {
color: #172033;
font-weight: 600;
}
.gender-icon {
color: #ef7aa9;
font-size: 22px;
font-weight: 600;
}
.fake-checkbox {
display: inline-block;
width: 42px;
height: 42px;
border: 2px solid rgba(218, 226, 238, 0.72);
background: rgba(245, 247, 250, 0.88);
}
.current-table {
flex-shrink: 0;
background: rgba(242, 247, 255, 0.9);
box-shadow: 0 -18px 26px rgba(236, 244, 255, 0.9);
td {
height: 104px;
border-bottom: none;
color: #4f8bd3;
}
.rank-col {
color: #4f8bd3;
}
}
</style>