409 lines
12 KiB
Markdown
409 lines
12 KiB
Markdown
# 运行手册
|
||
|
||
> 本文档是项目运行的唯一事实来源。后续操作优先按本文档执行,若实际命令与文档不符,先验证真实行为,再更新本文档。
|
||
|
||
## 技术栈
|
||
|
||
| 层级 | 技术 | 版本 |
|
||
|---|---|---|
|
||
| 前端框架 | React + TypeScript | ^19 |
|
||
| 构建工具 | Vite | ^6 |
|
||
| 后端框架 | Express.js | ^4 |
|
||
| 数据库 | SQLite(WAL 模式) | 内置 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>
|
||
```
|
||
|
||
### Q3:SQLite 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 迁移
|
||
# 查看具体错误日志
|
||
```
|
||
|
||
### Q6:TypeScript 类型错误
|
||
|
||
```bash
|
||
npx tsc -b --force # 强制重建类型缓存
|
||
```
|
||
|
||
### Q7:`npm run build` 失败
|
||
|
||
1. 先确认 TypeScript 无误:`npx tsc -b`
|
||
2. 查看 Vite 构建输出中的具体错误
|
||
3. 检查是否有新增依赖未安装
|
||
|
||
### Q8:seed 数据消失
|
||
|
||
- 原因:`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` |