|
|
пре 2 недеља | |
|---|---|---|
| .vscode | пре 7 месеци | |
| public | пре 7 месеци | |
| src | пре 2 недеља | |
| .gitignore | пре 7 месеци | |
| AGENTS.md | пре 2 недеља | |
| README.md | пре 2 недеља | |
| index.html | пре 2 недеља | |
| orval.config.ts | пре 6 месеци | |
| package-lock.json | пре 1 месец | |
| package.json | пре 1 месец | |
| todo.md | пре 2 месеци | |
| todoDetail.md | пре 2 месеци | |
| tsconfig.app.json | пре 6 месеци | |
| tsconfig.json | пре 7 месеци | |
| tsconfig.node.json | пре 7 месеци | |
| vite.config.ts | пре 6 месеци |
基于 Vue 3 + TypeScript + Vite 6 的在线判题(Online Judge)前端,提供题目浏览与提交、比赛与排名、后台管理等功能。
| 分类 | 选型 |
|---|---|
| 框架 | Vue 3.5(<script setup> SFC) |
| 语言 | TypeScript 5.8 |
| 构建 | Vite 6 |
| 路由 | Vue Router 4 |
| 状态管理 | Pinia 3 + pinia-plugin-persistedstate |
| UI 组件库 | Element Plus 2(中文语言环境) |
| HTTP | Axios(统一拦截器) |
| 接口生成 | Orval 7(基于 OpenAPI 生成 TypeScript 客户端与类型) |
| 代码编辑器 | Monaco Editor(当前仅 Java) |
| Markdown | ByteMD(GFM + 代码高亮) |
| 时间处理 | Day.js |
http://localhost:8080(提供 /v3/api-docs OpenAPI 文档)npm install
npm run dev # 本地开发,默认 http://localhost:5173
npm run build # vue-tsc 类型检查 + 生产构建,产物在 dist/
npm run preview # 本地预览构建产物
npm run api:gen # 依据后端 OpenAPI 文档重新生成接口代码
开发环境通过 Vite 代理转发请求,/api 前缀会被重写后转发到 http://localhost:8080:
// vite.config.ts
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
Axios 实例的 baseURL 取 import.meta.env.VITE_API_BASE_URL,未配置时回退为 /api(即走上面的代理)。如需直连后端或部署到其他地址,创建 .env.local 覆盖即可:
VITE_API_BASE_URL=http://localhost:8080
src/util/axios-instance.ts 集中处理鉴权与提示:
localStorage.token 读取令牌并注入 Authorization: Bearer <token>。code 为 0 或 200 视为成功并直接返回 response.data;失败时统一弹出后端返回的 message 并 reject。401 统一提示并跳转登录页。message 固定为 success,因此拦截器不再弹成功提示;操作成功文案与表单校验提示保留在视图层。视图层不再处理接口错误提示,只需 try/catch 保留必要的状态重置、页面跳转等控制流。
Orval 读取后端 OpenAPI 文档,按标签拆分为控制器文件,类型输出到 src/api/models,并使用自定义 mutator 走统一的 Axios 实例:
// orval.config.ts 关键配置
input: { target: 'http://localhost:8080/v3/api-docs' },
output: {
mode: 'tags',
client: 'axios',
target: './src/api/client.ts',
schemas: './src/api/models',
clean: true,
override: {
mutator: { path: './src/util/axios-instance.ts', name: 'customAxiosInstance' },
query: { useQuery: true, useMutation: true }
}
},
hooks: { afterAllFilesWrite: 'prettier --write' }
后端每完成一批接口后执行 npm run api:gen,产物示例:
src/api/
├── auth-controller.ts # 登录认证
├── category-controller.ts # 题目分类
├── contest-controller.ts # 比赛
├── problem-controller.ts # 题目
├── submission-controller.ts # 代码提交与判题
├── user-controller.ts # 用户管理
└── models/ # 请求/响应 DTO、VO 与枚举
视图层按控制器调用接口,类型统一从 @/api/models 引入:
import { getProblemController } from '@/api/problem-controller'
import type { ProblemVo, QueryByPage1Params } from '@/api/models'
const page = await getProblemController().queryByPage1({ pageNum, pageSize } as QueryByPage1Params)
src/api目录由 Orval 生成(clean: true,每次生成会清空重建),不要手动修改。
src/
├── api/ # Orval 生成的接口客户端与数据模型
├── components/ # 公共组件(MonacoEditor)
├── layout/ # 布局:UserLayout(顶部导航)、AdminLayout(侧边菜单)
├── router/ # 路由定义与权限守卫
├── store/ # Pinia:meta(站点配置)、user(登录态)
├── type/ # 手工维护的公共类型
├── util/ # axios-instance、markdown、format-time、submission 状态映射
├── view/
│ ├── admin/ # 后台:题目/分类/比赛/用户管理
│ ├── contest/ # 比赛列表、详情、排名
│ ├── problem/ # 题目列表、详情
│ └── submission/ # 我的提交
├── App.vue
└── main.ts # 应用入口:注册 Router、Pinia、Element Plus(zh-cn)
路径别名 @ 指向 src(见 vite.config.ts)。
| 路径 | 页面 | 访问权限 |
|---|---|---|
/login |
登录 | 公开 |
/ |
首页 | 公开 |
/about |
关于 | 公开 |
/problems |
题目列表 | 公开 |
/problems/:id |
题目详情 | 公开(提交需登录) |
/contests |
比赛列表 | 公开 |
/contests/:id |
比赛详情 | 公开(报名需登录) |
/contests/:id/ranking |
比赛排名 | 公开 |
/submissions |
我的提交 | 公开(仅展示本人记录) |
/admin/problems |
题目管理 | admin / coach |
/admin/categories |
分类管理 | admin / coach |
/admin/contests |
比赛管理 | admin / coach |
/admin/users |
用户管理 | admin |
权限通过路由 meta 声明,由 router.beforeEach 统一守卫:
meta.requiresAdmin:仅 admin 可访问。meta.requiresCoach:admin 与 coach 均可访问。redirect 参数跳转登录页,登录后返回原页面。布局菜单同样按角色过滤:顶部导航的“后台管理”入口只对 admin/coach 展示,后台侧边菜单按每条路由的 meta 过滤,用户管理仅对 admin 可见。
角色取值:admin(管理员)、coach(教练)、user(普通用户)、guest(未登录)。
Pinia 采用 setup 语法 store,并开启 persist 持久化,刷新后登录态与站点配置保留在 localStorage:
useLoginUserStore(store/user.ts):loginUser 状态、userId getter、loginSuccess / logoutUser 等 action。useMetaStore(store/meta.ts):站点标题、Logo、后端地址。util/submission.ts 维护提交状态到展示文案的映射(getStatusInfo),覆盖 SUBMITTED、JUDGING、AC、WA、TLE、MLE、RE、CE、SE,并给出对应的 Element Plus 标签类型。
util/format-time.ts 的 formatTime()(YYYY-MM-DD HH:mm),无效值回退为 -。util/markdown.ts 导出的异步组件 MdEditor / MdViewer(按需加载,避免首屏引入编辑器体积)。<script setup lang="ts">,路径引用使用 @/ 别名。npm run build(含 vue-tsc 严格类型检查)。