 xuyueandClaude Opus 5.5
|
06c679579c
|
fix(前台): 地址打错不再是一整页空白,题目、比赛、题单不存在时写明是哪样不存在、给一个回去的按钮
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-13 22:03:49 +08:00 |
|
 xuyueandClaude Opus 5.5
|
da30ae1875
|
style(比赛/题单/班级PK/统计): 图例和规则说明跟排名页一样多,读起来像说明书
Deploy / deploy (push) Canceled after 0s
比赛排名表底下的图例和用时规则、比赛题目页的「做对人数随时更新」和计分规则、
老师比赛班级页的「学生看不到这一页 · 15 秒刷新一次」和键盘提示;
题单详情的「加入以后…才算进度」「做对一道会自动回到这里」、题单列表的
「老师现在布置的」「加入过的在前」、老师题单进度表底部的颜色说明;
班级 PK 里「格子里是…」「条 = …」「皇冠 = …」「一根柱一档…」「这学期,最近 N 周」、
只有一个班做过的题那行、「根据上面这些数字写,仅供参考」;
统计里「没选班级,先按班级列出来…」「点方块在新页面打开这一条」;
流程图节点库的「拖拽节点到画布中」,都拿掉。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-10 14:17:42 +08:00 |
|
 xuyueandClaude Opus 5.5
|
1e441411a5
|
style(前台): 页面整体放大一档,列表不再挤成一团;宽屏上内容居中,不再铺满到两边
Deploy / deploy (push) Canceled after 0s
- index.css 加一张 --oj-* 尺寸表和 .oj-page 外壳,二十几个前台页面改成引用它:
正文 14→15、页标题 20→24、题目/提交列表行高 40→48、比赛/公告 56、筛选控件 28→34
- 阅读型页面限宽 1200 居中;题目/提交列表 1600、比赛详情 1400、排名 1280、班级 PK 1440
- 1280 宽的筛选条放大后放不下,题目/提交列表在 1366 以下退回 13px 的字
- 班级 PK 五个班起格子里不写「一次 x%」(只剩「一次 8…」);赛跑图线尾的数字不再被截掉
- 手机上比赛列表的「没参加」挪到比赛名下面;题单里「以前做对过 · 旧代码先藏着」缩成「做对过」
- 题目详情页(编辑器)和后台不在这一轮
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-10 12:43:12 +08:00 |
|
 xuyueandClaude Opus 5.5
|
0e68c434fc
|
fix(题单): 奖章阶梯下一个奖章的金圈上沿被切掉;「总分」换成「做对几道」后漏发的奖章补上
Deploy / deploy (push) Canceled after 0s
- 阶梯外层为了窄屏横向滚动设了 overflow-x,纵向也跟着被裁,画在图标外的金圈上沿没了
- 0026 只改了奖章条件没按新条件补发:玩家国度每题 5 / 10 分,题数够了、总分不够的人
没拿到「成分复杂」,页面还显示「再做对 1 道」。0029 按现在的条件只补不收(按 9-29 备份
只有 1 人;其余两枚换算前后等价,也没有人会因新条件被收回)。排在删列 0028 之后,
因为 0028 可能已经在线上执行过
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-09 22:24:18 +08:00 |
|
 xuyueandClaude Opus 5.5
|
c17f88cdbb
|
feat(题单): 题单页重做——布置中的放最上面,详情顶上一条进度和奖章阶梯,没加入看得见题点不进;老师看全班、后台一屏编辑
Deploy / deploy (push) Canceled after 0s
- 列表:布置中的大卡片(做对几道、布置到哪天、再做对几道拿哪个奖章),其余小卡片,加入过的在前
- 详情:进度 + 奖章阶梯(下一个金圈「再做对 n 道」),题目两列写几点做对 / 交过几次;
以前做对过的标「旧代码先藏着」;做完整条变绿,烟花只在做完那一刻放
- 老师多一个「全班情况」页签;题目页从链接直接进来没加入时,条上给「加入题单」
- 后台一屏编辑:布置一周 / 两周 / 四周 / 选日期,公开开关,粘贴多个题号一次加,拖动排序,奖章只剩两种条件
- 旧代码藏着的地方都说清楚是哪个题单、哪天之后能看
- 修掉列表卡片把选做题数说成「新加了 N 题」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-09 22:01:39 +08:00 |
|
xuyue
|
d25b5dbe6d
|
update
Deploy / deploy (push) Canceled after 0s
|
2026-09-27 19:36:28 +08:00 |
|
 xuyueandClaude Opus 5.5
