初始提交:全国记者站管理系统

This commit is contained in:
selfrelease
2026-08-01 23:09:49 +08:00
commit 45fbba0308
96 changed files with 21514 additions and 0 deletions
+409
View File
@@ -0,0 +1,409 @@
# 运行手册
> 本文档是项目运行的唯一事实来源。后续操作优先按本文档执行,若实际命令与文档不符,先验证真实行为,再更新本文档。
## 技术栈
| 层级 | 技术 | 版本 |
|---|---|---|
| 前端框架 | React + TypeScript | ^19 |
| 构建工具 | Vite | ^6 |
| 后端框架 | Express.js | ^4 |
| 数据库 | SQLiteWAL 模式) | 内置 node:sqlite |
| 包管理器 | npm | >= 18 |
| 图表库 | Recharts | latest |
| 图标库 | Lucide React | latest |
| 进程管理 | concurrently | latest |
| 测试框架 | Jest + Supertest | latest |
## 本地环境
### 环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
| `API_PORT` | `8787` | 后端 API 监听端口 |
### 端口清单
| 服务 | 地址 | 说明 |
|---|---|---|
| 前端开发服务器 | http://localhost:5177 | Vite 热重载 |
| 后端 API | http://localhost:8787 | Express REST API |
| 前端代理 | `/api``http://localhost:8787` | Vite proxy 配置 |
### 依赖服务
- 无外部依赖服务(SQLite 为内置文件数据库)
## 安装
```bash
npm install
```
> 若 `node_modules` 已存在,跳过此步骤。
## 开发命令
### 全栈启动(推荐)
```bash
npm run dev
```
同时启动:
- API 服务(`node --watch server/index.js`)监听 `127.0.0.1:8787`
- 前端 Vite 开发服务器监听 `localhost:5173`API 请求代理至 `8787`
### 单独启动
```bash
# 仅前端(需 API 已在运行)
npm run web
# 仅后端(需前端已在运行)
npm run server
```
### 后端启动(独立,不热重载)
```bash
npm run server:start
```
## 构建
```bash
npm run build
```
- 前端编译输出至 `dist/`
- TypeScript 类型检查:`tsc -b`
- 两者均通过才算构建成功
## 测试
```bash
npm test
```
运行 `server/tests/integration.test.cjs`,覆盖所有 API 端点的集成测试。测试使用独立临时数据库,测试完成后自动清理。
## 数据库
### 数据文件
- 开发数据库:`data/reporter-station.db`SQLite
- WAL 文件:`data/reporter-station.db-wal`Write-Ahead Log
- SHM 文件:`data/reporter-station.db-shm`
### 迁移管理
使用 `migrations/_runner.js` 管理所有 schema 变更:
```bash
# 查看迁移状态
node migrations/_runner.js --status
# 应用所有待执行迁移
node migrations/_runner.js migrate
# 执行指定迁移(幂等,CREATE TABLE IF NOT EXISTS
node migrations/_runner.js migrate --target 005
# 回滚最后一条迁移
node migrations/_runner.js rollback
# 重置数据库(删除并重建所有表,重新 seed)
node migrations/_runner.js reset
# 查看 seed 数据
node migrations/_runner.js --seed
```
迁移按文件名数字前缀顺序执行(001~009)。新增迁移时,使用下一个可用数字前缀。
### 数据库结构
**work_records**V0.1
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | TEXT PK | 主键,格式 `WK-YYYYMMDD-XXXX` |
| `title` | TEXT | 工作标题 |
| `type` | TEXT | 工作类型(文字稿件/视频供稿/...) |
| `reporter` | TEXT | 记者姓名 |
| `station` | TEXT | 所属记者站 |
| `occurred_date` | TEXT | 发生/刊发日期 |
| `platform` | TEXT | 媒体/平台 |
| `status` | TEXT | 状态(draft/station_review/headquarters_review/returned/archived |
| `score` | INTEGER | 考核得分(可为 null |
| `description` | TEXT | 工作说明 |
| `review_note` | TEXT | 审核意见 |
| `updated_at` | TEXT | 更新时间 |
**audit_logs**V0.1
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `record_id` | TEXT FK | 关联 work_records.id |
| `actor_role` | TEXT | 操作者角色 |
| `actor_name` | TEXT | 操作者姓名 |
| `action` | TEXT | 操作类型(submit/pass/return |
| `from_status` | TEXT | 原状态 |
| `to_status` | TEXT | 新状态 |
| `score` | INTEGER | 打分 |
| `note` | TEXT | 意见 |
| `created_at` | TEXT | 操作时间 |
**people**V0.1.1
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `code` | TEXT UNIQUE | 人员编码 |
| `name` | TEXT | 姓名 |
| `role` | TEXT | 角色(headquarters/station/reporter |
| `title` | TEXT | 职务 |
| `phone` | TEXT | 联系电话 |
| `station` | TEXT | 所属记者站 |
| `status` | TEXT | 状态(active/inactive |
| `joined_at` | TEXT | 入职日期 |
**stations**V0.1.1
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `code` | TEXT UNIQUE | 记者站编码 |
| `name` | TEXT | 记者站名称 |
| `region` | TEXT | 所属区域 |
| `established_at` | TEXT | 成立日期 |
**notices**V0.1.1
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `title` | TEXT | 通知标题 |
| `content` | TEXT | 通知内容 |
| `type` | TEXT | 类型(notice/circular/guide |
| `priority` | TEXT | 优先级(normal/important/urgent |
| `created_by` | TEXT | 创建人 |
| `created_at` | TEXT | 创建时间 |
| `expire_at` | TEXT | 过期时间 |
**rules**V0.2
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `code` | TEXT UNIQUE | 规则编码 |
| `name` | TEXT | 规则名称 |
| `description` | TEXT | 规则说明 |
| `period_type` | TEXT | 周期类型(quarterly/custom |
| `period_start` | TEXT | 周期开始日期 |
| `period_end` | TEXT | 周期结束日期 |
| `status` | TEXT | 状态(draft/active/archived |
| `version` | INTEGER | 版本号 |
| `parent_id` | INTEGER FK | 父版本 ID |
| `created_by` | TEXT | 创建人 |
| `created_at` | TEXT | 创建时间 |
| `updated_at` | TEXT | 更新时间 |
**rule_items**V0.2
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `rule_id` | INTEGER FK | 关联 rules.id |
| `category` | TEXT | 指标类别(quantity/quality/efficiency/compliance |
| `name` | TEXT | 指标名称 |
| `metric_key` | TEXT | 指标键名 |
| `weight` | REAL | 权重 |
| `min_score` | REAL | 最低分 |
| `max_score` | REAL | 最高分 |
| `formula_type` | TEXT | 计算公式类型 |
| `formula_params` | TEXT | 公式参数(JSON |
| `display_order` | INTEGER | 排序 |
| `enabled` | INTEGER | 是否启用 |
**scores**V0.2
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INTEGER PK | 自增主键 |
| `rule_id` | INTEGER FK | 关联 rules.id |
| `station` | TEXT | 站点名称 |
| `period` | TEXT | 考核周期 |
| `period_type` | TEXT | 周期类型 |
| `total_score` | REAL | 综合得分 |
| `quality_score` | REAL | 质量分 |
| `quantity_score` | REAL | 数量分 |
| `efficiency_score` | REAL | 时效分 |
| `compliance_score` | REAL | 合规分 |
| `items` | TEXT | 明细(JSON 数组) |
| `computed_at` | TEXT | 计算时间 |
## API 接口文档
### 认证
所有 API 请求需携带 HTTP header
```
x-user-role: headquarters | station | reporter
```
### 核心接口
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/health` | 公开 | 健康检查 |
| GET | `/api/records` | 全部 | 查询工作记录(按角色隔离) |
| POST | `/api/records` | reporter/station | 新建记录 |
| PATCH | `/api/records/:id/review` | station/hq | 审核(通过/退回) |
| GET | `/api/records/:id/audit` | 全部 | 流转记录(按角色隔离) |
### 人员与站点(V0.1.1
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/people` | 全部 | 查询人员列表 |
| POST | `/api/people` | station | 新建人员 |
| PATCH | `/api/people/:id` | station/hq | 编辑人员 |
| GET | `/api/stations` | 全部 | 查询记者站列表 |
| POST | `/api/stations` | hq | 新建记者站 |
### 通知公告(V0.1.1
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/notices` | 全部 | 查询通知列表 |
| POST | `/api/notices` | hq | 发布通知 |
| POST | `/api/notices/:id/read` | 全部 | 标记已读 |
| GET | `/api/notices/unread-count` | 全部 | 未读数量 |
### 统计(V0.1.1
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/stats/overview` | 全部 | 总览统计(站点角色仅限本站) |
| GET | `/api/stats/records` | hq | 记录统计(支持按周期/站点/类型筛选) |
| GET | `/api/stats/scores` | hq | 评分统计(支持按周期/站点筛选) |
### 考核规则(V0.2
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/rules` | hq | 查询规则列表(支持 status 筛选) |
| GET | `/api/rules/:id` | hq | 查询规则详情(含指标项) |
| POST | `/api/rules` | hq | 创建规则(含指标项) |
| PATCH | `/api/rules/:id` | hq | 编辑规则(草稿状态) |
| POST | `/api/rules/:id/activate` | hq | 激活规则 |
### 评分(V0.2
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | `/api/scores` | hq | 查询评分列表(支持 period/station 筛选) |
| GET | `/api/scores/:id` | hq | 查询评分详情(含明细) |
| POST | `/api/scores/compute` | hq | 触发评分计算 |
### 请求示例
```bash
# 以总部管理员身份查询全部记录
curl -H "x-user-role: headquarters" http://localhost:8787/api/records
# 以记者身份提交新记录
curl -X POST http://localhost:8787/api/records \
-H "Content-Type: application/json" \
-H "x-user-role: reporter" \
-d '{"title":"测试稿件","type":"文字稿件","date":"2026-08-01","platform":"测试平台"}'
# 以分站负责人身份审核通过
curl -X PATCH http://localhost:8787/api/records/WK-202608-XXXX/review \
-H "Content-Type: application/json" \
-H "x-user-role: station" \
-d '{"decision":"pass","score":8,"note":"审核通过"}'
# 总部创建考核规则
curl -X POST http://localhost:8787/api/rules \
-H "Content-Type: application/json" \
-H "x-user-role: headquarters" \
-d '{"name":"2026年度Q3考核","period_type":"quarterly","items":[{"category":"quantity","name":"发稿数量","metric_key":"count_total","weight":0.4,"formula_type":"count","formula_params":{}},{"category":"quality","name":"审核得分","metric_key":"avg_score","weight":0.6,"formula_type":"avg_score","formula_params":{}}]}'
# 总部触发评分计算
curl -X POST http://localhost:8787/api/scores/compute \
-H "Content-Type: application/json" \
-H "x-user-role: headquarters" \
-d '{"rule_id":1,"period":"2026-Q3","period_type":"quarterly"}'
```
## 常见问题
### Q1:端口 8787 被占用
```bash
# 查找占用进程
lsof -i :8787
# 杀死进程(替换 PID
kill -9 <PID>
# 或换端口
API_PORT=8788 npm run dev
```
### Q2:端口 5173 被占用
```bash
lsof -i :5173
kill -9 <PID>
```
### Q3SQLite WAL 文件导致"database is locked"
- 通常因多个进程同时写入
- 解决:确保只有一个服务实例运行
- WAL 文件异常时,删除 `.db-wal``.db-shm` 后重启服务
### Q4:前端热重载不生效
- 确认 Vite dev server 正在运行(看终端输出 "ready in xxx ms"
- 清除浏览器缓存或使用无痕模式
### Q5:迁移执行失败
```bash
# 查看迁移状态
node migrations/_runner.js --status
# 检查是否有 pending 迁移
# 查看具体错误日志
```
### Q6TypeScript 类型错误
```bash
npx tsc -b --force # 强制重建类型缓存
```
### Q7`npm run build` 失败
1. 先确认 TypeScript 无误:`npx tsc -b`
2. 查看 Vite 构建输出中的具体错误
3. 检查是否有新增依赖未安装
### Q8seed 数据消失
- 原因:`work_records` 表已有数据时,seed 逻辑不执行
- 解决:手动删除数据库文件后重启服务,或使用 `node migrations/_runner.js reset`
## 后续开发参考
- 需求文档:`pmdocs/0-req-RSMS.md`
- 产品文档:`pmdocs/1-prd-RSMS.md`
- 任务文档:`pmdocs/2-task-RSMS.md`
- 架构决策:`pmdocs/adr/`
- 需求变更:`pmdocs/changes/`
- 原始需求参考:`req.md`