# safety-timer **Repository Path**: rikh/safety-timer ## Basic Information - **Project Name**: safety-timer - **Description**: 安全员工作计时系统:H5 计时 + 排班 + 审批 + 工时导出(Vue3 + Express + SQLite) - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-19 - **Last Updated**: 2026-10-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: 工时系统 ## README # Safety Timer · 安全员工作计时系统 > 面向试验车队的「安全员计时 + 排班 + 审批 + 工时导出」一体化系统。 > > 安全员用手机扫码开始/暂停/结束计时,后台自动生成工单号、按班次校验、沉淀可审计的工时明细。 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![Node](https://img.shields.io/badge/Node.js-%E2%89%A520-339933) ![SQLite](https://img.shields.io/badge/SQLite-better--sqlite3-003B57) ![Vue](https://img.shields.io/badge/Vue-3-42b883) ![Vite](https://img.shields.io/badge/Vite-5-646cff) ![Element Plus](https://img.shields.io/badge/Element%20Plus-2-409eff) ![Vant](https://img.shields.io/badge/Vant-4-1989fa) --- ## 1. 它解决什么问题 试验车队的安全员工时,常见做法是微信群报点、Excel 手工登记: - 开始/结束时间靠人记,暂停、等待、换车容易漏; - 工单号手工编,跨天容易重号; - 排班在表里、计时在群里,谁该上哪辆车说不清; - 月底汇总靠人肉拼表,口径不统一; - 换组织、强制结束、任务延时没有审批留痕。 Safety Timer 把这些收进一个系统: - **H5 计时**:选车 → 填出车里程 → 开始 → 暂停/恢复 → 结束 → 填收车里程与备注; - **后台排班**:班次字典、单人/批量/复制/导入、排班看板; - **审批中心**:换组织/供应商、强制结束、任务延时,流程/节点/会签可配置; - **工时导出**:标准 12 列 + 详细工时时间轴,Excel / CSV; - **审计留痕**:工单号、状态变更、备注、登录日志、审批日志全部落库。 ## 2. 界面预览 > 以下为界面演示,数据均为示例,不含真实业务数据。 **安全员 H5 · 今日车辆与计时** ![H5 工作台](assets/screenshots/h5-work.png) **管理后台 · 详细工时** ![详细工时](assets/screenshots/admin-detail.png) --- ## 3. 30 秒了解架构 ```mermaid flowchart LR H5["安全员 H5
Vue3 + Vant"] --> API["Express API
Node.js"] ADMIN["管理后台
Vue3 + Element Plus"] --> API API --> TIMER["计时服务
状态机 / 互斥 / 工单号"] API --> SCHED["排班服务
班次 / 排班 / 越权留痕"] API --> APPR["审批中心
流程 / 节点 / 会签"] API --> EXP["导出服务
Excel / CSV"] TIMER --> DB[("SQLite
WAL")] SCHED --> DB APPR --> DB EXP --> DB ``` **两个关键词:** 1. **后端权威**:开始/暂停/结束的时间以服务端为准;前端只展示,不参与最终计时。 2. **单进程同源**:Express 同时提供 API 与前端静态资源,部署只需要一个 Node 进程 + 一个 SQLite 文件。 ## 4. 功能清单 ### 4.1 安全员 H5 - 账号登录,首次登录强制改密; - 只显示「今天排给我的车」,也可切换查看全部车辆(非排班车需二次确认并留痕); - 开始计时必须填**出车里程**,结束必须填**收车里程**; - 暂停必须填备注;恢复、结束、追加备注; - 实时显示净时长(自动扣除暂停); - 我的记录:按日期查看本人工单与时间轴; - 我的排班:按日期区间查看班次与车辆。 ### 4.2 管理后台 | 模块 | 能力 | |:--|:--| | 安全员 | 新增/编辑/停用、批量导入、开通 H5 账号、重置密码 | | 车辆 | 车架号、工位、组织、供应商、默认发起人;批量导入 | | 字典 | 组织、供应商、发起人维护 | | 排班 | 班次字典、单人排班、批量排班、复制、导入、排班看板 | | 审批 | 流程模板、节点/审批人/会签、自定义申请字段、审批/驳回/撤销 | | 计时记录 | 按日期/安全员/车架号查询,强制结束(走审批) | | 详细工时 | 含暂停、里程、排班备注、时间轴的明细 | | 账号与角色 | admin / engineer / fleet_admin 三角色,模块级读写权限 | | 登录日志 | 成功/失败、来源 IP、账号锁定记录 | | 导出 | 标准工时 Excel/CSV、详细工时 Excel/CSV | ### 4.3 自动化 - **工单号**:`WO-YYYYMMDD-序号`,按天自增,事务内生成; - **互斥**:同一安全员同一时间只能有一条进行中的计时(数据库唯一索引兜底); - **自动结束**:超过班次结束时间 + 缓冲(默认 30 分钟)仍未结束的工单,可自动结束; - 默认 `observe`(只扫描、只打日志、零写入); - 显式设置 `AUTO_END_MODE=live` 才真正写库; - **登录限流**:账号连续失败锁定 + 同 IP 失败上限; - **改密失效旧 token**:`pwd_ver` 版本号机制,改密/重置后旧 token 立即失效。 ## 5. 技术栈 - **前端**:Vue 3 + Vite 5 + TypeScript + Vue Router + Pinia - H5:Vant 4 - 后台:Element Plus - **后端**:Node.js ≥ 20 + Express 4 + better-sqlite3(SQLite,WAL) - **导出**:SheetJS (`xlsx`) - **鉴权**:HMAC-SHA256 无状态 token + HttpOnly Cookie;口令 scrypt 加盐派生 - **部署**:单进程 Node + Nginx 反代(可选 systemd) ## 6. 快速开始 ### 6.1 环境要求 - Node.js **≥ 20**(推荐 22 LTS) - npm ≥ 9 - Linux / macOS / Windows 均可;生产建议 Linux ### 6.2 安装与启动 ```bash git clone https://gitee.com/rikh/safety-timer.git cd safety-timer # 安装前后端依赖 npm run install:all # 构建前端(输出到 web/dist) npm run build # 首次启动:初始化管理员(至少 8 位,初始化后可不再设置) export ADMIN_INIT_PASSWORD='ChangeMe_123456' npm start # 打开 # 后台:http://localhost:19300/admin/login # H5 :http://localhost:19300/login ``` > 首次启动时,如果数据库里没有任何后台账号,且没有设置 `ADMIN_INIT_PASSWORD` / `ADMIN_PASSWORD`,服务会拒绝启动并提示。 ### 6.3 开发模式 ```bash # 终端 1:后端(19300) npm run dev:server # 终端 2:前端(5173,自动代理 /api 到 19300) npm run dev:web ``` 开发模式访问 `http://localhost:5173`。 ### 6.4 创建第一个安全员账号 1. 用初始管理员登录 `/admin/login`; 2. 「安全员」→ 新增安全员(姓名、工号、组别); 3. 点「开通账号」,设置登录名与初始密码; 4. 安全员用手机打开 `/login`,首次登录会强制改密; 5. 「排班」给该安全员安排今天/某天的车辆与班次; 6. 安全员进入「今日车辆」即可开始计时。 ### 6.5 演示数据(可选) ```bash # 写入安全员/车辆/组织/供应商/发起人示例数据(幂等) npm run seed # 生成完整演示数据:账号、排班、最近 6 天 + 今天早间工单、1 条进行中工单 DB_FILE=/tmp/safety-timer-demo.db npm run demo # H5 账号:E1001~E1004 / demo1234 ``` ## 7. 配置 所有配置通过环境变量注入;生产建议使用 systemd `EnvironmentFile`,不要把口令写进代码或仓库。 | 变量 | 默认值 | 说明 | |:--|:--|:--| | `PORT` | `19300` | 监听端口 | | `HOST` | `0.0.0.0` | 监听地址 | | `DB_FILE` | `server/safety-timer.db` | SQLite 文件路径 | | `ADMIN_USER` | `admin` | 初始管理员用户名 | | `ADMIN_INIT_PASSWORD` | 无 | 首次启动初始化管理员;仅在账号表为空时生效 | | `ADMIN_PASSWORD` | 无 | 环境变量单口令兜底通道;**不推荐长期开启** | | `AUTH_SECRET` | 无 | 固定 HMAC 密钥(字符串);优先级最高 | | `AUTH_SECRET_FILE` | `~/.safety-timer/auth-secret` | 密钥文件(64 位 hex);不存在会自动生成 | | `TZ` | 系统时区 | 建议 `Asia/Shanghai` | | `AUTO_END_MODE` | `observe` | `observe` 只观察 / `live` 真写库 | | `AUTO_END_BUFFER_MIN` | `30` | 班次结束后缓冲分钟数 | | `AUTO_END_MAX_HOURS` | `0` | 硬顶小时数;`0` 表示不启用 | | `AUTO_END_INTERVAL_MS` | `120000` | 扫描间隔(毫秒) | | `AUTO_END_DISABLED` | `0` | `1` 关闭自动结束 | | `SCHEDULE_ENFORCE` | 未设置 | `1` 启用排班硬校验;未设置时按代码内默认策略 | 完整示例见 [`.env.example`](.env.example)。 ## 8. 目录结构 ``` safety-timer/ ├─ server/ # Express API + SQLite │ ├─ app.js # 路由装配 / 静态托管 │ ├─ index.js # 启动入口 │ ├─ db.js # 建表 / 增量迁移 │ ├─ middleware/ # 鉴权 / 限流 │ ├─ migrations/ # 排班 / 审批 / 登录日志 │ ├─ routes/ # REST 路由 │ ├─ services/ # 计时 / 排班 / 导出 / 自动结束 │ ├─ seed.js # 示例数据 │ └─ test/ # Vitest + Supertest ├─ web/ # Vue3 前端(H5 + 后台) │ ├─ src/views/h5/ # 安全员 H5 │ ├─ src/views/admin/ # 管理后台 │ └─ vite.config.ts ├─ deploy/ # systemd / Nginx / 备份模板 ├─ docs/ # 架构 / API / 权限 / 部署 / 运维 └─ assets/ # 截图与赞赏码 ``` ## 9. 核心业务规则 1. **一人一车**:同一安全员同一时间只能有一条 `running/paused` 的计时;数据库部分唯一索引兜底。 2. **工单号**:`WO-YYYYMMDD-001`,按天重置;使用 `BEGIN IMMEDIATE` 事务,避免并发重号。 3. **净时长**:`试验实际时长 = Σ work 段`,暂停段不计入;暂停秒数单独累计。 4. **排班优先**:H5 默认只列今日排班车辆;非排班车需二次确认并写入 `schedule_override` 留痕。 5. **班次校验**:未到班次开始时间不可开始;同日同人同车已结束不可再次开始。 6. **里程校验**:出车/收车里程必填;收车里程不得小于出车里程,并与上次收车里程做偏差校验。 7. **导出只含已结束工单**:`stopped` 才进入正式报表,进行中不导出。 8. **自动结束安全默认**:默认 `observe`,只记录「将结束」日志,不写库;确认无误后再切 `live`。 ## 10. 角色与权限 | 模块 | admin | engineer(工程师) | fleet_admin(车队管理员) | |:--|:--:|:--:|:--:| | 安全员 读 | ✅ | ✅ | ✅ | | 安全员 写 | ✅ | ❌ | ✅ | | 车辆 读 | ✅ | ✅ | ✅ | | 车辆 写 | ✅ | ✅ | ❌ | | 字典 读写 | ✅ | ✅ | ✅ | | 排班 读写 | ✅ | ✅ | ✅ | | 审批 读写 | ✅ | ✅ | ✅ | | 计时/详细工时 | ✅ | ✅ | ✅ | | 账号管理 | ✅ | ❌ | ❌ | | 登录日志 | ✅ | ❌ | ❌ | 安全员 H5 使用独立的 `operator` 身份,只能操作**本人**的计时和排班,不能访问后台。 详见 [docs/roles-and-permissions.md](docs/roles-and-permissions.md)。 ## 11. API - 统一前缀:`/api` - 成功:`{ "ok": true, "data": ... }` - 失败:`{ "ok": false, "code": "...", "message": "..." }` - 鉴权:后台 `st_auth` Cookie 或 `Authorization: Bearer `;H5 使用 `st_op` Cookie / Bearer 常用接口: | 方法 | 路径 | 说明 | |:--|:--|:--| | `POST` | `/api/admin/login` | 后台登录 | | `POST` | `/api/h5/login` | 安全员 H5 登录 | | `GET` | `/api/h5/my-vehicles` | 今日排班车辆 | | `POST` | `/api/sessions/start` | 开始计时 | | `POST` | `/api/sessions/:id/pause` | 暂停(需备注) | | `POST` | `/api/sessions/:id/resume` | 恢复 | | `POST` | `/api/sessions/:id/stop` | 结束(需收车里程) | | `GET` | `/api/admin/export` | 标准工时导出 | | `GET` | `/api/admin/export/detail` | 详细工时导出 | 完整列表与参数见 [docs/api.md](docs/api.md)。 ## 12. 部署 生产建议: - Node 进程由 systemd 托管; - Nginx 终止 HTTPS 并反代到 `127.0.0.1:19300`; - SQLite 放在独立数据目录(如 `/var/lib/safety-timer`),定期热备份; - 不要直接把 19300 暴露到公网,至少加 HTTPS 与访问控制。 模板与步骤: - [deploy/safety-timer.service](deploy/safety-timer.service) - [deploy/nginx.conf.example](deploy/nginx.conf.example) - [deploy/backup.sh.example](deploy/backup.sh.example) - [docs/deployment.md](docs/deployment.md) - [docs/operations.md](docs/operations.md) ## 13. 安全说明 - 口令使用 **scrypt + 随机盐**,只存派生值,不存明文; - Token 使用 **HMAC-SHA256** 无状态签名,默认 30 天;`pwd_ver` 改密即失效; - Cookie 为 `HttpOnly`,生产必须启用 HTTPS; - 登录失败有限流/锁定; - 首次启动不再内置弱口令,必须显式设置初始化口令; - 生产请设置固定的 `AUTH_SECRET`,并限制数据库文件权限(`0600`)。 发现安全问题请阅读 [SECURITY.md](SECURITY.md),不要在公开 Issue 中贴敏感信息。 ## 14. 许可证与第三方组件 - 本项目代码:**MIT License**(见 [LICENSE](LICENSE)) - [Vue.js](https://github.com/vuejs/core):MIT - [Vite](https://github.com/vitejs/vite):MIT - [Element Plus](https://github.com/element-plus/element-plus):MIT - [Vant](https://github.com/youzan/vant):MIT - [Express](https://github.com/expressjs/express):MIT - [better-sqlite3](https://github.com/WiseLibs/better-sqlite3):MIT - [SheetJS](https://github.com/SheetJS/sheetjs):Apache-2.0 > 仓库内**不包含**任何真实业务数据、账号、密钥或内网地址。示例数据均为虚构。 ## 15. 贡献 欢迎 Issue / PR。提交前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md),并确保: ```bash npm test npm run build ``` --- ## ☕ 赞赏 如果这个项目帮到了你,欢迎请作者喝杯咖啡。**完全自愿**,不影响使用与更新。

微信支付收款码 支付宝收款码

左:微信支付(推荐) · 右:支付宝