diff --git a/apps/web/src/oj/classroom/board.vue b/apps/web/src/oj/classroom/board.vue index 37e1e00..f3c6242 100644 --- a/apps/web/src/oj/classroom/board.vue +++ b/apps/web/src/oj/classroom/board.vue @@ -13,8 +13,9 @@ import type { ClassBoard, ClassBoardCell, ClassBoardStudent } from "utils/types" * 学生一道都没交(占三分之一),交了 3 次以上还没过的平均不到 1 个。所以排序是 * 没交过 → 卡住 → 在做 → 做完,没交过的名单还单独摆在最上面,老师扫一眼就能走过去。 * - * 班级**不记本地**,默认由后端猜最近两小时在交题的班 —— 理由见 StatisticsPanel.vue - * 那段注释(上一节课的班悄悄留在框里,老师看的整个是别人的班)。 + * 班级**不记本地**,默认由后端猜最近两小时在交题的班 —— 记本地的话,上一节课的班会 + * 悄悄留在框里,老师看的整个是别人的班。(提交统计弹窗是记一小时的,见 composables/statisticsQuery.ts, + * 那边是老师自己手选的查询;这里后端本来就猜得出,没必要冒这个险。) */ /** 同一道题今天交了这么多次还没过,算卡住 */ diff --git a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue index 1baa769..4fe7900 100644 --- a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue +++ b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue @@ -142,9 +142,10 @@ import { formatISO, sub, type Duration } from "date-fns" import type { FlowchartStatistics } from "@oj2/contract" import { getFlowchartStatistics } from "oj/api" -import { PANEL_DURATION_OPTIONS, FLOWCHART_CRITERIA_ORDER } from "utils/constants" +import { PANEL_DURATION_OPTIONS, FLOWCHART_CRITERIA_ORDER, STORAGE_KEY } from "utils/constants" import { durationFromValue } from "utils/functions" import { useHiddenStudents } from "../composables/hiddenStudents" +import { useStatisticsQuery } from "../composables/statisticsQuery" import { Doughnut, Radar, Bar } from "vue-chartjs" import { Chart as ChartJS, @@ -189,11 +190,8 @@ const message = useMessage() const durationOptions: SelectOption[] = [...PANEL_DURATION_OPTIONS] -const query = reactive({ - username: props.username, - problem: props.problem, - duration: durationOptions[0].value, -}) +// 班级、题号、时间段记一小时,理由见 useStatisticsQuery +const { query, save: saveQuery } = useStatisticsQuery(STORAGE_KEY.FLOWCHART_STATISTICS_QUERY, props) const data = reactive({ totalCount: 0, @@ -447,6 +445,7 @@ async function handleStatistics() { try { const res = await getFlowchartStatistics(duration, query.problem, query.username) Object.assign(data, res) + saveQuery() } catch (error) { message.error("获取流程图统计失败") console.error("获取流程图统计失败:", error) diff --git a/apps/web/src/shared/components/StatisticsPanel.vue b/apps/web/src/shared/components/StatisticsPanel.vue index 64a66ec..3634479 100644 --- a/apps/web/src/shared/components/StatisticsPanel.vue +++ b/apps/web/src/shared/components/StatisticsPanel.vue @@ -157,9 +157,10 @@ import { h } from "vue" import { formatISO, sub, type Duration } from "date-fns" import { getSubmissionStatistics, getSubmissionStatisticsItems } from "oj/api" -import { PANEL_DURATION_OPTIONS } from "utils/constants" +import { PANEL_DURATION_OPTIONS, STORAGE_KEY } from "utils/constants" import { useConfigStore } from "../store/config" import { useHiddenStudents } from "../composables/hiddenStudents" +import { useStatisticsQuery } from "../composables/statisticsQuery" import { Doughnut } from "vue-chartjs" import { Chart as ChartJS, ArcElement, Title, Tooltip, Legend } from "chart.js" import { NFlex, NTag, NText, NTooltip, type DataTableRowKey } from "naive-ui" @@ -385,19 +386,8 @@ const classOptions = computed( })) ?? [], ) -/** - * 班级/用户**不记本地**:这个选择框每次打开都从空的开始。 - * - * 曾经把上次的班级写进 localStorage(statisticsClass),下次打开自动带上。但那正是 - * 最危险的地方 —— 下课换了班、或者换个人坐这台机器,上一个人查的班悄悄留在框里, - * 老师没注意就按了「统计」,看到的整个是别人的班。宁可多选一次。 - * 登录框那份 LOGIN_CLASS 是另一回事:那记的是这台机器的身份,不是一次临时查询。 - */ -const query = reactive({ - username: props.username, - problem: props.problem, - duration: options[0].value, -}) +// 班级、题号、时间段记一小时,理由见 useStatisticsQuery +const { query, save: saveQuery } = useStatisticsQuery(STORAGE_KEY.STATISTICS_QUERY, props) const count = reactive({ total: 0, @@ -675,6 +665,7 @@ async function fetchStatistics() { .map((item) => item.trim()) .filter(Boolean) const res = await getSubmissionStatistics(duration, query.problem, query.username) + saveQuery() queriedProblemCount.value = new Set(problems.map((p) => p.toLowerCase())).size count.total = res.submissionCount count.accepted = res.acceptedCount diff --git a/apps/web/src/shared/composables/statisticsQuery.ts b/apps/web/src/shared/composables/statisticsQuery.ts new file mode 100644 index 0000000..436c264 --- /dev/null +++ b/apps/web/src/shared/composables/statisticsQuery.ts @@ -0,0 +1,65 @@ +import { reactive } from "vue" +import { PANEL_DURATION_OPTIONS } from "utils/constants" +import storage from "utils/storage" + +/** + * 统计面板的查询条件(班级/用户、题号、时间段),**记一小时**:一小时内再打开, + * 框里是上次查的那一组。 + * + * 老师一节课里会反复开关这个弹窗(看一眼、关掉、走下去辅导、回来再开),每次都从空的 + * 选班级、敲题号很烦。但也不能一直记着 —— 曾经把上次的班级永久写进 localStorage, + * 下课换了班、或者换个人坐这台机器,上一个人查的班悄悄留在框里,老师没注意就按了 + * 「统计」,看到的整个是别人的班。一小时是折中:一节课够用,隔一节课就自己清掉。 + * 注意**连堂**时下一节仍在一小时内,框里会是上一个班,这一点是接受了的。 + * + * 一小时从「最后一次查询」算起(调用方在每次查完后调 `save`,自动刷新也算), + * 开着面板盯一整节课不会中途过期。 + * + * 调用方传进来的初值优先:题目页固定是这道题、提交列表带着自己的筛选条件, + * 那是比缓存更明确的上下文;为空的那几项才用缓存补上。 + * 登录框那份 LOGIN_CLASS 是另一回事:那记的是这台机器的身份,不是一次临时查询。 + * + * @param storageKey localStorage 的键。**两个面板各用各的** —— 流程图题和代码题的 + * 题号不是一批,互相带过去只会查出一片空。 + */ +export function useStatisticsQuery( + storageKey: string, + initial: { username: string; problem: string }, +) { + const TTL = 60 * 60 * 1000 + + type Cached = { username: string; problem: string; duration: string; savedAt: number } + + function load(): Partial { + try { + const cached: Cached | null = storage.get(storageKey) + if (!cached || Date.now() - cached.savedAt > TTL) return {} + return cached + } catch { + return {} + } + } + + const cached = load() + + const query = reactive({ + username: initial.username || cached.username || "", + problem: initial.problem || cached.problem || "", + // 选项表改过之后旧缓存里的值可能已经不在里面了,认不出来就回到第一档 + duration: PANEL_DURATION_OPTIONS.some((option) => option.value === cached.duration) + ? cached.duration! + : PANEL_DURATION_OPTIONS[0].value, + }) + + function save() { + storage.set(storageKey, { + // 选择框点了清除是 null,不是空串 + username: query.username ?? "", + problem: query.problem ?? "", + duration: query.duration, + savedAt: Date.now(), + } satisfies Cached) + } + + return { query, save } +} diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts index d095327..79fcb44 100644 --- a/apps/web/src/utils/constants.ts +++ b/apps/web/src/utils/constants.ts @@ -156,6 +156,8 @@ export const STORAGE_KEY = { ADMIN_SIDER_COLLAPSED: "adminSiderCollapsed", DEMO_MODE: "demoMode", LOGIN_CLASS: "loginClass", + STATISTICS_QUERY: "statisticsQuery", + FLOWCHART_STATISTICS_QUERY: "flowchartStatisticsQuery", } export const DIFFICULTY = {