From 649d9fdfccb806fe7376dea5641bf734ff0f2ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E6=98=8A?= Date: Tue, 7 Jul 2026 14:39:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(hrm):=20=E4=BF=AE=E5=A4=8D=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E6=95=B0=E6=8D=AE=E7=A9=BA=E5=80=BC=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 针对印章详情和流程详情页面,在缺少流程实例ID时提前返回,避免调用接口产生无效请求和脏数据 --- CLAUDE.md | 95 +++++++++++++++++++ .../components/BizDetailContainer/index.vue | 8 ++ .../src/views/hrm/requests/sealDetail.vue | 4 + 3 files changed, 107 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..adebcab --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,95 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +福安德综合办公系统 (FAD OA) — 基于 RuoYi-Flowable-Plus 二次开发,面向工程项目的投标管理、进度控制、成本核算及日常 OA 办公。 + +**Tech Stack**: Spring Boot 2.7 + MyBatis-Plus 3.5 + Flowable 6.8 + Vue 2 + Element UI + MySQL 8.0 + Redis + +## Build & Run + +```bash +# Backend build (dev profile, skip tests) +mvn clean package -D maven.test.skip=true -P dev + +# Backend build (prod) +mvn clean package -D maven.test.skip=true -P prod + +# Frontend dev server (port 80) +cd ruoyi-ui && npm run dev + +# Frontend lint +cd ruoyi-ui && npm run lint + +# Frontend prod build +cd ruoyi-ui && npm run build:prod +``` + +Active Maven profile defaults to `dev` (`pom.xml` line 72). Backend entry point: `ruoyi-admin/`. + +## Database + +Init script: `script/sql/mysql/ry-vue-flowable-xg.sql`. Module-specific migrations live in `sql/` (e.g. `oa_approval.sql`, `warehouse_*.sql`, `hrm_*.sql`). + +Dev DB: `fad_oa_dev` @ 49.232.154.205:13306 +Prod DB: `fad_oa` @ 49.232.154.205:13306 +Redis: 49.232.154.205:6379 (password: WANGyu11!) + +**Menu SQL pattern**: When adding menus to `sys_menu`, use fixed snowflake IDs (not RAND()). Reference `sql/warehouse_phase0_menu.sql` for the correct pattern. Menu changes require COMMIT + restart backend to flush Redis cache — clearing Redis is not enough on its own. + +## Module Architecture + +| Module | Purpose | +|--------|---------| +| `ruoyi-admin/` | Spring Boot entry point, YAML configs | +| `ruoyi-framework/` | Security (Sa-Token), interceptors, global config | +| `ruoyi-system/` | Users, roles, menus, depts, dicts, posts | +| `ruoyi-flowable/` | Flowable workflow engine integration | +| `ruoyi-oa/` | Core OA: projects, contracts, bidding, finance, warehouse, approval | +| `ruoyi-ui/` | Vue 2 frontend (Element UI) | +| `fad-hrm/` | HRM: employees, attendance, leave, travel, seal, payroll, flow tasks | +| `fad-app/` | Mobile app API endpoints | +| `fad-export/` | Foreign trade public web pages | +| `fad-rolling-mill/` | Rolling mill business module | +| `klp-wms/` | Warehouse management module | + +## Dynamic Menu System (Critical) + +Menus are **not hardcoded in Vue Router**. They are stored in `sys_menu` DB table and delivered by `GET /getRouters`. The frontend flow: + +1. `ruoyi-ui/src/store/modules/permission.js` → `GenerateRoutes` action +2. Calls `getRouters()` → `@/api/menu.js` → `GET /getRouters` +3. Backend returns JSON with `component` paths (e.g. `"hrm/employee/index"`) +4. `filterAsyncRouter()` resolves strings to actual Vue components via dynamic `import()` +5. Routes are added at runtime via `router.addRoutes()` + +**Implication**: Adding a new page requires both a `sys_menu` INSERT (with correct `component` path) AND the actual `.vue` file at `ruoyi-ui/src/views/`. No changes to `router/index.js` are needed for C-type page menus — only hidden/detail routes go there. + +F-type menus (buttons) use `perms` field for permission checks (e.g. `system:user:add`). M-type menus are directory containers with no component. + +## HRM Module Structure + +The HRM module (`fad-hrm/` backend, `ruoyi-ui/src/views/hrm/` frontend) is a self-contained HR system built on a custom flow engine (NOT Flowable — uses its own `hrm_flow_*` tables). + +**Two separate approval systems exist**: +- **HRM flow engine**: `hrm_flow_task`, `hrm_flow_instance` etc. — handles leave/travel/seal/reimburse/appropriation workflows. Pages: `hrm/requests/` +- **OA approval center**: `oa_approval_instance` table — lightweight generic approval for purchase/contract. Pages: `oa/approval/` + +Both have items named "我的审批" in the menu. They serve different data sources and must NOT be conflated. + +## Key Auth Patterns + +- Uses **Sa-Token** (not Spring Security), configured in `application.yml` +- `@SaCheckPermission` for API-level permission checks +- Frontend permission checks: `v-hasPermi` directive and `auth.hasPermiOr()` in `@/plugins/auth` +- Menu visibility is controlled by `sys_role_menu` table — role-to-menu many-to-many + +## API Response Conventions + +The backend uses two response wrappers: +- `TableDataInfo`: `{ total, rows, code, msg }` — paginated lists → frontend accesses `res.rows` +- `R`: `{ data, code, msg }` — single entities → frontend accesses `res.data` + +Frontend API modules are in `ruoyi-ui/src/api/`. The axios interceptor in `@/utils/request` unwraps the HTTP response, so `.then(res => ...)` receives the JSON body directly (not the full axios response). diff --git a/ruoyi-ui/src/views/hrm/components/BizDetailContainer/index.vue b/ruoyi-ui/src/views/hrm/components/BizDetailContainer/index.vue index b2780b0..0c22f05 100644 --- a/ruoyi-ui/src/views/hrm/components/BizDetailContainer/index.vue +++ b/ruoyi-ui/src/views/hrm/components/BizDetailContainer/index.vue @@ -295,6 +295,10 @@ export default { } }, async loadComment () { + if (!this.flowInstance || !this.flowInstance.instId) { + this.commentList = [] + return + } try { const res = await listFlowComment({ instId: this.flowInstance.instId }) this.commentList = res.rows @@ -352,6 +356,10 @@ export default { } }, async loadAssignTask () { + if (!this.detail.instId) { + this.assignTasks = [] + return + } try { const res = await listAssignTask(this.detail.instId) this.assignTasks = res?.data || [] diff --git a/ruoyi-ui/src/views/hrm/requests/sealDetail.vue b/ruoyi-ui/src/views/hrm/requests/sealDetail.vue index b541060..d366f1c 100644 --- a/ruoyi-ui/src/views/hrm/requests/sealDetail.vue +++ b/ruoyi-ui/src/views/hrm/requests/sealDetail.vue @@ -427,6 +427,10 @@ export default { }) }, async loadAssignTask () { + if (!this.seal || !this.seal.instId) { + this.assignTasks = [] + return + } try { // const res = await getTodoTaskByBiz('seal', this.currentBizId) console.log(this.seal)