Files
ny-exam-platform/docs/dev/api.md
T
nxxy335 d05fd4a9b5 feat(export/dashboard): 导出增强与大屏公开化
导出-每次考试/多考试对比:
- 合格次数阈值等标记项抽为常显区块,三类成绩表共用
- 附缺考名单扩展到多考试导出(取所选考试全都没交的人)
- 块表每场固定2列(成绩+合并空位),修复漏推空串导致的整行左移/串列
- 列宽按实际内容估算,合并区空位只给最小宽度
- 合格次数始终显示实际次数(含0),仅 ≥ 阈值标绿底
- 打印标题改为「成绩明细」,缺考名单分页单独成页
- 打印样式加 print-color-adjust:exact,绿底/红字可正常印出

数据大屏:
- ?class= 班级大屏免登录公开访问,接口侧放行并按班级裁剪后回传
- 单班大屏移除退出按钮,仅首页保留
- 修复红绿判定真bug:membersTable 误用 window.DASH(DASH为let声明取不到)
  改为 scoreGrade() 直接读自由变量,按后台合格率正确判红/黄/绿

测试/样例/文档同步更新,全量回归通过。
2026-09-27 22:04:17 +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 默认 两行表头的「每次考试」块表:一列「第几次」被所有考试共用,每场考试占 2 列(考试名横跨 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 } })

相关