|
|
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 |
|
|
|
4fd04f106a
|
fix(ui): 组锁成员可从提交页返回工作台 + 免假警告(R192)
现象(用户实测):本科组其他成员进入提交页 → 组锁生效不能提交,但页面无任何返回
入口,只能浏览器后退;移动端/内嵌浏览器更退不出去 ⇒ 被困在提交页。
根因三层,缺一不可:
1. /competition 是全屏页(App.vue 只有 <router-view />,无全局导航)⇒ 页面内不给
按钮就真的没有出口。
2. 上传模式的「被锁」分支只有 el-alert 文案 + disabled 上传控件,从来没有动作按钮
(客观题模式被锁是 router.push('/dashboard') 直接踢走,故历史上未暴露)。
3. onBeforeRouteLeave 对 examStarted && !isTimeUp 一律弹「离开将丢失当前提交进度」——
对被锁用户是假警告(本机没有任何可提交内容),加了按钮也会被二次劝退。
改动(唯一文件):
- 上传卡片 header 新增常驻「← 返回工作台」——不依赖 examStarted(顶部倒计时栏在未开始
时不存在,放那儿会漏)。
- 组锁 prompt 下方新增 .team-locked-actions:主按钮 + 引导「本组作品已由队友提交,您无需
任何操作」;alert description 补「您本人无需提交,可直接返回工作台」。
- 新增 leaveCompetition():仅「已选文件未上传」才确认一次 → resetExam() → push('/dashboard')。
⛔ 必须先 resetExam() 再 push:否则 ①守卫再弹一次(双确认)②store 是全局单例,
定时器残留 ⇒ 回工作台后倒计时仍在跑。用 resetExam 而非只 stopExam,是为了与守卫里
「确认离开后」的清理同口径(两套清理逻辑必漏项)。
- onBeforeRouteLeave 新增豁免 lockedNoContent = isUploadMode && teamLocked。
⛔ 豁免面必须写窄,不能只判 teamLocked:当前两者等价(考试组不拉 uploadStatus),
但只写 teamLocked 会把考试组一并纳入,将来考试组一旦拉 status 就静默取消「交卷前离开」的确认。
验证(真实组锁数据 + 真 chromium,非构造):
- 库里本就有现场:benke_submissions 归属 contestant 11(郑佳怡),contestant 12 同
team_uid=1 ⇒ 真接口返回 teamLocked=true / memberCount=5 / canUpload=false。
- 登录态用 jwt_fallback_secret 自签 token 注入 localStorage(未改任何人口令)。
- 组锁视角 8/8 PASS(header 出口 · 组锁区块 + 引导 · 上传仍 disabled · 点击落 /dashboard ·
messageBox=0 · console 零错误);归属人视角 5/5 PASS(无组锁区块 · 出口仍在 · 不误弹)。
- 变异验证:两按钮加 v-if="false" ⇒ 4 条断言转红;备份还原后 diff 为空、复跑 8/8 复绿。
- 数据零改动:12 号口令未变 · benke_submissions 仍 1 行 · 落盘 PDF 字节数原样。
- 静态:verify_frontend_static.mjs PASS(TDZ 0 / F2 0)· SFC compileTemplate 0 error。
|
2026-10-21 16:49:11 +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 |
|
|
|
45e77d9f69
|
build(deps): vite 5.4.21 → 8.3.4(含 plugin-vue 6)+ 修 CSS 拆块导致的主题/悬停回归
安全:消掉 2 条 high。vite 跨 3 个 major(5→8,rollup→rolldown);
@vitejs/plugin-vue 5.2.4 → 6.0.10(peer 覆盖 vite ^5||^6||^7||^8);
锁文件同步刷新使 nanoid / postcss / source-map-js 三条一并消解。
🔴 升级引入的 CSS 顺序回归(本轮实测发现并修复,不是理论风险):
rolldown 把 Element Plus 基础样式拆成独立 css-*.css 块,且 index.html 中
排在入口 CSS **之后**;旧 rollup 把 EP 与 global.css 合并在同一入口 CSS,
EP 在前、项目在后。两者覆盖语句特异性相同 ⇒ 谁在后谁赢 ⇒ 新顺序下反被 EP 压过。
取证:生产旧包 index-CVAb-2kd.css 内 EP 规则 @27768 < 项目覆盖 @57506(旧=正确);
新包 index-rMbH9bHe.css(先)vs css-CNpL_Sag.css(后)。
· --el-color-primary 退回 #409eff ⇒ 登录按钮由深藏青变 Element 亮蓝;
· 文字型按钮 hover 底退回不透明 #f5f7fa ⇒ 此前修好的「白底白字」缺陷复现。
修法=让覆盖与加载顺序无关(⛔ 不是调顺序,顺序由打包器决定不可靠):
· :root → html:root (0,0,1,1 > 0,0,1,0)
· .el-button.is-text:not(.is-disabled):hover/:focus 加 :root 前缀(4 → 5 个类级)
验证(四级对照,全部可复现):
1. 顺序单变量实验(两组页面除 <link> 顺序外完全相同):
修复前 A=SAME? → primary #1a3a5c vs #409eff、hover rgba(...) vs #f5f7fa(DIFF);
修复后两种顺序计算样式完全相同(PASS)。
2. 与生产旧包(旧 vite5 构建)逐项比对 computed style:primary / light-3 /
主按钮底色 / hover 底色 全部 SAME。
3. 登录页截图 sha256 与生产旧包**逐字节相同**(f5052aa7260b7b47)——
修复前为 9f074bb1c44329b3。
4. 真 chromium 加载构建产物(vite preview):/login、/admin 首屏、
成绩管理、选手管理全部 0 console error / 0 pageerror;
TDZ 静态门禁 38 文件 PASS。
产物:入口 index-DTFvE02d.js · 157 资源 · 73 个 .gz(两次构建 hash 一致=可复现)。
全量电池 57 步:退出码 0 · FAIL 0 · SKIP 0 · 未运行 0(1 项 N/A 为数据前提不成立)。
|
2026-10-21 12:05:29 +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 |
|
|
|
1b569a38e6
|
fix(download): 哨兵按 x-file-size → CD size= 回落(R103 收口)
配套 backend app/utils/sendFile.js 的同一处修复(见该提交):X-Accel-Redirect
分支下 nginx 会丢弃上游自定义响应头 X-File-Size,故后端把同一字节数写进
RFC 6266 §4.1 的 disposition 扩展参数 `size=`(实测 CD 能穿透 X-Accel)。
本提交:
· 新增 readDeclaredSize(headers):先取 x-file-size,缺失或非正数时回落解析
Content-Disposition 的 `; size=<n>`;两通道都取不到返回 NaN(调用方跳过校验,
优雅降级,不误伤)。
· assertRealFile() 改用它,替换原先只读 x-file-size 的 parseInt。
· 判据可靠性:filename*= 的值是百分号编码的,其中不可能出现字面量 `size=`,
且 `size=` 必须落在参数边界(行首或 `;` 之后),故不会与文件名误匹配。
验证:
· 本地判别力自证 verify_cd_size_channel.js:pass=12 fail=0
(F0 断言探针判据与本源码逐字同源;F0b 断言确实经由 readDeclaredSize;
C1 变异测试证明旧形态解不出 ⇒ 判据有判别力;D1/D2/D3 覆盖无误伤边界)
· 生产 ARM B(经 nginx)实测 CD 的 size=19009 穿透成功
(r106_prod_sentinel.js pass=11 fail=0)
|
2026-10-12 11:06:10 +08:00 |
|
|
|
3085616605
|
feat(admin): 小组视图「已激活」筛选改按「已有成员激活(可参赛)」口径
R90续三:与后端 getTeams 口径修正同步。
- 筛选下拉文案:已激活(全组已激活)→ 已有成员激活(可参赛)
未激活(含部分激活)→ 暂无成员激活
- 汇总行「已激活」改为「全员激活」(与「部分激活」并列,避免与新口径混淆)
- 「当前筛选后」补充全员激活 / 部分激活分布
- 说明行改解释新语义(不再解释上下两处人数差额)
- 删除失效的 teamPartialExtra;teamFilteredSummary 增补
filteredActivated / filteredPartial 的防御式读取
生产已部署并复验:线上产物与本地产物逐字节一致(sha256 4604d3f1…),
新文案全部在线上 JS 中检出、旧文案完全消失。
|
2026-10-09 15:53:21 +08:00 |
|
|
|
092910404a
|
feat(admin): 选手列表新增小组编号/所属学校列,小组视图汇总双口径
R90 人工测试反馈修复(方案 1 + 方案 3-A):
- 选手列表新增「小组编号」「所属学校」两列,便于与小组视图横向对照
- 小组视图汇总行改为「全集 + 当前筛选后」三段式,并新增差额说明行
· 明确解释「选手列表激活人数」与「已激活小组成员数」的差额来源
(差额 = 部分激活小组内已激活的选手)
- 新增 teamFilteredSummary / teamPartialExtra computed
· 防御式读取:后端未同步时自动退回全量口径,不显示 undefined
- 新增 accountTeamLabel() 显示助手与 .team-summary-filtered / .team-summary-hint 样式
生产已部署并复验:线上产物与本地产物逐字节一致(sha256 3d09ea90…),
新增 UI 文案均已在线上 JS 中检出。
|
2026-10-09 15:27:39 +08:00 |
|
|
|
27665c2186
|
feat(admin): 作品管理新增「提交状态」筛选(已提交 / 未提交)
- 工具栏新增「提交状态」下拉(已提交/未提交,clearable),class 与文案均与「成绩总览」同款逐字对齐
- 判据抽为单一真相源 isWorkSubmitted = (row.files||[]).length > 0,同时供表格「未提交」文案 / 计数 / 筛选三处使用,杜绝列表与筛选打架
- filteredSubmissions 增加状态分支(先姓名后状态),分页与 total 自动联动
- 新增计数「共 N 人 · 已提交 X · 未提交 Y |当前筛选出 M 人」;批量下载按钮补 title 明示「下载全组、不受筛选影响」
- 线上验证:R87 已部署(入口 index-Dhsp29Gt.js,gzip_static 命中 CL=106660,旧入口 404)
|
2026-10-09 10:40:55 +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 |
|
|
|
680faacd1d
|
feat(admin): 成绩管理 Tab 分区 + 作品管理移除中职/高职 + 分页最小档补 5
R80 三项 UI 改造(单文件 src/views/Admin.vue,+78/-37):
1. 作品管理:移除「中职组 / 高职组」两个选项 —— 这两组已取消 Word 作品上传,
留着会让人误以为可以下载到作品。顺带清掉因此变成恒真的死判据
v-if="submissionGroup === 'benke' || submissionGroup === 'jiaoshi'"(批量下载作品按钮)。
后端 getWorks 仍支持 4 组,保留兼容不删。
2. 分页:9 处 page-sizes 统一补最小 5 档([5,10,20,50];账号管理 [5,10,20,50,100])。
试卷管理 3 处原本已是 5 档,无需改。动因:成绩管理表体量大、翻页粒度需要更细。
3. 成绩管理:由「一段纵向长页」改为「顶栏 + 三页签」——
- 顶栏只留组别级动作:组别选择 / 发布·撤回成绩 / 导出Excel / 批量下载作品
- 表级筛选(姓名 / 提交状态 / 日期范围)下沉到「成绩总览」页签,
否则切到别的页签时筛选框仍在,会造成「筛的是哪张表」的歧义
- el-tabs:成绩总览(始终)/ 考生答题记录(仅中职、高职)/ 考生大题作答查证(同上)
- 大题作答详情 el-dialog 放在 el-tabs 之外:留在页签内会随 v-if 卸载而销毁弹窗状态
🔴 唯一高危边界:activeScoreTab 停在后两个页签时切到本科/教师,两个页签 v-if 消失
⇒ el-tabs 无 active 内容 ⇒ 页签栏在、内容区全空(白屏)。治法:在
watch(scoreExportGroup) 内复位为 'overview' —— 用 watch 而非 onScoreGroupChange,
因为 watch 默认 pre-flush 会在组件重渲染之前执行,不会先闪一帧空白。
验证:verify_frontend_tdz PASS(activeScoreTab 声明顺序,本页历史真炸过的坑)·
@vue/compiler-sfc parse/compileTemplate 0 错 · ui-runtime-check 真 chromium 17 步 17/17 PASS。
变异自证 2 轮:恢复中职/高职选项 ⇒ expectNoText 2 项变红;拆掉切组复位 ⇒ 切本科组后
expectText("提交状态") 命中 0 处变红。
|
2026-10-08 15:19:50 +08:00 |
|
|
|
4e84257ed8
|
fix(auth): 密码框反自动填充兜底 —— 进入「设置密码」步骤必须为空
测试人员反馈:激活流程进入「设置密码」步骤时,密码框内已出现 123456(用户未做任何输入)。
取证结论 —— 前端源码不存在任何向密码框写值的赋值点:
- step3Form.password 初值为空,密码框只有 v-model、无 :value 绑定
- 全站唯一 .password = 赋值点是 Login.vue 的清空逻辑
- git log -S "123456" -- Activate.vue 显示该字面量历史上仅出现一次,
且是 d16c261 新增的 placeholder 文案,从无任何版本把它当默认值
=> 残留只能来自浏览器密码管理器自动填充(测试账号默认口令恰为 123456,
浏览器保存后会在同源同类表单上回填)
治法(行为级保证,不依赖成因判定):
- 新增 utils/passwordField.js:purgeAutofilledPasswords()
仅在表单模型为空时才清 DOM 密码框并同步 v-model,绝不覆盖用户真实输入
- Activate.vue / ForgotPassword.vue 对称改造:
1) placeholder 去掉 123456,弱口令示例移到独立 .pwd-tip 提示行
2) 新增 enterStep3():清空密码字段 -> 进第三步 -> DOM 挂载后再兜底清一次
3) 第三步 el-form 增加 autocomplete="off"
验证:
- TDZ 静态门禁 verify_frontend_tdz.mjs PASS
- deploy/build-frontend.sh EXIT=0(入口 assets/index-DwFQsYjy.js)
- 部署后线上核验:入口 hash 与本地一致、关键 chunk 全 200、
旧 chunk 全部保留(新旧共存,无缓存期 404)、gzip_static 命中预压缩产物、
线上 Activate chunk md5 与本地构建逐字节一致
|
2026-10-08 11:10:54 +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 |
|
|
|
39d3da0c93
|
fix(exam): 选择题选项等宽居中 + 成绩发布门控加固 + 已交卷禁止再进入
|
2026-09-30 15:46:41 +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 |
|
|
|
d16c261273
|
fix(auth): 口令输入框提示与后端策略对齐(6-20 位,勿用弱口令)
配套 R63 后端口令强度策略:两处 placeholder 由「至少6位」改为「6-20 位,勿用 123456 等弱口令」,让考生在前端即知道限制,避免填完才被后端拒绝。
刻意不在前端实现黑名单校验:那会与后端 lib/password_blacklist.json 形成两份会漂移的清单(后端才是权威闸门)。
|
2026-09-30 11:56:45 +08:00 |
|
|
|
a47e823277
|
fix(auth): 激活页清除残留登录态 + 全站密码框显式 autocomplete 语义
R61/R62:两个独立缺陷(测试人员反馈「设置密码框已有 123456 + 激活后直接进管理后台」)。
① 激活后进管理后台 —— 安全缺陷(公用电脑 = 越权)
Activate.vue 激活成功与「返回登录」都跳 /login,而 router guard
(router/index.js:203-222)见到 isLoggedIn && role==='admin' 会重定向到 /admin。
浏览器残留 admin 登录态时(测试人员刚用 admin 操作过后台),考生会被
直接送进管理后台;且 /api/admin/* 全部挂 adminAuth(router.js:25)
⇒ 残留 token 有效时管理接口真能调。
修法:激活页按「未登录」语义处理 —— onMounted 与激活成功后均 clearAuth()。
② 设置密码页密码框被浏览器预填
密码框未显式声明 autocomplete,而 EP el-input 该 prop 默认 "off",
Chromium 对 type=password 会忽略 off,改用该站点已保存凭据自动填充。
密码规则仅 min:6 ⇒ "123456" 恰好合法 ⇒ 用户不知情下即把密码设成 123456。
修法:6 个密码框显式声明语义 —— 设密码/重置密码 new-password(4 处),
登录 current-password(2 处,含管理员 tab)。
验证:真浏览器 21/21 断言通过(含「残留 admin 态直接访问 /login 必跳 /admin」
对照臂,证明断言非恒真);TDZ 扫描 PASS(26 文件);权威脚本构建
157 文件 / 70 .gz;生产零中断上线(MainPID/NRestarts/ActiveEnterTimestamp 未变)。
|
2026-09-30 11:39:53 +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 |
|
|
|
3f8316da47
|
perf(frontend): RichEditor 异步化拆分 Admin chunk(1021 kB → 232 kB)(BACKLOG C6)
定性:Admin-*.js 达单 chunk 1,021 kB(gzip 340 kB),而其中约 800 kB 是**进入管理端
根本用不到**的富文本编辑器。
- Admin.vue 静态 import RichEditor.vue,后者静态 import @wangeditor/editor
(dist/index.esm.js = 818,777 B)⇒ 编辑器全量并进 Admin chunk。
- 实测三处使用点(446 大题题干 / 1384 新闻 / 1476 轮播)**全部位于 el-dialog 内**,
而 el-dialog 初始 rendered=false 不渲染内容 ⇒ 首屏不该付这份成本。
- 全仓反证:RichEditor 只被 Admin.vue 引用;@wangeditor 特征串在入口 chunk 0 命中。
处置:改为 defineAsyncComponent 按需加载(最小改动 16+/2−,只动 Admin.vue)
- src/views/Admin.vue: import RichEditor → defineAsyncComponent({ loader: () => import(...) })
- 加 loadingComponent:用 **render 函数**而非 template 字符串 —— 本工程为 runtime-only
构建,字符串模板需要运行时编译器,会直接报错。
- vue import 列表补 defineAsyncComponent 与 h。
- 无需 manualChunks:唯一引用点 + 动态 import ⇒ Vite 自动分片。
实测(真 chromium + 静态服务 + /api 反代真实后端):
Admin-*.js 1,045,614 B → 231,715 B(−77.8%)
RichEditor-*.js 新增 810,862 B(按需加载;gzip 281,958 B)
首屏 JS 合计 1,684,778 B → 872,736 B(−48.1%)
首屏最大单 chunk 1,045,614 B → 283,833 B(−72.9%)
验证:
- 新增 perf-r34/c6-chunk-split-check.mjs:21/21 全绿(首屏不含 wangeditor;打开新闻与
轮播两个弹窗后编辑器 chunk 按需请求且渲染成功;0 pageerror / 0 Failed to resolve)
- 🔴 判别力(变异测试):对**拆分前**的 dist 跑同一脚本,恰好 5 项判红(A4 首屏含
wangeditor / A5a 1,684,778 B 超预算 / A5b 1,045,614 B 超 500 kB / B2·B3 无按需请求)
—— 证明断言真的在守,而非「守卫根本没跑」。
- 回归:perf-r34/dist-admin-check.mjs 9/9(el-table 20 行 / 分页中文 / ElMessageBox
中文按钮 / 零 Failed to resolve)⇒ 管理端功能零回归。
- TDZ 扫描 37 文件 PASS · vite build exit 0 · .gz 预压缩 70 个(含 RichEditor.js.gz)
|
2026-09-29 12:49:58 +08:00 |
|
|
|
ecf8d39f03
|
chore(frontend): 移除孤儿路由 /upload 与 Upload.vue(BACKLOG B2)
定性:/upload 是全仓零 UI 入口的孤儿路由。
- Dashboard.enterContest() 对所有组别统一 router.push('/competition');
全仓 push('/upload') / replace('/upload') / to="/upload" / name:'Upload' 0 命中,
Upload.vue 只被 router 一处 import。
- 本科/教师的答案文件上传链路早已由 Competition.vue 的 exam-upload-mode 完整承担
(zip 独占 / pdf-docx 互斥 / 组合校验 / 覆盖式重传 / 删除)。
- 该页对中职/高职会调用后端不支持的分组接口
(GET /api/upload/{zhongzhi,gaozhi}/status|submissions → 「无效的组别,仅支持 benke/jiaoshi」),
因守卫只弹 warning 且注释明写"不强制跳转",随后仍无条件发请求 ⇒ 3 条 console 报错。
处置(三方案中的 ①;②接线会使 Competition.vue 上传模式被孤立,属功能倒退;③被①涵盖):
- src/router/index.js: 删 import Upload 与 /upload 路由块(-9 行)
- src/views/Upload.vue: git rm(-735 行)
- 保留 src/api/upload.js:stores/exam.js 与 Competition.vue 仍在用
getUploadStatus / uploadWork / deleteWork / getSubmissions
验证:
- 前置:全项目(docs/deploy/nginx/bat/ps1/后端)零外部引用;Upload.vue 无具名导出
- vite build exit 0;dist 异步分片列表已无 Upload-*.js(残留 upload-*.js 是 MD5 库分片,0 页面文案)
- TDZ 扫描 37 文件 PASS(原 38,正好少一)
- 真浏览器:/login 9/9 · /upload 现落 404 页且 0 pageerror / 0 console error
(修复前:渲染旧上传页 + 3 条 console 报错)
|
2026-09-29 12:17:48 +08:00 |
|
|
|
0416ac3360
|
fix(frontend): Upload.vue 3 处 h('el-button') 改为显式 ElButton(BACKLOG B1)
修复前:Vue3 中 h('el-button') 的字符串标签不解析为组件,创建原生自定义元素
<el-button> ⇒ 无 EP 样式 · type/circle/text 失效 · 「开始上传」的 loading 与
primary 全失效(点击后无进行中反馈)· icon: h(Delete) 被写成垃圾属性。
修法:ElButton 已在同文件 309 行导入 ⇒ 3 处字符串改标识符(ElProgress/ElTag/
ElIcon 已在 R36 迁移)。最小改动:3 insertions / 3 deletions。
验证(新增 perf-r34/upload-check.mjs:静态服务 + 真后端代理 + 真 chromium +
拦截 POST /api/upload/** 防真实写库):
18/18 全绿 —— 「开始上传」cls='el-button el-button--primary is-loading'
bg=rgb(26,58,92) == --el-color-primary · 删除按钮 is-circle+el-button--danger ·
「重新上传」is-text+el-button--danger · 0 个原生 el-button 自定义元素 ·
0 Failed to resolve · 0 pageerror · 2 个写请求被拦截(零真实写库)
TDZ 扫描 38 文件 PASS · vite build exit 0
⚠️ 另发现(不在本次修复范围,见 BACKLOG B2):/upload 是全仓零 UI 入口的
孤儿路由,且对中职/高职会调用后端不支持的分组接口(3 条 console 错误)。
|
2026-09-29 12:01:57 +08:00 |
|
|
|
86f3b6c28c
|
chore(frontend): 删除孤儿 auto-imports.d.ts
该文件由 unplugin-auto-import 生成(Jun 17 遗留),但该插件既不在
package.json 也未安装,文件永不再被重生成 ⇒ 孤儿。
危害:它把 76 个「运行时并不存在」的全局(ref/ElMessage/h/...)声明为合法,
漏写 import 时 IDE/tsc 不报错、运行到该行才 ReferenceError —— 陷阱制造机。
(R36 已实测:本项目全部走显式 import,且 R36 明确否决 auto-import 方案。)
闭环取证:
- 全项目(含 backend/deploy/database/scripts)grep 'auto-imports' = 0 引用
- 扫描器核验:无任何源码「未导入即使用」这 76 个全局(0 命中)
- 删除后 vite build 成功(exit 0),主包 hash index-C_gVjC9x.js 与 R36 完全一致
⇒ 不影响任何运行时/产物(.d.ts 不参与打包)
- 项目无 typescript/vue-tsc 依赖,build 不做类型检查 ⇒ 删除无类型回归面
|
2026-09-29 11:50:19 +08:00 |
|
|
|
ea350fb2e0
|
feat(frontend): Element Plus 改按需引入,首屏 425.1->158.8KB(R36)
- vite.config.js: 新增 unplugin-vue-components + ElementPlusResolver;
optimizeDeps.include 动态生成 118 项无扩展名 specifier(glob/.mjs 皆静默失效)
- main.js: 删 app.use(ElementPlus) 与全量 CSS;改 provideGlobalConfig(locale, app, true);
显式引入 4 个函数式组件样式(message/message-box/notification/loading)
- views/Upload.vue: 3 处 h(resolveComponent('el-xxx')) 改显式组件引用;清 2 处死导入
- devDep: unplugin-vue-components@^32.1.0
- .gitignore: 忽略 vite.config.js.timestamp-*.mjs
验证:dist 渲染 9/9 · dist 登录后 9/9 · dev 通道致命错误 0/告警 0
瘦身:首屏 425.1->158.8KB(-62.6%);主包 JS 1112.9->281.09KB
|
2026-09-29 11:44:15 +08:00 |
|
|
|
042e0a8e1f
|
feat(frontend): SortableJS 改为本地依赖,移除公网 CDN 运行时依赖(R33)
- package.json: 新增 sortablejs@^1.15.0(dependencies)
- src/main.js: import Sortable + 挂载 window.Sortable
(保留全局挂载形式 => Admin.vue 的 window.Sortable.* 零改动)
- index.html: 删除 cdn.jsdelivr.net 的 <script>,留注释说明原因
- 原因: 内网/离线部署时 CDN 不可达 => window.Sortable 为 undefined,
而 Admin.vue:3089/3276 的 `if (!window.Sortable) return` 会静默吞掉
新闻/轮播的拖拽排序(界面零提示)
- 验证: 真 chromium 16/16 PASS(含整站 0 次 CDN 请求、宿主元素 expando
Sortable<数字> 探针、news/carousel handle 正确、0 pageerror/0 console.error);
构建 ✓ built in 18.57s / VITE_EXIT=0; TDZ src 38 + dist 26 全 PASS
|
2026-09-29 09:57:48 +08:00 |
|
|
|
cb672b621a
|
feat(admin): 导入结果提示区分「忽略非参赛选手」条数(R32)
- onExcelImport 读取 result.ignored 并在成功提示中单列展示
- 控制台 console.info 输出 ignoredBy 身份明细(报名人/指导教师/领队)
- 仅账号导入这一处;客观题导入提示未动
|
2026-09-29 09:40:51 +08:00 |
|
|
|
2ca82d0da3
|
refactor(admin): 评分入口收敛 + 大题小问得分可见(R30)
- 文案:「考生大题作答查证」提示由「作品评分」改为「大题评分」
(保存后将写入该考生「大题评分」,总分 = 客观题得分 + 大题评分)
- 删除「考生答题记录」的评分操作:操作列(评分按钮)+ 评分弹窗 +
examScoreDialog* / openExamScoreDialog / saveExamScore + .exam-score-form 样式
⇒ 大题评分统一由「考生大题作答查证」按小问给分,word_score 不再有两条口径
⛔ 刻意保留:adminApi.setScores(作品管理 batchSaveWorkScores 仍在用)
与后端 PUT /api/admin/scores/:group(10+ 门禁脚本依赖:verify_full_e2e /
exam_regression_test / verify_large_scale_17 / verify_agg_key_mismatch 等)
- 新增「小问得分」列(「大题得分」之后):后端 listAnswersForAdmin 本就返回
score1/2/3 ⇒ 纯前端改动、未动后端。新增 subScoresOf / hasSubScores / fmtSubScores;
hasSubScores 用于区分「未评分」与「三问都给 0 分」(二者不可都显示成 0)
验证:
- TDZ 顺序扫描:src 38 文件 + dist 打包产物 26 文件 全 PASS
- 真 chromium(playwright)ui-runtime-check.mjs:16/16 断言全过 / 致命错误 0 / 告警 0
(含 expectText "10 / 8 / 9" 直证小问得分列渲染;DOM dump 证实答题记录表头
已无「操作」列、大题查证表头含「小问得分」)
- dist 重建 built in 13.00s / VITE_EXIT=0;产物「作品评分」= 0,「小问得分」「大题评分」在
|
2026-09-28 17:44:55 +08:00 |
|
|
|
e5579f034e
|
fix(admin): 修复成绩管理页 TDZ 崩溃——watch 早于 ref 声明(R29 回归)
浏览器实测报错:
ReferenceError: Cannot access 'scoreExportGroup' before initialization
at setup (Admin.vue:2559:7)
根因:R29 板块迁移时把 `watch(scoreExportGroup, …)` 写在 2559 行,而
`const scoreExportGroup = ref('')` 在 2810 行。**watch() 会立即执行一次
source 取值来收集依赖**,故 setup 阶段即触发 TDZ。
(对照:watch(questionGroup,…) 安全,其声明在 1784 行。)
修法:把该 watch 下移到 `scoreExportGroup` / `isExamScoreGroup` 声明之后
(成绩管理节内),原位置留注释说明原因,避免后人再次上移。
验证(本轮新增两道门禁工具,均在 .workbuddy/tools/,未入仓库):
- AST 顺序扫描 verify_frontend_tdz.mjs:对已提交的坏版本 c470e79 精确命中
Admin.vue:2559(变异测试);修复后 src 38 文件 + dist 打包产物 26 文件全 PASS。
- 真 chromium(playwright)ui-runtime-check.mjs:致命运行错误 0、告警 0、
18/18 断言通过(中职→本科→高职切组、迁移板块显隐、导出Excel、
批量下载作品按组显隐、评分弹窗「客观分(卷面)」单行左对齐)。
- dist 已重建:built in 12.36s、VITE_EXIT=0。
教训:vite build 只打包、不执行 setup,构建绿对 TDZ 是假绿。
|
2026-09-28 17:20:37 +08:00 |
|
|
|
c470e7922e
|
feat(admin): 功能分布优化——板块迁入成绩管理 + 导出 Excel + 废弃按钮清理(R29)
- 试卷管理只留试卷相关:「考生答题记录」(含评分弹窗)与「考生大题作答查证」
整段迁至成绩管理页,外层 <template v-if="isExamScoreGroup"> 仅中职/高职可见
- 组别绑定改跟随成绩页 scoreExportGroup(避免串组):新增 isExamScoreGroup /
refreshExamCtxData / refreshScorePageData + watch(scoreExportGroup);ensureBigData 收窄为只刷题库
- 导出按钮:导出CSV → 导出Excel(GET /api/admin/export-excel/:group,fallback .xlsx)
- 中职/高职已取消 Word 作品上传:成绩页与作品管理页「批量下载作品」按组别 v-if 隐藏
- 评分弹窗文字错位修复:label-width 120px + label-position="left" + 标签左对齐不换行
- 5 个纯白按钮补 type(info/primary plain)提升可见度
|
2026-09-28 16:42:39 +08:00 |
|
|
|
fb9c48aaac
|
fix(admin): 复核页选择题选项与答案显示 + 阅卷导出 Word 按钮 + 评分两位小数(R28)
- 新增 reviewOptionList() / reviewOptionTextOf() / reviewAnswerCell():
防御性解包 options(非数组一律降级空数组),选项列表改为 v-for 遍历对象数组;
选择题的「考生作答」「正确答案」显示为「A. GPS」,彻底消除「只显示字母不知指向哪个选项」的疑虑;
判断题仍显示「正确/错误」。两列宽 96 -> 130。
- 阅卷区:el-table 增加 type="selection" + row-key="contestantId";
新增「导出选中 Word(n)」「导出全部 Word」及行内「导出Word」,
沿用 safeDownload 直接命中 GET /api/admin/big-answers/:group/export?ids=。
- fmtScore / bigScoreTotal 改两位小数;3 处 el-input-number 改 :precision="2" :step="0.25"。
构建验证:node ./node_modules/vite/bin/vite.js build => VITE_EXIT=0(built in 19.61s),
产物中含 review-options / 导出全部 Word / big-answers/{group}/export 三元 fallbackName(.docx|.zip)。
|
2026-09-28 15:50:56 +08:00 |
|
|
|
397356f141
|
feat(admin): 大题录入题干加「不得含参考答案」提示(R27)
- 题干表单项下新增 .big-form-tip:明确「不得出现参考答案 / 标准答案 / 评分标准,
也不得给出带具体参数值的指令示例(如 >OUTMSG,COM1,GGA,1*3B)—— 考生可直接照抄」;
并给出正确做法:只写格式定义(参数用方括号占位,例 >OUTMSG[,Port],Message,Period*CS)。
- 新增对应 CSS(.big-form-tip / .big-form-tip code)。
- 后端拦截消息经既有 ElMessage.error(e?.message) 展示(Admin.vue:1968),无需改动。
构建验证:vite build ✓ built in 11.32s,VITE_EXIT=0。
|
2026-09-28 11:58:17 +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 |
|
|
|
bdfa4a9618
|
R20:Admin.vue —— 页面状态刷新修复 + 大题导出 Word + 评分一位小数
C 页面状态刷新(修复测试人员反馈的「切组 / 评分后需手动刷新」):
- onQuestionTypeChange:移除 `bigQuestions.length === 0` 守卫(切组后旧数组仍非空
⇒ 永不重新拉取,界面停留在上一组数据)
- saveBigScores / saveExamScore:原来各只刷新一侧列表(评分后「查看」侧仍是旧值)
- 收敛为唯一刷新入口 refreshQuestionData(),onMenuSelect / watch(questionGroup) /
评分收尾 / 切组 全部走它
- 新增 ensureBigData():按「数据属于哪个组」(bigDataGroup) 判定是否拉取
- fetchBigQuestions / fetchBigAnswers:固定发起时的组别,响应回来若组别已变则丢弃
(防切组瞬间旧响应串组显示)
B 大题评分一位小数:
- 3 处 el-input-number 加 :step="0.5" :precision="1"
- 新增 fmtScore():整数不带小数点(27),非整数保留一位(27.4),空/非法回 '--'
- bigScoreTotal 收敛到一位小数(防 0.1+0.2 浮点脏值显示)
D 大题导出 Word:
- 工具栏新增「导出选中Word(N)」「导出全部Word」
- 大题表加多选(reserve-selection)+ 选中态跟踪
- exportBigQuestions():走 safeDownload(已含 JSON 错误分支,避免把错误信封存成
.docx);过滤不属于当前组别的陈旧 ID
计划外修复:
- exportBigQuestions 边界:选中 ID 全失效时原会因 qs 为空而静默退化成「导出全部」,
与用户意图相反;已改为提示而不导出
验证:VITE_EXIT=0(多次构建,Admin chunk hash 已更新)。
未 push:须先上线验证通过再单独授权。
|
2026-09-23 18:30:01 +08:00 |
|
|
|
cddf91d13a
|
fix(admin): 中职/高职管理后台「Word」口径改为「大题分」并清理死列
问题(用户 2026-09-22 报障):
管理后台 试卷管理 → 考生答题记录 中,
①「答题情况」显示的是「Word分」,与现行综合大题口径不符;
② 多出一列「Word 答案」(针对已废弃的 Word 上传通道,中职/高职恒为空)。
改动(5 处):
①「答题情况」列标签:Word分 → 大题分
② 删除整列「Word 答案」(含 hasWordFile 判断与下载按钮)
③ 评分弹窗 label:Word 答案分 → 大题分
④ 删除孤儿方法 downloadExamWord()(删除后已无调用者)
⑤ 成绩管理区中职/高职分支:删「Word文件」列,Word分 → 大题分
(v-else 本科/教师分支的「文档分(Word)」保持不变)
影响面:仅中职/高职分支显示口径;本科/教师组 Word 通道不受影响。
数据列仍为 contestants.word_score(历史列名沿用),仅文案口径调整。
验证:vite build EXIT=0(9.88s);产物指纹「大题分」1 次 /「Word分」0 /「Word 答案」0;
GET /api/admin/submissions/zhongzhi 返回 6 行字段齐全,零回归。
|
2026-09-22 13:05:30 +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 |
|