refactor(前端): 全站共用的班级下拉、胶囊配色挪出提交列表目录,编辑器不再偷读题目页的 store
Deploy / deploy (push) Canceled after 0s

- classLabel / groupClassOptions / classSelectOptions 挪到 utils/classes.ts,useTone 挪到
  shared/composables/tone.ts:原来放在 oj/submission/ 下,被前台、后台、shared 引了 50 多处
- 流程图编辑器的草稿键改由题目页传进来(键名不变,学生存着的草稿照样读得到)。原来编辑器
  自己读题目页的 store 和路由拼,后台出题页也用这个组件,那边 store 是空的
- SQL 表名补全改由题目页传表进来。原来补全直接读题目页的 store,自学页的 SQL 代码块会
  补出上一道看过的题的表名

实跑:提交列表、题目页、排名、后台题目页、SQL 题、流程图题打开无报错;SQL 题输入 stu
补出 students;流程图加一步,草稿照旧存在 flowchart-v2-problem-<题号>。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-16 14:59:55 +08:00
1 parent 465dbfd42f
commit 309ca0ece2
59 files changed
+168 -142

No files matched your search

@@ -6,7 +6,7 @@ import { useMermaid } from "shared/composables/useMermaid"
import { sortFlowchartCriteria } from "utils/constants"
import type { FlowchartSubmission, FlowchartSubmissionListItem } from "utils/types"
import { FlowchartSubmissionStatus } from "utils/types"
import { useTone } from "../composables/tone"
import { useTone } from "shared/composables/tone"
import { submissionTimeText } from "../utils"
import FlowchartState from "./FlowchartState.vue"
import UserName from "shared/components/UserName.vue"
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { FlowchartSubmissionStatus } from "utils/types"
import { useTone, type Tone } from "../composables/tone"
import { useTone, type Tone } from "shared/composables/tone"
/**
* 流程图提交在列表里的那个胶囊:评完的直接是「A 级」,没评完的是
@@ -2,7 +2,7 @@
import { Icon } from "@iconify/vue"
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
import type { SUBMISSION_RESULT } from "utils/types"
import { useTone, type Tone } from "../composables/tone"
import { useTone, type Tone } from "shared/composables/tone"
const props = defineProps<{
result: SUBMISSION_RESULT
@@ -14,8 +14,9 @@ import { submissionCaseResults } from "utils/functions"
import type { Submission, SubmissionListItem } from "utils/types"
import { useCopySubmission } from "../composables/copySubmission"
import { useSubmissionExplain } from "../composables/explain"
import { useTone } from "../composables/tone"
import { classLabel, submissionTimeText } from "../utils"
import { useTone } from "shared/composables/tone"
import { classLabel } from "utils/classes"
import { submissionTimeText } from "../utils"
import CodeView from "./CodeView.vue"
import ExplainNote from "./ExplainNote.vue"
import StatusPill from "./StatusPill.vue"
@@ -19,8 +19,9 @@ import { parseTime, submissionCaseResults } from "utils/functions"
import type { Submission } from "utils/types"
import { useCopySubmission } from "../composables/copySubmission"
import { useSubmissionExplain } from "../composables/explain"
import { useTone } from "../composables/tone"
import { classLabel, submissionTimeText } from "../utils"
import { useTone } from "shared/composables/tone"
import { classLabel } from "utils/classes"
import { submissionTimeText } from "../utils"
import CodeView from "./CodeView.vue"
import ExplainNote from "./ExplainNote.vue"
import StatusPill from "./StatusPill.vue"
@@ -3,7 +3,7 @@ import { explainPythonCompileError } from "oj/problem/utils/pythonError"
import { explainRuntimeError } from "oj/problem/utils/runtimeError"
import { SubmissionStatus } from "utils/constants"
import type { Submission } from "utils/types"
import { useTone } from "./tone"
import { useTone } from "shared/composables/tone"
export interface SubmissionExplain {
/** 错在第几行(编辑器里那一行标红),说不出来的为 null */
@@ -1,50 +0,0 @@
import { useThemeVars } from "naive-ui"
import { useDark } from "@vueuse/core"
export type Tone = "success" | "error" | "warning" | "info" | "default"
export function rgba(hex: string, alpha: number) {
const value = hex.replace("#", "")
const full =
value.length === 3
? value
.split("")
.map((c) => c + c)
.join("")
: value.slice(0, 6)
const n = parseInt(full, 16)
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`
}
/**
* 提交列表里各种小胶囊的配色:浅底 + 深一档的字(设计稿「提交列表重设计 · 定稿」)。
* 从主题色里取,暗色下字换成主题色本身(深色字放在暗底上看不清)。
* 不用 color-mix:Chrome 105 没有(111 才有),机房有一部分还是 105。
*/
export function useTone() {
const theme = useThemeVars()
const isDark = useDark()
return function tone(kind: Tone) {
const t = theme.value
const base = {
success: t.successColor,
error: t.errorColor,
warning: t.warningColor,
info: t.infoColor,
default: t.textColor3,
}[kind]
const pressed = {
success: t.successColorPressed,
error: t.errorColorPressed,
warning: t.warningColorPressed,
info: t.infoColorPressed,
default: t.textColor2,
}[kind]
return {
color: isDark.value ? base : pressed,
background: kind === "default" ? t.actionColor : rgba(base, isDark.value ? 0.16 : 0.12),
solid: base,
}
}
}
+3 -2
View File
@@ -31,8 +31,9 @@ import { FlowchartSubmissionStatus } from "utils/types"
import FlowchartState from "./components/FlowchartState.vue"
import StatusPill from "./components/StatusPill.vue"
import UserName from "shared/components/UserName.vue"
import { useTone } from "./composables/tone"
import { classLabel, classSelectOptions, submissionClockText, submissionDayText } from "./utils"
import { useTone } from "shared/composables/tone"
import { classLabel, classSelectOptions } from "utils/classes"
import { submissionClockText, submissionDayText } from "./utils"
/**
* 提交列表(设计稿「提交列表重设计 · 定稿」):左边列表、右边常驻选中那条的代码。
-55
View File
@@ -1,4 +1,3 @@
import { NO_CLASS } from "@oj2/contract"
import { parseTime } from "utils/functions"
/**
@@ -33,57 +32,3 @@ export function submissionDayText(time: string) {
if (day.slice(0, 4) === now.slice(0, 4)) return parseTime(time, "M月D日")
return parseTime(time, "YYYY年M月D日")
}
/**
* 班级下拉的标签:`253` → `25计算机3班`,和统计面板、班级 PK 同一个写法。
* null(接口里没填班级的号)和 NO_CLASS(筛选里的「没填班级」)都写成「没填班级」
*/
export function classLabel(className: string | null) {
if (className === null || className === NO_CLASS) return "没填班级"
if (!/^\d{3,}$/.test(className)) return className
return `${className.slice(0, 2)}计算机${className.slice(2)}班`
}
type ClassOption = { label: string; value: string }
/**
* 全站的班级下拉都按年级分组:「25 级」底下列 251、252……,新的年级在上,组里按班号排
* (11 班排在 2 班后面)。年级是班号前两位,和排名的「本年级」同一个口径。
* `first` 给了就把那个年级提到最前(班级 PK 加班时先给同年级的)。
* `toOption` 换掉选项长相(题单的「n/m 人加入」、登录框的 ks 前缀);不是数字班号的
* 名字不分组,按原顺序跟在最后。
*/
export function groupClassOptions<T extends ClassOption = ClassOption>(
names: string[],
toOption: (name: string) => T = (name) => ({ label: classLabel(name), value: name }) as T,
first?: string,
) {
const groups = new Map<string, string[]>()
const rest: string[] = []
for (const name of new Set(names)) {
if (!/^\d{3,}$/.test(name)) {
rest.push(name)
continue
}
const grade = name.slice(0, 2)
groups.set(grade, [...(groups.get(grade) ?? []), name])
}
const grouped = [...groups]
.sort((a, b) => (a[0] === first ? -1 : b[0] === first ? 1 : b[0].localeCompare(a[0])))
.map(([grade, list]) => ({
type: "group" as const,
label: `${grade} 级`,
key: `grade-${grade}`,
children: list.sort((a, b) => a.localeCompare(b, "zh", { numeric: true })).map(toOption),
}))
return [...grouped, ...rest.map(toOption)]
}
/**
* 班级下拉的选项:配置里的班按年级分组,最后一项「没填班级」(这批号也要能和普通班一样
* 圈出来看)。带进来的班不在配置里(老班、手打的)也要显示得出来,不然框里是空的却在筛
*/
export function classSelectOptions(list: string[], current: string) {
const all = current && current !== NO_CLASS ? [current, ...list] : list
return [...groupClassOptions(all), { label: classLabel(NO_CLASS), value: NO_CLASS }]
}