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>
91 lines
3.2 KiB
TypeScript
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 }
|
|
}
|