17 Commits

Author SHA1 Message Date
1f4cfc280e R194: 作品管理改「小组维度」列表 + 成绩/提交列表 includeIdle 透传
- src/views/Admin.vue
  · 作品管理一行 = 一个小组(小组编号/作品目录 + 提交状态 + 该组文件与评分 + 人数 + 提交人),
    替代原「按人平铺」(同组 6 人 6 行、5 行未提交)
  · 搜索口径改「小组编号」(姓名已不在列表展示);计数单位 人→组
  · PDF 预览常驻说明 + 「在新标签打开」出口(iframe 白屏时不误导「系统没加载」)
- src/api/admin.js:getSubmissions/getScores 增加 params,供 includeIdle 口径透传
2026-10-22 09:41:57 +08:00
96d16129f7 feat(ui): 上传页组锁提示 + 作品列表小组编号与单组下载(R190)
- Competition.vue:新增「组状态」面板(本组是否已有人上传 / 小组编号 / 组内人数);
  被组锁时置灰上传框与上传按钮,并显示归属人提示与组内已上传文件清单。
  文案一律取后端返回(⛔ 不在前端另拼)。删除本人作品后刷新组状态。
- Admin.vue:作品表新增「小组编号 / 作品目录」列 + 「下载本组压缩包」按钮
  (teamUid 优先、回落 aggregationUid,走 /api/admin/team-zip/:group)。
- api/upload.js:getUploadStatus 支持传 axios config(用于 silent 抑制重复弹窗)。

验证:前端 TDZ 扫描 3 文件 PASS;deploy/build-frontend.sh 构建通过,
产物含 team-status-panel / 小组编号 / 下载本组压缩包 等新文案。
2026-10-21 15:20:47 +08:00
ab1c67dbc5 feat(admin-ui): 时间并发冲突自动回读 + 轮播排序脏标记 + 权限热校准
- api/admin.js:setTimes(data, expected);⚠️ 只在基线**非空**时才带 expectedTimes
  (空对象会让后端误判"所有组都要求校验"而恒冲突)。
- Admin.vue(时间设置):timesBaseline 记录 fetchTimes 载入时的服务端旧值,
  ⛔ 不用表单当前值(那样永远等于提交值 ⇒ 校验恒真 = 假保护)。冲突时自动
  fetchTimes 重载(表单已过期,拿它重试只会再冲突);其它失败保留用户输入。
- Admin.vue(轮播):carouselOrderDirty —— 拖拽只改前端顺序、不落库,
  此前卡片"排序: N"用的是渲染下标,拖完立刻变新序号,看起来已生效、刷新却回退。
  现加"排序未保存"标签 + 按钮文案变化,未保存状态可见。
- Admin.vue(权限):切菜单时 8s 节流重拉 /api/admin/me,权限变化即提示。
  原先前端只在 onMounted 拉一次 ⇒ 被改权限后必须手动刷新页面,
  否则停在旧菜单上每个请求都撞 403 且无任何提示。⛔ 不用 location.reload()
  (会丢未提交的表单输入)。
