# 运行手册 > 本文档是项目运行的唯一事实来源。后续操作优先按本文档执行,若实际命令与文档不符,先验证真实行为,再更新本文档。 ## 技术栈 | 层级 | 技术 | 版本 | |---|---|---| | 前端框架 | 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 # 或换端口 API_PORT=8788 npm run dev ``` ### Q2:端口 5173 被占用 ```bash lsof -i :5173 kill -9 ``` ### 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`