feat(排名): 个性签名挂到排名上,领奖台上每人一句、「你」卡里就地改;全服排名只列到第 100 名,展开不再卡死

- 领奖台:头像旁边一个签名气泡(手机在头像上面),只露两行,长的指上去看全文
- 「你」卡:自己的签名带「改」,就地改不用跳设置页;没写的提示「写一句个性签名」;前一名、后一名名字下面带他们的签名
- 赛道:桌面有签名的名字后面一个引号,指上去看;手机直接写在名字后面
- 个性签名以后限 30 个字(老的长签名不动,榜上只露两行)
- 老师在名单「⋯」或台上气泡的「×」能清空不合适的签名(DELETE /rankings/mood/:userId)
- 全服名单最多列到第 100 名:原来展开会一次摊开一千五百多人把页面卡死

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-10 09:52:06 +08:00
1 parent 540d88355c
commit 2553c6ddfe
10 files changed
+588 -69

No files matched your search

+218 -11
View File
@@ -1,6 +1,8 @@
<script setup lang="ts">
import type { RankRow } from "@oj2/contract"
import { MOOD_MAX, type RankRow } from "@oj2/contract"
import { useThemeVars } from "naive-ui"
import { updateProfile } from "oj/api"
import { useUserStore } from "shared/store/user"
import UserName from "shared/components/UserName.vue"
import { gapText, toPass } from "../utils"
import RankAvatar from "./RankAvatar.vue"
@@ -18,6 +20,9 @@ export interface MiniRank {
*
* 用词只说位置(前一名 / 后一名 / 进步最大),不用「追」「对手」「冲」:用户指出
* 「要追的」「追你」会被中学生读成追求谁,「冲得最猛」也容易误会。
*
* 个性签名(设计稿「签名 A」):自己的签名挂在大字下面,「改」就地改,不用跳去设置页;
* 没写的给一句「写一句个性签名」。前一名、后一名的签名挂在他们名字下面。
*/
const props = defineProps<{
me: RankRow
@@ -31,8 +36,39 @@ const props = defineProps<{
/** 手机:前后一名的说明换到名字下面一行,别被截断 */
compact?: boolean
}>()
const emit = defineEmits<{ moodSaved: [mood: string | null] }>()
const theme = useThemeVars()
const message = useMessage()
const userStore = useUserStore()
const editing = ref(false)
const draft = ref("")
const saving = ref(false)
watch(editing, (open) => {
if (open) draft.value = props.me.mood ?? ""
})
async function saveMood() {
const mood = draft.value.trim()
if (mood.length > MOOD_MAX) {
message.warning(`个性签名最多 ${MOOD_MAX} 个字`)
return
}
saving.value = true
try {
await updateProfile({ mood })
if (userStore.profile) userStore.profile.mood = mood || null
emit("moodSaved", mood || null)
editing.value = false
message.success(mood ? "签名改好了" : "签名清空了")
} catch {
message.error("没保存上,再试一次")
} finally {
saving.value = false
}
}
const headline = computed(() => {
const { me, ahead, third } = props
@@ -77,6 +113,70 @@ function threatNote(me: RankRow, behind: RankRow) {
</div>
</div>
</div>
<n-popover
v-model:show="editing"
trigger="click"
placement="bottom-start"
:style="{ width: '300px' }"
>
<template #trigger>
<button v-if="me.mood" class="mood" aria-label="改个性签名">
<span class="quote-mark" aria-hidden="true">“</span>
<n-ellipsis class="mood-text" :line-clamp="2" :tooltip="false">{{ me.mood }}</n-ellipsis>
<span class="mood-edit">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M4 20h4L19 9l-4-4L4 16z" />
</svg>
改
</span>
</button>
<button v-else class="mood empty">
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M4 20h4L19 9l-4-4L4 16z" />
</svg>
写一句个性签名,上了领奖台全班都看得到
</button>
</template>
<form class="mood-form" @submit.prevent="saveMood">
<label class="mood-label" for="rank-mood-input">个性签名</label>
<n-input
v-model:value="draft"
:input-props="{ id: 'rank-mood-input' }"
:maxlength="MOOD_MAX"
show-count
clearable
placeholder="写点什么,让大家认识你"
autofocus
/>
<span class="mood-hint">排名、个人主页上都会显示</span>
<div class="mood-actions">
<n-button size="small" @click="editing = false">取消</n-button>
<n-button size="small" type="primary" attr-type="submit" :loading="saving">
保存
</n-button>
</div>
</form>
</n-popover>
<div v-if="headline" class="headline">
<svg
width="18"
@@ -97,14 +197,24 @@ function threatNote(me: RankRow, behind: RankRow) {
<div v-if="ahead" class="rival">
<span class="tag chase">前一名</span>
<RankAvatar :username="ahead.user.username" :avatar="ahead.avatar" :size="20" />
<UserName :username="ahead.user.username" class="rival-name" />
<span class="note">{{ chaseNote(me, ahead) }}</span>
<div class="rival-body">
<div class="rival-line">
<UserName :username="ahead.user.username" class="rival-name" />
<span class="note">{{ chaseNote(me, ahead) }}</span>
</div>
<span v-if="ahead.mood" class="rival-mood" :title="ahead.mood">“{{ ahead.mood }}”</span>
</div>
</div>
<div v-if="behind" class="rival">
<span class="tag threat">后一名</span>
<RankAvatar :username="behind.user.username" :avatar="behind.avatar" :size="20" />
<UserName :username="behind.user.username" class="rival-name" />
<span class="note">{{ threatNote(me, behind) }}</span>
<div class="rival-body">
<div class="rival-line">
<UserName :username="behind.user.username" class="rival-name" />
<span class="note">{{ threatNote(me, behind) }}</span>
</div>
<span v-if="behind.mood" class="rival-mood" :title="behind.mood">“{{ behind.mood }}”</span>
</div>
</div>
<div v-if="minis.length" class="minis">
<button v-for="mini in minis" :key="mini.label" class="mini" @click="mini.go">
@@ -206,24 +316,121 @@ function threatNote(me: RankRow, behind: RankRow) {
.rival {
display: flex;
align-items: center;
align-items: flex-start;
gap: 8px;
font-size: 13px;
min-width: 0;
}
.compact .rival {
display: grid;
grid-template-columns: auto auto 1fr;
.rival > .tag {
margin-top: 2px;
}
.rival-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.rival-line {
display: flex;
align-items: center;
row-gap: 1px;
gap: 8px;
min-width: 0;
}
.compact .rival-line {
flex-direction: column;
align-items: flex-start;
gap: 1px;
}
.compact .note {
grid-column: 3;
white-space: normal;
}
.rival-mood {
font-size: 12px;
color: v-bind("theme.textColor3");
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mood {
display: flex;
align-items: flex-start;
gap: 6px;
width: 100%;
padding: 8px 10px;
border: 1px solid rgba(24, 160, 88, 0.3);
border-radius: 6px;
background: v-bind("theme.cardColor");
color: v-bind("theme.textColor1");
font: inherit;
font-size: 13px;
line-height: 1.45;
text-align: left;
cursor: pointer;
}
.mood.empty {
align-items: center;
border-style: dashed;
color: #18a058;
}
.mood:hover {
border-color: #18a058;
}
.quote-mark {
color: rgba(24, 160, 88, 0.55);
font-family: Georgia, serif;
font-size: 22px;
line-height: 18px;
}
.mood-text {
flex: 1;
min-width: 0;
word-break: break-all;
}
.mood-edit {
display: inline-flex;
align-items: center;
gap: 3px;
padding-top: 1px;
font-size: 12px;
color: #18a058;
white-space: nowrap;
}
.mood-form {
display: flex;
flex-direction: column;
gap: 8px;
}
.mood-label {
font-size: 13px;
font-weight: 600;
}
.mood-hint {
font-size: 12px;
color: v-bind("theme.textColor3");
}
.mood-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.rival-name {
flex-shrink: 0;
max-width: 120px;
+195 -37
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { RankRow } from "@oj2/contract"
import { useThemeVars } from "naive-ui"
import { useBreakpoints } from "shared/composables/breakpoints"
import UserName from "shared/components/UserName.vue"
import { useRankPalette } from "../palette"
import RankAvatar from "./RankAvatar.vue"
@@ -8,17 +9,22 @@ import RankAvatar from "./RankAvatar.vue"
/**
* 领奖台:前三名站在台阶上,第一名戴皇冠。赛道从第 4 名接着排,前三不再出现第二遍。
* 你的前一名站在台上时台阶描蓝边、挂「前一名」,和赛道里的蓝条是一回事。
*
* 个性签名挂在台上(设计稿「签名 A」):桌面是头像旁边一个气泡,像在说一句话;手机台阶窄,
* 气泡放到头像上面。只露两行,长的指上去(手机点一下)看全文。老师能在气泡上清空它。
*/
const props = defineProps<{
rows: RankRow[]
meId?: number
chaseId?: number
threatId?: number
teacher?: boolean
}>()
defineEmits<{ open: [username: string] }>()
defineEmits<{ open: [username: string]; clearMood: [row: RankRow] }>()
const theme = useThemeVars()
const palette = useRankPalette()
const { isDesktop } = useBreakpoints()
/** 台阶从左到右是 2、1、3 */
const steps = computed(() =>
@@ -27,41 +33,66 @@ const steps = computed(() =>
.filter((step): step is { place: number; row: RankRow } => !!step.row),
)
const HEIGHTS: Record<number, number> = { 1: 50, 2: 38, 3: 30 }
/** 台阶比原来矮一截:台上多了签名气泡,矮下来赛道才放得进机房第一屏(41 人的班也够) */
const HEIGHTS: Record<number, number> = { 1: 40, 2: 30, 3: 26 }
</script>
<template>
<div class="podium">
<div class="podium" :class="{ phone: !isDesktop }">
<div v-for="{ place, row } in steps" :key="place" class="step" :class="`p${place}`">
<div class="top">
<svg
v-if="place === 1"
class="crown"
width="18"
height="18"
viewBox="0 0 24 24"
fill="#f2c94c"
stroke="#9a6700"
stroke-width="1.6"
stroke-linejoin="round"
role="img"
aria-label="第一名"
>
<path d="M3 8l4.5 4L12 5l4.5 7L21 8l-2 11H5z" />
</svg>
<span v-if="row.user.id === chaseId" class="tag chase">前一名</span>
<span v-else-if="row.user.id === threatId" class="tag threat">后一名</span>
<span v-else-if="row.user.id === meId" class="tag me">你</span>
<div class="who">
<span class="face">
<svg
v-if="place === 1"
class="crown"
width="18"
height="18"
viewBox="0 0 24 24"
fill="#f2c94c"
stroke="#9a6700"
stroke-width="1.6"
stroke-linejoin="round"
role="img"
aria-label="第一名"
>
<path d="M3 8l4.5 4L12 5l4.5 7L21 8l-2 11H5z" />
</svg>
<RankAvatar
:username="row.user.username"
:avatar="row.avatar"
:size="place === 1 ? 34 : 28"
:me="row.user.id === meId"
/>
</span>
<div class="words">
<div class="name-line">
<button class="name" @click="$emit('open', row.user.username)">
<UserName :username="row.user.username" />
</button>
<span v-if="row.user.id === chaseId" class="tag chase">前一名</span>
<span v-else-if="row.user.id === threatId" class="tag threat">后一名</span>
<span v-else-if="row.user.id === meId" class="tag me">你</span>
</div>
<div v-if="row.mood" class="bubble">
<n-ellipsis
:line-clamp="2"
:expand-trigger="isDesktop ? undefined : 'click'"
:tooltip="isDesktop ? { contentStyle: { maxWidth: '300px' } } : false"
>
{{ row.mood }}
</n-ellipsis>
<button
v-if="teacher"
class="clear"
aria-label="清空他的签名"
title="清空他的签名"
@click="$emit('clearMood', row)"
>
×
</button>
</div>
</div>
</div>
<RankAvatar
:username="row.user.username"
:avatar="row.avatar"
:size="place === 1 ? 34 : 28"
:me="row.user.id === meId"
/>
<button class="name" @click="$emit('open', row.user.username)">
<UserName :username="row.user.username" />
</button>
<div
class="block"
:style="{
@@ -98,19 +129,146 @@ const HEIGHTS: Record<number, number> = { 1: 50, 2: 38, 3: 30 }
}
.step {
width: 170px;
flex: 0 1 272px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
/* 桌面:头像在左,右边名字一行、签名气泡一行,像一句聊天 */
.who {
display: flex;
align-items: flex-end;
gap: 8px;
padding: 0 4px;
min-width: 0;
}
.face {
position: relative;
display: inline-flex;
flex-shrink: 0;
}
.crown {
position: absolute;
left: -7px;
top: -11px;
transform: rotate(-18deg);
}
.words {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
}
.top {
height: 18px;
.name-line {
display: flex;
align-items: center;
gap: 4px;
gap: 5px;
min-width: 0;
}
.bubble {
position: relative;
padding: 3px 9px;
border: 1px solid v-bind("theme.dividerColor");
border-radius: 8px;
background: v-bind("theme.actionColor");
font-size: 12px;
line-height: 16px;
color: v-bind("theme.textColor2");
word-break: break-all;
}
.clear {
position: absolute;
right: -7px;
top: -7px;
width: 16px;
height: 16px;
padding: 0;
border: 1px solid v-bind("theme.borderColor");
border-radius: 8px;
background: v-bind("theme.cardColor");
color: v-bind("theme.textColor3");
font-size: 12px;
line-height: 13px;
cursor: pointer;
display: none;
}
.bubble:hover .clear,
.clear:focus-visible {
display: block;
}
.clear:hover {
color: v-bind("theme.errorColor");
border-color: v-bind("theme.errorColor");
}
/* 手机:台阶窄,气泡挪到头像上面,下面依次头像、名字 */
.phone .step {
flex: 1 1 0;
gap: 3px;
}
.phone .who {
flex-direction: column;
align-items: center;
gap: 3px;
padding: 0;
}
.phone .words {
display: contents;
}
.phone .name-line {
max-width: 100%;
flex-direction: column;
gap: 1px;
}
.phone .face {
order: -1;
margin-top: 14px;
}
.phone .crown {
left: 50%;
top: -17px;
margin-left: -9px;
transform: none;
}
.phone .bubble {
order: -2;
width: 100%;
box-sizing: border-box;
margin-bottom: 4px;
padding: 3px 6px;
font-size: 11.5px;
line-height: 15px;
}
.phone .bubble::after {
content: "";
position: absolute;
left: 50%;
bottom: -5px;
width: 8px;
height: 8px;
margin-left: -4px;
background: v-bind("theme.actionColor");
border-right: 1px solid v-bind("theme.dividerColor");
border-bottom: 1px solid v-bind("theme.dividerColor");
transform: rotate(45deg);
}
.tag {
@@ -157,7 +315,7 @@ const HEIGHTS: Record<number, number> = { 1: 50, 2: 38, 3: 30 }
align-items: flex-start;
justify-content: center;
gap: 10px;
padding-top: 5px;
padding-top: 3px;
}
.place {
+61 -11
View File
@@ -14,11 +14,16 @@ import RankAvatar from "./RankAvatar.vue"
* 手机(设计稿「手机版」):窄屏上条只剩几十像素宽,看不出谁多谁少,所以条改成每一行的
* 底色;默认只列前几行和你附近几行,「看全班 N 人」点开。换范围 / 时间段时父组件换 key
* 重新挂载,折叠状态跟着复位。
*
* 个性签名(设计稿「签名 A」):桌面一行只有一百来像素的条,放不下一句话,所以有签名的
* 名字后面挂一个引号,指上去看;手机每行本来就有空,签名直接写在名字后面。
*/
const props = defineProps<{
rows: RankRow[]
total: number
complete: boolean
/** 名单最多列到第几名(全服 100),折叠处的「第 x–y 名」到此为止 */
cap?: number | null
/** 条的满格是几道(第 2 名的数,第 1 名常常一骑绝尘) */
scale: number
meId?: number
@@ -38,6 +43,7 @@ const emit = defineEmits<{
submissions: [username: string]
analysis: [username: string]
hide: [row: RankRow]
clearMood: [row: RankRow]
}>()
const theme = useThemeVars()
@@ -73,7 +79,9 @@ const items = computed<Item[]>(() => {
list.push({ kind: "row", row })
last = row.rank
}
const end = props.complete ? (props.rows.at(-1)?.rank ?? last) : props.total
const end = props.complete
? (props.rows.at(-1)?.rank ?? last)
: Math.min(props.total, props.cap ?? props.total)
if (last < end) list.push({ kind: "gap", from: last + 1, to: end })
return list
})
@@ -129,15 +137,19 @@ function width(row: RankRow) {
return `${Math.max(1.5, Math.min(100, (row.solved / Math.max(1, props.scale)) * 100))}%`
}
const menu: DropdownOption[] = [
{ label: "看他的提交", key: "submissions" },
{ label: "看他的智能分析", key: "analysis" },
{ label: "不计入排名…", key: "hide" },
]
function menuOf(row: RankRow): DropdownOption[] {
return [
{ label: "看他的提交", key: "submissions" },
{ label: "看他的智能分析", key: "analysis" },
...(row.mood ? [{ label: "清空他的签名…", key: "mood" }] : []),
{ label: "不计入排名…", key: "hide" },
]
}
function onMenu(key: string, row: RankRow) {
if (key === "submissions") emit("submissions", row.user.username)
else if (key === "analysis") emit("analysis", row.user.username)
else if (key === "mood") emit("clearMood", row)
else emit("hide", row)
}
</script>
@@ -168,6 +180,7 @@ function onMenu(key: string, row: RankRow) {
<button class="who" @click="emit('open', item.row.user.username)">
<UserName :username="item.row.user.username" />
</button>
<span class="mood">{{ item.row.mood }}</span>
<span v-if="item.row.user.id === chaseId" class="tag chase">前一名</span>
<span v-else-if="item.row.user.id === threatId" class="tag threat">后一名</span>
<span v-else-if="item.row.user.id === hotId" class="tag hot">进步最大</span>
@@ -182,7 +195,7 @@ function onMenu(key: string, row: RankRow) {
<n-dropdown
v-if="teacher"
trigger="click"
:options="menu"
:options="menuOf(item.row)"
@select="(key: string) => onMenu(key, item.row)"
>
<button class="more" aria-label="更多">⋯</button>
@@ -196,9 +209,28 @@ function onMenu(key: string, row: RankRow) {
:size="16"
:me="item.row.user.id === meId"
/>
<button class="who" @click="emit('open', item.row.user.username)">
<UserName :username="item.row.user.username" />
</button>
<n-tooltip :disabled="!item.row.mood" :style="{ maxWidth: '300px' }">
<template #trigger>
<button class="who" @click="emit('open', item.row.user.username)">
<UserName :username="item.row.user.username" />
<svg
v-if="item.row.mood"
class="quote"
width="11"
height="11"
viewBox="0 0 24 24"
fill="currentColor"
role="img"
aria-label="有个性签名"
>
<path
d="M4 18v-5c0-4 2-7 6-8l1 2c-2 1-3 2.5-3 4h3v7zm9 0v-5c0-4 2-7 6-8l1 2c-2 1-3 2.5-3 4h3v7z"
/>
</svg>
</button>
</template>
{{ item.row.mood }}
</n-tooltip>
<span class="bar-box">
<span class="bar" :style="{ width: width(item.row), background: barColor(item.row) }" />
</span>
@@ -218,7 +250,7 @@ function onMenu(key: string, row: RankRow) {
<n-dropdown
v-if="teacher"
trigger="click"
:options="menu"
:options="menuOf(item.row)"
@select="(key: string) => onMenu(key, item.row)"
>
<button class="more" aria-label="更多">⋯</button>
@@ -295,7 +327,18 @@ function onMenu(key: string, row: RankRow) {
.prow .who {
width: auto;
max-width: 120px;
}
/* 手机:签名跟在名字后面,一行放不下就省略 */
.mood {
flex: 1;
min-width: 0;
font-size: 12px;
color: v-bind("theme.textColor3");
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prow .rank {
@@ -351,9 +394,16 @@ function onMenu(key: string, row: RankRow) {
text-align: left;
cursor: pointer;
display: flex;
align-items: center;
gap: 3px;
min-width: 0;
}
.quote {
flex-shrink: 0;
color: v-bind("theme.textColor3");
}
.bar-box {
flex-grow: 1;
height: 11px;
+38 -4
View File
@@ -8,7 +8,7 @@ import type {
WeeklyChampion,
} from "@oj2/contract"
import { useThemeVars } from "naive-ui"
import { getClassBattle, getRankBoard, getWeeklyChampions, setRankHidden } from "oj/api"
import { clearMood, getClassBattle, getRankBoard, getWeeklyChampions, setRankHidden } from "oj/api"
import { classLabel } from "oj/submission/utils"
import UserName from "shared/components/UserName.vue"
import { useBreakpoints } from "shared/composables/breakpoints"
@@ -199,11 +199,14 @@ const subtitle = computed(() => {
const b = board.value
if (!b) return ""
const head = b.scope === "class" ? `${b.total} 人` : `${b.total} 人做对过`
const part = b.complete
? ""
: b.me
const capped = !!b.cap && b.total > b.cap
const part = !b.complete
? b.me
? " · 前面一段 + 你附近一段"
: ` · 只列前面 ${b.rows.length} 名`
: capped
? ` · 只列前 ${b.cap} 名`
: ""
return `${head} · 一样多的,先做到的在前${part}`
})
/** 手机上标题旁边那句:只说从哪天起、跟什么时候比 */
@@ -342,6 +345,32 @@ function hide(row: RankRow) {
})
}
/** 签名改了 / 清了:榜上这个人的每一处(赛道、领奖台、「你」卡、前后一名)就地换掉,不重取 */
function setMood(userId: number, mood: string | null) {
const b = board.value
if (!b) return
for (const row of [...b.rows, b.me, b.ahead, b.behind])
if (row?.user.id === userId) row.mood = mood
}
function onMoodSaved(mood: string | null) {
if (meId.value) setMood(meId.value, mood)
}
function confirmClearMood(row: RankRow) {
dialog.warning({
title: "清空个性签名",
content: `${row.user.username} 的签名「${row.mood}」会被清空。只清这一句,他之后还能重新写。`,
positiveText: "清空",
negativeText: "取消",
onPositiveClick: async () => {
await clearMood(row.user.id)
setMood(row.user.id, null)
message.success("签名已清空")
},
})
}
async function restore(userId: number) {
await setRankHidden(userId, false)
message.success("已恢复")
@@ -414,7 +443,9 @@ async function restore(userId: number) {
:me-id="meId"
:chase-id="board.ahead?.user.id"
:threat-id="board.behind?.user.id"
:teacher="teacher"
@open="open"
@clear-mood="confirmClearMood"
/>
<RankTrack
:key="`${board.scope}|${board.period}|${board.className}`"
@@ -422,6 +453,7 @@ async function restore(userId: number) {
:whole-label="board.scope === 'class' ? `看全班 ${board.total} 人` : undefined"
:total="board.total"
:complete="board.complete"
:cap="board.cap"
:scale="scale"
:me-id="meId"
:chase-id="board.ahead?.user.id"
@@ -435,6 +467,7 @@ async function restore(userId: number) {
@submissions="openSubmissions"
@analysis="openAnalysis"
@hide="hide"
@clear-mood="confirmClearMood"
/>
</template>
<div v-else-if="!loading" class="empty">排名没取到,刷新一下试试</div>
@@ -452,6 +485,7 @@ async function restore(userId: number) {
:label="`${scopeLabel(board.scope)} · ${periodLabel(board.period)}`"
:minis="minis"
:compact="!isDesktop"
@mood-saved="onMoodSaved"
/>
<div v-else-if="board.hidden" class="note-card">
<b>你现在不计入排名</b>