feat(提交列表): 今日提交数旁加「统计」按钮,弹框给今天的提交统计

筛到今天之后标签旁边出现「统计」,弹框里是全站今天的提交概况:总提交 / 正确 /
判题中 / 正确率 / 参与人数,外加按钟点的 24 格分布、按语言、按判题结果,以及
今天最热的 10 道题。

新接口 GET /submissions/today-statistics,公开、只出聚合数,口径和那颗标签一致
(东八区今天 + 非比赛提交):

- 钟点分桶走 time.ts 的 localTime()。`extract(hour from create_time)` 按会话时区
  算,容器是 UTC,整张分布图会左移 8 小时;
- 正确率的分母摘掉未判完的条数,正确数含 AST_CHECK_FAILED;
- 热门题只算 visible 的题目 —— 这个接口不需要登录,不能拿它探未发布题目的标题;
  「提交列表对学生全开」关掉时这张表整个不下发,跟提交列表同一个开关(数字照给,
  否则标签说 21、弹框说 0)。

前端组件异步加载,不进本路由的关键路径;小时分布是纯 CSS 柱状图,没有引 chart.js。
柱子和基线取 useThemeVars(),深浅色都跟着走,「现在」那一格是基线上一段主色刻度。
流程图那档不给这颗按钮 —— 流程图提交在另一张表、只有 AI 评级没有判题状态。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 08:23:45 -06:00
parent 5eee13fe81
commit e600fd24cf
5 changed files with 451 additions and 14 deletions

View File

@@ -49,6 +49,9 @@ const StatisticsPanel = defineAsyncComponent(
const FlowchartStatisticsPanel = defineAsyncComponent(
() => import("shared/components/FlowchartStatisticsPanel.vue"),
)
const TodayStatistics = defineAsyncComponent(
() => import("./components/TodayStatistics.vue"),
)
const SubmissionDetail = defineAsyncComponent(() => import("./detail.vue"))
const FlowchartScoreDetail = defineAsyncComponent(
() => import("./components/FlowchartScoreDetail.vue"),
@@ -114,6 +117,8 @@ const { query, clearQuery } = usePagination<SubmissionQuery>({
const submissionID = ref("")
const problemDisplayID = ref("")
const [statisticPanel, toggleStatisticPanel] = useToggle(false)
// 「今日提交数」旁边那颗「统计」按钮的弹框
const [todayPanel, toggleTodayPanel] = useToggle(false)
const [codePanel, toggleCodePanel] = useToggle(false)
const [scoreDetailPanel, toggleScoreDetailPanel] = useToggle(false)
@@ -241,6 +246,12 @@ function problemClicked(row: SubmissionListItem | FlowchartSubmissionListItem) {
}
}
// 今日统计弹框里点题目。那颗按钮只在 route.name === "submissions" 上出现
// (今日提交数本身就只在那一页拉),所以不用管比赛里的题目路由
function openProblem(displayId: string) {
window.open("/problem/" + displayId, "_blank")
}
function showCodePanel(id: string, problem: string) {
toggleCodePanel(true)
submissionID.value = id
@@ -581,19 +592,33 @@ const flowchartColumns = computed(() => {
</n-button>
</n-form-item>
</n-form>
<n-tag
v-if="todayCount > 0"
checkable
:checked="query.today === '1'"
type="success"
size="large"
@update:checked="(v: boolean) => (query.today = v ? '1' : '0')"
>
<n-gradient-text v-if="query.today !== '1'" type="success">
今日提交数{{ todayCount }}
</n-gradient-text>
<template v-else>今日提交数{{ todayCount }}</template>
</n-tag>
<n-flex v-if="todayCount > 0" align="center" :size="8">
<n-tag
checkable
:checked="query.today === '1'"
type="success"
size="large"
@update:checked="(v: boolean) => (query.today = v ? '1' : '0')"
>
<n-gradient-text v-if="query.today !== '1'" type="success">
今日提交数{{ todayCount }}
</n-gradient-text>
<template v-else>今日提交数{{ todayCount }}</template>
</n-tag>
<!--
筛到今天之后才出现流程图那档不给 这个统计只算代码提交
流程图提交在另一张表只有 AI 评级没有判题状态点开会是一份
对不上标签数字的统计
-->
<n-button
v-if="query.today === '1' && query.language !== 'Flowchart'"
quaternary
type="success"
@click="toggleTodayPanel(true)"
>
统计
</n-button>
</n-flex>
</n-space>
<n-data-table
v-if="query.language === 'Flowchart'"
@@ -636,6 +661,15 @@ const flowchartColumns = computed(() => {
:username="query.username"
/>
</n-modal>
<n-modal
v-model:show="todayPanel"
preset="card"
:style="{ maxWidth: isDesktop && '700px', maxHeight: '80vh' }"
:content-style="{ overflow: 'auto' }"
title="今日提交统计"
>
<TodayStatistics @open-problem="(id: string) => openProblem(id)" />
</n-modal>
<n-modal
v-model:show="codePanel"
preset="card"