feat(admin): R176-R180 公示表与新闻撤回 + 管理端多管理员权限(C 层)+ 交卷冻结
- R176 成绩公示表引擎(lib/publicitySheet.js + lib/assets/publicity-template.xlsx 官方模板基底) 与指导教师回填(lib/teacherBackfill.js);赛项名称/编号改为配置项 - R177 新闻「发布 ↔ 撤回」互斥:撤回只置 is_draft=1,不删除数据 - R178 C 层 RBAC:lib/adminPermissions.js(13 权限点 / 70 条路由规则,唯一 SoT) + middleware/auth.js 三段判定(默认拒绝 · is_super 单点 · token_version) + controller/service/adminUser.js + lib/auditLog.js + model/adminAuditLog.js - R178 B1 交卷冻结:service.exam.assertNotSubmitted(bizCode 4006 单一判据源) - R178 B④ 提交口径拆分:formalSubmitted / submitted / answeredCount 三字段 - R178 成绩总览补小组编号:resolveTeamLabels(与选手管理同源,含兜底与三态) - R181 生产迁移配套:admins +7 列、admin_audit_logs(scripts/migrate_r178_admin_rbac.py)
This commit is contained in:
@@ -110,6 +110,19 @@ module.exports = app => {
|
||||
|
||||
// ==================== 管理后台(需管理员认证) ====================
|
||||
|
||||
// ---------- R178 多管理员 + 权限 ----------
|
||||
// 这些路由与下面所有 /api/admin/* 一样只挂 adminAuth:
|
||||
// 权限判定在 middleware/auth.js 的 adminOnly 分支内完成(单一真相源
|
||||
// lib/adminPermissions.js 的 ROUTE_TABLE,未登记路由默认拒绝)。
|
||||
// ⛔ 不要在这里逐条写权限参数——那会与映射表形成第二套真相源。
|
||||
router.get('/api/admin/me', adminAuth, controller.adminUser.me);
|
||||
router.get('/api/admin/admin-users', adminAuth, controller.adminUser.list);
|
||||
router.post('/api/admin/admin-users', adminAuth, controller.adminUser.create);
|
||||
router.put('/api/admin/admin-users/:id/permissions', adminAuth, controller.adminUser.updatePermissions);
|
||||
router.put('/api/admin/admin-users/:id/password', adminAuth, controller.adminUser.resetPassword);
|
||||
router.put('/api/admin/admin-users/:id/status', adminAuth, controller.adminUser.setStatus);
|
||||
router.delete('/api/admin/admin-users/:id', adminAuth, controller.adminUser.remove);
|
||||
|
||||
// 数据统计
|
||||
router.get('/api/admin/stats', adminAuth, controller.admin.stats);
|
||||
|
||||
@@ -119,6 +132,12 @@ module.exports = app => {
|
||||
// ⚠️ 路径段固定、无通配,与既有路由无匹配冲突。
|
||||
router.get('/api/admin/teams', adminAuth, controller.admin.teams);
|
||||
router.post('/api/admin/import-excel', adminAuth, controller.admin.importExcel);
|
||||
// 指导教师回填(R176):报名表 → contestants.teacher_name。
|
||||
// 🔴 与 import-excel 是**两条不同通路**:导入对已存在身份证号一律 skip,
|
||||
// 无法用于"重导一次补字段";本接口只写 teacher_name,不碰账号/队伍/成绩。
|
||||
// ⚠️ 路径段固定(backfill-teacher 为字面量),且与下方 PUT /:id 方法不同,无匹配冲突。
|
||||
// 破坏面定级:非破坏性(白名单单列写入、按组限定、可重复执行且幂等)
|
||||
router.post('/api/admin/contestants/backfill-teacher', adminAuth, controller.admin.backfillTeachers);
|
||||
// 选手信息编辑(R108):让「导入之后就改不了」的历史限制成为过去 —— 身份证号 / 姓名 /
|
||||
// 手机号可事后纠正(典型场景:报名表证号录入错误,人工核实后修正)。
|
||||
// ⚠️ 路径段固定(/contestants/:id),与既有路由无匹配冲突;PUT 方法在 admin 段内唯一。
|
||||
@@ -132,6 +151,14 @@ module.exports = app => {
|
||||
router.get('/api/admin/export-csv/:group', adminAuth, controller.admin.exportCsv);
|
||||
// R29:成绩导出改为真正的 Excel(.xlsx)(与 CSV 共用同一数据源 buildScoreExportTable)
|
||||
router.get('/api/admin/export-excel/:group', adminAuth, controller.admin.exportExcel);
|
||||
// 成绩公示表导出(R176):按大赛参考模板生成 xlsx 半成品(获奖等级留空待人工填)。
|
||||
// ⚠️ 与 export-excel 是两个不同产物:export-excel 是**内部核查表**(含身份证号/手机号),
|
||||
// 本接口是**对外公示表**(不含任何身份证号/手机号),⛔ 两者不可互相替代。
|
||||
router.get('/api/admin/export-publicity/:group', adminAuth, controller.admin.exportPublicity);
|
||||
// 赛项信息(R176):成绩公示表标题的「赛项名称 / 赛项编号」,管理后台可维护。
|
||||
// ⚠️ 两个值只影响公示表标题文案,与成绩/账号/队伍无关。
|
||||
router.get('/api/admin/competition-item', adminAuth, controller.admin.competitionItemInfo);
|
||||
router.put('/api/admin/competition-item', adminAuth, controller.admin.updateCompetitionItemInfo);
|
||||
router.get('/api/admin/batch-download/:group', adminAuth, controller.admin.batchDownload);
|
||||
router.get('/api/admin/works/:group', adminAuth, controller.admin.works);
|
||||
|
||||
@@ -206,6 +233,9 @@ module.exports = app => {
|
||||
// 图片上传(轮播图等)
|
||||
router.post('/api/admin/upload/image', adminAuth, controller.admin.uploadImage);
|
||||
router.post('/api/admin/upload/news-image', adminAuth, controller.admin.uploadNewsImage);
|
||||
// 新闻附件上传(R176):成绩公示表以附件形式挂在新闻里,而不是把链接写进正文
|
||||
// (正文会被人工作编辑,手写链接极易被删)。与 news-image 同目录、同访问前缀。
|
||||
router.post('/api/admin/upload/news-file', adminAuth, controller.admin.uploadNewsFile);
|
||||
|
||||
// 下载考生答案 Word 文件
|
||||
router.get('/api/admin/submissions/:group/:id/word', adminAuth, controller.admin.downloadWord);
|
||||
|
||||
Reference in New Issue
Block a user