2026-10-21 13:44:38 +08:00
c8097fab00 feat(admin): R176-R180 公示表设置/新闻附件与撤回/管理员权限页 + 小组编号列 + 筛选清空修复
- R176 公示表设置区(赛项名称/编号两个配置项)+ 成绩管理下载半成品
- R177 新闻详情附件块 + 「发布 ↔ 撤回」按钮成对(v-if/v-else),撤回不删除
- R178 管理员账号页:新增/编辑权限/重置口令/禁用/删除;侧栏身份区显示角色
- R178 无权限空态;菜单按 can() 裁剪(体验层,最终以后端 403 为准)
- R178 成绩总览新增「小组编号」列(三态:编号 / 队伍 #N / 未编组)
- R178 筛选清空修复:Element Plus value-on-clear 默认 undefined ⇒ 改白名单判定 + value-on-clear=""
2026-10-20 11:07:31 +08:00
b7ec541223 feat(admin): R173/R174 大题成绩导入预览(三小问四列制)
- Admin.vue 预览弹窗:由「分数 + 均分到三小问」改为逐项展示第 1/2/3 小问原始填报值 + 合计列
  · 合计由三项求和实时显示,⛔ 表格不再出现均分口径
  · 新增 bigScoreSubCell(row, idx):非数值原样显示,便于定位"填错的是哪格"
  · 状态映射补 SUB_MISSING(小问留空)/ SUB_INVALID(小问非法)/ SUB_OUT_OF_RANGE(小问超上限)
  · 弹窗宽 920 → 1080(四列 + 合计 + 说明)
- src/api/admin.js:previewBigScoreImport 返回结构注释同步(subRaws / subs / total)
2026-10-19 13:03:54 +08:00
4e7dfe09c8 feat(admin): 账号表新增「编辑」入口与选手信息编辑弹窗(R108)
· 账号表新增操作列(fixed=right)+ 编辑按钮
  · 编辑弹窗:可改 name/phone/id_number;group 只读并说明原因(改组别会令
    已作答答卷与新组别抽题/发布开关错配);口径说明 alert + warnings 区
  · api/admin.js 新增 updateContestant(silent: true,调用方负责提示)
  · 修正一处死代码:初版按 e.response.status 判 409/404 —— 实测证伪:
    helper.error() 不改 HTTP 状态,业务错 = HTTP 200 + body.code != 0,
    拦截器以 new Error(res.message) reject(该 Error 无 response 属性)
    => 改用 e.message
2026-10-12 17:20:47 +08:00
e7179bfe54 feat(admin): 选手管理新增「小组视图」—— 激活状态与所属学校清单
- api/admin.js:新增 getTeams({ group, activated, includeTest })

- Admin.vue:列表/小组视图切换、组别 + 激活状态 + 含测试三筛选、汇总条、小组表格(所属学校 / 成员数 / 已激活 / 状态)

线上验证:R86 已部署(dist 157 文件 / 70 个 .gz),线上入口 index-DQf5Bj8S.js
2026-10-09 09:33:07 +08:00
ac7637f3e1 feat(admin): 综合大题页签增加 Excel 模板下载与批量导入(R73)
api/admin.js 新增 importBigQuestionsExcel(silent:true,错误提示由调用方统一给出,避免同族双弹)。
Admin.vue 大题工具栏新增「下载Excel模板」「Excel批量导入」两按钮 + 隐藏 file input,
复用既有 safeDownload 与 ref 模式;按钮 title 明确提示「先下载模板、按模板列填写、
组别以当前选中组别为准」,解决测试人员找不到模板入口的问题。
2026-10-08 10:08:44 +08:00
0fca4bd996 fix(ui): 其余视图错误提示收敛为「拦截器统一弹」,消除同族双弹
承接 R64(Admin.vue)已定口径,本轮覆盖其余全部视图。

根因:src/api/request.js 响应拦截器已覆盖全部失败路径,且每种失败只弹一次
⇒ 调用方 catch 内再 ElMessage.error 即必然双弹。helper.error 不改 HTTP 状态,
故删除调用方提示不会丢后端文案。

删除 11 处重复提示(跨 5 文件):
- Login.vue        管理员登录 catch(adminLogin 未传 silent ⇒ 拦截器必弹)
- Activate.vue     handleStep1 / handleActivate / resendSms
- ForgotPassword.vue  handleStep1 / handleReset / resendSms
- Competition.vue  submitUploadFiles / deleteUploadedWork / onMounted 外层 catch
- Dashboard.vue    enterContest(warning 形态,同族)

刻意保留(已有显式理由,不得误删):
- silent 路径调用方唯一提示:Login 选手登录、Competition 大题加载/保存/交卷/自动交卷
- 客户端校验(未发请求)、原生 fetch(MockExam 样例模板)、独立 axios 实例(Materials)、
  el-upload on-success(Admin)

另:
- src/api/request.js 增补「错误提示口径」权威块(全站单一源 + 四类例外),
  五个视图各加一行口径指针注释,防回退。
- 无行为变更:仅移除重复提示,finally/return/refreshCaptcha/router.push 全部保留。
2026-09-30 12:57:55 +08:00
ff7137aab6 fix(admin): 错误提示收敛为「拦截器统一弹」,消除同族双弹
背景:Admin.vue 大量 catch { ElMessage.error(...) } 与 api/request.js 响应拦截器重复——\n一次失败请求用户会同时看到两条报错。拦截器已覆盖全部失败路径(业务码≠0 / 403/404/500 /\n超时 / 网络异常),故调用方 catch 不再弹提示。

分类(23 处 → 删 15 / 留 8):\n- 删 15:通用失败(拦截器已弹),保留状态回滚与 console.error\n- 留 2 + 请求加 silent:true:两个导入接口——后端 admin.js 明写「前端已统一加\n  『Excel 导入失败:』前缀,这里只回传纯错误,避免重复前缀」,删调用方会破坏跨层契约\n- 留 1:resetTestData(已 silent);留 3:客户端校验(未发请求);留 2:非 axios 路径

Admin.vue 文件头新增「错误提示口径(R64 收敛)」约定块防回退。\n验证:静态终态 23→9(应删项 0 残留)/ TDZ 15 文件 / 真浏览器 dev+产物 max=1 /\n变异自证 max=2 且还原 md5 一致 / ui-runtime-check 5-5 / build 157 文件 70 .gz /\nci-gates.sh 10 步 EXIT=0。
2026-09-30 12:41:28 +08:00
ef32d30a57 fix(admin): 「重置测试数据」报错只提示一次(收敛重复弹窗)
resetTestData() 未传 silent,生产环境下后端 403(生产环境禁止重置测试数据)会被响应拦截器(request.js:166)与调用方 catch(Admin.vue:1707)各弹一次,用户在后台看到两条完全相同的报错(R58 反馈)。

改为 silent: true —— 由调用方按上下文统一弹一次「重置失败:<后端原文>」。
2026-09-30 11:06:08 +08:00
f77330ad27 fix(frontend): 重置测试数据文案对齐后端契约(未激活+需短信激活)并透传后端 message
- Admin.vue 确认弹窗:「已激活 + 默认口令」→「未激活 + 默认口令」
  (后端 service/admin.js:1619 实际置 activated:false)
- Admin.vue 提示行:「重置后可直接用默认口令登录重测」→「需先用默认口令完成短信激活才能登录重测」
- Admin.vue 成功提示: 改为透传后端 message(后端 :1730 已含正确指引),不再自拼后丢弃
- api/request.js: 新增可选开关 returnMessage(不传时默认行为完全不变)
- api/admin.js: resetTestData 启用该开关,后端成为唯一真相源

依据: service/admin.js:1619/:1730 + scripts/verify_testdata_mgmt.py:5-9「Req2 重置须重置账号激活状态」
2026-09-30 10:27:40 +08:00
4e7493a809 chore(frontend): 删除分片上传死代码(BACKLOG C3 附带清理)
src/api/upload.js: 删除 uploadChunk / mergeChunks / getChunkStatus
- 三者全仓 0 调用点(唯一调用方 Upload.vue 已在 B2 清理孤儿路由时删除)
- 后端路由 /api/upload/:group/{chunk,merge,chunk-status} 与对应控制器方法**刻意保留**
  —— 只清前端死代码,不动服务端契约(避免影响外部调用方与后续复用)
- 在用的作品上传走 uploadWork()(单次流式 POST /api/upload/:group),不是分片通道

验证:
- vite build exit 0(11.21s)
- 51 个 JS chunk 的 sha256 与改前**完全一致**(Admin-C4QmBgiQ.js 均 231,715 B)
  => 删的是被 tree-shaking 早已剔除的代码,产物字节级无变化
- 管理端功能回归 dist-admin-check.mjs 9/9
- TDZ 静态扫描 37 文件 PASS
2026-09-29 13:17:55 +08:00
37d2641e49 feat(admin): 后台新增「成绩复核」——检索考生 + 全卷逐题明细弹窗(R26)
- api/admin.js: 新增 reviewSearch(姓名/身份证/电话任一,可选限定组别)与 getContestantScore(group, id)
- Admin.vue: 菜单新增「成绩复核」(Search 图标);检索区(关键词 + 可选组别下拉)+ 结果列表 + 全卷明细弹窗
- 明细含客观题逐题:题号 / 题型 / 题干 / 选项 / 考生答案 / 正确答案 / 判定 / 得分/满分
- 异常标记:题干缺失(questionMissing)、答案与判定矛盾(answerMatches≠isCorrect)
- 判断题答案 'true'/'false' 显示为「正确/错误」;未作答显示「未作答」且不判错
- 综合大题展示三小问作答与得分;本科/教师组展示作品文件清单并支持下载(不回传 file_path)
- 弹窗用考生自身组别请求接口,避免前端传错组照错人(后端 409 兜底)
- 回传正确答案仅管理员可见,与后端 adminAuth 门禁配套

构建验证:vite build 通过(✓ built in 14.80s,退出码 0)
2026-09-28 10:15:50 +08:00
c2551b64e8 feat(auth/exam): 登录重试同源 + 静默刷新透传 + 交卷链路加固(R5/R8)
前端重试与后端闸门同源(须成对上线)
- src/api/request.js: 新增 parseRetryAfterMs / computeLoginRetryDelayMs,
  退避取 max(指数退避, Retry-After) + ±20% 抖动 + 服务端值封顶;
  silent 透传(doSilentRefresh 不再吞掉 silent 标记)。
- src/api/auth.js / src/stores/auth.js: 登录 429 走同源重试;错误分类区分
  "TCP 未握手(可重试)"与"业务码失败(不可重试)"。
- src/stores/exam.js: 交卷链路顺序红线(取卷 -> 作答 -> submit-all 严格顺序),
  答案不丢 / 重复不脏 / 窗口不锁死。
- src/views/Competition.vue: 中职/高职综合大题区块(3 x textarea,2000 字上限
  与右下角计数);本科/教师组保留 Word 上传入口。

验证:源码抽取测试 8/8(含判别力自证:不读 header 的对照实现必 FAIL);
npm run build BUILD_RC=0(12.82s / 1711 modules);真实交卷端到端 code=0。
2026-09-22 09:17:06 +08:00
1bfe007149 feat: 综合大题改造(替代 Word 上传)+ MockExam 按组分支
中职/高职组以「综合大题」替代原 Word 文件上传:管理员在后台录入题干
(wangEditor 富文本,含表格),系统按组随机抽题,考生在 3 个小问内作答,
每小题 10 分、大题合计 30 分。

- api/admin.js / api/exam.js / stores/exam.js:大题题库 CRUD、抽题、作答与评分链路
- Competition.vue:Word 上传区块替换为大题作答区(3 小问 + 字数上限 + 富文本题干渲染)
- Admin.vue:大题题库管理、考生作答查看与人工评分
- MockExam.vue:按组分支——中职/高职显示综合大题,其余组保持 Word 答题;侧栏与提交提示同步
- RichEditor.vue + utils/richtext.js:题干富文本渲染前做白名单消毒(零依赖,纵深防御)

说明:big_questions / big_question_answers 建表迁移 023/024 随后端仓库提交。
2026-09-21 11:17:59 +08:00
WorkBuddy
e8f0195b61 chore: 前端首次基线提交
运行必须源码:src/、public/、index.html、package.json 及构建配置。
已忽略运行非必须:node_modules/、dist/、.env*。
2026-08-17 12:25:42 +08:00