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

134 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 接口一览
所有接口统一走 `api.php?action=<name>`,响应为 JSON:
```jsonc
// 成功
{ "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`(纯函数,可单测)。
## 前端调用约定
```js
// 读
const d = await api('data', { params: { projectId, force: 1 } })
// 写:api() 会自动注入 _csrf
await api('roster_save', { json: { students } })
```
## 相关
- [架构与目录](./architecture.md)
- [测试](./testing.md)