From 93f80740effea9304b9369c50c0e3a01d4261215 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 13 Oct 2026 22:03:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=85=AC=E5=91=8A):=20=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E9=A1=B5=E6=94=B9=E6=88=90=E5=B7=A6=E8=BE=B9=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E3=80=81=E5=8F=B3=E8=BE=B9=E5=85=A8=E6=96=87=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E5=86=8D=E4=B8=80=E6=9D=A1=E6=9D=A1=E7=82=B9=E5=BC=80?= =?UTF-8?q?=E5=BC=B9=E6=A1=86=EF=BC=9B=E9=A6=96=E9=A1=B5=EF=BC=88=E5=AD=A6?= =?UTF-8?q?=E7=94=9F=E5=92=8C=E8=80=81=E5=B8=88=EF=BC=89=E7=9A=84=E5=85=AC?= =?UTF-8?q?=E5=91=8A=E5=8D=A1=E7=89=87=E7=82=B9=E4=B8=80=E6=9D=A1=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E6=89=93=E5=BC=80=E9=82=A3=E4=B8=80=E6=9D=A1=EF=BC=8C?= =?UTF-8?q?=E8=80=81=E5=B8=88=E9=A6=96=E9=A1=B5=E4=B9=9F=E6=94=B9=E6=88=90?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E4=B8=89=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- apps/web/src/oj/announcement/list.vue | 325 ++++++++++++++++++-------- apps/web/src/oj/home/TeacherHome.vue | 109 ++++----- apps/web/src/oj/home/dashboard.vue | 35 +-- 3 files changed, 294 insertions(+), 175 deletions(-) diff --git a/apps/web/src/oj/announcement/list.vue b/apps/web/src/oj/announcement/list.vue index 90456ff..b485f24 100644 --- a/apps/web/src/oj/announcement/list.vue +++ b/apps/web/src/oj/announcement/list.vue @@ -1,84 +1,151 @@ - + diff --git a/apps/web/src/oj/home/TeacherHome.vue b/apps/web/src/oj/home/TeacherHome.vue index cae9ecc..1b7615a 100644 --- a/apps/web/src/oj/home/TeacherHome.vue +++ b/apps/web/src/oj/home/TeacherHome.vue @@ -2,38 +2,37 @@ import type { ClassToday } from "@oj2/contract" import type { AnnouncementListItem } from "utils/types" import { useThemeVars } from "naive-ui" -import { getAnnouncement, getAnnouncementList, getClassToday } from "oj/api" +import { getAnnouncementList, getClassToday } from "oj/api" import { classLabel } from "oj/submission/utils" import { useUserStore } from "shared/store/user" -import { parseTime, zonedParts } from "utils/functions" +import { parseTime, zonedParts, zonedYear } from "utils/functions" /** * 老师的首页(设计稿「首页重设计」HomeTeacher)。原来老师看到的和学生一样(继续学习、最近提交), * 对老师没用。现在最上面是「今天的课」:今天在上课的每个班一行 —— 正在上课 / 几点下课了、 * 布置了几道、几人交过、全做对几人,每道题一根小条,点「课堂看板」进那个班。 - * 下面是常用入口和置顶的公告 + * 下面是常用入口和最新三条公告(和学生首页一样,置顶的排最前) */ const theme = useThemeVars() const router = useRouter() const userStore = useUserStore() const today = ref(null) -const pinned = ref([]) +const announcements = ref([]) /** 最后一次有人交题在这么久以内,算「正在上课」 */ const LIVE_MS = 15 * 60 * 1000 onMounted(async () => { - const [classes, announcements] = await Promise.allSettled([ - getClassToday(), - getAnnouncementList(0, 10), - ]) + const [classes, latest] = await Promise.allSettled([getClassToday(), getAnnouncementList(0, 3)]) if (classes.status === "fulfilled") today.value = classes.value - if (announcements.status === "fulfilled") { - pinned.value = announcements.value.results.filter((item) => item.top) - } + if (latest.status === "fulfilled") announcements.value = latest.value.results }) +function dateText(value: string) { + return parseTime(value, zonedParts(value)!.year === zonedYear() ? "M月D日" : "YYYY年M月D日") +} + const greeting = computed(() => { const hour = zonedParts(new Date())!.hour if (hour < 5) return "夜深了" @@ -74,16 +73,6 @@ const shortcuts = computed(() => { label: "后台", to: userStore.isSuperAdmin ? "/admin" : "/admin/problem/list", show: true }, ].filter((it) => it.show), ) - -const announcementShow = ref(false) -const announcementTitle = ref("") -const announcementContent = ref("") -async function openAnnouncement(item: AnnouncementListItem) { - const res = await getAnnouncement(item.id) - announcementTitle.value = item.title - announcementContent.value = res.content - announcementShow.value = true -} @@ -299,15 +281,36 @@ async function openAnnouncement(item: AnnouncementListItem) { gap: 10px; } -.pinned { - flex-direction: row; - align-items: center; - gap: 10px; - padding: 14px 18px; - cursor: pointer; +.card-head { + display: flex; + align-items: baseline; } -.pinned:hover { - border-color: v-bind("theme.primaryColor"); +.ann-row { + display: flex; + align-items: center; + gap: 10px; + min-height: 46px; + padding: 0 6px; + border-top: 1px solid v-bind("theme.dividerColor"); + border-radius: 4px; + color: inherit; + text-decoration: none; +} + +.ann-row:first-of-type { + border-top: 0; +} + +.ann-row:hover { + background: v-bind("theme.hoverColor"); +} + +.ann-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } diff --git a/apps/web/src/oj/home/dashboard.vue b/apps/web/src/oj/home/dashboard.vue index 8cbf353..928d629 100644 --- a/apps/web/src/oj/home/dashboard.vue +++ b/apps/web/src/oj/home/dashboard.vue @@ -2,7 +2,6 @@ import { continueStep, doneCount, lessonPath, splitTitle, type TutorialType } from "oj/learn/lesson" import { Icon } from "@iconify/vue" import { - getAnnouncement, getAnnouncementList, getClassActivity, getKnowledgeMap, @@ -14,7 +13,6 @@ import { getWeeklyRank, } from "oj/api" import SubmissionResultTag from "shared/components/SubmissionResultTag.vue" -import { useBreakpoints } from "shared/composables/breakpoints" 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" @@ -43,7 +41,6 @@ interface Track { const router = useRouter() const userStore = useUserStore() -const { isDesktop } = useBreakpoints() const tracks = ref([]) const contests = ref([]) @@ -313,17 +310,6 @@ function contestTime(contest: Contest) { } return `${parseTime(contest.startTime, "M月D日 HH:mm")} 开始 · 时长 ${duration(contest.startTime, contest.endTime)}` } - -const announcementShow = ref(false) -const announcementTitle = ref("") -const announcementContent = ref("") - -async function openAnnouncement(item: AnnouncementListItem) { - const res = await getAnnouncement(item.id) - announcementTitle.value = item.title - announcementContent.value = res.content - announcementShow.value = true -}