From 11368813cc1897f2ba03821eb4d15ac8d8b61118 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 28 Sep 2026 18:19:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=81=E7=A8=8B=E5=9B=BE):=20=E3=80=8C?= =?UTF-8?q?=E5=86=99=E4=BB=A3=E7=A0=81=20/=20=E7=94=BB=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=9B=BE=E3=80=8D=E5=8F=98=E6=88=90=E5=B7=A5=E5=85=B7=E6=A0=8F?= =?UTF-8?q?=E4=B8=8A=E7=9A=84=E5=88=87=E6=8D=A2=EF=BC=8CAI=20=E7=9A=84?= =?UTF-8?q?=E8=AF=84=E5=88=86=E5=92=8C=E5=8E=86=E6=AC=A1=E5=88=86=E6=95=B0?= =?UTF-8?q?=E6=8C=AA=E8=BF=9B=E7=BB=93=E6=9E=9C=E9=A1=B5=E7=AD=BE=EF=BC=8C?= =?UTF-8?q?=E9=A2=98=E5=8D=95=E9=87=8C=E4=B9=9F=E8=83=BD=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 流程图题的工具栏最左边是「写代码 / 画流程图」。原来流程图藏在语言下拉里,叫一个名叫 Flowchart 的「语言」;画完一道题之后它还被记成语言偏好,下一道能画的题一打开就是画布 - 「交给 AI 点评」交完切到「结果」页签:评的时候写着「AI 正在看你的图」、画布照样能改; 评完是分数、评语、改进建议、四项得分,顶上一排历次分数,点哪次看哪次。 原来是提交按钮旁一个 1000px 的弹框,盖住画布,翻历次评分要一页一页点 - 画到 A / S:给「照着它写代码 →」,题单里 1.5 秒后回题单(和代码通过一样), 课堂条当场标成做完;只在这一页上刚评完的那次才算,进页面读到以前的 A 不跳 - 「把这一版载回画布」只在画布和那一版不一样时出现;载回、进页面读回草稿后都缩放到装下整张图 - 题单入口能画流程图了(原来只有题库入口能画),题单里的代码草稿、流程图草稿各存一份, 加入题单之前写的、画的不会一进题单就摆在编辑器里 - 手机上不给「画流程图」(节点库是拖放,手机上拖不动),题面里以前画好的那张照样看得到 Co-Authored-By: Claude Opus 5.5 --- apps/web/CLAUDE.md | 2 +- apps/web/src/oj/api.ts | 14 +- .../src/oj/problem/components/ContextBar.vue | 24 +- .../oj/problem/components/EditorToolbar.vue | 24 +- .../oj/problem/components/FlowchartResult.vue | 388 ++++++++++++++++++ .../oj/problem/components/ProblemContent.vue | 16 +- .../oj/problem/components/ProblemEditor.vue | 36 +- .../src/oj/problem/components/ResultPane.vue | 9 +- .../oj/problem/components/ResultTabLabel.vue | 18 + .../problem/components/SubmissionEffects.vue | 14 + .../oj/problem/components/SubmitFlowchart.vue | 338 ++------------- apps/web/src/oj/store/code.ts | 23 +- apps/web/src/oj/store/flowchart.ts | 147 +++++-- apps/web/src/oj/store/problem.ts | 42 +- .../components/FlowchartEditor/index.vue | 38 +- ...2026-09-28-problem-page-redesign-design.md | 2 +- 16 files changed, 747 insertions(+), 388 deletions(-) create mode 100644 apps/web/src/oj/problem/components/FlowchartResult.vue diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md index 42ba7e1..cd67c69 100644 --- a/apps/web/CLAUDE.md +++ b/apps/web/CLAUDE.md @@ -58,7 +58,7 @@ API 调用不按模块分:学生端全在 `oj/api.ts`、后台全在 `admin/ap 跨端的(登录、资料、标签、验证码)在 `shared/api.ts`。 Shared logic lives in `shared/`: -- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `loginSummary` (AI activity summary), `collab` (help-request queue + collab room), `achievement` (解锁弹窗队列), `myFlowchart` (流程图弹窗的 mermaid 源码) +- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `loginSummary` (AI activity summary), `collab` (help-request queue + collab room), `achievement` (解锁弹窗队列), `myFlowchart` (题面「你画的流程图」那张 A/S 图的 mermaid 源码) - `composables/` — `aiStream`, `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `collabDoc` (Yjs binding for the collab channel), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `darkTransition` (View Transitions,111 以下走降级分支), `hiddenStudents` (统计面板的「请假隐藏」), `chartTheme`, `breakpoints`, `maxkb`, `learnProgress`, `rarity` - `layout/` — `default.vue` and `admin.vue` layout wrappers - `api.ts` — shared API calls (auth, profile, tags, captcha) diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts index f902f08..b01da55 100644 --- a/apps/web/src/oj/api.ts +++ b/apps/web/src/oj/api.ts @@ -23,8 +23,7 @@ import { type YearlyAc, type ProblemList, type CreateFlowchartResponse, - type FlowchartCurrent, - type FlowchartDetail, + type FlowchartScores, type FlowchartList, type FlowchartSubmission, type AiDetail, @@ -437,14 +436,9 @@ export function retryFlowchartSubmission(submissionId: string) { return api.post<{ status: string }>(`flowcharts/${encodeURIComponent(submissionId)}/retry`) } -export function getCurrentProblemFlowchartSubmission(problemId: number) { - return api.get(`problems/${problemId}/flowchart/current`) -} - -export function getFlowchartSubmissionDetail(problemId: number, page = 0) { - return api.get(`problems/${problemId}/flowchart/history`, { - params: { page }, - }) +/** 自己在这道题上评完的每一次(早的在前),加入题单之前的已经滤掉 */ +export function getFlowchartScores(problemId: number) { + return api.get(`problems/${problemId}/flowchart/scores`) } // ==================== 题单相关API ==================== diff --git a/apps/web/src/oj/problem/components/ContextBar.vue b/apps/web/src/oj/problem/components/ContextBar.vue index 2d6b29f..424afa0 100644 --- a/apps/web/src/oj/problem/components/ContextBar.vue +++ b/apps/web/src/oj/problem/components/ContextBar.vue @@ -3,6 +3,7 @@ import { storeToRefs } from "pinia" import type { RouteLocationRaw } from "vue-router" import { getProblemSetDetail, getProblemSetProblems } from "oj/api" import { useContestStore } from "oj/store/contest" +import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart" import { useLessonStore } from "oj/store/lesson" import { useProblemStore } from "oj/store/problem" import { useUserStore } from "shared/store/user" @@ -30,7 +31,11 @@ const userStore = useUserStore() const { problem } = storeToRefs(useProblemStore()) const currentId = computed(() => problem.value?._id ?? "") -/** 刚在这一页上做出来(不是本来就做过):换题时 id 跟着变,那一次不算 */ +/** + * 刚在这一页上做出来(不是本来就做过):代码通过,或者流程图刚评到 A / S。 + * 换题时 id 跟着变,那一次不算;流程图只认这一页上刚评完的(evaluatedSeq) + */ +const flowchartStore = useFlowchartStore() function onSolvedHere(callback: () => void) { watch( () => [problem.value?.id, problem.value?.myStatus] as const, @@ -38,6 +43,12 @@ function onSolvedHere(callback: () => void) { if (id !== undefined && id === previousId && status === 0 && previousStatus !== 0) callback() }, ) + watch( + () => flowchartStore.evaluatedSeq, + () => { + if (isFlowchartPass(flowchartStore.latestRating.grade)) callback() + }, + ) } // ==================== 课堂 ==================== @@ -124,13 +135,18 @@ const setBar = computed(() => { params: { problemSetId: ctx.value.problemSetId, problemID: item.problem._id }, } : null - const done = list.filter((item) => item.isCompleted).length + // 进度只数必做题,和题单页的「完成进度 2 / 8(另有 3 道选做)」同一个口径; + // 一道必做都没有的题单才数全部 + const required = list.some((item) => item.isRequired) + ? list.filter((item) => item.isRequired) + : list + const done = required.filter((item) => item.isCompleted).length return { title: problemSet.value.title, home: { name: "problemset", params: { problemSetId: ctx.value.problemSetId } }, done, - total: list.length, - percentage: list.length ? (done / list.length) * 100 : 0, + total: required.length, + percentage: required.length ? (done / required.length) * 100 : 0, position: index >= 0 ? index + 1 : null, previous: index >= 0 ? link(list[index - 1]) : null, next: index >= 0 ? link(list[index + 1]) : null, diff --git a/apps/web/src/oj/problem/components/EditorToolbar.vue b/apps/web/src/oj/problem/components/EditorToolbar.vue index edfd939..694ffc6 100644 --- a/apps/web/src/oj/problem/components/EditorToolbar.vue +++ b/apps/web/src/oj/problem/components/EditorToolbar.vue @@ -44,7 +44,20 @@ const ctx = useProblemPageContext() const userStore = useUserStore() const codeStore = useCodeStore() const problemStore = useProblemStore() -const { problem, languages } = storeToRefs(problemStore) +const { problem, codeLanguages, canDraw } = storeToRefs(problemStore) + +/** + * 「写代码 / 画流程图」是两种作业,不是两门语言。原来流程图藏在语言下拉里,叫一个名叫 + * Flowchart 的「语言」—— 有的题流程图交了几百次、代码个位数,入口却在下拉的第一项里。 + */ +const drawing = computed(() => codeStore.code.language === "Flowchart") +const mode = computed({ + get: () => (drawing.value ? "draw" : "code"), + set: (value: "draw" | "code") => { + if (value === "draw") problemStore.switchLanguage("Flowchart") + else problemStore.switchLanguage(problemStore.supportedLanguage(codeStore.preferredLanguage())) + }, +}) const { isDesktop } = useBreakpoints() @@ -183,7 +196,7 @@ const handleMenuSelect = (key: string) => { // computed:换题时组件复用,选项得跟着新题的语言走 const languageOptions = computed(() => - languages.value.map((it) => ({ + codeLanguages.value.map((it) => ({ label: () => h(NFlex, { align: "center" }, () => [ h(Icon, { @@ -239,8 +252,15 @@ const goEdit = () => {