闸门再补 13 个端点:AI 系列(detail / solved / duration / heatmap / login-summary / pinned)、站内信列表、教程列表与学习进度、题目逐年 AC、 流程图列表。学生端读接口至此基本覆盖(写操作与纯前端伪状态不接)。 文档订正(原文写着"后端是 ../OnlineJudge 的 Django 5"、"utils/http.ts"、 "utils/permissions.ts",这些都早已不存在): - 项目概述改成 OJ2 前端,并点明要兼容机房老 Chrome; - HTTP 客户端改成实际存在的 utils/api.ts; - 新增「Contract guard」一节,把**失败策略**(记日志 + 放行原始数据、不抛错) 和排查入口(window.__OJ2_CONTRACT_DRIFT__)写清楚,并提醒同一个 schema 后端也在 parse、收紧前要用生产数据核验; - Related Repository 指向同仓的 ../api 与 packages/contract。 验证:vue-tsc 与 vite build 通过;13 个新端点对真实接口全部通过 (11 通过 / 0 失败 / 0 跳过)。
6.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
OJ2 的前端(OJ2/apps/web),代码从上一代 ojnext/ 原样搬来、只替换了 API 层
(ojnext 与 ../OnlineJudge 都已下线且完全冻结,一行都不改)。Vue 3 + TypeScript,
Vite(Rolldown 内核)、Naive UI、Pinia、Vue Router。
要兼容机房的老 Chrome(< 94):vite.config.ts 的 legacy 配置与
mermaid-legacy 等 fallback 依赖不能动,理由写在该文件的注释里。
Commands
npm start # Start dev server on port 5173
npm run build # Production build
npm run build:staging # Staging build
npm run build:test # Test build
npm fmt # Format with Prettier
No test suite is configured. Linting is via Prettier only.
Architecture
Directory Structure
src/
├── shared/ # Cross-cutting concerns: layout, stores, composables, API
├── oj/ # User-facing features (problems, submissions, contests, etc.)
├── admin/ # Admin panel features
├── utils/ # Constants, types, HTTP client, helpers
├── routes.ts # Route definitions (two top-level: ojs, admins)
├── main.ts # App entry point
└── App.vue # Root component with Naive UI theme setup
Module Pattern
Each feature module (under oj/ or admin/) typically has:
views/— page-level Vue componentscomponents/— feature-specific componentsapi.ts— API calls specific to the feature
Shared logic lives in shared/:
store/— Pinia stores:user(auth/roles),config(site-wide settings),authModal(login/signup form state),screenMode(problem split-screen layout),loginSummary(AI activity summary),collab(help-request queue + collab room)composables/—pagination(URL-synced),websocket(reconnect + heartbeat),collabDoc(Yjs binding for the collab channel),configUpdate(WS-pushed config sync),useMermaid(lazy Mermaid render),breakpoints,maxkblayout/—default.vueandadmin.vuelayout wrappersapi.ts— shared API calls (auth, profile, tags, captcha)
Auto-Imports
Configured via unplugin-auto-import and unplugin-vue-components. You do not need to manually import:
- Vue APIs (
ref,computed,watch, etc.) - Vue Router (
useRouter,useRoute) - Pinia (
defineStore,storeToRefs) - VueUse composables
- Naive UI composables (
useDialog,useMessage,useNotification,useLoadingBar) - Naive UI components (all
N*components) - Naive UI types (
DataTableColumn,FormRules,FormItemRule,SelectOption,UploadCustomRequestOptions,UploadFileInfo,MenuOption,DropdownOption)
Generated type declaration files: src/auto-imports.d.ts, src/components.d.ts.
Path Aliases
utils → ./src/utils
oj → ./src/oj
admin → ./src/admin
shared → ./src/shared
HTTP Client
utils/api.ts — Axios instance with interceptors (baseURL: "/api",
withCredentials). It unwraps both the axios envelope and the backend's
{ data } envelope, so callers get the payload directly. All API calls proxy
through the dev server (see vite.config.ts).
Contract guard (utils/contract.ts)
@oj2/contract 的 zod schema 是前后端唯一的形状来源,utils/types.ts 只做
z.infer 派生与少量前端专有的收窄(都写了理由)。读接口应当走守卫:
const endpoint = `problems/${encodeURIComponent(id)}`
return contract("GET /problems/:id", problemDetailSchema, await api.get<unknown>(endpoint))
失败策略是「记日志 + 放行原始数据」,不抛错。 形状对不上时:控制台打一条带
端点和字段路径的记录、去重后记进 window.__OJ2_CONTRACT_DRIFT__、然后返回原始
数据让页面继续渲染。面向学生的生产站点,少一个字段的代价远小于白屏。
排查线上分歧就是打开控制台敲 window.__OJ2_CONTRACT_DRIFT__;某条路径长期为空之后,
那条路径可以升级成硬失败(直接 schema.parse),在那之前不要改。
改动 schema 时要记住同一个 schema 后端也在 parse(如
submissionDetailSchema.parse 在路由里),所以收紧一个字段前先用生产数据核一遍,
否则一条不符合的历史记录会让整个列表 500。
Key Utilities
utils/constants.ts— Judge status codes, language IDs, difficulty levels, contest typesutils/types.ts— 契约类型的派生与前端专有收窄(不是手写的一份平行类型)utils/contract.ts— 运行时契约闸门,见上utils/judge.ts— Judge-related utilitiesutils/renders.ts— Table column render helpers for Naive UI DataTable
Environment Variables
Variables prefixed with PUBLIC_ are injected at build time. Env files: .env, .env.staging, .env.test.
| Variable | Purpose |
|---|---|
PUBLIC_OJ_URL |
Backend REST API base URL |
PUBLIC_WS_URL |
WebSocket server URL |
PUBLIC_ENV |
Environment name (dev/staging/production) |
PUBLIC_CODE_URL |
Code execution service |
PUBLIC_JUDGE0_URL |
Judge0 API |
PUBLIC_MAXKB_URL |
Knowledge base service |
PUBLIC_ICONIFY_URL |
Iconify icon CDN |
Routing
Routes are defined in src/routes.ts with two root routes: ojs (user-facing) and admins (admin panel). Route meta fields used:
requiresAuth— redirect to login if not authenticatedrequiresSuperAdmin— super admin onlyrequiresProblemPermission— problem management access
Real-time Features
- WebSocket via composable in
shared/composables/for submission status updates - Yjs over the
/ws/collabchannel for classroom help requests and collaborative code editing (students raise a hand, teachers join their editor). The server is a dumb relay — it authenticates, assigns rooms, and forwards frames without parsing them. Seedocs/specs/2026-08-28-collab-help-request-design.md.
Related Repository
后端就在同一个仓库的 ../api(Bun + Hono + Drizzle,编译成单二进制),
契约在 ../../packages/contract。不要再去看 OnlineJudge/ —— 那是已下线的
Django 后端,只作参照、完全冻结。详见 ../CLAUDE.md。