refactor: 一批名不副实的命名改掉(只动一端,不碰契约)

后端:
- getUserByToken → authenticateToken:它会删会话、续 TTL、记在线,不是纯读
- todayStart → dayStart(instant):本来就接受任意时刻,不只是「今天」
- objectValue → asRecord,删掉 4 份一模一样的私有拷贝和 `as toObject` 别名
- 报错文案 "does not exists" / "Already be a public problem" 改通顺

前端:
- utils/types 里 Contest / User / Problem 三个别名删掉:Contest 原来指后台
  比赛、契约里同名的却是学生侧。现在 Contest = 学生侧、AdminContest = 后台,
  后台用户表单叫 AdminUserForm,题目详情直接用 ProblemDetail
- SubmissionInfo → ContestSubmissionInfo,免得和 submission.info 撞名
- utoa / atou → compressToBase64 / decompressFromBase64(实际是 zlib + base64),
  encode / decode → base64EncodeUtf8 / base64DecodeUtf8
- filterResult → toProblemRow、toProblemListItem → toAdminProblemRow,
  行字段 submission / rate → submissionCount / acRate
- 组件:Modal → AddFromLibraryModal、Form → EditorToolbar、
  ContestEditor → BasicEditor(题单也在用它)、rank 的 Index → RankMedal、
  Name → UserNameCell、Password → PasswordCell、ContestType → ContestAccessTag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-27 16:08:21 +08:00
1 parent 572a818d49
commit 5ae830a7f9
62 files changed
+284 -320

No files matched your search