|
9412ba3cf6
|
refactor(契约): 展示题号统一叫 problemDisplayId,题单 id 统一叫 problemSetId
Deploy / deploy (push) Canceled after 0s
前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。
- 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成
problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items /
今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象
或数字 id,这几处是字符串,最容易看错
- 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成
?problemDisplayId=(提交列表、流程图列表、三个统计接口)
- 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存,
统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets
路径、前端路由参数一致。drizzle 的列键跟表名走,没动
- aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。
ai_analysis.data 里存的历史快照仍是 user,没有代码读回它
- 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-09-27 16:08:31 +08:00 |
|
 xuyueandClaude Opus 5.5
|
7a390323fe
|
refactor(前端): 接口错误改成 ApiError 类,catch 不再写 any
拦截器原来 reject 一个裸对象 `{ error, data }`,没有类型也没有类型守卫,于是 46 处
`catch (err: any)` 直接读 `err.data` / `err.error` —— 代码 bug 抛出的 TypeError 也按
接口错误读,弹出来的是 undefined 或英文。
- utils/api.ts:reject 改成 `ApiError extends Error`,字段名沿用 error(错误码)/
data(文案),存量读法语义不变;加 isApiError / errorCode / errorMessage。
errorMessage 对非接口错误一律用 fallback,不把 TypeError 的英文甩给学生
- 46 处 catch 全部换掉 any(45 处接口错误 + pythonSyntaxCheck 里 Skulpt 的错误对象)
- 断网等没有响应体的情况,文案从英文 "Request failed" 换成「网络异常,请检查网络后重试」
浏览器实跑核过:登录密码错误仍显示「用户名或密码不正确」(错误码分支)、
接口错误 reject 出来是 ApiError、网络不通时拿到中文文案、非接口错误走 fallback。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-09-27 15:12:40 +08:00 |
|
 xuyueandClaude Opus 5
|
d7a6414735
|
fix(题单): complete_time 翻回「只设不清」;完成题单后那个必然报错的 tab
三件事,都是上一轮结论没查扎实留下的。
## complete_time
f9354b0 把「退回未完成时清空 complete_time」定成两边统一的行为,理由写的是
「快照里 complete_time 非空 ⟺ is_completed」。那个检查只做了一个方向(已完成但
没有完成时间 = 0 条),反方向没查 —— 实际有 4 条 is_completed=false 却留着
complete_time,全在题单 8:那批人 2025-11-14 在它还只有 6 题时完成过,老师后来
加到 12 题,进度退回未完成,完成时间保留了下来。
旧栈是故意不清的(problemset/models.py:218 只有 `if is_completed and not
complete_time` 这一条赋值),语义是「曾经完成于」。清空是重写时在学生路径上引入的,
上一版又把它推广到了后台路径。
翻回只设不清。「未完成 + 有完成时间」是允许的组合。反过来清空的代价不可逆:往一个
100 人已完成的题单里加一道题、再改主意删掉,这 100 个人的历史完成时间就一起被冲成
「现在」—— 上一轮的实跑已经在题单 9 上复现过。
## 两处注释订正
「旧后端不做加题后的重算」这个说法是错的,本仓早先的注释里有,上一版我照搬进了
services/problemset.ts 和提交信息。旧栈用 signals 做了,而且两件事都做:
problemset/signals.py 在 ProblemSetProblem 的 post_save / post_delete 上重算全部
参与者进度、再重算该题单全部奖章。重写时 views 里翻不到显式调用就当成没做,于是
奖章那一半漏了 —— 53 条应发未发正是这么来的。
另外补上 53 条里那 23 条的出处:旧栈的管理命令 fix_problemset_progress 按实际 AC
补 progress_detail,而 signals 不挂在 Progress 上,所以进度补了、奖章没补。
## 用户进度 tab
detail.vue 的 showTabs 写的是「超管 或 自己完成了题单」,而它渲染的
UserProgressView 调的是 requireTeacher 的接口。两边正好错开:
- 学生做完题单 → tab 出现 → 点进去 403(实跑确认:已完成该题单的 student 拿到
403 permission-denied,devadmin 拿到 200)。而 loadUserProgress 没有 try/catch、
loading.value = false 又写在 await 之后,转圈永远停不下来。
- Teacher Admin 看不到这一栏,尽管他们才是它的目标用户、也是唯一调得动的角色。
条件换成 isTeacherOrAbove,取数补 try/finally。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
|
2026-08-31 08:42:40 -06:00 |
|
 xuyueandClaude Opus 5
