Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+661
@@ -0,0 +1,661 @@
|
||||
# 风影智慧办公 · 全系统转交文档
|
||||
|
||||
更新日期:2026-09-01
|
||||
当前手机包:**3.1.8+318**(`com.fengying.oa.mobile`)
|
||||
本次生产修复与当前真实状态:**先读 `/opt/HANDOFF.md`,再读本文件第 0、2、3、14 节和 `.cursor/rules/`。**
|
||||
|
||||
本文件覆盖 **Web + API + 手机 + IM + 库表 + 密钥 + 发版**。不是只写手机端。
|
||||
**含生产密钥,不要推到公开 git。**
|
||||
|
||||
---
|
||||
|
||||
## 0. 你接手后立刻要知道的事
|
||||
|
||||
1. 这是一家约四十人的工程/软件公司内部 OA,从旧 Java OA(生产 MySQL 库 `oa`)迁到本仓库。
|
||||
2. 用户同时用 **网页** `https://oa.fysxkj.com` 和 **安卓包**。IM 是自研 TCP,不是环信。
|
||||
3. 办公模块有固定产品规则(第 3 节)。乱改列表筛选或审批不写下游,会被用户打回。
|
||||
4. 2026-09-01 的 API 数据修复、旧终止投标恢复兼容和项目类型修复已经发到生产;精确结果与备份位置见 `/opt/HANDOFF.md`。后续不要依据本文件旧版本描述判断是否已部署。
|
||||
5. 回复用户用**简体中文**。不要主动 `git commit` / `push`,除非用户说。
|
||||
6. 改菜单后必须跑 `apps/api/prisma/sync-menus.ts`,侧栏才更新。
|
||||
7. 手机登录/刷新必须带 `clientKind: 'mobile'`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品与架构
|
||||
|
||||
一套 API,三端消费:
|
||||
|
||||
| 端 | 路径 | 技术 | 入口 |
|
||||
|---|---|---|---|
|
||||
| API | `apps/api` | NestJS 11 + Prisma 6 + PostgreSQL | `/api/v1`,Swagger `/api/v1/docs` |
|
||||
| Web | `apps/web` | React 18 + Vite 5 + Ant Design 5 + React Query + Zustand | 路由 `apps/web/src/App.tsx` |
|
||||
| 手机 | `apps/native` | Flutter,包名 **必须** `com.fengying.oa.mobile` | 底栏:消息 / 工作台 / 通讯录 / 我 |
|
||||
| IM | `apps/im-server/server.js` | Node TCP 8901 + 本机 HTTP 8902 | MobileIMSDK 协议 |
|
||||
|
||||
生产:
|
||||
|
||||
- 站点:`https://oa.fysxkj.com`
|
||||
- REST:`https://oa.fysxkj.com/api/v1`
|
||||
- IM TCP:`47.96.23.244:8901`
|
||||
- APK:`https://oa.fysxkj.com/fengying-oa.apk`
|
||||
- 远端代码:`/data/fengying-oa/`
|
||||
- systemd:`fengying-api.service`、`fengying-im.service`(模板在 `infra/fengying-cloud/`)
|
||||
- 生产 API 端口 **3010**(nginx 反代);本机 nest 常在 **3000**
|
||||
|
||||
本机构建机 hostname `fykj`,公网不是 `47.96.23.244`。
|
||||
|
||||
租户字段 `tenantId` 目前固定 `'1'`,没有做多租户产品化。
|
||||
|
||||
---
|
||||
|
||||
## 2. 仓库地图
|
||||
|
||||
```
|
||||
/opt
|
||||
├── DELIVERY.md ← 本文件
|
||||
├── .cursor/rules/office-process.mdc
|
||||
├── apps/api Nest 后端
|
||||
│ ├── prisma/schema.prisma 库表
|
||||
│ ├── prisma/menus.ts 侧栏菜单源
|
||||
│ ├── prisma/seed.ts 演示数据
|
||||
│ ├── prisma/sync-menus.ts 菜单写入 Permission
|
||||
│ ├── src/*.controller.ts 业务入口
|
||||
│ ├── data/app-release.json 手机 OTA
|
||||
│ └── .env 本机密钥
|
||||
├── apps/web/src
|
||||
│ ├── App.tsx 全部路由
|
||||
│ ├── pages/ 业务页
|
||||
│ ├── store/auth.ts 登录态
|
||||
│ ├── api/client.ts axios + sessionStorage
|
||||
│ └── labels.ts 枚举中文
|
||||
├── apps/native Flutter
|
||||
├── apps/im-server/server.js
|
||||
└── infra/fengying-cloud nginx / systemd / compose 模板
|
||||
```
|
||||
|
||||
本机常用:
|
||||
|
||||
```bash
|
||||
cd /opt/apps/api && npx nest start # http://localhost:3000/api/v1
|
||||
cd /opt/apps/web && pnpm dev # http://localhost:5173
|
||||
cd /opt/apps/im-server && node server.js # TCP 8901
|
||||
cd /opt/apps/native && flutter build apk --release && bash scripts/publish-apk.sh
|
||||
```
|
||||
|
||||
改 Prisma:`cd /opt/apps/api && npx prisma migrate dev`。
|
||||
改菜单:改 `prisma/menus.ts` 后 `npx ts-node --transpile-only prisma/sync-menus.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 硬约束(违反就是返工)
|
||||
|
||||
### 3.1 个人办公怎么长
|
||||
|
||||
规则文件:`.cursor/rules/office-process.mdc`
|
||||
|
||||
- 列表筛选用 **`ApplyBucketBar`**(Segmented + `?bucket=`),有选中态。
|
||||
- 标准桶:全部 / 待审批 / 已通过 / 已驳回。领域附加桶可以加,不能替代标准桶。
|
||||
- 禁止页头三个无选中态的 `Link`(「全部」「待审批」「外带未还」这种)。
|
||||
- 「我的申请」=`/office/flow` 是**跟踪**;人事/用章/办公申请是**发起**;「待我审批」是**审批**。不要做成三套互不相通的列表。
|
||||
- 申请人页只看自己的单;台账在业务模块(用章登记、资产管理、采购申请)。
|
||||
|
||||
审批通过必须写下游:
|
||||
|
||||
| 申请 | 落到 |
|
||||
|---|---|
|
||||
| 资产购置 `ASSET_BUY` | 采购单 + 待入库台账 + 付款待办 |
|
||||
| 资产领用 / 用车 | 占用记录(挂台账 id),**交回不改保管人** |
|
||||
| 出差(人事) | 差旅报销草稿 |
|
||||
| 用章外带 | 归还待办 |
|
||||
|
||||
保管人 = 台账责任人。占用 = 谁正在用。占用时不要把保管人改成持有人。
|
||||
|
||||
实现:`apps/api/src/office/office-apply.ts`。
|
||||
|
||||
### 3.2 登录会话
|
||||
|
||||
`apps/api/src/auth/auth.service.ts` + Web `apps/web/src/api/client.ts`
|
||||
|
||||
- Web 和手机**可以同时在线**。
|
||||
- 同一账号不能开两个 Web(后登挤掉先登)。JWT 含 `sid`。
|
||||
- 手机 `clientKind: 'mobile'`:refresh **不轮换**,access 7 天,refresh 3650 天,除非退出一直在线。
|
||||
- Web token 用 **sessionStorage** + BroadcastChannel(同浏览器多标签共享)。关浏览器再开要重登。
|
||||
- 桌面端只能扫码,**不能账密**。`clientKind: 'desktop'` 账密会被拒绝。页 `/login/desktop`;手机「我 → 确认电脑登录」;Deep Link `fengyingoa://qr`。
|
||||
- 登出不带 refresh 时只撤 web/desktop,不踢手机。
|
||||
|
||||
### 3.3 安卓
|
||||
|
||||
- `applicationId`:`com.fengying.oa.mobile`。`namespace` 仍是 `com.fysxkj.fengying_oa`,不要改混。
|
||||
- 当前 release 用 **debug 签名**。华为控制台正式 SHA256 对不上 debug,HMS token 可能拿不到。
|
||||
- **不要**加 `com.huawei.agconnect` Gradle 插件(AGP 8.6 会炸)。HMS 依赖 + `agconnect-services.json` + Manifest `appid` 留着即可。
|
||||
|
||||
### 3.4 数据范围
|
||||
|
||||
`AuthUser.dataScope`:`ALL` / `DEPT` / `SELF`。列表过滤在 `apps/api/src/common/data-scope.ts`。改列表不要只改前端。
|
||||
|
||||
---
|
||||
|
||||
## 4. 角色、权限、菜单
|
||||
|
||||
菜单源:`apps/api/prisma/menus.ts`,写入 `Permission`(`permType=MENU`)。
|
||||
`admin` / `owner` 前端 `canSee` 直接放行全部。其他人比 `permissions` 前缀。
|
||||
|
||||
种子角色(`prisma/seed.ts`,生产还会有导入岗位):
|
||||
|
||||
| code | 名称 | dataScope | 大致权限 |
|
||||
|---|---|---|---|
|
||||
| admin | 系统管理员 | ALL | * |
|
||||
| owner | 企业负责人 | ALL | * |
|
||||
| biz_director | 商务总监 | DEPT | office/party/seal/bid/contract/finance/report |
|
||||
| biz_staff | 商务专员 | SELF | office/party/seal/bid/contract |
|
||||
| tech_director | 技术总监 | DEPT | office/bid/project/report |
|
||||
| rd_director / 3d_director / video_director / material_director | 各技术条线总监 | DEPT | 见 `role-groups.ts` |
|
||||
| finance | 财务 | ALL | office/finance/contract/report |
|
||||
| hr | 人事 | ALL | office/hr + 人事设置 |
|
||||
| employee / pm / 各 *\_staff | 员工/项目经理 | SELF | office + 工时等 |
|
||||
| seal / asset / cashier … | 导入岗位 | 视脚本 | `fix-oa-posts.ts` |
|
||||
|
||||
总监集合:`DIRECTOR_ROLES`。可发短信:`SMS_SEND_ROLES`。可派工作:`WORK_ASSIGN_ROLES`。
|
||||
`bid-workflow.ts` 仍保留旧的 `SPECIAL_COORDINATORS` 常量用于兼容,但当前特殊筛选/特殊制标的办理人加载逻辑是全体在职人员,禁止再改回写死两人。
|
||||
|
||||
首次登录非 admin:改密 + 完善档案(`/office/onboard`)。`mustChangePassword` / `profileCompletedAt`。
|
||||
|
||||
Web 侧栏:`AppLayout` 用 `/auth/me` 带回的 `menus`。
|
||||
发公告:`hr` 与各总监(`NOTICE_PUBLISH_ROLES` in `App.tsx`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. Web 功能全表(按侧栏)
|
||||
|
||||
路由全部在 `apps/web/src/App.tsx`。没有单独的移动端 Web。
|
||||
|
||||
### 5.1 工作台 `/workbench`
|
||||
|
||||
`OverviewPage`:待办/待审批数字、天气问候、快捷入口。API:`GET /office/overview`、`GET /office/weather`。
|
||||
|
||||
### 5.2 个人办公
|
||||
|
||||
| 菜单 | 路径 | 页面 | API 要点 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 待办事项 | `/office/todos` | OfficeTodosPage | `GET /office/todos` `PATCH /office/todos/:id` | 待办可标已办 |
|
||||
| 待我审批 | `/office/approvals` | OfficeApprovalsPage | `GET /office/approvals` `POST .../decide` | 统一审批入口 |
|
||||
| 我的申请 | `/office/flow` | MyFlowPage | `GET /office/flow` | 只跟踪自己的单 |
|
||||
| 我的日程 | `/office/calendar` | CalendarPage | `GET /office/calendar` | 含证照/印章逾期、开标等 |
|
||||
| 工作汇报 | `/office/reports` | WorkReportPage | `/office/reports*` | 可从工时草稿带入 |
|
||||
| 我的费用 | `/office/expenses` | MyExpensesPage + Expense/Loan 详情 | `/office/my-expenses` `/expenses` `/loans` | 员工发起报销/借款 |
|
||||
| 人事申请 | `/office/hr-apply` | HrApplyPage / HrLeaveDetailPage | `/leave-requests` | 请假加班出差外出离职晋升 |
|
||||
| 用章申请 | `/office/seal-apply` | SealRequestListPage mode=apply | `/seal-requests` | 发起;台账在证照用章 |
|
||||
| 办公申请 | `/office/apply` | OfficeApplyPage | `/office/applies` | 购置/领用/用车 |
|
||||
| 公司公告 | `/office/notices` | NoticePage / NoticeDetailPage | `/office/notices` | 员工阅读 |
|
||||
| 发送短信 | `/office/sms` | OfficeSmsPage | `/sms/*` | 总监/人事/admin |
|
||||
| 工作安排 | `/office/assign` | WorkAssignPage | `/office/work-assignments` | 总监派任务 |
|
||||
| 消息 | `/office/im` | ImPage | `/im/*` | Web IM + 通话弹层 |
|
||||
| 员工通讯 | `/office/directory` | DirectoryPage | `/staff` `/directors` | 点人可开聊 |
|
||||
|
||||
出差通过后应落差旅报销草稿(人事申请下游)。
|
||||
|
||||
### 5.3 往来单位
|
||||
|
||||
| 菜单 | 路径 | 页面 | API |
|
||||
|---|---|---|---|
|
||||
| 招标单位 | `/party/tenders` | TenderPartyPage | `/parties` |
|
||||
| 参与主体 | `/party/entities` | LegalEntityPage | `/legal-entities` |
|
||||
| 联系名录 | `/party/contacts` | ContactDirectoryPage | `/contacts` |
|
||||
|
||||
本机已洗过 5 家主体:随行科技(默认,历史投标多挂这里)、风影软件、风影工程、山东鲁兵、隆创。脚本 `prisma/wash-legal-entities.ts`。OA dump 几乎没有「软件/工程」历史投标,不会自动拆单。
|
||||
|
||||
### 5.4 证照用章
|
||||
|
||||
| 菜单 | 路径 | 页面 | API |
|
||||
|---|---|---|---|
|
||||
| 资质台账 | `/seal/qualifications` | QualificationListPage | `/qualifications` |
|
||||
| 证照借用 | `/seal/borrows` | BorrowListPage | `/credential-borrows` |
|
||||
| 用章登记 | `/seal/requests` | SealRequestListPage mode=register | `/seal-requests` |
|
||||
| 印章台账 | `/seal/registry` | SealRegistryPage | `/seals` |
|
||||
|
||||
印章状态:`IN_STOCK` 在库,`OUT` 外带(Web 台账有特判)。外带审批通过写归还待办。
|
||||
|
||||
### 5.5 投标管理(核心)
|
||||
|
||||
状态机:`apps/api/src/bidding/bid-workflow.ts`
|
||||
列表桶:`BidListPage` + `?bucket=`
|
||||
|
||||
```
|
||||
普通 FLOW:
|
||||
项目筛选 TECH_INITIAL → FETCH → TECH_FINAL
|
||||
→ 标书制作 DRAFTING → REVIEW1 → REVIEW2 → PRINT
|
||||
→ 投标执行 LIST → PENDING
|
||||
→ 开标结果 WON / LOST / FAILED
|
||||
随时可 TERMINATED(权限内)
|
||||
|
||||
特殊 SPECIAL:特殊筛选 / 特殊制标的办理人从**全公司在职人员**中选择,不再写死刘运泽、陈铖。
|
||||
```
|
||||
|
||||
| 菜单 | 路径 | bucket |
|
||||
|---|---|---|
|
||||
| 项目筛选 | `/bid/screening` | screening(可新建) |
|
||||
| 特殊筛选 | `/bid/special-screening` | 特殊 |
|
||||
| 标书制作 | `/bid/drafting` | drafting |
|
||||
| 特殊制标 | `/bid/special-drafting` | drafting+special |
|
||||
| 投标执行 | `/bid/execution` | execution |
|
||||
| 开标结果 | `/bid/result` | result |
|
||||
| 终止投标 | `/bid/terminated` | terminated |
|
||||
|
||||
详情 `/bid/cases/:id`:`BidDetailPage`。新建:`BidCreatePage`。
|
||||
中标:`ensureWonLedger` 立刻落 **合同草稿 + 项目台账**(幂等)。
|
||||
未中/终止:可「编辑」登记对方中标单位/金额 → 报表「落标对照」。
|
||||
API:`/bid-cases`,`PATCH /bid-cases/:id/competitor`。
|
||||
|
||||
### 5.6 合同管理
|
||||
|
||||
中标自动生成 `kind=TENDER` 草稿。直签/其他在本页新建,不互相重复。
|
||||
|
||||
| 菜单 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| 投标合同 | `/contract/tender` | 中标来的 |
|
||||
| 直签合同 | `/contract/direct` | 不经投标 |
|
||||
| 其他合同 | `/contract/incremental` | 其余 |
|
||||
| 人事合同 | `/contract/hr` | `LaborContractPage` `/labor-contracts` |
|
||||
|
||||
详情 `/contract/:id`。API `/contracts`。
|
||||
|
||||
### 5.7 资产管理
|
||||
|
||||
数字资产 `/asset/digital`、固定资产 `/asset/fixed`:`AssetRegistryPage`,`/registered-assets`。
|
||||
状态含 `IN_STOCK` / `PENDING_IN` / 占用。入库:`POST /registered-assets/:id/inbound`。
|
||||
领用占用走办公申请,不在本页把保管人改掉。
|
||||
|
||||
### 5.8 项目管理
|
||||
|
||||
中标自动写入台账。直签可在台账补登。
|
||||
|
||||
| 菜单 | 路径 | API |
|
||||
|---|---|---|
|
||||
| 项目台账 | `/project/list` | `/projects` |
|
||||
| 任务分解 | `/project/tasks` | `/project-tasks` |
|
||||
| 工时填报 | `/project/timesheets` | `/timesheets` |
|
||||
| 资源看板 | `/project/resources` | OpsPages ResourceBoardPage |
|
||||
| 项目验收 | `/project/acceptance` | 同一 ProjectListPage mode=acceptance |
|
||||
| 采购申请 | `/project/purchases` | PurchasePage(购置审批下游) |
|
||||
| 器材占用 | `/project/assets` | AssetPage 占用记录 |
|
||||
|
||||
详情 `/project/:id`。预算/报销:商务差旅进商务,技术部门差旅/工时/采购进技术(页面 description 已写)。
|
||||
|
||||
### 5.9 财务收支
|
||||
|
||||
员工在「我的费用」发起;财务在本模块确认、付款、开票。草稿财务看不到(`canSeeSubmittedFinance`)。
|
||||
|
||||
| 菜单 | 路径 | API |
|
||||
|---|---|---|
|
||||
| 费用报销 | `/finance/expenses` | `/expenses` |
|
||||
| 借支记录 | `/finance/loans` | `/loans` |
|
||||
| 投标/履约/质保金 | `/finance/bonds/*` | `/bonds` type=TENDER/PERFORMANCE/WARRANTY |
|
||||
| 开票收款 | `/finance/invoices` | `/invoices` |
|
||||
| 付款执行 | `/finance/payments` | `/payments` |
|
||||
|
||||
报销必须挂费用对象:投标 / 正式项目 / 部门 / 行政,规则 `assertCostObject`。中标前挂投标,中标后挂项目,不要同时挂。
|
||||
|
||||
### 5.10 组织人事
|
||||
|
||||
| 菜单 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| 在职员工 | `/hr/employees` | 档案、证件、银行卡 |
|
||||
| 组织管理 | `/hr/org` | 部门树 |
|
||||
| 入转调离 | `/hr/lifecycle` | EmploymentEvent |
|
||||
| 假勤结果 | `/hr/attendance` | 月导入 + 打卡 |
|
||||
| 薪酬核算 | `/hr/payroll` | 核算批次 |
|
||||
| 绩效发放 | `/hr/performance` | |
|
||||
| 发布公告 | `/hr/notices` | 人事发 |
|
||||
| 规章制度 | `/hr/policies` | 同类 Notice,kind 不同 |
|
||||
|
||||
### 5.11 经营报表
|
||||
|
||||
| 菜单 | 路径 | API |
|
||||
|---|---|---|
|
||||
| 投标转化 | `/report/conversion` | `/reports/bid-conversion` |
|
||||
| 落标对照 | `/report/lost` | `/reports/competitor-wins` |
|
||||
| 项目利润 | `/report/profit` | `/reports/profit` |
|
||||
| 部门人效 | `/report/efficiency` | `/reports/efficiency` |
|
||||
| 资金占用 | `/report/cash` | `/reports/cash` |
|
||||
|
||||
页面在 `OpsPages.tsx`。
|
||||
|
||||
### 5.12 系统设置
|
||||
|
||||
| 菜单 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| 员工账号 | `/system/acl` | 用户/角色 |
|
||||
| 人事参数 | `/system/hr-params` | |
|
||||
| 考勤设置 | `/system/attendance-rules` | |
|
||||
| 数据字典 | `/system/dicts` | |
|
||||
| 权限设置 | `/system/perms` | 菜单授权 |
|
||||
| 编号规则 | `/system/numbers` | `nextBizNo` |
|
||||
| 短信服务 | `/system/sms` | 签名/模板/绑定事件 |
|
||||
| 消息通道 | `/system/messages` | 占位页,文案可能过时(IM 已做) |
|
||||
| 手机端升级 | `/system/app-release` | 写 `app-release.json` |
|
||||
| 操作日志 | `/system/logs` | |
|
||||
|
||||
公开:`GET /system/app-release`(手机 OTA)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 核心流程(改业务先对这条链)
|
||||
|
||||
### 投标 → 合同/项目
|
||||
|
||||
筛选 → 取标 → 终审 → 制标 → 一审二审 → 印封 → 执行 → 开标。
|
||||
**WON** → 合同草稿 + 项目台账。售前报销仍挂投标;正式花费挂项目。
|
||||
|
||||
### 报销 / 借款
|
||||
|
||||
员工草稿 → 提交 PENDING → 财务/总监审批 APPROVED → 付款执行。借款还有 OPEN / OFFSET 冲账。
|
||||
|
||||
### 办公申请
|
||||
|
||||
发起 PENDING → 审批(admin/owner/hr/biz_director)→ 按 kind 写采购/占用/待入库。
|
||||
|
||||
### 用章
|
||||
|
||||
申请 → 审批 → 外带则归还待办;台账 status 在库/外带。
|
||||
|
||||
### 人事假勤
|
||||
|
||||
`LeaveRequest.kind`:LEAVE / OVERTIME / TRIP / OUT / RESIGN / PROMOTE。出差通过 → 报销草稿。
|
||||
|
||||
统一待办 `TodoTask`、统一审批 `ApprovalTask`(`bizType` 区分投标/合同/报销/用章/人事/办公)。
|
||||
待我审批一页消化,不要为每个 biz 再做孤立审批页。
|
||||
|
||||
---
|
||||
|
||||
## 7. API 地图
|
||||
|
||||
前缀均 `/api/v1`。鉴权 JWT,除标注 Public。
|
||||
|
||||
| 模块 | 文件 | 主要路径 |
|
||||
|---|---|---|
|
||||
| 登录 | `auth/` | `/auth/login` `/auth/refresh` `/auth/me` `/auth/logout` `/auth/profile` `/auth/password` `/auth/qr/*` |
|
||||
| 办公 | `office/` | `/office/*` 待办审批日程公告申请汇报工作安排天气地理 |
|
||||
| 投标 | `bidding/` | `/bid-cases` |
|
||||
| 合同 | `contract/` | `/contracts` |
|
||||
| 项目 | `project/` | `/projects` `/project-tasks` `/timesheets` |
|
||||
| 用章 | `seal/` | `/seal-requests` `/seals` `/qualifications` `/credential-borrows` |
|
||||
| 财务 | `finance/` | `/expenses` `/loans` `/bonds` `/invoices` `/payments` |
|
||||
| 人事 | `hr/` | `/employees` `/leave-requests` `/payroll` `/attendance` `/labor-contracts` `/org` |
|
||||
| 主数据 | `master/` | `/parties` `/legal-entities` `/contacts` `/registered-assets` |
|
||||
| 系统 | `system/` | `/system/menus` `/system/acl` `/system/dicts` `/system/numbers` `/system/app-release` `/system/logs` |
|
||||
| 报表 | `report/` | `/reports/*` |
|
||||
| 文件 | `files/` | `POST /files` `GET /files/:id` |
|
||||
| IM | `im/` | `/im/conversations` `/im/messages` `PATCH /im/messages/:id` `/im/calls*` `/im/read` |
|
||||
| 推送 | `push/` | 设备 token 注册与下发 |
|
||||
| 短信 | `sms/` | 签名模板发送 OTP |
|
||||
| 通讯录 | `business/` | `/staff` `/directors` |
|
||||
| 健康 | `health/` | `/health` |
|
||||
| 协议 | `legal/` | 用户协议页 |
|
||||
|
||||
响应外包一层 `{ data }`,Web 用 `unwrapList` / `body.data`。手机 `oa_client.dart` 同样解包。
|
||||
错误:`HttpErrorFilter`。请求号:`X-Request-Id`。
|
||||
|
||||
地理:`/office/geo/*` 走腾讯位置服务,`TENCENT_MAP_KEY`。
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据模型(Prisma)
|
||||
|
||||
完整定义:`apps/api/prisma/schema.prisma`。
|
||||
|
||||
**账号**:User、RefreshToken(`clientKind`)、AuthQrTicket、Role、Permission、UserRole、PushDevice
|
||||
**组织人事**:Department、Employee、LaborContract、EmploymentEvent、AttendanceMonth、AttendancePunch、LeaveRequest、HrSetting、AttendanceRule、PayrollRun、PayrollItem
|
||||
**主数据**:LegalEntity、BusinessParty、PartyContact、DictItem、NumberRule、Qualification、SealItem、RegisteredAsset
|
||||
**投标**:BidCase(含 `winnerName/Amount/Note`)、BidReviewer、BidAssignee、BidDocVersion、BidActionLog
|
||||
**合同项目**:Contract、ContractReviewer、PaymentMilestone、Project、ProjectBudget、ProjectTask、Timesheet、ProjectAcceptance、ProjectAcceptMaterial、ProjectChange
|
||||
**财务**:ExpenseClaim、ExpenseLine、LoanRecord、BondRecord、InvoiceRecord、PaymentRecord、PurchaseRequest
|
||||
**办公**:TodoTask、ApprovalTask、WorkReport、WorkAssignment、OfficeApply、Notice、AssetOccupancy、CredentialBorrow、SealRequest
|
||||
**IM**:ImConversation、ImConversationMember、ImMessage(`fingerprint` 唯一)、ImReadCursor、ImMessageReceipt、ImCall、ImCallMember、ImCallSignal
|
||||
**其它**:FileAsset、SmsSendLog、SmsEventBind、SmsOtp、OperationLog
|
||||
|
||||
编号:`NumberRule` + `src/system/numbering.ts`。注意:**项目筛选、特殊筛选的招标编号和合同编号必须手填,系统不得另生成隐藏编号**;报销等内部单据仍可使用系统编号。
|
||||
|
||||
---
|
||||
|
||||
## 9. 手机端
|
||||
|
||||
Flutter。版本:`apps/native/lib/app_config.dart` + `pubspec.yaml` + `test/widget_test.dart` + `apps/api/data/app-release.json`,四者一起改。
|
||||
|
||||
默认连生产 API / IM。底栏四页:`messages_page`、`workbench_page`、`directory_page`、`mine_page`。
|
||||
|
||||
工作台宫格来自登录菜单。`open_module.dart` / `module_list_page.dart` 把菜单码映射到 REST 列表(投标→`/bid-cases` 等)。系统设置、发短信、工作台里的「消息」入口故意不打开(消息在底栏)。详情 `record_detail_page.dart` + `biz_route.dart` 反查详情 API。中文:`labels.dart`。
|
||||
|
||||
聊天/通话的关键文件:聊天 `chat_page.dart`,来电 `home_shell.dart` + `call_page.dart`。当前版本为 3.1.8+318;音视频、截屏提醒、撤回再编辑、会话已读后保留等需求必须按 `/opt/HANDOFF.md` 的“未完成/需真机回归”继续验证,不能仅凭旧交接宣称已修。
|
||||
|
||||
打包:
|
||||
|
||||
```bash
|
||||
cd /opt/apps/native && flutter build apk --release && bash scripts/publish-apk.sh
|
||||
```
|
||||
|
||||
产物:`/opt/apps/native/dist/fengying-oa.apk`。本机 root 跑 Flutter 会警告,仍能编过。下一版必须在 **3.1.8+318** 基础上递增。
|
||||
|
||||
OTA:比较 `build`。后台「手机端升级」。
|
||||
|
||||
---
|
||||
|
||||
## 10. IM
|
||||
|
||||
协议:4 字节大端长度 + UTF-8 JSON Protocal。登录用 OA accessToken 调 `GET /auth/me`。
|
||||
|
||||
**新 `server.js`**:`Map<userId, Set<socket>>`,同一账号多端/重连 **不再 kick**。旧版会踢旧连接,客户端若把 `_want=false` 会永久断线。
|
||||
生产必须换新 `server.js` 长连接才稳。新 APK 即使对旧 IM 也不会再「被踢后永不再连」。
|
||||
|
||||
HTTP 消息仍落 Postgres。TCP 负责推送。通话信令走 `/im/calls/:id/signal`(HTTP 轮询),WebRTC + 国内 STUN。
|
||||
|
||||
`PATCH /im/messages/:id`:2 分钟内改自己的文本。
|
||||
|
||||
---
|
||||
|
||||
## 11. 文件、短信、推送、地图
|
||||
|
||||
- 文件:`FilesService` 存 MinIO(本机 docker)或本地 `uploads/`。下载带鉴权。手机 `AuthImage` / `fileBytes` 有内存缓存。
|
||||
- 短信:阿里云 Dysmsapi。系统设置申请签名/模板,事件绑定在 `SmsEventBind`。投标节点会 `emitSms`。
|
||||
- 推送:vivo / OPPO / 魅族 / 华为。设备 `PushDevice`。华为 App ID `118301849`。
|
||||
- 地图:腾讯。选点 `location_pick_page.dart` + `tencent-lbs`;POI 用真实距离,不要写死「100m内」。
|
||||
|
||||
---
|
||||
|
||||
## 12. 前端约定
|
||||
|
||||
**Web**
|
||||
|
||||
- 新列表:Ant Design Table + 中文 `zh()` / `StatusTag`,不要直接渲染枚举码。
|
||||
- 申请类列表:`ApplyBucketBar` + URL `bucket`。
|
||||
- 抽屉表单:`FormDrawer.tsx`。附件:`FileAttachments.tsx`。
|
||||
- 枚举中文:`apps/web/src/labels.ts`,与手机 `labels.dart` 对齐。
|
||||
- token 只放 sessionStorage。
|
||||
|
||||
**手机**
|
||||
|
||||
- 企业微信风:`theme.dart`、`widgets/wecom.dart`。
|
||||
- 英文字段:补 `fieldZh` / `zhMap`,不要在单个页写死。`OUT` 人事=外出、印章=外带,有冲突时按字段上下文。
|
||||
|
||||
---
|
||||
|
||||
## 13. 脚本与历史数据
|
||||
|
||||
`apps/api/prisma/` 里常见脚本(跑前看文件头,生产库只读):
|
||||
|
||||
| 脚本 | 用途 |
|
||||
|---|---|
|
||||
| seed.ts | 空库演示 |
|
||||
| sync-menus.ts | 菜单/权限 |
|
||||
| import-hr.ts / import-prod.ts | 从旧 OA 导人事/业务 |
|
||||
| wash-legal-entities.ts | 参与主体清洗 |
|
||||
| wash-oa-ledger.ts / wash-oa-flow-finance.ts | 台账/财务清洗 |
|
||||
| sync-oa-attachments.ts | 附件 |
|
||||
| grant-admin-equivalents.ts / ensure-prod-admin.ts | 管理员 |
|
||||
| fix-oa-posts.ts / fix-expense-applicants.ts | 岗位与报销申请人 |
|
||||
|
||||
旧 OA 在生产 MySQL `oa`(只读扒数,用户禁止改生产旧库、禁止整用户过来)。dump 曾放 `/opt/import/prod/`。
|
||||
|
||||
---
|
||||
|
||||
## 14. 密钥与账号(Codex 直接用,不必再问)
|
||||
|
||||
### 生产 SSH
|
||||
|
||||
```
|
||||
主机:47.96.23.244
|
||||
用户:root
|
||||
密码:VYTnNdfFfrX7xTP
|
||||
代码目录:/data/fengying-oa/
|
||||
```
|
||||
|
||||
```bash
|
||||
export SSHPASS='VYTnNdfFfrX7xTP'
|
||||
RSH='ssh -o StrictHostKeyChecking=no'
|
||||
DEST=root@47.96.23.244
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no "$DEST" 'hostname; systemctl is-active fengying-api fengying-im'
|
||||
```
|
||||
|
||||
发版:
|
||||
|
||||
```bash
|
||||
export SSHPASS='VYTnNdfFfrX7xTP'
|
||||
RSH='ssh -o StrictHostKeyChecking=no'
|
||||
DEST=root@47.96.23.244
|
||||
# 先 nest build、确认 APK 已 publish
|
||||
sshpass -e rsync -az -e "$RSH" /opt/apps/api/dist/ $DEST:/data/fengying-oa/apps/api/dist/
|
||||
sshpass -e rsync -az -e "$RSH" /opt/apps/api/data/app-release.json $DEST:/data/fengying-oa/apps/api/data/app-release.json
|
||||
sshpass -e rsync -az -e "$RSH" /opt/apps/web/dist/ $DEST:/data/fengying-oa/apps/web/dist/ # 若本机有最新 web 构建
|
||||
sshpass -e rsync -az -e "$RSH" /opt/apps/im-server/server.js $DEST:/data/fengying-oa/apps/im-server/server.js
|
||||
sshpass -e rsync -az -e "$RSH" /opt/apps/native/dist/fengying-oa.apk $DEST:/data/fengying-oa/apps/web/dist/fengying-oa.apk
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no "$DEST" 'systemctl restart fengying-api fengying-im; sleep 2; systemctl is-active fengying-api fengying-im'
|
||||
```
|
||||
|
||||
生产 API `.env` 在远端 `/data/fengying-oa/apps/api/.env`。上去先 `cat`,**不要用本机 development 的 JWT/Postgres 覆盖生产**。发 IM/API 后确认 Prisma 已有 `RefreshToken.clientKind`、`AuthQrTicket`、`BidCase.winner*`。
|
||||
|
||||
### 旧 OA MySQL(只读)
|
||||
|
||||
```
|
||||
用户:root
|
||||
密码:BtH9ywG8gFTXG9w!
|
||||
库:oa
|
||||
```
|
||||
|
||||
### 本机 API `apps/api/.env`
|
||||
|
||||
```
|
||||
NODE_ENV=development
|
||||
APP_PORT=3000
|
||||
DATABASE_URL=postgresql://ops:ops_dev_only@localhost:5432/enterprise_ops
|
||||
REDIS_URL=redis://localhost:6379
|
||||
MINIO_ACCESS_KEY=opsadmin
|
||||
MINIO_SECRET_KEY=opsadmin_dev_only
|
||||
JWT_ACCESS_SECRET=change-me-access-secret-dev-only
|
||||
JWT_REFRESH_SECRET=change-me-refresh-secret-dev-only
|
||||
POSTGRES_USER=ops
|
||||
POSTGRES_PASSWORD=ops_dev_only
|
||||
POSTGRES_DB=enterprise_ops
|
||||
|
||||
ALIYUN_SMS_ACCESS_KEY_ID=LTAI5t9DdPycJp7sabQyEWRc
|
||||
ALIYUN_SMS_ACCESS_KEY_SECRET=RPFVqKDUpEvxo3IsqlxLB5qIywfheM
|
||||
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
|
||||
ALIYUN_SMS_REGION=cn-hangzhou
|
||||
ALIYUN_SMS_DEFAULT_SIGN=江苏风影随行科技
|
||||
ALIYUN_RAM_LOGIN=yongkangdai10@1801267455383083.onaliyun.com
|
||||
|
||||
TENCENT_MAP_KEY=L6NBZ-47KLI-OL7G2-UMROZ-XPI3T-SLFC7
|
||||
|
||||
IM_TCP_PORT=8901
|
||||
IM_HTTP_PORT=8902
|
||||
IM_INTERNAL_TOKEN=fengying-im-internal
|
||||
|
||||
VIVO_PUSH_APP_ID=106114371
|
||||
VIVO_PUSH_APP_KEY=b521c805229a00e85a53d4b535a0cafe
|
||||
VIVO_PUSH_APP_SECRET=2e1536ed-ed2f-4cd6-88d1-67aa9a93eeba
|
||||
|
||||
OPPO_PUSH_APP_ID=37438377
|
||||
OPPO_PUSH_APP_KEY=e23832b52fbb46e18de6eef5affda2e9
|
||||
OPPO_PUSH_APP_SECRET=e9cf0bf35052484ebcaf15d56576468b
|
||||
OPPO_PUSH_MASTER_SECRET=e9cf0bf35052484ebcaf15d56576468b
|
||||
|
||||
MEIZU_PUSH_APP_ID=157643
|
||||
MEIZU_PUSH_APP_KEY=4263a8d18de442b6a17203866ce1546d
|
||||
MEIZU_PUSH_APP_SECRET=2a0bafc2d47a470f8b2aad3f182d2377
|
||||
|
||||
HUAWEI_PUSH_APP_ID=118301849
|
||||
HUAWEI_PUSH_CLIENT_ID=118301849
|
||||
HUAWEI_PUSH_CLIENT_SECRET=85c01c5c853d571e2d83fd8629efb21e9b2cad0bdfadd6f32c2c5b42e38d7888
|
||||
```
|
||||
|
||||
本机 IM `.env` 写的是 `OA_API_URL=http://127.0.0.1:3010/api/v1`,本机 nest 常在 3000,连本机 IM 请改 3000。生产 systemd:`OA_API_URL=http://127.0.0.1:3010/api/v1`。
|
||||
|
||||
### 华为 AGC
|
||||
|
||||
- 包名 `com.fengying.oa.mobile`,App ID `118301849`
|
||||
- 服务端 Client Secret:`85c01c5c853d571e2d83fd8629efb21e9b2cad0bdfadd6f32c2c5b42e38d7888`
|
||||
- JSON:`apps/native/android/app/agconnect-services.json`
|
||||
- JSON `client.client_id`:`1993069421077225280`
|
||||
- JSON `client.client_secret`:`93585E101B911EFF0F99F215050CD3E4297F67CB004E0D8E070AAB1EEDD7E725`
|
||||
- JSON `client.api_key`:`DgEDAFLpMeUxEsIEiKams+kYWThvAg0ioMuL1rW9BEP4i71BuChEPD7Udg1AbDKbg+ljwD+pBACxktLqHwmUlSxOCwsLHiRAKQDBFw==`
|
||||
- `cp_id`:`10086000847744494`;`product_id`:`101653523864520415`
|
||||
|
||||
### OPPO Maven
|
||||
|
||||
```
|
||||
https://maven.columbus.heytapmobi.com/repository/releases/
|
||||
username: nexus
|
||||
password: c0b08da17e3ec36c3870fed674a0bcb36abc2e23
|
||||
```
|
||||
|
||||
### 腾讯地图 / vivo Manifest
|
||||
|
||||
- `TENCENT_MAP_KEY=L6NBZ-47KLI-OL7G2-UMROZ-XPI3T-SLFC7`
|
||||
- Manifest vivo:`api_key=b521c805229a00e85a53d4b535a0cafe`,`app_id=106114371`
|
||||
|
||||
### 本机演示账号
|
||||
|
||||
`admin` / `Admin@123456`。种子其他人常见 `Demo@123456` 或导入后 `123456`。不要拿去撞生产。
|
||||
|
||||
---
|
||||
|
||||
## 15. 发生产清单
|
||||
|
||||
1. `npx nest build`,同步 `apps/api/dist/`
|
||||
2. 若改了 Web:`pnpm --filter web build`,同步 `apps/web/dist/`
|
||||
3. 同步 `server.js`、APK、`app-release.json`
|
||||
4. `systemctl restart fengying-api fengying-im`
|
||||
5. 后台确认手机端升级为当前最新 build(现为 318)
|
||||
6. 真机覆盖安装当前最新版(现为 3.1.8)
|
||||
|
||||
**APK 保留规则:**云主机只保留当前 OTA 指向的最新安装包(对外统一为 `apps/web/dist/fengying-oa.apk`)。每次发版先精确列出 `/data/fengying-oa` 下的 `.apk` / `.aab`,确认新包可下载后删除其他旧包,不要在 `apps/native/dist` 等目录留历史副本。
|
||||
|
||||
API/IM/APK 是否为同一版必须分别核对,不能只看网页或 OTA JSON。
|
||||
|
||||
---
|
||||
|
||||
## 16. 已知坑 / 未完成
|
||||
|
||||
1. 生产 API 已同步 2026-09-01 的数据修复代码;Web、IM、APK 后续每次仍要分别核对版本和构建时间。
|
||||
2. 华为正式推送:缺正式签名 + AGConnect 插件未进构建。
|
||||
3. `OUT` 中文:人事外出 vs 印章外带。
|
||||
4. 系统设置「消息通道」文案还写「本期不做原生 IM」,已过时。
|
||||
5. Web IM 与手机 IM 都可用,但通话依赖双方打开页 + STUN,失败别先改来电弹层。
|
||||
6. 手机工作台对报表/部分系统页映射为 `null`,点了可能没页。
|
||||
7. 打开 docx 依赖手机已装 WPS/Office。
|
||||
8. 历史投标主体不会自动拆到软件/工程公司。
|
||||
9. `apps/native/README.md`、部分 api README 版本号过时,以 `app_config.dart` 和本文件为准。
|
||||
10. 薪酬/绩效/资源看板有页面,深度以现网数据为准,缺的是补流程不是再铺空壳。
|
||||
11. 问题视频:`/opt/问题视频/`。来电测试对象常为「戴永康」。
|
||||
|
||||
---
|
||||
|
||||
## 17. 建议接着做的顺序
|
||||
|
||||
1. 用第 14 节发生产(API + Web + IM + APK + OTA),不要再问密码。
|
||||
2. 真机覆盖 2.3.2:来电能关、消息上屏、图片/地图能点、印章「在库」。
|
||||
3. 网页重新登录:确认扫码登电脑、Web 互踢、侧栏「落标对照」。
|
||||
4. 再改业务:遵守第 3 节办公规则;改投标走 `bid-workflow.ts`,不要只改一个列表页。
|
||||
5. 英文残留:补 `labels.ts` / `labels.dart`。
|
||||
6. 华为推送真机(正式签名)。
|
||||
7. 用户新需求再开功能,不要平行重构目录。
|
||||
|
||||
改完菜单同步 `sync-menus.ts`。改完枚举两边 labels 一起改。改完版本四个文件一起改。
|
||||
Reference in New Issue
Block a user