+6 -6
View File
@@ -1,7 +1,7 @@
import { TIME_ZONE_OFFSET_MINUTES, toAdminType } from "@oj2/contract"
import type { JudgeCaseResult, JudgeInfo } from "@oj2/contract"
import { getTime, intervalToDuration, parseISO, type Duration } from "date-fns"
import { Submission, User } from "./types"
import { AdminUser, Submission } from "./types"
import { JUDGE_STATUS, USER_TYPE } from "./constants"
import {
strFromU8,
@@ -283,7 +283,7 @@ export function debounce<T extends (...args: any[]) => any>(
}
}
export function getUserRole(role: User["adminType"]): {
export function getUserRole(role: AdminUser["adminType"]): {
type: "default" | "info" | "warning" | "error"
label: "普通" | "生管" | "师管" | "超管"
} {
@@ -315,7 +315,7 @@ export function unique<T>(arr: T[]): T[] {
return [...new Set(arr)]
}
export function encode(string?: string): string {
export function base64EncodeUtf8(string?: string): string {
try {
return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? "")))
} catch (error) {
@@ -324,7 +324,7 @@ export function encode(string?: string): string {
}
}
export function decode(bytes?: string): string {
export function base64DecodeUtf8(bytes?: string): string {
try {
if (!bytes) return ""
const latin = atob(bytes)
@@ -339,14 +339,14 @@ export function decode(bytes?: string): string {
}
}
export function utoa(data: string): string {
export function compressToBase64(data: string): string {
const buffer = strToU8(data)
const zipped = zlibSync(buffer, { level: 9 })
const binary = strFromU8(zipped, true)
return btoa(binary)
}
export function atou(base64: string): string {
export function decompressFromBase64(base64: string): string {
const binary = atob(base64)
const buffer = strToU8(binary, true)
const unzipped = unzlibSync(buffer)
+7 -4
View File
@@ -1,5 +1,5 @@
import axios from "axios"
import { decode, encode } from "./functions"
import { base64DecodeUtf8, base64EncodeUtf8 } from "./functions"
import type { Code, LANGUAGE } from "./types"
const http = axios.create({ baseURL: import.meta.env.PUBLIC_JUDGE0_URL })
@@ -16,7 +16,7 @@ const JUDGE0_LANGUAGE_ID: Partial<Record<LANGUAGE, number>> = {
}
export async function createTestSubmission(code: Code, input: string) {
const encodedCode = encode(code.value)
const encodedCode = base64EncodeUtf8(code.value)
const id = JUDGE0_LANGUAGE_ID[code.language]
if (id === undefined) {
return { status: null, output: `${code.language} 不支持在线试运行` }
@@ -26,7 +26,7 @@ export async function createTestSubmission(code: Code, input: string) {
const payload = {
source_code: encodedCode,
language_id: id,
stdin: encode(input),
stdin: base64EncodeUtf8(input),
redirect_stderr_to_stdout: true,
compiler_options: compilerOptions,
}
@@ -36,7 +36,10 @@ export async function createTestSubmission(code: Code, input: string) {
const data = response.data
return {
status: data.status && data.status.id,
output: [decode(data.compile_output), decode(data.stdout)]
output: [
base64DecodeUtf8(data.compile_output),
base64DecodeUtf8(data.stdout),
]
.join("\n")
.trim(),
}
+13 -28
View File
@@ -10,7 +10,6 @@ import type {
EmbeddedSubmission as ContractEmbeddedSubmission,
Message as ContractMessage,
Grade,
ProblemDetail,
ProblemDifficulty,
JudgeStatus,
AstRules,
@@ -41,7 +40,7 @@ export interface AcmProblemsStatus {
* 后台用户管理里的用户。`rawPassword` 是明文密码,只有超管专属接口下发 ——
* 老师要能查学生密码,见契约 adminUserSchema 的注释。
*/
export type User = AdminUser & {
export type AdminUserForm = AdminUser & {
// 编辑表单里临时填的新密码,不在响应里
password?: string
}
@@ -111,20 +110,6 @@ export type { UploadTestCaseResponse } from "@oj2/contract"
*/
export type { ProblemTestCaseScore as Testcase } from "@oj2/contract"
/**
* 题目详情。**直接取契约** —— `languages` / `template` 的收窄已经搬进
* `problemDetailSchema`(`z.array(problemLanguageSchema)` 与
* `z.partialRecord(problemLanguageSchema, …)`),这里原来那份
* `Omit<ProblemDetail, "languages" | "template"> & {...}` 与契约**双向可赋值**,
* 即完全等价,是一层没有内容的重复(已用类型探针验证)。
*
* 原来它还多挂三个可选字段(`hasAstRules` / `visible` / `answers`)。它们在
* 契约里**本来就有**(`hasAstRules` 在题目列表项上、`ProblemDetail` 带的是
* `astRequirements`),少数管理端调用点需要补充时应该就地声明自己的类型,
* 不该让一个全局别名对所有调用方声称这些字段存在。
*/
export type Problem = ProblemDetail
/**
* AST 代码要求。原来这里手抄的那份少了 label / exact / outer / inner ——
* 编辑器写得出 label 和 exact,判题机也认,只有这个类型不认。形状在契约里。
@@ -174,15 +159,15 @@ export type BlankProblem = Omit<
mermaidCode: string
}
export interface ProblemFiltered {
export interface ProblemRow {
_id: string
id: number
title: string
// 比赛进行中难度不下发,见 contract 的 maskedProblemDifficultySchema
difficulty: "简单" | "中等" | "困难" | null
tags: string[]
submission: number
rate: string
submissionCount: number
acRate: string
status: "not_test" | "passed" | "failed"
author: string
allowFlowchart: boolean
@@ -190,7 +175,7 @@ export interface ProblemFiltered {
hasAstRules: boolean
}
export interface AdminProblemFiltered {
export interface AdminProblemRow {
_id: string
id: number
title: string
@@ -314,11 +299,11 @@ export type {
ClassUserRank,
} from "@oj2/contract"
/** 后台比赛。oj 侧的 contestSchema 永远不含 password,后台要能看到(告诉学生) */
export type Contest = AdminContest
/** 学生侧的比赛:不含 password / visible */
export type { Contest as OjContest } from "@oj2/contract"
/**
* 学生侧的比赛:不含 password / visible。后台用 `AdminContest` ——
* oj 侧的 contestSchema 永远不含 password,后台要能看到(告诉学生)
*/
export type { Contest, AdminContest } from "@oj2/contract"
export type BlankContest = Omit<
AdminContest,
@@ -331,10 +316,10 @@ export type BlankContest = Omit<
>
/**
* `acm_contest_rank.submission_info` 的 JSONB 内容。**形状已搬进契约**
* (`contestSubmissionInfoSchema`),这里保留别名给既有调用点。
* `acm_contest_rank.submission_info` 的 JSONB 内容,形状在契约
* (`contestSubmissionInfoSchema`)。别和 `submission.info`(判题结果)混了。
*/
export type { ContestSubmissionInfo as SubmissionInfo } from "@oj2/contract"
export type { ContestSubmissionInfo } from "@oj2/contract"
/**
* 榜单行。`submissionInfo` 的收窄(JSONB 原文的 snake_case 形状)已经搬进