From 9ae6287fb1dadde996a5a6bbac7fc56b53553e00 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 19 Oct 2026 22:11:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=81=E7=A8=8B=E5=9B=BE):=20=E6=B2=A1?= =?UTF-8?q?=E7=94=BB=E5=AF=B9=E4=B9=9F=E5=88=86=E5=BE=97=E5=87=BA=E8=BF=9C?= =?UTF-8?q?=E8=BF=91=E2=80=94=E2=80=94=E4=BA=94=E6=A1=A3=E8=AF=B4=E6=B3=95?= =?UTF-8?q?=E3=80=81=E5=AE=8C=E6=88=90=E5=BA=A6=E6=9D=A1=E3=80=81=E6=AF=94?= =?UTF-8?q?=E4=B8=8A=E6=AC=A1=E5=A4=9A=E5=AF=B9=E4=BA=86=E5=87=A0=E7=BB=84?= =?UTF-8?q?=EF=BC=9B=E5=8F=B3=E6=A0=8F=E7=9A=84=E5=9B=BE=E6=94=BE=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 只说「还没画对」,学生改了几次还是这一句,看不出在往前走。等级照旧存 S/A/B/C, 标题换成话:画对了,画得很清楚 / 画对了 / 快画对了 / 对了一部分 / 还没画对; 下面一根完成度条,走对几组亮几段;第二次起没画对时说比上次多(少)对了几组 (详情接口带上同一道题上一次走对了几组)。提交列表右栏的图从 42% 加宽到 62%, 小图也放大填满图框(最多 1.5 倍)。 Co-Authored-By: Claude Opus 5.5 --- apps/api/src/routes/flowchart.ts | 27 +++++- .../oj/problem/components/FlowchartResult.vue | 7 +- .../submission/components/FlowchartPane.vue | 26 +++++- .../src/shared/components/FlowchartNote.vue | 88 ++++++++++++++++++- apps/web/src/utils/flowchart.ts | 11 +++ packages/contract/src/flowchart.ts | 8 ++ 6 files changed, 154 insertions(+), 13 deletions(-) diff --git a/apps/api/src/routes/flowchart.ts b/apps/api/src/routes/flowchart.ts index b6e6156..6149dfa 100644 --- a/apps/api/src/routes/flowchart.ts +++ b/apps/api/src/routes/flowchart.ts @@ -12,7 +12,7 @@ import { NO_CLASS, STUDENT_ROLES, } from "@oj2/contract" -import { and, asc, count, desc, eq, inArray, isNull, sql, type SQL } from "drizzle-orm" +import { and, asc, count, desc, eq, inArray, isNull, lt, sql, type SQL } from "drizzle-orm" import { Hono } from "hono" import { requireAuth, requireTeacher, type AppEnv } from "../auth/middleware" @@ -80,9 +80,31 @@ function cutoffsFor(user: AuthUser, rows: Array<{ userId: number; problemId: num return problemSetLockCutoffs(user.id, [...new Set(own)]) } +/** 上一次走对了几组(见契约 previousCases):同一个人、同一道题、更早、评完了的那一次 */ +async function previousCases(flowchart: typeof schema.flowchartSubmission.$inferSelect) { + const [previous] = await db + .select({ details: schema.flowchartSubmission.aiCriteriaDetails }) + .from(schema.flowchartSubmission) + .where( + and( + eq(schema.flowchartSubmission.userId, flowchart.userId), + eq(schema.flowchartSubmission.problemId, flowchart.problemId), + eq(schema.flowchartSubmission.status, 2), + lt(schema.flowchartSubmission.createTime, flowchart.createTime), + ), + ) + .orderBy(desc(schema.flowchartSubmission.createTime)) + .limit(1) + const details = asRecord(previous?.details) + if (details.version !== 2 || !Array.isArray(details.cases)) return null + const cases = details.cases.map((item) => asRecord(item)) + return { passed: cases.filter((item) => item.ok === true).length, total: cases.length } +} + function flowchartData( flowchart: typeof schema.flowchartSubmission.$inferSelect, username: string, + previous: { passed: number; total: number } | null = null, ) { return { id: flowchart.id, @@ -101,6 +123,7 @@ function flowchartData( aiModel: flowchart.aiModel, processingTime: flowchart.processingTime, evaluationTime: flowchart.evaluationTime, + previousCases: previous, } satisfies FlowchartSubmission } @@ -716,7 +739,7 @@ flowchartRoutes.get("/flowcharts/:id", requireAuth, async (c) => { const user = c.get("user")! if (!row || !canView(user, row.flowchart, row.problem, await cutoffsFor(user, [row.flowchart]))) return failure(c, 404, "flowchart-not-found", "Submission does not exist") - return success(c, flowchartData(row.flowchart, row.username)) + return success(c, flowchartData(row.flowchart, row.username, await previousCases(row.flowchart))) }) flowchartRoutes.post("/flowcharts/:id/retry", requireAuth, async (c) => { diff --git a/apps/web/src/oj/problem/components/FlowchartResult.vue b/apps/web/src/oj/problem/components/FlowchartResult.vue index df712dc..7690b45 100644 --- a/apps/web/src/oj/problem/components/FlowchartResult.vue +++ b/apps/web/src/oj/problem/components/FlowchartResult.vue @@ -9,7 +9,7 @@ import { sortFlowchartCriteria } from "utils/constants" import { parseTime } from "utils/functions" import { mermaidOrder, signature } from "shared/components/FlowchartEditor/model" import FlowchartNote from "shared/components/FlowchartNote.vue" -import { caseReport } from "utils/flowchart" +import { caseReport, flowchartVerdict } from "utils/flowchart" /** * 「结果」页签里流程图作业的那一块:AI 在评 / 评完了几分 / 历次分数 / 评语和改进建议。 @@ -252,9 +252,7 @@ function goCode() {
{{ current.grade }} 级 - {{ - report.passed === report.total ? "画对了" : "还没画对" - }} + {{ flowchartVerdict(report, current.grade) }} 这道题算做完了
@@ -287,6 +285,7 @@ function goCode() { :report="report" :feedback="detail.aiFeedback" :suggestions="detail.aiSuggestions" + :previous="detail.previousCases" class="block" /> diff --git a/apps/web/src/oj/submission/components/FlowchartPane.vue b/apps/web/src/oj/submission/components/FlowchartPane.vue index 12fbbaa..db1c42f 100644 --- a/apps/web/src/oj/submission/components/FlowchartPane.vue +++ b/apps/web/src/oj/submission/components/FlowchartPane.vue @@ -11,7 +11,7 @@ import { submissionTimeText } from "../utils" import FlowchartState from "./FlowchartState.vue" import UserName from "shared/components/UserName.vue" import FlowchartNote from "shared/components/FlowchartNote.vue" -import { caseReport } from "utils/flowchart" +import { caseReport, flowchartVerdict } from "utils/flowchart" /** * 流程图模式的右栏:左边画出来的图,右边四项评分和建议。原来要点编号再开一个 @@ -89,9 +89,26 @@ async function load() { detail.value = res await nextTick() await renderFlowchart(graph.value, res.mermaidCode) + fitGraph(graph.value) markFocus(graph.value) } +/** + * 图填满图框:大图照旧缩小,小图也放大,但最多到原尺寸的 1.5 倍(再大字就粗笨了)。 + * 原来只缩不放,比大小这种五六个框的图在一大块空白中间只占一小团 + */ +function fitGraph(el: HTMLElement | null) { + const svg = el?.querySelector("svg") + const w = Number.parseFloat(svg?.getAttribute("width") ?? "") + const h = Number.parseFloat(svg?.getAttribute("height") ?? "") + if (!svg || !w || !h) return + if (!svg.getAttribute("viewBox")) svg.setAttribute("viewBox", `0 0 ${w} ${h}`) + svg.style.width = "100%" + svg.style.height = "100%" + svg.style.maxWidth = `${w * 1.5}px` + svg.style.maxHeight = `${h * 1.5}px` +} + /** * 要改的那个框标红:AI 对着答案指出的,没有就退回程序找到的第一处出错的框。 * pele 把 Mermaid 的框号放在节点 的 data-id 上 @@ -141,7 +158,6 @@ function barColor(score: number, max: number) { /** 2026-10 起的「走例子」报告;老提交是 null,照旧显示四项分 */ const report = computed(() => caseReport(shown.value?.aiCriteriaDetails)) -const allPassed = computed(() => !!report.value && report.value.passed === report.value.total) const openCase = ref(null) /** 逐组数据默认收着,老师和学生一样(设计稿 FlowNotePane);翻到下一张收回去 */ const casesOpen = ref(false) @@ -275,13 +291,14 @@ const canRetry = computed( 这次评分失败了{{ teacher ? ",可以点「重新评分」" : "" }} @@ -512,8 +529,9 @@ const canRetry = computed( background: v-bind("theme.cardColor"); } +/* 图占大头(设计稿 FlowNotePane):42% 时蜜橘分拣这种宽图只剩三成大,框里的字看不清 */ .graph-box { - flex: 0 0 42%; + flex: 0 0 62%; min-width: 0; display: flex; flex-direction: column; diff --git a/apps/web/src/shared/components/FlowchartNote.vue b/apps/web/src/shared/components/FlowchartNote.vue index 4ada4fd..594d777 100644 --- a/apps/web/src/shared/components/FlowchartNote.vue +++ b/apps/web/src/shared/components/FlowchartNote.vue @@ -19,6 +19,8 @@ const props = defineProps<{ report: FlowchartCaseReport feedback: string | null suggestions: string | null + /** 上一次走对了几组(详情接口的 previousCases) */ + previous?: { passed: number; total: number } | null /** 提交列表右栏窄:字小一号 */ compact?: boolean }>() @@ -40,6 +42,24 @@ const better = computed(() => (passed.value ? (props.suggestions?.trim() ?? "") /** 收起时连点开的那一组一起收掉(画布回到只标要改的框)。先算好再写:外面接着 v-model 时, * 写完马上读 open.value 读到的还是旧值 */ +/** + * 「比上次多对了 n 组」:只在这次没画对时说(画对了标题已经够了),一样多不说; + * 两次的组数不一样(老师中途改了例子)就没法比,也不说 + */ +const compare = computed(() => { + const previous = props.previous + if (passed.value || !previous || previous.total !== props.report.total) return null + const diff = props.report.passed - previous.passed + if (diff > 0) + return { up: true, text: `比上次多对了 ${diff} 组${props.compact ? "" : ",接着改"}` } + if (diff < 0) + return { + up: false, + text: `比上次少对了 ${-diff} 组${props.compact ? "" : ",看看刚才改了哪里"}`, + } + return null +}) + function toggle() { const next = !open.value open.value = next @@ -49,6 +69,28 @@ function toggle() {