Files
ny-exam-platform/docs/dev/api.md
T
nxxy335 e87986fcc6 多考试块表:每场考试改为单列并修正第几次列合并
- ny_assemble_block_sheet() 中每场考试由 2 列改为 1 列,删除历史遗留的
  「是否最佳」空白列,消除每场右侧多余空白窄列
- 删除「第几次」列的纵向合并,改为逐行显示 1/2/3…,不再被吞成 1
- 合格次数行同步改为每场 1 列;未考合并加 $end > $start 跨行守卫,
  规避单行学生产生的非法 1x1 合并(Excel 修复提示)
- 列宽按考试名估算(CJK 记 2 半角宽),clamp 8~18
- 同步更新 test-multi-export.php(95 项含 4 条防回归断言)、
  make-samples.php(李四数学改为真正整场未考)、导出/接口/测试文档
  及样例预览文件;全量回归 404 项 0 失败
2026-10-06 12:22:16 +08:00

7.9 KiB
Raw Blame History

接口一览

所有接口统一走 api.php?action=<name>,响应为 JSON:

// 成功
{ "ok": true, "data": { ... } }
// 失败
{ "ok": false, "error": "错误信息" }
  • 未登录时返回 { "ok": false, "error": "...", "auth": false },前端会清会话并跳登录页;
  • 写操作必须在 JSON 体里带 _csrf 字段(或 X-CSRF-Token 头), 令牌从 config_get 接口获取。

写操作白名单

config_save、roster_save、roster_import、roster_clear、 unmatched_map、project_config_save

接口清单

认证与配置

Action 方法 参数 说明
auth_check GET — 是否已登录
config_get GET — 返回配置(密码不回显)+ csrf_token + storage / db_driver / db_connected
config_save POST JSON 配置对象 保存;返回 conn_changed 与 ping 结果
config_test POST 连接信息 测试连接,不落库
ping GET — 探测卷王是否可达
diag GET — 诊断信息(存储后端、DB 状态等)

只有连接信息(地址 / 前缀 / 账号 / 密码 / 证书校验)变化时,config_save 才会清除令牌并重新登录。

数据

Action 方法 参数 说明
projects GET — 卷王项目(问卷 / 考试)列表
data GET projectId、nameQid、force 主系统数据:统计、成绩行、名单、缺考、未匹配
dashboard GET projectId、nameQid、force、class 按班级 / 组别聚合的看板数据。class 非空且未登录时,服务端只回这一个班(并标记 public)

data 响应关键字段:

stats        { total, submitted, absent, rate, avg, pass_rate, max, min, avg_duration }
rows[]       { sid, name, cls, grp, score, best_score, attempts, attempts_list[],
               submit_ts, submit_at, last_submit_ts, duration, duration_txt }
roster[]     { sid, name, cls, grp, login }
absent[]     缺考名单
unmatched[]  未匹配答卷
project_cfg  本卷配置
_cache       { cached, at }

名单

Action 方法 参数 说明
roster GET — 名单与元信息
roster_import POST multipart:file、mode=replace|append 导入(扩展名白名单 + 随机名 + 解析后即删)
roster_save POST JSON { students: [...] } 全量替换(DELETE + INSERT)
roster_clear POST — 清空
unmatched_map POST JSON { name, sid, cls, grp, login } 未匹配答卷补入名单(学号已存在则更新姓名)

每卷配置

Action 方法 参数 说明
project_config GET projectId 返回 cfg、effective(生效值)、global(全局默认)
project_config_save POST JSON { full_score, pass_rate, exempt[] } 留空/0 = 清除该项,回落全局

导出

Action 方法 参数 说明
export GET projectId、nameQid、cls、grp、mode=latest|all、scoreMode=latest|best、split=cls、sort、cols、skipTemp=1、noAbsent=1 成绩明细 .xlsx(all 时每人每次考试一行)
export_absent GET projectId、nameQid、cls、grp 缺考名单 .xlsx
export_multi GET projectIds(逗号分隔)、nameQid、mode=block|summary|both、wide=1、passTimes、markBest=0、passRow=0、scoreMode=latest|highest、cls / grp(可多值)、withCls / withGrp、includeTemp、format=json 「每次考试」块表 / 宽表 / 成绩对比 .xlsx(format=json 时返回装配结果供前端打印)
draw_export GET type=import|list 白名单导入模板 / 白名单列表