|
0b7b08f2cc
|
refactor(前端): 去掉 { error, data } 信封,api2 改名 api
Deploy / deploy (push) Has been cancelled
信封是 Django 时代的形状:拦截器手工造一个**恒为 null** 的 error 字段,再把
真正的载荷塞进 data。后端 http.ts 的 success 其实只返回 { data },那个 error
从头到尾没人用 —— 全站成功路径读 res.error 的只有 admin/api.ts 的
resetPassword 一处,而它自己就是个把信封拆开再重新包一遍的 shim。
代价是每个调用点都要 .data 一次:47 个组件、3 个 api 层文件、200 多处。
现在拦截器直接返回 response.data.data,ApiResponse<T> 退化成 T,文件末尾那句
`as unknown as Api2Client` 的类型谎言也少了一层。失败路径不动,仍然 reject
`{ error: 错误码, data: 文案 }` —— 和成功路径不对称是故意的,成功没有错误码
可言,接口注释里写清楚了。
顺带把 api2 改回 api:utils/ 下早就没有 api.ts 了,"2" 是迁移期用来和旧
client 区分的,现在只剩下让人多想一秒的作用。
## 怎么改的
**没有全局 sed。** 先把客户端的返回类型从 Promise<ApiResponse<T>> 改成
Promise<T>,让 vue-tsc 把每一处报出来(210 条),再按它给的 file:line:col
精确删 `.data`(192 处),剩下的手工处理:
- 6 处 `const { data } = await ...` 解构 → `const data = await ...`
- 3 个 api 层函数(getProfile / getProblem / getSubmission)自己手工造信封,
改成直接返回值;getProfile 的返回类型跟着从 ApiResponse<Profile|null>
变成 Profile|null
**类型检查抓不到的,人工把剩下的每一处 `.data` 过了一遍** —— 载荷本身带
data 字段、或者载荷是索引签名时,`res.data` 照样过类型。这一遍捞出三条真 bug:
- `getTutorialList` 的载荷是 `{ [key: string]: TutorialListItem[] }`(按
python / c 分组)。索引签名让 `res.data` 编译通过、运行时是 undefined ——
改完信封之后教程列表会**两个 tab 全空且不报错**。实跑确认过修好了。
- `createExercise` / `updateExercise` 返回 `res.data`,而 Exercise 自己有
data 字段(练习内容)。两个调用方都不看返回值,所以类型和运行时都不响。
- `getSimilarProblems` 的 `.then(r => ({ ...r, data: r.data.map(...) }))`
删掉 .data 之后变成往对象里摊一个数组,能跑但形状是错的。
另外两处是**对的**,加了注释免得下次被"顺手清理"掉:
StatisticsPanel 的 `res.data` 是契约 submissionStatisticsSchema 自己的 data
字段(每个学生一行);download.ts 是独立 axios 实例,`res.data` 是 axios 的
响应体(zip 二进制,不走信封)。
## 验证
tsc(apps/api) 0 error、check:routes 168 条无遮蔽、vue-tsc 0 error、vite build
通过。**因为这改动碰的是每一个请求,静态检查不够,起了全套服务用浏览器实跑:**
- oj 侧 12 个页面 + 后台 13 个页面逐个打开,断言没有重定向、console 无报错。
- 关键页面进一步断言渲染出了真数据(后台用户列表 3 行、题目列表 10 行、
站点配置表单三个输入框有值、教程列表分组正确)。
- 三条写路径实打:重置密码(库里 student123 → 531554,表格当场刷新)、
公告可见性开关(走 getAnnouncement + editAnnouncement,就是手改解构那处,
库里 visible t → f)、提交代码(POST → 判题机真跑出 -2 → 提交列表和详情页
都正确渲染状态、语言、代码)。
- /rank 有一条 `{error: "class-missing"}` 的未捕获 reject,stash 掉本次改动
复现同样报错,**是既有问题**,不在本次范围内。
本地 dev 库为了打通后台测试改了三处,都只影响本机:devadmin 补了 email 和
user_profile 行(原来缺这两样,getProfile 报 profile-not-found,AUTHED 存不
进去,所有 /admin 路由被守卫弹回首页)、密码重置成 devpass123。冒烟用的教程/
公告/提交三条测试数据已删干净,题目和用户的提交计数也回滚了。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-25 22:42:20 -06:00 |
|
 xuyueandClaude Opus 5
