Files
OJ2/apps/web/src/utils/constants.ts
T
2026-10-26 13:52:24 +08:00

350 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { JudgeStatusValue } from '@oj2/contract'
import type { AchievementRarity, SUBMISSION_RESULT, ReactionKey } from './types'
// 与后端 judge/status.ts 的 JudgeStatus 逐条对齐(submitting 除外,见下)。
// 注意:原来 time_limit_exceeded 写成 `1 | 2`,TS 按位或算成 3,和
// memory_limit_exceeded 撞了同一个值 —— 后端这两个是分开的两个码。
export enum SubmissionStatus {
compile_error = -2,
wrong_answer = -1,
accepted = 0,
cpu_time_limit_exceeded = 1,
real_time_limit_exceeded = 2,
memory_limit_exceeded = 3,
runtime_error = 4,
system_error = 5,
pending = 6,
judging = 7,
partial_accepted = 8,
// 前端自造的伪状态:点了提交、还没拿到判题结果时本地先填 9。
// 后端永远不会下发它,所以契约的 judgeStatusSchema 里没有。
submitting = 9,
ast_check_failed = 10,
}
// 编译期对齐契约:契约加/改一个码而这里没跟,下面两行会当场编译不过。
type SyncedWithContract =
Exclude<`${SubmissionStatus}`, `${SubmissionStatus.submitting}`> extends `${JudgeStatusValue}`
? `${JudgeStatusValue}` extends `${Exclude<SubmissionStatus, SubmissionStatus.submitting>}`
? true
: never
: never
export const _submissionStatusSynced: SyncedWithContract = true
export enum ContestStatus {
initial = '2', // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位
not_started = '1',
underway = '0',
finished = '-1',
}
export enum ContestType {
public = 'Public',
private = 'Password Protected',
}
export const JUDGE_STATUS: {
[key in SUBMISSION_RESULT]: {
name: string
title: string
type: 'error' | 'success' | 'warning' | 'info'
}
} = {
'-2': {
name: '编译失败',
title: '编译失败',
type: 'warning',
},
'-1': {
name: '答案错误',
title: '答案错误',
type: 'error',
},
'0': {
name: '答案正确',
title: '答案正确',
type: 'success',
},
'1': {
name: '运行超时',
title: '运行超时',
type: 'error',
},
'2': {
name: '运行超时',
title: '运行超时',
type: 'error',
},
'3': {
name: '内存超限',
title: '内存超限',
type: 'error',
},
'4': {
name: '运行时错误',
title: '运行时错误',
type: 'warning',
},
'5': {
name: '系统错误',
title: '系统错误',
type: 'error',
},
'6': {
name: '等待评分',
title: '等待评分',
type: 'warning',
},
'7': {
name: '正在评分',
title: '正在评分',
type: 'warning',
},
'8': {
name: '部分正确',
title: '部分正确',
type: 'warning',
},
'9': {
name: '正在提交',
title: '正在提交',
type: 'info',
},
'10': {
name: '语法未通过',
title: '答案正确,但语法未通过',
type: 'success',
},
}
export const CONTEST_STATUS: {
[key in ContestStatus]: {
name: string
type: 'default' | 'success' | 'warning'
}
} = {
// 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位
'2': {
name: '未开始',
type: 'warning',
},
'1': {
name: '未开始',
type: 'warning',
},
'0': {
name: '进行中',
type: 'success',
},
// 灰的不是红的:列表里九成是已结束的比赛,一片红像满屏报错
'-1': {
name: '已结束',
type: 'default',
},
}
// 角色与题目权限的字符串在 `@oj2/contract` 的 roles.ts 定义,这里只是转出去,
// 调用方照旧写 `USER_TYPE.SUPER_ADMIN`。原先这里是这批字符串的第三份手抄副本。
export { USER_TYPE, PROBLEM_PERMISSION } from '@oj2/contract'
export const STORAGE_KEY = {
AUTHED: 'authed',
ACCESS_TOKEN: 'accessToken',
REFRESH_TOKEN: 'refreshToken',
LANGUAGE: 'problemLanguage',
ADMIN_PROBLEM: 'adminProblem',
ADMIN_SQL_PROBLEM: 'adminSqlProblem',
ADMIN_CODE_PROBLEM: 'adminCodeProblem',
ADMIN_PROBLEM_TAGS: 'adminProblemTags',
ADMIN_SIDER_COLLAPSED: 'adminSiderCollapsed',
DEMO_MODE: 'demoMode',
LOGIN_CLASS: 'loginClass',
LOGIN_MODE: 'loginMode',
RANK_CLASS: 'rankClass',
}
export const DIFFICULTY = {
Low: '简单',
Mid: '中等',
High: '困难',
} as const
const cSource = '#include<stdio.h>\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}'
const cppSource = '#include<iostream>\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}'
const pythonSource = ''
const javaSource = 'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}'
export const SOURCES = {
C: cSource,
'C++': cppSource,
Java: javaSource,
Python: pythonSource,
JavaScript: '',
Golang: '',
Flowchart: '',
SQL: '',
} as const
export const LANGUAGE_FORMAT_VALUE = {
C: 'c',
'C++': 'cpp',
Java: 'java',
Python: 'python',
JavaScript: 'javascript',
Golang: 'go',
Flowchart: 'flowchart',
SQL: 'sql',
} as const
export const LANGUAGE_SHOW_VALUE = {
Flowchart: '流程图',
C: 'C语言',
'C++': 'C++',
Java: 'Java',
Python: 'Python',
JavaScript: 'JS',
Golang: 'Go',
SQL: 'SQL',
} as const
export const ICON_SET = {
Flowchart: 'vscode-icons:file-type-drawio',
Python: 'devicon:python',
C: 'devicon:c',
'C++': 'devicon:cplusplus',
Java: 'devicon:java',
JavaScript: 'devicon:javascript',
Golang: 'devicon:go',
SQL: 'devicon:sqlite',
} as const
const cTemplate = `//TEMPLATE BEGIN
#include <stdio.h>
int main() {
printf("黄岩一职");
return 0;
}
//TEMPLATE END`
const cppTemplate = `//TEMPLATE BEGIN
#include <iostream>
int main() {
return 0;
}
//TEMPLATE END`
const blankTemplate = `//PREPEND BEGIN
//PREPEND END
//TEMPLATE BEGIN
//TEMPLATE END
//APPEND BEGIN
//APPEND END`
export const CODE_TEMPLATES = {
C: cTemplate,
'C++': cppTemplate,
Python: blankTemplate,
Java: blankTemplate,
JavaScript: blankTemplate,
Golang: blankTemplate,
Flowchart: blankTemplate,
SQL: blankTemplate,
} as const
// 成就稀有度
export const RARITY_LABEL: Record<AchievementRarity, string> = {
bronze: '青铜',
silver: '白银',
gold: '黄金',
platinum: '白金',
}
// 边框、色块用的原色:饱和度够高,明暗底上都醒目。
// 青铜往红偏(色相 23),黄金往纯黄推(色相 47),中间隔开两档亮度,
// 不然两个都落在橙棕区,扫一眼分不出来
export const RARITY_COLOR: Record<AchievementRarity, string> = {
bronze: '#c2703d',
silver: '#9fa6b2',
gold: '#f2c012',
platinum: '#7dd3fc',
}
// 文字要另配一套。上面那组是照深色底调的,搬到白底上白金只有 1.7:1、
// 黄金 2.2:1,12px 的稀有度标签根本看不清。取色见 useRarityColor
export const RARITY_TEXT_COLOR: Record<'dark' | 'light', Record<AchievementRarity, string>> = {
dark: {
bronze: '#e08d63',
silver: '#b6bcc7',
gold: '#f2c012',
platinum: '#7dd3fc',
},
light: {
bronze: '#a13d1e',
silver: '#6b7280',
gold: '#8f6f00',
platinum: '#0369a1',
},
}
// 时间范围配置
/**
* 流程图评分项的展示顺序(按分值从高到低)。
*
* `ai_criteria_details` 存在 jsonb 列里,而 Postgres 的 jsonb **不保留键序** ——
* 它按「键长度 + 字节序」重排,读出来会变成 完整性 / 清晰度 / 规范性 / 逻辑正确性,
* 40 分的那项排到最后。凡是要展示评分明细的地方都按这个顺序排,别直接遍历对象。
*/
export const FLOWCHART_CRITERIA_ORDER = ['逻辑正确性', '完整性', '规范性', '清晰度']
/** 按 FLOWCHART_CRITERIA_ORDER 排序,表里没有的键排在后面并保持原有相对顺序 */
export function sortFlowchartCriteria<T>(details: Record<string, T>): [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))
}
// 班级号的位数范围。学生用户名形如 ks<班级号><姓名>,班级号还要跟
// 网站配置里的班级列表对得上。
// 后端 OnlineJudge/utils/shortcuts.py 的 CLASS_NAME_MIN/MAX_DIGITS
// 是同一条规则的另一份,改这里必须同时改那边。
export const CLASS_NAME_MIN_DIGITS = 3
export const CLASS_NAME_MAX_DIGITS = 4
/** 合法班级号:3~4 位纯数字 */
export const CLASS_NAME_RE = new RegExp(`^\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}$`)
/** 用户名开头的 ks<班级号>,用于从用户名里认出班级 */
export const USERNAME_CLASS_RE = new RegExp(`^ks\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}`)
/** 班级号作为数字时的上下界,给 n-input-number 用 */
export const CLASS_NAME_MIN_VALUE = 10 ** (CLASS_NAME_MIN_DIGITS - 1)
export const CLASS_NAME_MAX_VALUE = 10 ** CLASS_NAME_MAX_DIGITS - 1
export const REACTIONS: {
key: ReactionKey
label: string
icon: string
}[] = [
{
key: 'too_easy',
label: '太简单',
icon: 'fluent-emoji:smiling-face-with-sunglasses',
},
{ key: 'too_hard', label: '太难了', icon: 'fluent-emoji:exploding-head' },
{
key: 'confusing',
label: '没看懂',
icon: 'fluent-emoji:face-with-spiral-eyes',
},
{ key: 'buggy', label: '题目有错', icon: 'fluent-emoji:bug' },
{ key: 'learned', label: '学到了', icon: 'fluent-emoji:light-bulb' },
{ key: 'interesting', label: '有意思', icon: 'fluent-emoji:star-struck' },
{ key: 'want_explain', label: '想听讲解', icon: 'fluent-emoji:books' },
]