初始提交:全国记者站管理系统
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
-- Migration: 001_init_schema
|
||||
-- Description: 初始化基础表结构(从 server/index.js 迁移)
|
||||
-- Date: 2026-08-01
|
||||
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work_records (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
reporter TEXT NOT NULL,
|
||||
station TEXT NOT NULL,
|
||||
occurred_date TEXT NOT NULL,
|
||||
platform TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
score INTEGER,
|
||||
description TEXT,
|
||||
review_note TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
record_id TEXT NOT NULL REFERENCES work_records(id),
|
||||
actor_role TEXT NOT NULL,
|
||||
actor_name TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
from_status TEXT,
|
||||
to_status TEXT NOT NULL,
|
||||
score INTEGER,
|
||||
note TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Migration: 002_seed_data
|
||||
-- Description: 初始种子数据(7 条工作记录)
|
||||
-- Date: 2026-08-01
|
||||
|
||||
INSERT OR IGNORE INTO work_records
|
||||
(id, title, type, reporter, station, occurred_date, platform, status, score, description, review_note, updated_at)
|
||||
VALUES
|
||||
('WK-202607-086', '暑运客流持续攀升,多部门保障出行', '文字稿件', '林晓', '北京记者站', '2026-07-30', '全国日报', 'station_review', NULL, NULL, NULL, '2026-07-31 09:42:00'),
|
||||
('WK-202607-081', '老街更新:城市记忆与新消费共生', '视频供稿', '周宁', '北京记者站', '2026-07-29', '新闻客户端', 'headquarters_review', 8, NULL, NULL, '2026-07-30 17:26:00'),
|
||||
('WK-202607-074', '长三角一体化重点项目集中签约', '重要报道', '陈屿', '上海记者站', '2026-07-28', '全国日报', 'headquarters_review', 12, NULL, NULL, '2026-07-29 15:18:00'),
|
||||
('WK-202607-063', '县域公共文化服务观察', '图片供稿', '许言', '浙江记者站', '2026-07-26', '新闻周刊', 'returned', NULL, NULL, '请补充刊发版面截图,并核对发布日期。', '2026-07-28 11:05:00'),
|
||||
('WK-202607-052', '防汛一线应急响应纪实', '文字稿件', '方澄', '广东记者站', '2026-07-24', '全国日报', 'archived', 10, NULL, NULL, '2026-07-26 16:31:00'),
|
||||
('WK-202607-041', '融合报道生产能力专题培训', '培训参与', '林晓', '北京记者站', '2026-07-22', '总部培训中心', 'archived', 3, NULL, NULL, '2026-07-23 10:20:00'),
|
||||
('WK-202607-032', '社区养老服务站走访', '文字稿件', '周宁', '北京记者站', '2026-07-18', '新闻客户端', 'draft', NULL, NULL, NULL, '2026-07-18 18:42:00');
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Migration: 003_create_people
|
||||
-- Description: 人员管理表
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS people (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
station TEXT NOT NULL,
|
||||
title TEXT,
|
||||
phone TEXT,
|
||||
joined_at TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO people
|
||||
(code, name, station, title, phone, joined_at, status)
|
||||
VALUES
|
||||
('PERSON_001', '林晓', '北京记者站', '记者', '13800001001', '2024-03-01', 'active'),
|
||||
('PERSON_002', '周宁', '北京记者站', '记者', '13800001002', '2024-05-15', 'active'),
|
||||
('PERSON_003', '陈屿', '上海记者站', '记者', '13900001001', '2023-09-01', 'active'),
|
||||
('PERSON_004', '许言', '浙江记者站', '记者', '13900001002', '2024-01-10', 'active'),
|
||||
('PERSON_005', '方澄', '广东记者站', '记者', '13900001003', '2023-11-20', 'active'),
|
||||
('PERSON_006', '苏明远', '北京记者站', '分站负责人', '13800001003', '2023-01-01', 'active'),
|
||||
('PERSON_007', '林致远', '总部', '总部管理员', '13800001000', '2022-01-01', 'active'),
|
||||
('PERSON_008', '赵雨薇', '北京记者站', '记者', '13800001004', '2025-02-01', 'inactive');
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Migration: 004_create_stations
|
||||
-- Description: 记者站管理表
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
region TEXT,
|
||||
address TEXT,
|
||||
leader TEXT,
|
||||
phone TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
established_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO stations
|
||||
(code, name, region, address, leader, phone, status, established_at)
|
||||
VALUES
|
||||
('STATION_BJ', '北京记者站', '华北', '北京市朝阳区', '苏明远', '010-12345678', 'active', '2020-01-01'),
|
||||
('STATION_SH', '上海记者站', '华东', '上海市浦东新区', '李华东', '021-12345678', 'active', '2020-01-01'),
|
||||
('STATION_ZJ', '浙江记者站', '华东', '杭州市西湖区', '张浙江', '0571-12345678', 'active', '2021-03-01'),
|
||||
('STATION_GD', '广东记者站', '华南', '广州市天河区', '王广东', '020-12345678', 'active', '2021-06-01'),
|
||||
('STATION_SC', '四川记者站', '西南', '成都市武侯区', '刘四川', '028-12345678', 'active', '2022-01-01'),
|
||||
('STATION_HB', '湖北记者站', '华中', '武汉市武昌区', '陈湖北', '027-12345678', 'inactive', '2022-06-01');
|
||||
@@ -0,0 +1,38 @@
|
||||
-- Migration: 005_create_notices
|
||||
-- Description: 通知公告及回执表
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
priority TEXT NOT NULL DEFAULT 'normal',
|
||||
scope TEXT NOT NULL DEFAULT 'all',
|
||||
stations TEXT,
|
||||
roles TEXT,
|
||||
attachment TEXT,
|
||||
published_by TEXT NOT NULL,
|
||||
published_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notice_receipts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
notice_id INTEGER NOT NULL REFERENCES notices(id),
|
||||
receiver_name TEXT NOT NULL,
|
||||
receiver_role TEXT NOT NULL,
|
||||
read INTEGER NOT NULL DEFAULT 0,
|
||||
confirmed INTEGER NOT NULL DEFAULT 0,
|
||||
read_at TEXT,
|
||||
confirmed_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO notices
|
||||
(code, title, content, priority, scope, published_by, published_at)
|
||||
VALUES
|
||||
('NOTICE_20260801_001', '关于开展第三季度考核工作的通知', '各记者站请于8月15日前完成第三季度工作记录提交,总部将在8月底完成评分。', 'high', 'all', '林致远', '2026-08-01 09:00:00'),
|
||||
('NOTICE_20260801_002', '记者站年度考核评分标准调整说明', '自本通知发布之日起,新提交的稿件将按更新后的评分标准执行。详见附件。', 'normal', 'station', '林致远', '2026-08-01 10:30:00'),
|
||||
('NOTICE_20260720_001', '暑期安全生产宣传报道提示', '请各站注意收集暑期出行、安全生产等相关素材,及时投稿。', 'normal', 'all', '林致远', '2026-07-20 14:00:00');
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Migration: 006_optimize_indexes
|
||||
-- Description: 索引优化(audit_logs 表)
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_record_id ON audit_logs(record_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_actor_role ON audit_logs(actor_role);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_created_at ON audit_logs(created_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_station ON work_records(station);
|
||||
CREATE INDEX IF NOT EXISTS idx_work_reporter ON work_records(reporter);
|
||||
CREATE INDEX IF NOT EXISTS idx_work_status ON work_records(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_work_updated_at ON work_records(updated_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_people_station ON people(station);
|
||||
CREATE INDEX IF NOT EXISTS idx_people_status ON people(status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_stations_region ON stations(region);
|
||||
CREATE INDEX IF NOT EXISTS idx_stations_status ON stations(status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notice_receipt_notice ON notice_receipts(notice_id);
|
||||
@@ -0,0 +1,42 @@
|
||||
-- Migration: 007_create_rules
|
||||
-- Description: 考核规则及指标项表
|
||||
-- Date: 2026-08-01
|
||||
-- Refs: pmdocs/changes/2026-08-01-001-V0.2考核规则配置.md
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
period_type TEXT NOT NULL,
|
||||
period_start TEXT,
|
||||
period_end TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
parent_id INTEGER REFERENCES rules(id),
|
||||
created_by TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rule_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rule_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,
|
||||
category TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
metric_key TEXT NOT NULL,
|
||||
weight REAL NOT NULL,
|
||||
min_score REAL NOT NULL DEFAULT 0,
|
||||
max_score REAL NOT NULL DEFAULT 100,
|
||||
formula_type TEXT NOT NULL,
|
||||
formula_params TEXT,
|
||||
display_order INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rules_status ON rules(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_rules_period ON rules(period_start, period_end);
|
||||
CREATE INDEX IF NOT EXISTS idx_rule_items_rule ON rule_items(rule_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rule_items_category ON rule_items(category);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Migration: 008_create_scores
|
||||
-- Description: 考核评分结果表
|
||||
-- Date: 2026-08-01
|
||||
-- Refs: pmdocs/changes/2026-08-01-001-V0.2考核规则配置.md
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scores (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
rule_id INTEGER NOT NULL REFERENCES rules(id),
|
||||
reporter TEXT NOT NULL,
|
||||
station TEXT NOT NULL,
|
||||
period TEXT NOT NULL,
|
||||
period_type TEXT NOT NULL,
|
||||
total_score REAL,
|
||||
quality_score REAL,
|
||||
quantity_score REAL,
|
||||
efficiency_score REAL,
|
||||
compliance_score REAL,
|
||||
item_details TEXT,
|
||||
computed_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_scores_reporter ON scores(reporter);
|
||||
CREATE INDEX IF NOT EXISTS idx_scores_station ON scores(station);
|
||||
CREATE INDEX IF NOT EXISTS idx_scores_period ON scores(period);
|
||||
CREATE INDEX IF NOT EXISTS idx_scores_rule ON scores(rule_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_scores_unique ON scores(reporter, station, period);
|
||||
@@ -0,0 +1,95 @@
|
||||
-- Migration: 009_seed_default_rules
|
||||
-- Description: 插入默认考核规则模板
|
||||
-- Date: 2026-08-01
|
||||
-- Refs: pmdocs/changes/2026-08-01-001-V0.2考核规则配置.md
|
||||
|
||||
-- 默认考核规则模板(草稿状态,待总部激活)
|
||||
INSERT OR IGNORE INTO rules
|
||||
(code, name, description, period_type, period_start, period_end, status, version, created_by)
|
||||
VALUES
|
||||
('RULE_2026_Q3', '2026年第三季度考核规则', '第三季度综合考核,包含数量、质量、时效、合规四个维度', 'quarterly', '2026-07-01', '2026-09-30', 'draft', 1, '林致远');
|
||||
|
||||
-- 默认指标项(6个,覆盖四维度,权重之和为1.0)
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'quantity',
|
||||
'原创报道数量',
|
||||
'count_total',
|
||||
0.20,
|
||||
0, 100,
|
||||
'count',
|
||||
'{"threshold": 10, "base_score": 60, "bonus_per_item": 4, "max_bonus": 20}',
|
||||
1
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'quantity',
|
||||
'视频供稿数量',
|
||||
'count_video',
|
||||
0.10,
|
||||
0, 100,
|
||||
'count',
|
||||
'{"threshold": 5, "base_score": 60, "bonus_per_item": 5, "max_bonus": 20}',
|
||||
2
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'quality',
|
||||
'平均审核得分',
|
||||
'avg_score',
|
||||
0.25,
|
||||
0, 100,
|
||||
'avg_score',
|
||||
'{}',
|
||||
3
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'quality',
|
||||
'原创稿件占比',
|
||||
'original_ratio',
|
||||
0.15,
|
||||
0, 100,
|
||||
'rate',
|
||||
'{"target": 0.6, "base_score": 60, "bonus_per_pct": 40}',
|
||||
4
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'efficiency',
|
||||
'按时提交率',
|
||||
'on_time_rate',
|
||||
0.15,
|
||||
0, 100,
|
||||
'rate',
|
||||
'{"target": 0.9, "base_score": 60, "bonus_per_pct": 40}',
|
||||
5
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
|
||||
INSERT OR IGNORE INTO rule_items
|
||||
(rule_id, category, name, metric_key, weight, min_score, max_score, formula_type, formula_params, display_order)
|
||||
SELECT
|
||||
r.id,
|
||||
'compliance',
|
||||
'审核通过率',
|
||||
'pass_rate',
|
||||
0.15,
|
||||
0, 100,
|
||||
'rate',
|
||||
'{"target": 0.85, "base_score": 60, "bonus_per_pct": 40}',
|
||||
6
|
||||
FROM rules r WHERE r.code = 'RULE_2026_Q3';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Migration: 010_add_soft_delete
|
||||
-- Description: 为人员、站点、工作记录添加逻辑删除字段
|
||||
-- Date: 2026-08-01
|
||||
|
||||
ALTER TABLE people ADD COLUMN deleted_at TEXT;
|
||||
ALTER TABLE stations ADD COLUMN deleted_at TEXT;
|
||||
ALTER TABLE work_records ADD COLUMN deleted_at TEXT;
|
||||
ALTER TABLE work_records ADD COLUMN attachments TEXT;
|
||||
ALTER TABLE work_records ADD COLUMN created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'));
|
||||
@@ -0,0 +1,32 @@
|
||||
-- Migration: 011_add_auth_and_audit
|
||||
-- Description: 登录会话表 + 系统操作日志表(全覆盖审计)
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS login_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
user_code TEXT NOT NULL,
|
||||
user_name TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
station TEXT,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
revoked_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
actor_role TEXT NOT NULL,
|
||||
actor_name TEXT NOT NULL,
|
||||
module TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target_type TEXT,
|
||||
target_id TEXT,
|
||||
detail TEXT,
|
||||
ip_address TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_login_sessions_token ON login_sessions(token);
|
||||
CREATE INDEX IF NOT EXISTS idx_system_logs_actor ON system_logs(actor_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_system_logs_module ON system_logs(module);
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Migration: 012_add_record_extras
|
||||
-- Description: 工作记录版本历史表 + 人员调站历史表
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS record_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
record_id TEXT NOT NULL REFERENCES work_records(id),
|
||||
version_no INTEGER NOT NULL,
|
||||
title TEXT,
|
||||
type TEXT,
|
||||
platform TEXT,
|
||||
description TEXT,
|
||||
attachments TEXT,
|
||||
edited_by TEXT NOT NULL,
|
||||
edited_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_record_versions_record ON record_versions(record_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS person_transfers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
person_id INTEGER NOT NULL REFERENCES people(id),
|
||||
from_station TEXT NOT NULL,
|
||||
to_station TEXT NOT NULL,
|
||||
reason TEXT,
|
||||
operated_by TEXT NOT NULL,
|
||||
transferred_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_person_transfers_person ON person_transfers(person_id);
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Migration: 013_add_appeals
|
||||
-- Description: 申诉复议表
|
||||
-- Date: 2026-08-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS appeals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
record_id TEXT NOT NULL REFERENCES work_records(id),
|
||||
appellant TEXT NOT NULL,
|
||||
station TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
handler TEXT,
|
||||
handler_role TEXT,
|
||||
response TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
handled_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_record ON appeals(record_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_status ON appeals(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_appeals_appellant ON appeals(appellant);
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Migration: 014_add_notice_extras
|
||||
-- Description: 通知公告增加定时发布和撤回字段
|
||||
-- Date: 2026-08-01
|
||||
|
||||
ALTER TABLE notices ADD COLUMN scheduled_at TEXT;
|
||||
ALTER TABLE notices ADD COLUMN withdrawn_at TEXT;
|
||||
ALTER TABLE notices ADD COLUMN status TEXT NOT NULL DEFAULT 'published';
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Migration: 015_seed_system_logs
|
||||
-- Description: 插入系统操作日志示例数据
|
||||
-- Date: 2026-08-01
|
||||
|
||||
INSERT INTO system_logs (actor_role, actor_name, module, action, target_type, target_id, detail, ip_address, created_at) VALUES
|
||||
('headquarters', '林致远', 'auth', 'login', NULL, NULL, '总部管理员登录系统', '127.0.0.1', datetime('now', 'localtime', '-3 hours')),
|
||||
('reporter', '林晓', 'record', 'create', 'record', 'R2026_001', '创建工作记录《北京暴雨直击》', '127.0.0.1', datetime('now', 'localtime', '-170 minutes')),
|
||||
('reporter', '林晓', 'record', 'save_draft', 'record', 'R2026_002', '保存草稿《上海地铁新线开通》', '127.0.0.1', datetime('now', 'localtime', '-165 minutes')),
|
||||
('station', '苏明远', 'record', 'pass', 'record', 'R2026_001', '审核通过《北京暴雨直击》', '127.0.0.1', datetime('now', 'localtime', '-150 minutes')),
|
||||
('station', '苏明远', 'record', 'return', 'record', 'R2026_003', '审核退回《广州城中村改造》', '127.0.0.1', datetime('now', 'localtime', '-140 minutes')),
|
||||
('headquarters', '林致远', 'people', 'create', 'person', 'PERSON_011', '新增记者赵雨晴', '127.0.0.1', datetime('now', 'localtime', '-120 minutes')),
|
||||
('headquarters', '林致远', 'people', 'update', 'person', 'PERSON_003', '更新记者联系方式', '127.0.0.1', datetime('now', 'localtime', '-110 minutes')),
|
||||
('headquarters', '林致远', 'people', 'transfer', 'person', 'PERSON_005', '从北京记者站调入上海记者站', '127.0.0.1', datetime('now', 'localtime', '-100 minutes')),
|
||||
('station', '苏明远', 'notices', 'create', 'notice', 'N006', '发布通知《关于第三季度考核安排》', '127.0.0.1', datetime('now', 'localtime', '-90 minutes')),
|
||||
('reporter', '林晓', 'appeals', 'create', 'appeal', 'APPEAL_003', '对评分结果提出申诉', '127.0.0.1', datetime('now', 'localtime', '-80 minutes')),
|
||||
('headquarters', '林致远', 'appeals', 'uphold', 'appeal', 'APPEAL_001', '驳回申诉 APPEAL_001', '127.0.0.1', datetime('now', 'localtime', '-70 minutes')),
|
||||
('headquarters', '林致远', 'appeals', 'overturn', 'appeal', 'APPEAL_002', '通过申诉 APPEAL_002,调整评分', '127.0.0.1', datetime('now', 'localtime', '-65 minutes')),
|
||||
('headquarters', '林致远', 'export', 'records', NULL, NULL, '导出工作记录 CSV,共 156 条', '127.0.0.1', datetime('now', 'localtime', '-50 minutes')),
|
||||
('headquarters', '林致远', 'export', 'scores', NULL, NULL, '导出评分结果 CSV,共 42 条', '127.0.0.1', datetime('now', 'localtime', '-45 minutes')),
|
||||
('headquarters', '林致远', 'export', 'people', NULL, NULL, '导出人员名单 CSV,共 28 条', '127.0.0.1', datetime('now', 'localtime', '-40 minutes')),
|
||||
('reporter', '林晓', 'record', 'upload_attachment', 'record', 'R2026_004', '上传附件《现场照片.zip》', '127.0.0.1', datetime('now', 'localtime', '-35 minutes')),
|
||||
('station', '苏明远', 'record', 'withdraw', 'record', 'R2026_002', '撤回工作记录《上海地铁新线开通》', '127.0.0.1', datetime('now', 'localtime', '-30 minutes')),
|
||||
('headquarters', '林致远', 'auth', 'logout', NULL, NULL, '总部管理员退出系统', '127.0.0.1', datetime('now', 'localtime', '-20 minutes')),
|
||||
('reporter', '林晓', 'auth', 'login', NULL, NULL, '记者林晓登录系统', '127.0.0.1', datetime('now', 'localtime', '-15 minutes')),
|
||||
('station', '苏明远', 'auth', 'login', NULL, NULL, '分站负责人苏明远登录系统', '127.0.0.1', datetime('now', 'localtime', '-10 minutes')),
|
||||
('headquarters', '林致远', 'people', 'delete', 'person', 'PERSON_012', '删除已离职记者记录', '127.0.0.1', datetime('now', 'localtime', '-5 minutes')),
|
||||
('headquarters', '林致远', 'stations', 'update', 'station', 'ST_003', '更新广州记者站信息', '127.0.0.1', datetime('now', 'localtime', '-3 minutes'));
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 数据库迁移运行器
|
||||
* 按顺序执行 migrations/ 目录下的所有 .sql 文件
|
||||
* 记录已执行的迁移到 schema_migrations 表(幂等性保障)
|
||||
*
|
||||
* 用法:
|
||||
* node migrations/_runner.js # 执行所有待执行迁移
|
||||
* node migrations/_runner.js --status # 查看迁移状态
|
||||
* node migrations/_runner.js --reset # 重置数据库(慎用)
|
||||
*/
|
||||
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { readFileSync, readdirSync, mkdirSync, unlinkSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = join(__dirname, '..')
|
||||
const dataDir = join(root, 'data')
|
||||
|
||||
mkdirSync(dataDir, { recursive: true })
|
||||
const dbPath = join(dataDir, 'reporter-station.db')
|
||||
|
||||
/** 初始化数据库连接(确保 schema_migrations 表存在) */
|
||||
function initDb() {
|
||||
const db = new DatabaseSync(dbPath)
|
||||
db.exec(`
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
`)
|
||||
return db
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args.includes('--status')) {
|
||||
const db = initDb()
|
||||
const applied = db.prepare('SELECT name, applied_at FROM schema_migrations ORDER BY id').all()
|
||||
const allFiles = readdirSync(__dirname)
|
||||
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
|
||||
.sort()
|
||||
|
||||
console.log('\n迁移状态:')
|
||||
for (const f of allFiles) {
|
||||
const name = f.replace('.sql', '')
|
||||
const row = applied.find(a => a.name === name)
|
||||
if (row) {
|
||||
console.log(` [x] ${name} (applied at ${row.applied_at})`)
|
||||
} else {
|
||||
console.log(` [ ] ${name}`)
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.includes('--seed')) {
|
||||
const db = initDb()
|
||||
const seedFile = '002_seed_data.sql'
|
||||
const sql = readFileSync(join(__dirname, seedFile), 'utf-8')
|
||||
try {
|
||||
db.exec(sql)
|
||||
console.log(`已重新执行 seed 数据(${seedFile})`)
|
||||
} catch (err) {
|
||||
console.error(`seed 失败: ${err.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
db.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.includes('--reset')) {
|
||||
for (const ext of ['', '-wal', '-shm']) {
|
||||
const f = dbPath + ext
|
||||
if (existsSync(f)) { unlinkSync(f); console.log(`已删除: ${f}`) }
|
||||
}
|
||||
console.log('数据库已重置,重新启动服务将自动执行迁移。')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// 默认:执行待执行的迁移
|
||||
const db = initDb()
|
||||
const applied = new Set(db.prepare('SELECT name FROM schema_migrations').all().map(r => r.name))
|
||||
const allFiles = readdirSync(__dirname)
|
||||
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
|
||||
.sort()
|
||||
|
||||
let appliedCount = 0
|
||||
for (const file of allFiles) {
|
||||
const name = file.replace('.sql', '')
|
||||
if (applied.has(name)) {
|
||||
console.log(` 跳过 (已执行): ${name}`)
|
||||
continue
|
||||
}
|
||||
const sql = readFileSync(join(__dirname, file), 'utf-8')
|
||||
try {
|
||||
db.exec('BEGIN')
|
||||
db.exec(sql)
|
||||
db.prepare('INSERT INTO schema_migrations (name) VALUES (?)').run(name)
|
||||
db.exec('COMMIT')
|
||||
console.log(` [x] ${name}`)
|
||||
appliedCount++
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK')
|
||||
console.error(` [!] ${name} 执行失败: ${err.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
||||
if (appliedCount === 0) {
|
||||
console.log('\n没有待执行的迁移,数据库已是最新。')
|
||||
} else {
|
||||
console.log(`\n成功执行 ${appliedCount} 个迁移。`)
|
||||
}
|
||||
Reference in New Issue
Block a user