|
c2a81209d7
|
refactor(前端): 拆掉 camelCase→snake_case 转换层,契约成为唯一真相
utils/legacy.ts 是迁移期的临时层:新后端一律 camelCase,而组件读的还是
旧 Django 的 snake_case,于是在 api 层做一次递归键名重写。它自己的注释就
写了「迁移完成后这一层应当整体拆掉」。现在拆了。
代价不只是那 96 处包装:每个响应都要递归遍历整个对象重写一遍键名,而且
utils/types.ts 和 packages/contract 是两份真相 —— 手抄的那份还抄歪了好几处。
做法是按域推进,每域都用 vue-tsc 相对基线做差,确认零新增错误后再往下走。
前端的类型现在一律以契约为准,只在必要处窄化(比如 languages/template 的键
窄化成 LANGUAGE),删掉的重复定义包括 WebsiteConfig、LoginSummary、
AchievementSummary、ProblemSet、Contest、User、Profile、AdminTag、
StuckProblem 等等,其中 ClassComparison 有两个组件各手抄了一份。
## 顺带修掉的真 bug
- 管理端公告列表的「可见」开关每次都 400:列表响应被契约 omit 掉了 content,
而更新接口要求 content 必填,toggleVisible 把列表行原样回传。而且是乐观
翻转、不 await 不 catch,管理员看到开关动了、实际没存也没有提示。
改成先 GET 整条再 PUT,加失败提示。
- 删有提交的题时只显示笼统的「删除失败」:前端还在 match 旧 Django 的英文
文案,而后端返回的是 problem-has-submissions + 中文。连同另外 8 处同类
匹配一起改成判错误码 —— 文案是后端随时能改的,match 文案改一个字就静默失效。
- SubmissionStatus.time_limit_exceeded 写成 `1 | 2`,TS 按位或算成 3,和
memory_limit_exceeded 撞了同一个值。后端 judge/status.ts 里这是分开的
两个码,按后端拆成 cpu_/real_ 两项。当前没有代码读这两个成员,但
CLAUDE.md 明确要求判题状态码三处同步。
- 流程图历史翻到没有提交的那一页会直接抛:契约里 submission 是 nullable,
被 any 掩盖成看起来非空。补了 null 分支。
## 契约里被逼出来的三处不诚实
- grade 写成 z.string(),但 averageGrade() 在没有可用数据时返回空串,
前端三张图表拿它查 Record<Grade,...> 会查出 undefined。按实际收紧成
z.enum([...,""]),四个查表点都补了「无评级」分支。
- difficulty 写成 z.string()。核对过生产库 dump:956 道题只有
Low/Mid/High 三个值(761/149/46)。收紧成枚举。
- topReaction 写成 z.string(),既对不上前端渲染的 {type,count},也对不上
旧后端 get_top_reactions 下发的形状。改成正确形状并注明当前恒传 null。
## 明确保留 snake_case 的 54 处
判题沙箱原始输出(cpu_time/exit_code/output_md5/compile_output)、
statistic_info 内容(err_info/time_cost/ast_results)、submission_info
JSONB(is_ac/ac_time/error_number,回滚时旧后端还要读)、SQL 判题引擎的
total_rows/order_sensitive/changed_tables、WebSocket 的 submission_id、
以及数据库选项键 enable_maxkb。每一处都在类型定义旁写了为什么不能改。
language 没有跟着收紧契约 —— 它是配置项、随时可能加语言,收紧会让新语言
在后端 parse 时直接抛。改在 api 边界一处窄化。
## 另外
- utils/http.ts 整个模块已是死代码(四处引用全是 import type),删除。
- profile 的 blog/github/school/major/language 五个字段全链路空转,没有
任何组件读,从契约到类型一并摘除(数据库列不动)。
- admin/account.ts 往 user_profile 塞的 totalScore 是 OI 模式遗留,表里
没这一列。Drizzle 按表定义拼列名会把它静默丢弃,所以没出过错,是死代码。
验证:vue-tsc 143 → 54 条且无新增,apps/api tsc、check:routes、web build
全通过;各域响应形状逐条打接口核对过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-25 13:44:24 -06:00 |
|
xuyue
|
ae1fb329b5
|
feat(阶段1): 搬入 ojnext 为 apps/web,未改业务代码
|
2026-08-06 21:18:16 -06:00 |
|