Files
OJ2/apps/web/src/oj/rank/components/ClassBattle.vue
T
2026-10-26 13:52:24 +08:00

200 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { ClassBattleItem } from '@oj2/contract'
import { useThemeVars } from 'naive-ui'
import { classLabel } from 'utils/classes'
import { useRankPalette } from '../palette'
/**
* 班级对抗:全服各班这学期人均做对,绿字是这周人均涨了多少。按人均比,因为班级人数从
* 十来个到五十几个都有。列前 7 个班,自己的班不在里面就补在最后。
*/
const props = defineProps<{
items: ClassBattleItem[]
mine: string | null
teacher?: boolean
/** 放在手机页签卡里:不要自己的边框和标题 */
bare?: boolean
/** 显示「班级 PK」入口(登录了就有:学生也能比) */
pk?: boolean
}>()
const emit = defineEmits<{ pick: [className: string] }>()
const theme = useThemeVars()
const palette = useRankPalette()
const LIMIT = 7
const shown = computed(() => {
const head = props.items.slice(0, LIMIT)
const own = props.items.find((item) => item.className === props.mine)
return own && own.rank > LIMIT ? [...head, own] : head
})
const max = computed(() => Math.max(1, ...props.items.map((item) => item.perCapita)))
const summary = computed(() => {
const own = props.items.find((item) => item.className === props.mine)
if (!own) return ''
const who = props.teacher ? '这个班' : '你们班'
const faster = props.items.filter((item) => item.weekGain > own.weekGain).length + 1
const speed = own.weekGain > 0 ? ` · 这周涨得第 ${faster} 快` : ''
if (own.rank === 1) return `${who}第 1${speed}`
const prev = props.items[own.rank - 2]!
const need = Math.floor(prev.perCapita - own.perCapita) + 1
return `${who}第 ${own.rank} · 每人再做对 ${need} 道就超过${shortLabel(prev.className)}${speed}`
})
/** 同年级的班只说「3 班」,别的年级说全名 */
function shortLabel(className: string) {
if (props.mine && className.slice(0, 2) === props.mine.slice(0, 2)) return ` ${className.slice(2)} 班`
return ` ${classLabel(className)}`
}
</script>
<template>
<div class="battle" :class="{ bare }">
<div class="title">
<b v-if="!bare">班级对抗</b><span class="muted">人均做对</span>
<router-link v-if="pk" :to="{ path: '/class', query: mine ? { classes: mine } : {} }" class="pk">班级 PK ›</router-link>
</div>
<div v-for="item in shown" :key="item.className" class="row" :class="{ mine: item.className === mine }">
<span class="rank">{{ item.rank }}</span>
<button class="name" title="看班级详情" @click="emit('pick', item.className)">
{{ classLabel(item.className) }}
</button>
<span class="bar-box">
<span
class="bar"
:style="{
width: `${(item.perCapita / max) * 100}%`,
background: item.className === mine ? palette.me : palette.bar,
}"
/>
</span>
<span class="value">{{ item.perCapita }}</span>
<span class="gain">{{ item.weekGain > 0 ? `+${item.weekGain}` : '' }}</span>
</div>
<span v-if="!items.length" class="muted">这学期还没有班做对题</span>
<span v-if="summary" class="summary">{{ summary }}</span>
</div>
</template>
<style scoped>
.battle {
padding: 18px 20px;
display: flex;
flex-direction: column;
gap: 2px;
border: 1px solid v-bind('theme.borderColor');
border-radius: var(--oj-radius);
background: v-bind('theme.cardColor');
}
.battle.bare {
padding: 0;
border: 0;
background: none;
}
.title {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.title b {
font-size: var(--oj-fs-h2);
}
.muted {
font-size: var(--oj-fs-meta);
color: v-bind('theme.textColor3');
}
.row {
height: 30px;
display: flex;
align-items: center;
gap: 8px;
font-size: var(--oj-fs-sec);
}
.row.mine {
margin: 0 -8px;
padding: 0 8px;
border-radius: 4px;
background: rgba(24, 160, 88, 0.12);
}
.rank {
width: 16px;
font-size: var(--oj-fs-meta);
color: v-bind('theme.textColor3');
font-variant-numeric: tabular-nums;
}
.name {
width: 96px;
padding: 0;
border: 0;
background: none;
font: inherit;
color: v-bind('theme.primaryColor');
text-align: left;
white-space: nowrap;
cursor: pointer;
}
.name:hover {
text-decoration: underline;
}
.pk {
margin-left: auto;
font-size: var(--oj-fs-meta);
color: v-bind('theme.primaryColor');
text-decoration: none;
white-space: nowrap;
}
.mine .name {
font-weight: 700;
color: #18a058;
}
.bar-box {
flex-grow: 1;
height: 12px;
position: relative;
}
.bar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
border-radius: 2px 5px 5px 2px;
}
.value {
width: 36px;
text-align: right;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.gain {
width: 36px;
font-size: 12px;
color: #18a058;
font-variant-numeric: tabular-nums;
}
.summary {
margin-top: 8px;
font-size: var(--oj-fs-meta);
color: #18a058;
}
</style>