sort 取值:time / sid / score_desc / score_asc。

导出新参数

参数 适用接口 取值 说明
mode=all export 缺省 每次考试一行(同一学号考 N 次就有 N 行);mode=latest 为每人一行最新成绩
scoreMode=best export 缺省 每人一行的代表成绩改取最高成绩(默认 latest = 最近一次真实提交);mode=all 不生效
noAbsent=1 export、export_multi 缺省 不附缺考名单工作表(export_multi 口径:所选考试全都没交的人)
skipTemp=1 export 缺省 mode=all 时排除暂存草稿;includeTemp=1 可强制保留
mode=block export_multi 默认 两行表头的「每次考试」块表:一列「第几次」被所有考试共用(逐行 1、2、3…,不合并),每场考试占 1 列(第 1 行考试名 + 第 2 行「成绩」小标题),学生块末尾一行「合格次数」,学号·姓名纵向合并,没考的考试合并成「未考」(旧值 merged / attempts 等价)
wide=1 export_multi — 只传 1 个 projectId 时改用宽表:每人一行、第几次直接做列名(学号 / 姓名 / 第1次…第N次 / [合格次数]),由 ny_assemble_attempt_wide_sheet() 生成
passTimes export_multi 1 「合格次数」达标阈值:该场达到合格线的次数 ≥ 此值才标绿(默认 1)
markBest=0 export_multi 开 =0 取消「最高分标红」
passRow=0 export_multi 开 =0 不输出「合格次数」行(也就没有绿底)
mode=summary export_multi — 成绩对比:一行一个学生、一列一场考试(旧值 matrix 等价)
mode=both export_multi — 第一张块表 + 第二张成绩对比表
format=json export_multi — 不下载文件,ny_ok 返回 sheets(rows / merges / widths / header_rows / cell_styles)+ meta(考试名、场次、班级 / 组别、列开关、pass_line / pass_times / mark_best / pass_row / wide),供「多考试打印」渲染
cls / grp export_multi 新增 支持多选(逗号分隔),空 = 全部;与单考试 export 的筛选口径一致
withCls / withGrp export_multi 新增 强制出 / 不出班级、组别列;缺省由 ny_multi_col_flags() 判定:选 ≥2 个班级(组别)才出,只选 1 个班级且未选组别两列都不出
includeTemp=1 export_multi 缺省 不传 = 块表默认跳过暂存草稿(保留时标注 第N次(草稿))

块表由 ny_assemble_block_sheet() 生成,返回 rows(两行表头 + 学生块)/ merges / widths / header_rows=2 /cell_styles;宽表由 ny_assemble_attempt_wide_sheet() 生成,参数同上。 合格线按每场各自的 ny_resolve_score_cfg($pid, $c) 解析(本卷满分 × 合格率),不是全场统一一条线。

cell_styles 是 行,列 → red | green(行与列均 1-based),只有两类标记:

  • red —— 该场该生成绩 = 该场最高分(同分只标最早达成的那次);
  • green —— 该场该生成绩达到合格线的次数 ≥ passTimes(未达标不标记)。

XlsxWriter::renderMulti() 支持 merges(1-based 闭区间 [r1,c1,r2,c2])、header_rows(多行表头同样式) 与 cell_styles(单元格级红字 / 绿底);被合并的非首格写成「空值 + 同样式」以保证合并区域四周边框完整, 并跟随左上角首格的标记,避免绿底只覆盖半个格子。 前端 sheetToHtml() 把同一份 merges 换算成 colspan / rowspan、把 cell_styles 换算成红字 / 绿底, 所以打印与导出的 Excel 用同一套判定。 列显示规则 ny_multi_col_flags() 与排序比较函数同样放在 lib/multi_export.php(纯函数,可单测)。

前端调用约定

// 读
const d = await api('data', { params: { projectId, force: 1 } })
// 写:api() 会自动注入 _csrf
await api('roster_save', { json: { students } })

相关