From 8861393529f183f2304436ebcf1c1086b8267b77 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 27 Aug 2026 10:53:29 -0600 Subject: [PATCH] =?UTF-8?q?fix(=E6=B5=81=E7=A8=8B=E5=9B=BE):=20=E8=AF=84?= =?UTF-8?q?=E5=88=86=E6=98=8E=E7=BB=86=E7=9A=84=E9=A1=BA=E5=BA=8F=E6=98=AF?= =?UTF-8?q?=E4=B9=B1=E7=9A=84=EF=BC=8C40=20=E5=88=86=E7=9A=84=E9=82=A3?= =?UTF-8?q?=E9=A1=B9=E6=8E=92=E5=9C=A8=E6=9C=80=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI 是按分值从高到低返回的:`逻辑正确性(40) → 完整性(30) → 规范性(20) → 清晰度(10)`。 但 `ai_criteria_details` 存在 jsonb 列里,而 Postgres 的 jsonb **不保留键序** —— 它按「键长度 + 字节序」重排,读出来变成 `完整性 → 清晰度 → 规范性 → 逻辑正确性`, 权重最高的那项被排到了最后。 评分弹框和教师端的评分详情都是直接 `v-for` 遍历这个对象,所以两处都乱。 统计面板因为用的是写死的 `CRITERIA_ORDER`,一直是对的 —— 于是同一份数据在两个 地方的顺序还不一致。 把顺序抽到 `utils/constants` 共享,三处统一走 `sortFlowchartCriteria()`; 表里没有的键排到后面,AI 万一返回别的评分项也不会丢。 顺带把限流的提示改得能看懂:撞上 429 时原来只显示「流程图提交失败」,学生会以为 是自己的图有问题然后反复点,越点等得越久。现在按错误码分支,提示「提交太频繁了, 缓一会儿再交」。 Co-Authored-By: Claude Opus 5 --- .../oj/problem/components/SubmitFlowchart.vue | 22 +++++++++++++--- .../components/FlowchartScoreDetail.vue | 8 ++++-- .../components/FlowchartStatisticsPanel.vue | 5 ++-- apps/web/src/utils/constants.ts | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/apps/web/src/oj/problem/components/SubmitFlowchart.vue b/apps/web/src/oj/problem/components/SubmitFlowchart.vue index 6bbcda4..38e8bf3 100644 --- a/apps/web/src/oj/problem/components/SubmitFlowchart.vue +++ b/apps/web/src/oj/problem/components/SubmitFlowchart.vue @@ -3,6 +3,7 @@ import { toRefs } from "vue" // 工具函数 import { atou, utoa } from "utils/functions" +import { sortFlowchartCriteria } from "utils/constants" // 组合式函数 import { useBreakpoints } from "shared/composables/breakpoints" @@ -79,6 +80,11 @@ const suggestionLines = computed(() => splitSuggestionLines(evaluation.value.suggestions), ) +// jsonb 不保留键序,直接遍历会把 40 分的「逻辑正确性」排到最后 +const sortedCriteria = computed(() => + sortFlowchartCriteria(evaluation.value.criteria_details), +) + function splitSuggestionLines(suggestions?: string | null) { return suggestions ? suggestions @@ -251,9 +257,17 @@ async function submitFlowchartData() { } message.success("流程图已提交,请耐心等待评分") - } catch (error) { + } catch (error: any) { loading.value = false - message.error("流程图提交失败") + // 按错误码分支(见 utils/api.ts 的约定)。限流是最容易撞上的一种: + // 只说「提交失败」的话,学生会以为是自己的图有问题,然后反复点,越点越久 + if (error?.error === "too-many-submissions") { + message.warning("提交太频繁了,缓一会儿再交") + } else if (error?.error === "flowchart-not-allowed") { + message.error("这道题不接受流程图提交") + } else { + message.error("流程图提交失败") + } console.error("提交流程图失败:", error) } } @@ -496,12 +510,12 @@ onUnmounted(() => {
diff --git a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue index ed90cbc..b3730d2 100644 --- a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue +++ b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue @@ -70,12 +70,12 @@
@@ -109,6 +109,7 @@ import { Icon } from "@iconify/vue" import type { FlowchartSubmission } from "utils/types" import { useMermaid } from "shared/composables/useMermaid" +import { sortFlowchartCriteria } from "utils/constants" interface Props { submissionId: string @@ -146,6 +147,9 @@ const criteriaDetails = computed< }), ) }) +// jsonb 不保留键序,直接遍历会把 40 分的「逻辑正确性」排到最后 +const sortedCriteria = computed(() => sortFlowchartCriteria(criteriaDetails.value)) + const loading = ref(false) const rendering = ref(false) const showLargeImage = ref(false) diff --git a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue index 79f2204..6382cab 100644 --- a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue +++ b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue @@ -162,7 +162,7 @@ import { formatISO, sub, type Duration } from "date-fns" import type { FlowchartStatistics } from "@oj2/contract" import { getFlowchartStatistics } from "oj/api" -import { DURATION_OPTIONS } from "utils/constants" +import { DURATION_OPTIONS, FLOWCHART_CRITERIA_ORDER } from "utils/constants" import { Doughnut, Radar, Bar } from "vue-chartjs" import { Chart as ChartJS, @@ -366,7 +366,8 @@ const doughnutOptions = { }, } -const CRITERIA_ORDER = ["逻辑正确性", "完整性", "规范性", "清晰度"] +// 与评分明细共用同一份顺序,见 utils/constants +const CRITERIA_ORDER = FLOWCHART_CRITERIA_ORDER const hasRadarData = computed(() => CRITERIA_ORDER.some((k) => k in data.criteriaAverages), diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts index afe47f9..3485ea4 100644 --- a/apps/web/src/utils/constants.ts +++ b/apps/web/src/utils/constants.ts @@ -304,6 +304,31 @@ export const RARITY_TEXT_COLOR: Record< } // 时间范围配置 +/** + * 流程图评分项的展示顺序(按分值从高到低)。 + * + * `ai_criteria_details` 存在 jsonb 列里,而 Postgres 的 jsonb **不保留键序** —— + * 它按「键长度 + 字节序」重排,读出来会变成 完整性 / 清晰度 / 规范性 / 逻辑正确性, + * 40 分的那项排到最后。凡是要展示评分明细的地方都按这个顺序排,别直接遍历对象。 + */ +export const FLOWCHART_CRITERIA_ORDER = [ + "逻辑正确性", + "完整性", + "规范性", + "清晰度", +] + +/** 按 FLOWCHART_CRITERIA_ORDER 排序,表里没有的键排在后面并保持原有相对顺序 */ +export function sortFlowchartCriteria( + details: Record, +): [string, T][] { + const rank = (key: string) => { + const i = FLOWCHART_CRITERIA_ORDER.indexOf(key) + return i === -1 ? Number.MAX_SAFE_INTEGER : i + } + return Object.entries(details).sort(([a], [b]) => rank(a) - rank(b)) +} + export const DURATION_OPTIONS = [ { label: "本节课内", value: "hours:1" }, { label: "两节课内", value: "hours:2" },