Files
OJ2/apps/web/src/oj/submission/composables/listLive.ts
T
xuyueandClaude Opus 5.5 871861220e refactor(提交列表): 选中翻阅、实时更新、「这节课」、带条件去统计拆成四个 composable
list.vue 从 1834 行减到 1604 行,拆出 oj/submission/composables/ 下的
rowCursor(↑ ↓ 换条、页头页尾自动翻页)、listLive(正在评分的 3 秒刷新、第一页的
「有 N 条新提交」)、lessonFilter(「这节课」一键筛)、statsLink(和统计之间来回带条件)。
注释原样跟着走。

纯搬家。七个状态(列表、↓↓、↑、流程图、我的、手机列表、手机详情)改前改后截图一致;
连按 31 下 ↓ 翻到第 2 页,别人交一条后 18 秒内出「有 1 条新提交」,「这节课」筛到
班级 + 题号 + 今天。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:48:43 +08:00

91 lines
3.2 KiB
TypeScript

import { computed, ref, type Ref } from "vue"
import { useDocumentVisibility, useIntervalFn } from "@vueuse/core"
import { SubmissionStatus } from "utils/constants"
import {
FlowchartSubmissionStatus,
type FlowchartSubmissionListItem,
type SubmissionListItem,
} from "utils/types"
import type { Row, SubmissionQuery } from "./listQuery"
function isPending(row: Row) {
if ("result" in row) {
return row.result === SubmissionStatus.pending || row.result === SubmissionStatus.judging
}
return (
row.status === FlowchartSubmissionStatus.PENDING ||
row.status === FlowchartSubmissionStatus.PROCESSING
)
}
/**
* 提交列表的实时更新。两件事,一个定时器:
* - 本页有「正在评分」的,3 秒拉一次,把结果原地换上;
* - 第一页每 15 秒看一眼有没有新交的。**新的不直接插进来** —— 老师正看着第 5 行,
* 上面突然塞进 3 行,选中的那行就跑了。顶上给一条「有 N 条新提交」,点了才换。
* 页面切到后台就停。
*
* `requestSeq` 是列表那边的请求序号:拉的这期间筛选变了、翻页了,这份结果就作废。
*/
export function useListLive(options: {
query: SubmissionQuery
flowMode: Ref<boolean>
rows: Ref<Row[]>
submissions: Ref<SubmissionListItem[]>
flowcharts: Ref<FlowchartSubmissionListItem[]>
loading: Ref<boolean>
loaded: Ref<boolean>
fetchPage: () => Promise<{ results: Row[] }>
requestSeq: () => number
}) {
const { query, flowMode, rows, submissions, flowcharts } = options
const newCount = ref(0)
const newCountCapped = computed(() => newCount.value >= query.limit)
let lastPoll = Date.now()
const visibility = useDocumentVisibility()
const hasPending = computed(() => rows.value.some(isPending))
useIntervalFn(async () => {
if (visibility.value !== "visible" || options.loading.value || !options.loaded.value) return
const interval = hasPending.value ? 3000 : 15000
if (Date.now() - lastPoll < interval) return
if (!hasPending.value && query.page !== 1) return
lastPoll = Date.now()
const seq = options.requestSeq()
const mode = flowMode.value
let res
try {
res = await options.fetchPage()
} catch {
return
}
// 这期间筛选变了、翻页了,这份结果就作废
if (seq !== options.requestSeq() || mode !== flowMode.value) return
const fresh = new Map<string, Row>(res.results.map((row) => [row.id, row]))
if (mode) {
flowcharts.value = flowcharts.value.map(
(row) => (fresh.get(row.id) as FlowchartSubmissionListItem | undefined) ?? row,
)
} else {
submissions.value = submissions.value.map(
(row) => (fresh.get(row.id) as SubmissionListItem | undefined) ?? row,
)
}
if (query.page === 1) {
const current = new Set(rows.value.map((row) => row.id))
const newest = rows.value[0]?.createTime ?? ""
newCount.value = res.results.filter(
(row) => !current.has(row.id) && row.createTime > newest,
).length
}
}, 1000)
/** 刚点了重新判题 / 重新评分:下一秒就拉,不等满 3 秒 */
function pollSoon() {
lastPoll = 0
}
return { newCount, newCountCapped, pollSoon }
}