feat(统计): 提交记录和流程图的统计弹框记住上次查的班级、题号、时间段,一小时内再打开自动带上
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
893b74b1cb
commit
22459cc212
5 files changed
+80
-22
No files matched your search
@@ -13,8 +13,9 @@ import type { ClassBoard, ClassBoardCell, ClassBoardStudent } from "utils/types"
|
||||
* 学生一道都没交(占三分之一),交了 3 次以上还没过的平均不到 1 个。所以排序是
|
||||
* 没交过 → 卡住 → 在做 → 做完,没交过的名单还单独摆在最上面,老师扫一眼就能走过去。
|
||||
*
|
||||
* 班级**不记本地**,默认由后端猜最近两小时在交题的班 —— 理由见 StatisticsPanel.vue
|
||||
* 那段注释(上一节课的班悄悄留在框里,老师看的整个是别人的班)。
|
||||
* 班级**不记本地**,默认由后端猜最近两小时在交题的班 —— 记本地的话,上一节课的班会
|
||||
* 悄悄留在框里,老师看的整个是别人的班。(提交统计弹窗是记一小时的,见 composables/statisticsQuery.ts,
|
||||
* 那边是老师自己手选的查询;这里后端本来就猜得出,没必要冒这个险。)
|
||||
*/
|
||||
|
||||
/** 同一道题今天交了这么多次还没过,算卡住 */
|
||||
|
||||
@@ -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<FlowchartStatistics>({
|
||||
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)
|
||||
|
||||
@@ -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<SelectOption[]>(
|
||||
})) ?? [],
|
||||
)
|
||||
|
||||
/**
|
||||
* 班级/用户**不记本地**:这个选择框每次打开都从空的开始。
|
||||
*
|
||||
* 曾经把上次的班级写进 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
|
||||
|
||||
@@ -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<Cached> {
|
||||
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 }
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user