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 = () => {