Files
qubiaobiao 459f1adf6d 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)
2026-10-20 11:06:29 +08:00

142 lines
3.7 KiB
JavaScript
Raw Permalink 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.
'use strict';
/**
* 新闻模型 - News
*
* 存储新闻通知信息,支持置顶、微信公众号链接和按组别过滤显示。
* 表名:news
*/
module.exports = app => {
const { STRING, TEXT, BOOLEAN, DATE, INTEGER } = app.Sequelize;
const News = app.model.define('news', {
// 主键 ID,自增
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '新闻唯一标识',
},
// 新闻标题
title: {
type: STRING(200),
allowNull: false,
comment: '新闻标题',
},
// 新闻正文内容(支持 HTML)
content: {
type: TEXT,
allowNull: true,
defaultValue: null,
comment: '新闻正文(微信链接型新闻可为空)',
},
// 微信公众号文章链接
wx_link: {
type: STRING(500),
allowNull: true,
defaultValue: null,
comment: '微信公众号文章链接',
},
// 新闻类型:content(内容型)/ wx_link(微信链接型)
news_type: {
type: STRING(20),
allowNull: false,
defaultValue: 'content',
comment: '新闻类型:content=内容型, wx_link=微信链接型',
},
// 发布时间
publish_time: {
type: DATE,
allowNull: false,
comment: '发布时间',
},
// 是否置顶
pinned: {
type: BOOLEAN,
allowNull: false,
defaultValue: false,
comment: '是否置顶',
},
// 排序序号
sort_order: {
type: INTEGER,
allowNull: false,
defaultValue: 0,
comment: '排序序号,越小越靠前',
},
// 目标可见组别
group: {
type: STRING(20),
allowNull: false,
defaultValue: 'all',
comment: '目标可见组别',
},
// 标签(逗号分隔)
tags: {
type: STRING(500),
allowNull: true,
defaultValue: null,
comment: '标签,多个标签用逗号分隔',
},
// 草稿标记(R176)。成绩发布时会自动生成一条「成绩公示」预览新闻,
// 需人工编辑确认后才可对外发布 ⇒ 草稿期间必须前台不可见。
// 🔴 news.list / findById 的公开查询必须显式过滤 is_draft=0(默认值 0 保证历史 15 条不受影响)。
is_draft: {
type: INTEGER,
allowNull: false,
defaultValue: 0,
comment: '草稿标记:0=已发布, 1=草稿(前台不可见)',
},
// 附件(R176)。用于成绩公示表的文件公示:管理员上传 xlsx/pdf 后,
// 详情页渲染成独立下载按钮,不依赖正文富文本里手写链接
// (手写链接在人工编辑正文时极易被误删)。
attachment_url: {
type: STRING(500),
allowNull: true,
defaultValue: null,
comment: '附件下载地址(相对路径 /public/news/xxx)',
},
attachment_name: {
type: STRING(200),
allowNull: true,
defaultValue: null,
comment: '附件显示文件名(下载按钮文案)',
},
// 记录创建时间
created_at: {
type: DATE,
allowNull: false,
defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'),
comment: '创建时间',
},
// 记录更新时间
updated_at: {
type: DATE,
allowNull: false,
defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'),
comment: '更新时间',
},
}, {
tableName: 'news',
timestamps: false,
underscored: true,
indexes: [
{
fields: ['publish_time'],
},
{
fields: ['pinned', 'publish_time'],
},
{
fields: ['group'],
},
{
fields: ['sort_order'],
},
],
});
return News;
};