Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
530e1a3d5d
commit
195069450f
480 files changed
+15507
-24650
No files matched your search
@@ -1,31 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { continueStep, doneCount, lessonPath, splitTitle, type TutorialType } from "oj/learn/lesson"
|
||||
import { Icon } from "@iconify/vue"
|
||||
import {
|
||||
getAnnouncementList,
|
||||
getKnowledgeMap,
|
||||
getContestList,
|
||||
getLastVisit,
|
||||
getLearnProgress,
|
||||
getSubmissions,
|
||||
getTutorials,
|
||||
getWeeklyRank,
|
||||
} from "oj/api"
|
||||
import { getClassActivity } from "shared/api"
|
||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||
import { pickHeadline, type Headline } from "oj/user/knowledge"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import { ContestStatus, CONTEST_STATUS, JUDGE_STATUS, LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||
import { duration, parseTime, zonedParts, zonedYear } from "utils/functions"
|
||||
import type {
|
||||
AnnouncementListItem,
|
||||
ClassActivity,
|
||||
ClassActivityProblem,
|
||||
Contest,
|
||||
LastVisit,
|
||||
SubmissionListItem,
|
||||
WeeklyRank,
|
||||
} from "utils/types"
|
||||
import { continueStep, doneCount, lessonPath, splitTitle, type TutorialType } from 'oj/learn/lesson'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { getAnnouncementList, getKnowledgeMap, getContestList, getLastVisit, getLearnProgress, getSubmissions, getTutorials, getWeeklyRank } from 'oj/api'
|
||||
import { getClassActivity } from 'shared/api'
|
||||
import SubmissionResultTag from 'shared/components/SubmissionResultTag.vue'
|
||||
import { pickHeadline, type Headline } from 'oj/user/knowledge'
|
||||
import { useUserStore } from 'shared/store/user'
|
||||
import { ContestStatus, CONTEST_STATUS, JUDGE_STATUS, LANGUAGE_SHOW_VALUE } from 'utils/constants'
|
||||
import { duration, parseTime, zonedParts, zonedYear } from 'utils/functions'
|
||||
import type { AnnouncementListItem, ClassActivity, ClassActivityProblem, Contest, LastVisit, SubmissionListItem, WeeklyRank } from 'utils/types'
|
||||
|
||||
interface Track {
|
||||
type: TutorialType
|
||||
@@ -54,12 +37,7 @@ const knowledgeHeadline = ref<Headline | null>(null)
|
||||
const loaded = ref(false)
|
||||
|
||||
/** 头一回登录、还没交过:问候换成「欢迎」,告诉他题在哪找 */
|
||||
const firstVisit = computed(
|
||||
() =>
|
||||
!!lastVisit.value &&
|
||||
!lastVisit.value.previousLogin &&
|
||||
(userStore.profile?.submissionNumber ?? 0) === 0,
|
||||
)
|
||||
const firstVisit = computed(() => !!lastVisit.value && !lastVisit.value.previousLogin && (userStore.profile?.submissionNumber ?? 0) === 0)
|
||||
|
||||
/** 离上次来多少天。超过 30 天后端不给「上次来」卡,这里只在问候里说一句 */
|
||||
const awayDays = computed(() => {
|
||||
@@ -71,32 +49,27 @@ const longAway = computed(() => awayDays.value > 30)
|
||||
|
||||
const awayText = computed(() => {
|
||||
const previous = lastVisit.value?.previousLogin
|
||||
if (!previous) return ""
|
||||
if (!previous) return ''
|
||||
const days = awayDays.value
|
||||
const gap =
|
||||
days >= 365
|
||||
? `隔了 ${Math.floor(days / 365)} 年多`
|
||||
: days >= 60
|
||||
? `隔了 ${Math.floor(days / 30)} 个月`
|
||||
: `隔了 ${days} 天`
|
||||
const gap = days >= 365 ? `隔了 ${Math.floor(days / 365)} 年多` : days >= 60 ? `隔了 ${Math.floor(days / 30)} 个月` : `隔了 ${days} 天`
|
||||
return `上次来是 ${dateText(previous)},${gap}`
|
||||
})
|
||||
|
||||
const greeting = computed(() => {
|
||||
if (longAway.value) return "好久不见"
|
||||
if (longAway.value) return '好久不见'
|
||||
const hour = zonedParts(new Date())!.hour
|
||||
if (hour < 5) return "夜深了"
|
||||
if (hour < 11) return "早上好"
|
||||
if (hour < 13) return "中午好"
|
||||
if (hour < 18) return "下午好"
|
||||
return "晚上好"
|
||||
if (hour < 5) return '夜深了'
|
||||
if (hour < 11) return '早上好'
|
||||
if (hour < 13) return '中午好'
|
||||
if (hour < 18) return '下午好'
|
||||
return '晚上好'
|
||||
})
|
||||
|
||||
const weeklyText = computed(() => {
|
||||
const me = weekly.value?.me
|
||||
if (!weekly.value) return ""
|
||||
const where = weekly.value.scope === "class" ? "班里" : "全站"
|
||||
if (!me) return "本周还没有新通过的题,做出 1 题就能上进步榜"
|
||||
if (!weekly.value) return ''
|
||||
const where = weekly.value.scope === 'class' ? '班里' : '全站'
|
||||
if (!me) return '本周还没有新通过的题,做出 1 题就能上进步榜'
|
||||
return `本周新通过 ${me.solvedCount} 题,${where}第 ${me.rank} 名`
|
||||
})
|
||||
|
||||
@@ -109,10 +82,7 @@ async function loadTrack(type: TutorialType, label: string): Promise<Track | nul
|
||||
if (!titles.length) return null
|
||||
const progress = Object.fromEntries(rows.map((row) => [row.tutorialId, row]))
|
||||
const step = continueStep(titles, progress) ?? 1
|
||||
const lastViewedAt = rows.reduce<string | null>(
|
||||
(acc, row) => (row.lastViewedAt && (!acc || row.lastViewedAt > acc) ? row.lastViewedAt : acc),
|
||||
null,
|
||||
)
|
||||
const lastViewedAt = rows.reduce<string | null>((acc, row) => (row.lastViewedAt && (!acc || row.lastViewedAt > acc) ? row.lastViewedAt : acc), null)
|
||||
return {
|
||||
type,
|
||||
label,
|
||||
@@ -125,15 +95,13 @@ async function loadTrack(type: TutorialType, label: string): Promise<Track | nul
|
||||
}
|
||||
|
||||
async function loadTracks() {
|
||||
const res = await Promise.all([loadTrack("python", "Python"), loadTrack("c", "C 语言")])
|
||||
const res = await Promise.all([loadTrack('python', 'Python'), loadTrack('c', 'C 语言')])
|
||||
// 最近在学的那门排前面;两门都没碰过就按原顺序
|
||||
tracks.value = res
|
||||
.filter((t): t is Track => !!t)
|
||||
.sort((a, b) => (b.lastViewedAt ?? "").localeCompare(a.lastViewedAt ?? ""))
|
||||
tracks.value = res.filter((t): t is Track => !!t).sort((a, b) => (b.lastViewedAt ?? '').localeCompare(a.lastViewedAt ?? ''))
|
||||
}
|
||||
|
||||
async function loadContests() {
|
||||
const query = { offset: 0, limit: 5, keyword: "", tag: "" }
|
||||
const query = { offset: 0, limit: 5, keyword: '', tag: '' }
|
||||
const [underway, upcoming] = await Promise.all([
|
||||
getContestList({ ...query, status: ContestStatus.underway }),
|
||||
getContestList({ ...query, status: ContestStatus.not_started }),
|
||||
@@ -142,7 +110,7 @@ async function loadContests() {
|
||||
}
|
||||
|
||||
async function loadSubmissions() {
|
||||
const res = await getSubmissions({ myself: "1", offset: 0, limit: 6, language: "" })
|
||||
const res = await getSubmissions({ myself: '1', offset: 0, limit: 6, language: '' })
|
||||
submissions.value = res.results
|
||||
}
|
||||
|
||||
@@ -167,28 +135,25 @@ async function loadLastVisit() {
|
||||
/** 东八区的 YYYY-MM-DD,和后端 class-activity 的 day 同一个写法 */
|
||||
function dayKey(value: Date | string) {
|
||||
const p = zonedParts(value)!
|
||||
return `${p.year}-${String(p.month).padStart(2, "0")}-${String(p.day).padStart(2, "0")}`
|
||||
return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function dateText(value: string) {
|
||||
return parseTime(value, zonedParts(value)!.year === zonedYear() ? "M月D日" : "YYYY年M月D日")
|
||||
return parseTime(value, zonedParts(value)!.year === zonedYear() ? 'M月D日' : 'YYYY年M月D日')
|
||||
}
|
||||
|
||||
const WEEKDAYS = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"]
|
||||
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
|
||||
/** 「9月27日 周日上午」:机房一个班一周来一两次,说星期几和上下午比说几点几分好认 */
|
||||
function visitText(value: string) {
|
||||
const p = zonedParts(value)!
|
||||
const weekday = WEEKDAYS[new Date(Date.UTC(p.year, p.month - 1, p.day)).getUTCDay()]
|
||||
const part = p.hour < 12 ? "上午" : p.hour < 18 ? "下午" : "晚上"
|
||||
const part = p.hour < 12 ? '上午' : p.hour < 18 ? '下午' : '晚上'
|
||||
return `${dateText(value)} ${weekday}${part}`
|
||||
}
|
||||
|
||||
/** 班里的题是今天的(老师布置的就是今天):今天的课排最前,「上次来」只剩卡底一句 */
|
||||
const classToday = computed(
|
||||
() =>
|
||||
classActivity.value?.source === "teacher" || classActivity.value?.day === dayKey(new Date()),
|
||||
)
|
||||
const classToday = computed(() => classActivity.value?.source === 'teacher' || classActivity.value?.day === dayKey(new Date()))
|
||||
|
||||
const visit = computed(() => lastVisit.value?.summary ?? null)
|
||||
|
||||
@@ -197,68 +162,49 @@ const visit = computed(() => lastVisit.value?.summary ?? null)
|
||||
* 「上次来」卡已经讲了,班里卡不再把同一批题列一遍,只在卡底说一句
|
||||
*/
|
||||
const visitCoversClass = computed(
|
||||
() =>
|
||||
!!visit.value &&
|
||||
!!classActivity.value?.problems.length &&
|
||||
!classToday.value &&
|
||||
classActivity.value.day === dayKey(visit.value.lastSubmitTime),
|
||||
() => !!visit.value && !!classActivity.value?.problems.length && !classToday.value && classActivity.value.day === dayKey(visit.value.lastSubmitTime),
|
||||
)
|
||||
|
||||
const classSummaryText = computed(() => {
|
||||
const problems = classActivity.value?.problems ?? []
|
||||
const done = problems.filter((item) => item.myStatus === "accepted").length
|
||||
return done === problems.length
|
||||
? `那天班里一起做的 ${problems.length} 道,你都做对了`
|
||||
: `那天班里一起做了 ${problems.length} 道,你做对了 ${done} 道`
|
||||
const done = problems.filter((item) => item.myStatus === 'accepted').length
|
||||
return done === problems.length ? `那天班里一起做的 ${problems.length} 道,你都做对了` : `那天班里一起做了 ${problems.length} 道,你做对了 ${done} 道`
|
||||
})
|
||||
|
||||
/**
|
||||
* 「上次还有 N 道没过」那一句:上次来就是今天(上午上课、下午又登录)时不说 ——
|
||||
* 原来照样写「上次(10月13日)」,和今天是同一天
|
||||
*/
|
||||
const visitBeforeToday = computed(
|
||||
() =>
|
||||
!!lastVisit.value?.previousLogin &&
|
||||
dayKey(lastVisit.value.previousLogin) !== dayKey(new Date()),
|
||||
)
|
||||
const visitBeforeToday = computed(() => !!lastVisit.value?.previousLogin && dayKey(lastVisit.value.previousLogin) !== dayKey(new Date()))
|
||||
|
||||
/** 这节课下一道没做对的:卡底「接着做」那个大按钮 */
|
||||
const nextProblem = computed(
|
||||
() => classActivity.value?.problems.find((item) => item.myStatus !== "accepted") ?? null,
|
||||
)
|
||||
const doneInClass = computed(
|
||||
() => classActivity.value?.problems.filter((item) => item.myStatus === "accepted").length ?? 0,
|
||||
)
|
||||
const nextProblem = computed(() => classActivity.value?.problems.find((item) => item.myStatus !== 'accepted') ?? null)
|
||||
const doneInClass = computed(() => classActivity.value?.problems.filter((item) => item.myStatus === 'accepted').length ?? 0)
|
||||
|
||||
const solvedText = computed(() =>
|
||||
(visit.value?.solved ?? []).map((item) => `${item.problemDisplayId} ${item.title}`).join("、"),
|
||||
)
|
||||
const solvedText = computed(() => (visit.value?.solved ?? []).map((item) => `${item.problemDisplayId} ${item.title}`).join('、'))
|
||||
|
||||
/** 今天的就叫「今天」,往前找到的那天标出日期 —— 周一早上看到的是上周五的课 */
|
||||
const classActivityTitle = computed(() => {
|
||||
const day = classActivity.value?.day
|
||||
if (!day) return ""
|
||||
if (!day) return ''
|
||||
// 老师在课堂看板布置的,说成「老师布置的」,比「班里在做」更让人知道该做这几道
|
||||
if (classActivity.value?.source === "teacher") {
|
||||
if (classActivity.value?.source === 'teacher') {
|
||||
const language = classActivity.value.language
|
||||
return `这节课${language ? ` · 用${LANGUAGE_SHOW_VALUE[language]}做` : ""}`
|
||||
return `这节课${language ? ` · 用${LANGUAGE_SHOW_VALUE[language]}做` : ''}`
|
||||
}
|
||||
if (classToday.value) return "这节课"
|
||||
const [, month, date] = day.split("-").map(Number)
|
||||
if (classToday.value) return '这节课'
|
||||
const [, month, date] = day.split('-').map(Number)
|
||||
return `${month}月${date}日班里做了`
|
||||
})
|
||||
|
||||
const MY_STATUS: Record<
|
||||
ClassActivityProblem["myStatus"],
|
||||
{ label: string; type: "success" | "warning" | "default" }
|
||||
> = {
|
||||
accepted: { label: "已做对", type: "success" },
|
||||
tried: { label: "没做对", type: "warning" },
|
||||
none: { label: "还没做", type: "default" },
|
||||
const MY_STATUS: Record<ClassActivityProblem['myStatus'], { label: string; type: 'success' | 'warning' | 'default' }> = {
|
||||
accepted: { label: '已做对', type: 'success' },
|
||||
tried: { label: '没做对', type: 'warning' },
|
||||
none: { label: '还没做', type: 'default' },
|
||||
}
|
||||
|
||||
async function loadWeekly() {
|
||||
weekly.value = await getWeeklyRank(userStore.user?.className ? "class" : "global")
|
||||
weekly.value = await getWeeklyRank(userStore.user?.className ? 'class' : 'global')
|
||||
}
|
||||
|
||||
async function load() {
|
||||
@@ -294,21 +240,21 @@ function openSubmission(row: SubmissionListItem) {
|
||||
/** 刚刚 / 12 分钟前 / 今天 14:03 / 9月21日 */
|
||||
function relativeTime(value: string) {
|
||||
const diff = Date.now() - new Date(value).getTime()
|
||||
if (diff < 60_000) return "刚刚"
|
||||
if (diff < 60_000) return '刚刚'
|
||||
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`
|
||||
const now = zonedParts(new Date())!
|
||||
const then = zonedParts(value)!
|
||||
if (now.year === then.year && now.month === then.month && now.day === then.day) {
|
||||
return parseTime(value, "今天 HH:mm")
|
||||
return parseTime(value, '今天 HH:mm')
|
||||
}
|
||||
return parseTime(value, now.year === then.year ? "M月D日" : "YYYY年M月D日")
|
||||
return parseTime(value, now.year === then.year ? 'M月D日' : 'YYYY年M月D日')
|
||||
}
|
||||
|
||||
function contestTime(contest: Contest) {
|
||||
if (contest.status === ContestStatus.underway) {
|
||||
return `${parseTime(contest.endTime, "M月D日 HH:mm")} 结束`
|
||||
return `${parseTime(contest.endTime, 'M月D日 HH:mm')} 结束`
|
||||
}
|
||||
return `${parseTime(contest.startTime, "M月D日 HH:mm")} 开始 · 时长 ${duration(contest.startTime, contest.endTime)}`
|
||||
return `${parseTime(contest.startTime, 'M月D日 HH:mm')} 开始 · 时长 ${duration(contest.startTime, contest.endTime)}`
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -339,39 +285,21 @@ function contestTime(contest: Contest) {
|
||||
<div class="grid">
|
||||
<div class="column">
|
||||
<!-- 上课时最上面是这节课(设计稿 HomeStuClass):几道题做没做对,底下一个「接着做」 -->
|
||||
<n-card
|
||||
v-if="classToday && classActivity?.problems.length"
|
||||
:bordered="false"
|
||||
class="card lesson"
|
||||
>
|
||||
<n-card v-if="classToday && classActivity?.problems.length" :bordered="false" class="card lesson">
|
||||
<template #header>{{ classActivityTitle }}</template>
|
||||
<template #header-extra>
|
||||
<n-text depth="3" class="row-meta">
|
||||
{{ classActivity.problems.length }} 道做对了 {{ doneInClass }} 道
|
||||
</n-text>
|
||||
<n-text depth="3" class="row-meta"> {{ classActivity.problems.length }} 道做对了 {{ doneInClass }} 道 </n-text>
|
||||
</template>
|
||||
<router-link
|
||||
v-for="item in classActivity.problems"
|
||||
:key="item.problemDisplayId"
|
||||
:to="`/problem/${item.problemDisplayId}`"
|
||||
class="row"
|
||||
>
|
||||
<router-link v-for="item in classActivity.problems" :key="item.problemDisplayId" :to="`/problem/${item.problemDisplayId}`" class="row">
|
||||
<n-tag :type="MY_STATUS[item.myStatus].type" size="small" :bordered="false">
|
||||
{{ MY_STATUS[item.myStatus].label }}
|
||||
</n-tag>
|
||||
<span class="row-title">{{ item.problemDisplayId }} {{ item.title }}</span>
|
||||
<!-- 老师刚布置、还没人交的时候不显示「0/0」 -->
|
||||
<n-text v-if="item.userCount" depth="3" class="row-meta">
|
||||
{{ item.acceptedCount }}/{{ item.userCount }} 人做对
|
||||
</n-text>
|
||||
<n-text v-if="item.userCount" depth="3" class="row-meta"> {{ item.acceptedCount }}/{{ item.userCount }} 人做对 </n-text>
|
||||
</router-link>
|
||||
<div class="lesson-foot">
|
||||
<n-button
|
||||
v-if="nextProblem"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="router.push(`/problem/${nextProblem.problemDisplayId}`)"
|
||||
>
|
||||
<n-button v-if="nextProblem" type="primary" size="large" @click="router.push(`/problem/${nextProblem.problemDisplayId}`)">
|
||||
接着做 {{ nextProblem.problemDisplayId }} {{ nextProblem.title }} →
|
||||
</n-button>
|
||||
<n-text v-else type="success">这节课的题都做对了</n-text>
|
||||
@@ -379,41 +307,29 @@ function contestTime(contest: Contest) {
|
||||
<div v-if="visitBeforeToday && visit?.unsolved.length" class="visit-foot warn">
|
||||
<span class="dot"></span>
|
||||
<span class="row-title">
|
||||
上次({{ dateText(lastVisit!.previousLogin!) }})还有
|
||||
{{ visit.unsolved.length }} 道没做对:{{ visit.unsolved[0]!.problemDisplayId }}
|
||||
上次({{ dateText(lastVisit!.previousLogin!) }})还有 {{ visit.unsolved.length }} 道没做对:{{ visit.unsolved[0]!.problemDisplayId }}
|
||||
{{ visit.unsolved[0]!.title }}
|
||||
</span>
|
||||
<router-link :to="`/problem/${visit.unsolved[0]!.problemDisplayId}`" class="go">
|
||||
接着做 ›
|
||||
</router-link>
|
||||
<router-link :to="`/problem/${visit.unsolved[0]!.problemDisplayId}`" class="go"> 接着做 › </router-link>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 不在上课:上次没做完的排最前(设计稿 HomeStuAfter) -->
|
||||
<n-card v-if="visit && !classToday" :bordered="false" class="card">
|
||||
<template #header>
|
||||
{{ visit.unsolved.length ? "上次没做完" : "上次来" }} ·
|
||||
{{ visit.unsolved.length ? '上次没做完' : '上次来' }} ·
|
||||
{{ visitText(lastVisit!.previousLogin!) }}
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-text depth="3" class="row-meta">
|
||||
做对 {{ visit.solvedCount }} 道 · 交了 {{ visit.submissionCount }} 次
|
||||
</n-text>
|
||||
<n-text depth="3" class="row-meta"> 做对 {{ visit.solvedCount }} 道 · 交了 {{ visit.submissionCount }} 次 </n-text>
|
||||
</template>
|
||||
<router-link
|
||||
v-for="item in visit.unsolved"
|
||||
:key="item.problemDisplayId"
|
||||
:to="`/problem/${item.problemDisplayId}`"
|
||||
class="unsolved"
|
||||
>
|
||||
<router-link v-for="item in visit.unsolved" :key="item.problemDisplayId" :to="`/problem/${item.problemDisplayId}`" class="unsolved">
|
||||
<n-tag type="warning" size="small" :bordered="false">没做对</n-tag>
|
||||
<div class="unsolved-main">
|
||||
<span class="row-title">
|
||||
<n-text depth="3">{{ item.problemDisplayId }}</n-text> {{ item.title }}
|
||||
</span>
|
||||
<n-text depth="3" class="row-meta">
|
||||
交了 {{ item.attempts }} 次,最后一次是{{ JUDGE_STATUS[item.lastResult].name }}
|
||||
</n-text>
|
||||
<n-text depth="3" class="row-meta"> 交了 {{ item.attempts }} 次,最后一次是{{ JUDGE_STATUS[item.lastResult].name }} </n-text>
|
||||
</div>
|
||||
<n-button type="primary" tag="span">接着做</n-button>
|
||||
</router-link>
|
||||
@@ -421,34 +337,20 @@ function contestTime(contest: Contest) {
|
||||
<n-tag type="success" size="small" :bordered="false">已做对</n-tag>
|
||||
<span class="row-title">{{ visit.solvedCount }} 道:{{ solvedText }}</span>
|
||||
</div>
|
||||
<div v-if="visitCoversClass" class="visit-foot">
|
||||
<Icon icon="ph:check-bold" :width="14" />{{ classSummaryText }}
|
||||
</div>
|
||||
<div v-if="visitCoversClass" class="visit-foot"><Icon icon="ph:check-bold" :width="14" />{{ classSummaryText }}</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 前几天班里一起做的(不是今天、也不是上次来那天):只在有的时候出现 -->
|
||||
<n-card
|
||||
v-if="!classToday && classActivity?.problems.length && !visitCoversClass"
|
||||
:title="classActivityTitle"
|
||||
:bordered="false"
|
||||
class="card"
|
||||
>
|
||||
<n-card v-if="!classToday && classActivity?.problems.length && !visitCoversClass" :title="classActivityTitle" :bordered="false" class="card">
|
||||
<template #header-extra>
|
||||
<n-text depth="3" class="row-meta">{{ classActivity.className }} 班</n-text>
|
||||
</template>
|
||||
<router-link
|
||||
v-for="item in classActivity.problems"
|
||||
:key="item.problemDisplayId"
|
||||
:to="`/problem/${item.problemDisplayId}`"
|
||||
class="row"
|
||||
>
|
||||
<router-link v-for="item in classActivity.problems" :key="item.problemDisplayId" :to="`/problem/${item.problemDisplayId}`" class="row">
|
||||
<n-tag :type="MY_STATUS[item.myStatus].type" size="small" :bordered="false">
|
||||
{{ MY_STATUS[item.myStatus].label }}
|
||||
</n-tag>
|
||||
<span class="row-title">{{ item.problemDisplayId }} {{ item.title }}</span>
|
||||
<n-text v-if="item.userCount" depth="3" class="row-meta">
|
||||
{{ item.acceptedCount }}/{{ item.userCount }} 人做对
|
||||
</n-text>
|
||||
<n-text v-if="item.userCount" depth="3" class="row-meta"> {{ item.acceptedCount }}/{{ item.userCount }} 人做对 </n-text>
|
||||
</router-link>
|
||||
</n-card>
|
||||
|
||||
@@ -456,11 +358,9 @@ function contestTime(contest: Contest) {
|
||||
<n-card title="继续学习" :bordered="false" class="card">
|
||||
<router-link v-for="track in tracks" :key="track.type" :to="learnLink(track)" class="row">
|
||||
<n-text depth="3" class="track-label">{{ track.label }}</n-text>
|
||||
<span class="row-title">
|
||||
第 {{ track.step }} / {{ track.total }} 课 · {{ track.title }}
|
||||
</span>
|
||||
<span class="row-title"> 第 {{ track.step }} / {{ track.total }} 课 · {{ track.title }} </span>
|
||||
<n-button :type="track.lastViewedAt ? 'primary' : 'default'" tag="span" size="small">
|
||||
{{ track.lastViewedAt ? "继续" : "开始" }}
|
||||
{{ track.lastViewedAt ? '继续' : '开始' }}
|
||||
</n-button>
|
||||
</router-link>
|
||||
<n-empty v-if="loaded && !tracks.length" description="还没有公开的教程" />
|
||||
@@ -472,12 +372,7 @@ function contestTime(contest: Contest) {
|
||||
<template #header-extra>
|
||||
<router-link to="/submission?myself=1" class="more">全部</router-link>
|
||||
</template>
|
||||
<div
|
||||
v-for="row in submissions"
|
||||
:key="row.id"
|
||||
class="row clickable"
|
||||
@click="openSubmission(row)"
|
||||
>
|
||||
<div v-for="row in submissions" :key="row.id" class="row clickable" @click="openSubmission(row)">
|
||||
<SubmissionResultTag :result="row.result" size="small" />
|
||||
<span class="row-title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
||||
<n-text depth="3" class="row-meta">{{ relativeTime(row.createTime) }}</n-text>
|
||||
@@ -494,12 +389,7 @@ function contestTime(contest: Contest) {
|
||||
<template #header-extra>
|
||||
<router-link to="/contest" class="more">全部</router-link>
|
||||
</template>
|
||||
<router-link
|
||||
v-for="contest in contests"
|
||||
:key="contest.id"
|
||||
:to="`/contest/${contest.id}`"
|
||||
class="row stacked"
|
||||
>
|
||||
<router-link v-for="contest in contests" :key="contest.id" :to="`/contest/${contest.id}`" class="row stacked">
|
||||
<n-flex align="center" :size="8" :wrap="false">
|
||||
<n-tag :type="CONTEST_STATUS[contest.status].type" size="small" :bordered="false">
|
||||
{{ CONTEST_STATUS[contest.status].name }}
|
||||
@@ -514,12 +404,7 @@ function contestTime(contest: Contest) {
|
||||
<template #header-extra>
|
||||
<router-link to="/announcement" class="more">全部</router-link>
|
||||
</template>
|
||||
<router-link
|
||||
v-for="item in announcements"
|
||||
:key="item.id"
|
||||
:to="{ path: '/announcement', query: { id: item.id } }"
|
||||
class="row"
|
||||
>
|
||||
<router-link v-for="item in announcements" :key="item.id" :to="{ path: '/announcement', query: { id: item.id } }" class="row">
|
||||
<n-tag v-if="item.top" type="warning" size="small" :bordered="false">置顶</n-tag>
|
||||
<span class="row-title">{{ item.title }}</span>
|
||||
<n-text depth="3" class="row-meta">{{ dateText(item.createTime) }}</n-text>
|
||||
|
||||
Reference in new issue
Block a user