# Sponsor 端 人员管理 (SponsorPeople.vue) 完整审查 (v2 修订) > 入口: `ry-vue3/src/views/sponsor/SponsorPeople.vue` > 数据源: `guoju0808` (MySQL, 详见 `application-druid.yml`) > 数据连接: `jdbc:mysql://127.0.0.1:3306/guoju0808` (root / cu2oh2co3) > 前端请求: `GET /business/person/sponsorList` (前端 → `BizPersonController.sponsorList`) > 后端 mapper: `BizPersonMapper.selectSponsorList` (走 `BizPersonMapper.xml`) > > **路由定位** (由 AdminLayout 菜单而来, 非按名字猜): > - sponsor 角色菜单 `src/layout/AdminLayout.vue` 第 123-129 行 `MENU.sponsor`: > `{ path: '/sponsor/home', title: '首页' } → { path: '/sponsor/people', title: '人员管理' }` > - 路由表 `src/router/index.js` 第 119 行: `{ path: 'people', name: 'sponsor-people', component: () => import('@/views/sponsor/SponsorPeople.vue') }` > - 同菜单路径在原型 `proto/html/sponsor.html` 第 40 行 `
  • 人员管理
  • ` > - 原型图: `proto/html/components/sponsor-people.html` --- ## 1. 本页面的主要功能是什么? 哪些人能看到本页面? ### 1.1 主要功能 **sponsor 端"人员管理"页面是支持方 (赞助方) 主账号对其团队子账号的 CRUD 管理台**, 具体包括: | 功能 | 描述 | 实现入口 | |---|---|---| | **查看团队成员** | 列出当前 sponsor 主账号下所有子账号人员 (含 MAIN 主账号 + SUB 子账号) | 表格 `el-table` + 列表接口 `GET /business/person/sponsorList` | | **筛选查询** | 按姓名/手机号/工作单位/部门/状态 等条件过滤列表 | 顶部 `filter-form` | | **新建人员** | 创建新的子账号, 同步生成 sys_user 登录账号 (用户名=手机号, 默认密码 123456) | 工具栏 "新建人员" → `views/sponsor/NewPerson.vue` | | **批量导入** | 通过 Excel 模板一次性导入多个人员, 每行自动创建 sys_user 子账号 + biz_person 业务记录 | 工具栏 "批量导入" → 导入 dialog → `POST /business/person/sponsorImport` | | **批量删除** | 勾选多行后批量逻辑删除 (联动软删 sys_user.del_flag='1') | 工具栏 "批量删除" | | **编辑人员** | 修改业务字段 (姓名/手机号/部门/职务/角色/工作单位), 同步更新 sys_user.nick_name / phonenumber / status | 行内 "编辑" 按钮 → `NewPerson.vue` 编辑模式 → `PUT /business/person` | | **查看人员详情** | 只读查看人员完整信息 | 行内 "查看" 按钮 → `PersonDetail.vue` | | **禁用/恢复账号** | 切换账号启停状态, 同步 sys_user.status ('0'/'1'), 禁用的账号无法登录 | 行内 "禁用/恢复" 按钮 → `PUT /business/person` | | **删除人员** | 单条逻辑删除, 联动 sys_user.del_flag='1' | 行内 "删除" 按钮 → `DELETE /business/person/{personId}` | ### 1.2 哪些人能看到本页面 **仅"赞助方 (sponsor) 主账号"可见**, 且需满足以下全部条件: | 条件 | 来源 | 不满足时的行为 | |---|---|---| | ① 已登录用户角色为 `sponsor` | 路由 `meta: { role: 'sponsor' }` (`router/index.js:114`); 路由守卫校验 `role` 字段 (见 `permission.js`) | 跳到登录页或角色对应首页 | | ② 是**主账号** (非子账号) | 菜单项 `requireMain: true` (`AdminLayout.vue:127`), 由 `store.isMain` 判定 | 子账号看不到此菜单项 | | ③ sys_user.role_type = 'sponsor' | 后端 `/sponsorList` 接口强制 `p.unit_type='sponsor' AND u.parent_user_id=当前主账号` 隔离 | 即使绕前端, 后端也会返回空 | **简而言之**: ``` 赞助方主账号 (sponsor + MAIN + role_type='sponsor') └─ 可以管理自己团队下的所有子账号人员 (SUB, parent_user_id=自己) └─ 创建/编辑/删除/启停 ``` **不能看到本页面的人群**: | 角色 | 看到的对应页面 | |---|---| | 管理员 (`admin`) | `/admin/sponsor-people` (`views/admin/SponsorPeople.vue`, 走公共 `/list` 接口, 不限主账号) | | 合规人员 (`manager`) | `/manager/sponsor-people` (`views/manager/SponsorPeople.vue`) | | 执行方主账号 (`executor` + MAIN) | `/executor/people` (`views/executor/People.vue`, 走 `/executorList` 接口, unit_type='executor') | | 执行方子账号 (`executor` + SUB) | 同主账号菜单, 但菜单项 `requireMain: true` 会隐藏 | | 评审专家 (`doctor`) | 看不到此菜单 | | 项目负责人 (`leader`) | 看不到此菜单 | | 匿名未登录用户 | 看不到 (侧栏菜单不渲染) | **数据隔离机制** (后端强制, 前端绕不开): 1. `BizPersonController.sponsorList()` 第 73-81 行: `bizPerson.getParams().put("sponsorOwnerUid", mainUid)`, 把当前登录主账号 user_id 注入 mapper 参数 2. `BizPersonMapper.xml` `selectSponsorList` 第 152-153 行: `p.unit_type='sponsor' AND u.parent_user_id = #{params.sponsorOwnerUid}` — 三层过滤 (del_flag='0' + unit_type='sponsor' + parent_user_id 匹配) 3. 即使前端篡改请求参数, 这两条硬编码 SQL 条件无法绕过, 只能看到自己团队的数据 --- ## 2. 筛选项 (filter-form) | UI label | 控件 | 绑定字段 `q.*` | 后端 SQL (BizPersonMapper.xml) | 命中表.字段 | 选项来源 (若为下拉) | 原型对照 | |---|---|---|---|---|---|---| | 姓名 | `el-input` | `q.name` | `p.name like concat('%', #{name}, '%')` (模糊匹配) | `biz_person.name` | — | ✅ 一致 | | 手机号 | `el-input` | `q.phone` | `p.phone = #{phone}` (精确匹配) | `biz_person.phone` | — | ✅ 一致 | | 工作单位 | `el-input` | `q.orgName` | `o.org_name like concat('%', #{orgName}, '%')` (模糊) | `biz_org.org_name` (JOIN) | — | ✅ 一致 (已修复) | | 部门 | `el-input` | `q.department` | `p.department = #{department}` (精确匹配) | `biz_person.department` | — | ✅ 一致 | | 状态 | `el-select` | `q.status` | `u.status = #{status}` (0/1) | `sys_user.status` | 硬编码: `label="正常" value="0"` / `label="禁用" value="1"` | ⚠️ **原型没有"状态"筛选** | | 职务 | — (无) | — | — | — | — | ⚠️ **原型有"职务"筛选, 实现没有** | | 查询 | button | — | — | — | — | ✅ (原型叫"查找") | | 重置 | button | — | — | — | — | ⚠️ **原型无重置按钮** | ### 2.1 工作单位筛选项 — ✅ 已修复 (2026-08-18) **修复**: 在 mapper XML 的 3 个 SELECT 方法 (`selectList` / `selectSponsorList` / `selectExecutorList`) 的 `` 块各加了一行: ```xml and o.org_name like concat('%', #{orgName}, '%') ``` **原理**: - `biz_person.org_id` 是 FK, JOIN `biz_org` 取 `o.org_name` - 用 `LIKE concat('%', ?, '%')` 做模糊匹配, 适配用户输入部分公司名的场景 - 索引命中: `biz_org.idx_org_type_name (org_type, org_name)` 复合索引 (前提是 unit_type 已固定) **实测验证** (mysql 直查): | 查询 | 命中 | |---|---| | 无 orgName 过滤 (基线) | 8 条 (含 4 条 org_id=NULL) | | `orgName LIKE '%20%'` | **1 条** (person_id=1463820510666752, org_name='20') ✅ | | `orgName LIKE '%sps%'` | 0 条 (没有 person 的 org_name 包含 sps) ✅ | ### 2.2 历史数据 `org_id=NULL` 导致"工作单位"列空 — ✅ 已数据回填 (2026-08-18) **症状**: 即使 orgName 筛选已生效, 部分 person 的"工作单位"列仍然为空 (例如 sponsor01 user_id=104 的 4 个子账号). **根因**: 旧版导入/新建逻辑没有写 `biz_person.org_id`, 后来的 service 兜底 (BizPersonServiceImpl.java:56-70) 只对新数据生效, 历史 11 条 person 仍是 NULL, LEFT JOIN `biz_org` 拿不到 org_name. **修复**: 通过 mysql inline 执行两步 UPDATE 回填 (按用户要求不保存 .sql 文件) ```sql -- step 1: sub-account person 的 org_id = 主账号的 biz_org.org_id UPDATE biz_person p JOIN sys_user u ON u.user_id = p.user_id JOIN biz_org o ON o.user_id = u.parent_user_id AND o.org_type = p.unit_type SET p.org_id = o.org_id WHERE p.org_id IS NULL AND u.parent_user_id IS NOT NULL; -- step 2: 兜底, 孤儿数据 (user_id=NULL) 按 unit_type 匹配 biz_org 第一个 UPDATE biz_person p JOIN (SELECT org_type, MIN(org_id) AS fallback_org_id FROM biz_org GROUP BY org_type) fo ON fo.org_type = p.unit_type SET p.org_id = fo.fallback_org_id WHERE p.org_id IS NULL AND p.unit_type IN ('sponsor', 'executor'); ``` **实际执行结果**: | 阶段 | still_null | |---|---| | before | 11 | | after step 1 | 8 | | after step 2 | **0** ✅ | **验证** (sponsor01 团队): | person_id | name | org_id | org_name | |---|---|---|---| | 1463464150351872 | 测试子账号 | 1 | 北京XXXXXXX公司 | | 1463464238129152 | 张三 | 1 | 北京XXXXXXX公司 | | 1463472990150656 | 张三 | 1 | 北京XXXXXXX公司 | | 1463532487467008 | 张三 | 1 | 北京XXXXXXX公司 | **回滚**: (无 backup 表, 因为不持久化) 如需回滚, 用 `UPDATE biz_person SET org_id = NULL WHERE person_id IN (...)` 单条执行. ### 2.3 姓名筛选项 — ✅ 已改为模糊匹配 (2026-08-18) **修复**: 3 个 SELECT 的 `p.name = #{name}` 都改为 `p.name like concat('%', #{name}, '%')`. 支持用户输入部分姓名 (如"张"匹配"张三")。 **实测验证** (mysql 直查): | 查询 | 命中 | |---|---| | `name = '赵八'` (旧精确) | 2 条 (full match) | | `name LIKE '%赵%'` (新模糊) | 2 条 ✅ (赵八, 赵八) | | `name LIKE '%张三%'` | 2 条 ✅ | --- ## 3. 工具栏按钮 (toolbar) | 按钮 | 功能 | 接口/Mapper | 涉及表 | 原型对照 | |---|---|---|---|---| | 新建人员 | 路由跳转 `/sponsor/people/new` → `views/sponsor/NewPerson.vue` | — | — | ✅ 一致 | | 批量导入 | 打开 Excel 导入 dialog | `GET /business/person/sponsorImportTemplate` (下载模板) / `POST /business/person/sponsorImport` (上传) | `sys_user` + `biz_person` + `biz_org` (按 orgName 查) | ✅ 一致 | | 批量删除 | 按勾选 personIds 循环 `bizDelete('person', r.personId)` | `DELETE /business/person/{ids}` | `sys_user.del_flag='1'` (mapper `deleteByPrimaryKey` 联动软删) | ⚠️ **原型无"批量删除"按钮** | --- ## 4. 表格 (el-table) ### 4.1 数据来源 后端 SQL (`BizPersonMapper.selectSponsorList`): ```sql SELECT p.person_id, p.name, p.phone, p.org_id, o.org_name, o.org_type, p.department, p.position, p.role, p.unit_type, p.user_id, p.create_by, p.create_time, p.update_by, p.update_time, u.account_type, u.parent_user_id, u.status, u.del_flag FROM biz_person p LEFT JOIN biz_org o ON p.org_id = o.org_id LEFT JOIN sys_user u ON p.user_id = u.user_id WHERE u.del_flag = '0' AND p.unit_type = 'sponsor' -- SQL 硬编码 AND u.parent_user_id = #{params.sponsorOwnerUid} -- 当前登录主账号 -- 以下均为可选条件 (前端 q.* 拼上来): [AND p.name = #{name}] [AND p.phone = #{phone}] [AND p.org_id = #{orgId}] -- 前端没传 [AND p.department = #{department}] [AND p.position = #{position}] -- 前端没传 (无对应 input) [AND p.role = #{role}] -- 前端没传 (无对应 input) [AND u.status = #{status}] ORDER BY p.person_id DESC ``` > **强制隔离三层**: `u.del_flag='0'` + `p.unit_type='sponsor'` + `u.parent_user_id=当前主账号`. INNER JOIN 天然剔除 `user_id IS NULL` 的游离 person. ### 4.2 每列对应的数据库字段 | UI 列 | `row.*` 取值 | 数据库表.字段 | 原型对照 | |---|---|---|---| | 复选框 (selection) | `row.personId` | `biz_person.person_id` | ✅ | | 姓名 | `row.name` | `biz_person.name` | ✅ | | 手机号 | `row.phone` | `biz_person.phone` | ✅ | | 工作单位 | `row.orgName` | `biz_org.org_name` (LEFT JOIN) | ✅ | | 部门 | `row.department` | `biz_person.department` | ✅ | | 职务 | `row.position` | `biz_person.position` | ✅ | | **角色** | `row.role` | `biz_person.role` (仅当 `== 'supervisor'` 显示"监察员") | ⚠️ **原型无此列** (实现自行扩展) | | **账号类型** | `row.accountType` | `sys_user.account_type` (LEFT JOIN, MAIN=主账号 / SUB=子账号) | ⚠️ **原型无此列** (实现自行扩展) | | 状态 | `row.status` | `sys_user.status` (LEFT JOIN, '0'=正常 / '1'=禁用) | ✅ (原型显示"启用"/"禁用") | | **创建时间** | `row.createTime` | `biz_person.create_time` | ⚠️ **原型无此列** | | 操作 (4 按钮) | — | — | 见 §5 | --- ## 5. 行内操作按钮 (每行) | 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | 原型对照 | |---|---|---|---|---|---| | 查看 | `onView(row)` | 跳 `/sponsor/people/detail/{personId}` (`PersonDetail.vue`) | — | — | ✅ | | 编辑 | `onEdit(row)` | 跳 `/sponsor/people/edit/{personId}` (`NewPerson.vue` 编辑模式) | 后续走 `PUT /business/person` | — | ✅ | | 禁用 / 恢复 | `onToggleStatus(row)` | `PUT /business/person` (`bizUpdate('person', { personId, userId, status })`) | service `updateByPrimaryKey`: ① `biz_personMapper.updateByPrimaryKey` (XML 中 `` 不含 status 子句, 故 biz_person 无 status 列可改) ② `sysUserMapper.updateUser(u)` 同步 status 到 sys_user | `sys_user.status` | ✅ | | **删除** | `onRemoveOne(row)` | `DELETE /business/person/{personId}` (`bizDelete('person', personId)`) | `bizPersonMapper.deleteByPrimaryKey` → `UPDATE sys_user u INNER JOIN biz_person p ... SET u.del_flag='1' WHERE p.person_id=...` 联动软删 sys_user | `sys_user.del_flag` | ⚠️ **原型无"删除"操作** | ### 5.1 禁用/恢复的真实落点 - `BizPerson.status` 在 Java 端是 `transient` (Java 注释: "非持久化字段") - mapper `updateByPrimaryKey` 的 `` 块不含 status 子句 - 真正生效的是 service 里同步 `sys_user.status` (BizPersonServiceImpl.java:115-117) --- ## 6. Java Entity ↔ 数据库表 一致性 (实地校对 2026-08-18) ### 6.1 `biz_org` 真实 DDL (MySQL `SHOW CREATE TABLE` 实测) ```sql CREATE TABLE `biz_org` ( `org_id` bigint NOT NULL AUTO_INCREMENT, `org_name` varchar(200) NOT NULL, `org_type` varchar(20) NOT NULL DEFAULT 'sponsor' COMMENT 'sponsor支持方/execution执行方', `business_nature` varchar(20) DEFAULT NULL COMMENT '企业性质 私营/国营/中外合资/外资/其他', `address` varchar(500) DEFAULT NULL COMMENT '单位地址', `tax_no` varchar(50) DEFAULT NULL, `contact_name` varchar(50) DEFAULT NULL, `contact_phone` varchar(20) DEFAULT NULL, `intent_count` int DEFAULT '0' COMMENT '意向项目数', `status` varchar(20) DEFAULT '0' COMMENT '合作状态 合作中/待签约/已停用', `create_by` varchar(64) DEFAULT '', `create_time` datetime DEFAULT NULL, `update_by` varchar(64) DEFAULT '', `update_time` datetime DEFAULT NULL, `user_id` bigint DEFAULT NULL COMMENT '主账号sys_user.user_id', PRIMARY KEY (`org_id`), UNIQUE KEY `uk_org_user_type` (`user_id`,`org_type`), KEY `idx_org_type_status` (`org_type`,`status`), KEY `idx_org_type_name` (`org_type`,`org_name`), KEY `idx_org_user_type_name` (`user_id`,`org_type`,`org_name`) ) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支持单位表' ``` > **修订**: 上一版报告说"biz_org 不存在"是错的 — 实测 DB 中表存在, 索引齐全 (UK user_id+org_type, 三个复合索引都覆盖了 mapper 里的查询路径). 旧 dump 文件 `ry0808_mysql_dump.sql` 没包含此表是因为它是后来新增的 (dump 时间 2026-08-11, 表应是之后创建的). ### 6.2 `biz_person` 真实 DDL (MySQL `SHOW CREATE TABLE` 实测) ```sql CREATE TABLE `biz_person` ( `person_id` bigint NOT NULL DEFAULT '0' COMMENT '雪花ID', `user_id` bigint DEFAULT NULL COMMENT '关联系统用户ID', `name` varchar(50) NOT NULL, `phone` varchar(20) NOT NULL, `org_id` bigint DEFAULT NULL COMMENT '所属公司ID (FK: biz_org.org_id)', `department` varchar(100) DEFAULT NULL, `position` varchar(50) DEFAULT NULL, `role` varchar(20) DEFAULT NULL COMMENT '角色 项目负责人/会议执行/监察员/会务执行', `unit_type` varchar(20) DEFAULT 'execution' COMMENT 'execution执行/sponsor支持', `create_by` varchar(64) DEFAULT '', `create_time` datetime DEFAULT NULL, `update_by` varchar(64) DEFAULT '', `update_time` datetime DEFAULT NULL, PRIMARY KEY (`person_id`), UNIQUE KEY `uk_person_phone` (`phone`), UNIQUE KEY `uk_person_user_id` (`user_id`), KEY `idx_person_role` (`role`), KEY `idx_person_unit_type` (`unit_type`), KEY `idx_person_org_id` (`org_id`), KEY `idx_person_org_role` (`org_id`,`role`) ) ``` > **修订**: 上一版"biz_person 有 work_unit / status 列"是错的 — DB 当前 **没有** `work_unit` 列 (dump 旧, 后续删除了), 也没有 `status` 列 (status 唯一来源是 sys_user). Java 实体完全跟 DB 对得上, 没有列错位. ### 6.3 BizPerson.java ↔ biz_person 表 字段对照 | 实体字段 | 类型 | 表字段 | 类型 | 一致? | |---|---|---|---|---| | personId | String | `person_id` | bigint | ✅ | | userId | Long | `user_id` | bigint | ✅ | | name | String | `name` | varchar(50) | ✅ | | phone | String | `phone` | varchar(20) | ✅ | | orgId | Long | `org_id` | bigint | ✅ | | orgName | String (JOIN) | `biz_org.org_name` (LEFT JOIN) | — | ✅ 仅展示 | | orgType | String (JOIN) | `biz_org.org_type` (LEFT JOIN) | — | ✅ 仅展示 | | department | String | `department` | varchar(100) | ✅ | | position | String | `position` | varchar(50) | ✅ | | role | String | `role` | varchar(20) | ✅ | | status | transient | **表里无此列**,真值在 sys_user.status | — | ✅ 双源已修复 (DB 只剩 sys_user) | | unitType | String | `unit_type` | varchar(20) | ✅ | | createBy/Time, updateBy/Time | 标准 | 同名 | 同 | ✅ | | accountType | String (JOIN) | `sys_user.account_type` | — | ✅ 仅展示 | | parentUserId | Long (JOIN) | `sys_user.parent_user_id` | — | ✅ 仅展示 | | loginUsername / loginPassword | transient | 写到 sys_user.user_name / 加密 password | — | ✅ 仅创建时用 | --- ## 7. 索引检查 (实测 `SHOW INDEX FROM`) ### 7.1 `biz_person` 现有索引 (7 个) | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `person_id` | ✅ | | uk_person_phone | `phone` (UNIQUE) | ✅ 手机号精确查 | | uk_person_user_id | `user_id` (UNIQUE) | ✅ JOIN sys_user | | idx_person_role | `role` | ✅ role 精确查 | | idx_person_unit_type | `unit_type` | ✅ sponsor/executor 隔离 | | idx_person_org_id | `org_id` | ✅ JOIN biz_org | | idx_person_org_role | `org_id`,`role` (复合) | ✅ 复合过滤 | > **结论**: **没有缺失的关键索引**. department / position / name 走精确匹配无索引但属于小数据, 暂可接受. 若以后按部门/职务建索引, 可新增 `idx_person_department` / `idx_person_position`. ### 7.2 `biz_org` 现有索引 (5 个) | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `org_id` | ✅ | | uk_org_user_type | `user_id`,`org_type` (UNIQUE) | ✅ 一用户一类型一公司 | | idx_org_type_status | `org_type`,`status` | ✅ | | idx_org_type_name | `org_type`,`org_name` | ✅ | | idx_org_user_type_name | `user_id`,`org_type`,`org_name` | ✅ selectMySponsorCompany | > **结论**: 索引已覆盖 mapper 里所有查询路径, 无需新增. ### 7.3 `sys_user` 现有索引 - `PRIMARY KEY (user_id)` - `KEY idx_parent_user_id (parent_user_id)` - ⚠️ `status` / `account_type` / `role_type` 仍无单独索引. 但 `selectSponsorList` 中的 `u.status = ?` 过滤在 LEFT JOIN 后命中行数本就少 (主账号的子账号不会很多), 暂可接受. --- ## 8. 与原型的差异 (实现偏离) 通过菜单定位到的原型 `proto/html/components/sponsor-people.html`, 跟 `SponsorPeople.vue` 实现相比: ### 8.1 实现新增 (原型没有) | # | 项 | 实现 | 原型 | |---|---|---|---| | 1 | "状态" 筛选下拉 | ✅ (正常/禁用) | ❌ 无 | | 2 | "重置" 按钮 | ✅ | ❌ 无 | | 3 | "角色" 列 | ✅ (只显示"监察员" tag) | ❌ 无 | | 4 | "账号类型" 列 | ✅ (主账号/子账号 tag) | ❌ 无 | | 5 | "创建时间" 列 | ✅ | ❌ 无 | | 6 | "删除" 行内按钮 | ✅ | ❌ 无 (原型只有查看/编辑/禁用-恢复) | | 7 | "批量删除" 工具栏按钮 | ✅ | ❌ 无 | | 8 | 分页 `el-pagination` | ✅ (10/20/50, jumper) | ❌ 原型不分页 (静态 5 行示例) | | 9 | 导入结果明细 dialog | ✅ (table 展示每行 ok/fail) | ❌ 原型只有 alert 提示 | ### 8.2 原型有但实现缺失 | # | 项 | 实现 | 原型 | |---|---|---|---| | 1 | "职务" 筛选输入框 | ❌ 无 (后端 mapper 里有 p.position = ? 字段, 但前端无对应输入框) | ✅ 有 (`职务: [输入框]`) | | 2 | 红色提示 `*新建时,管理员只能新建监察员` | ❌ 无 | ✅ 有 | | 3 | 红色提示 `*批量导入模板与列表表项一致` | ❌ 无 (工具栏) | ✅ 有 | | 4 | 红色提示 `*点击禁用后,状态变为禁用,按钮变成恢复;禁用后,账号无法登录,提示请联系管理员` | ❌ 无 (表格下方) | ✅ 有 | | 5 | 导入 modal 内 `取消` / `确定` 按钮 | ❌ 只有"关闭" | ✅ 有 | | 6 | 导入 modal 内 红色提示 `*批量导入模板xls与列表表项一致` | ❌ 无 | ✅ 有 | ### 8.3 文字 / 标签差异 | 位置 | 原型 | 实现 | |---|---|---| | 页面标题 (h1) | `人员管理` | breadcrumb `首页 / 人员管理` (无 h1) | | 状态 tag 文案 | `启用` / `禁用` | `正常` / `禁用` | | 查询按钮 | `查找` | `查询` | ### 8.4 总结: 实现 vs 原型 - 实现扩展性强 (新增角色/账号类型/创建时间/批量删除/分页), **超出原型范围**, 是改进 - 实现有 3 处原型有但缺失的红色提示和 1 个"职务"筛选 — **回退补漏** - "工作单位" 筛选虽在原型有, 但实现里**实际不生效** (后端未接 orgName), 这是 **bug** - 实现与原型在视觉布局上对齐 (page-card / breadcrumb / filter-form / toolbar / table 风格一致) --- ## 9. 表字段冗余 / 设计问题 (修订) 1. **`biz_org.org_name` 与 `biz_support_unit.unit_name` / `biz_execution_unit.company_name`**: 三处并存表示"单位名". **biz_org 重构后未清理旧表**. 旧表 (biz_support_unit / biz_execution_unit / biz_service_org) 仍存在 DB, 应做一次审计决定是否 DROP. 2. **`accountType / parentUserId / orgName / orgType` 在 BizPerson 上 `LEFT JOIN`**: 是只读展示字段, 但 mapper XML 把它们塞到 resultMap, 前端依赖. 若未来要分库 / 视图, 会很别扭. 可考虑抽出 `BizPersonListVO` 与实体分离. 3. **`biz_person.user_id` 唯一约束 (`uk_person_user_id`)**: 一个 sys_user 子账号最多对应一个 biz_person, 符合"一人一员"语义, ✅ 设计正确. 4. **`biz_person.org_id` 与 `biz_org.user_id + org_type` 的关系**: 一个 biz_person 属于一个 org, org 通过 `(user_id, org_type)` 唯一确定其主账号. 主账号注册时一条 biz_org 记录, 子账号的 org_id 必须等于主账号的 org_id — 当前 service (BizPersonServiceImpl.java:54-70) 实现了兜底逻辑, ✅ 设计闭环. --- ## 10. 待修复列表 (按优先级, 修订版) | # | 问题 | 文件 | 修复建议 | 严重度 | |---|---|---|---|---| | 1 | ~~"工作单位" 筛选不生效 (后端未接)~~ | `BizPersonMapper.xml` 3 处 SELECT | ✅ **已修复** — 加 `o.org_name like concat('%', #{orgName}, '%')`, 实测通过 (见 §2.1) | ~~P0~~ | | 1.5 | ~~历史数据 `biz_person.org_id=NULL` 导致"工作单位"列空~~ | (inline mysql, 不落 .sql 文件) | ✅ **已修复** — 数据回填 (11 → 0 NULL), 见 §2.2 | ~~P0~~ | | 2 | 缺"职务"筛选输入框 | `SponsorPeople.vue` filter-form | 加 `` | P1 | | 3 | 缺原型 3 处红色提示 | `SponsorPeople.vue` toolbar / 表格下 / import modal | 按原型补 `*新建时,管理员只能新建监察员` 等 | P2 | | 4 | 导入 modal 缺"取消/确定"按钮 | `SponsorPeople.vue` import dialog | 按原型补按钮 (当前只有"关闭") | P2 | | 5 | 状态文案与原型不一致 (启用 vs 正常) | `SponsorPeople.vue` | 与产品对齐: 用 `正常` 还是 `启用` | P3 | | 6 | 旧表 (biz_support_unit / biz_execution_unit / biz_service_org) 与 biz_org 共存 | DB DDL | 审计后 DROP 旧表 (确认无引用) | P3 | --- ## 11. 引用清单 - 前端: `ry-vue3/src/views/sponsor/SponsorPeople.vue` - 前端 API: `ry-vue3/src/api/business/person.js` (`listSponsorPerson`) - 路由: `ry-vue3/src/router/index.js` 第 113-124 行 (sponsor 路由块) - 菜单: `ry-vue3/src/layout/AdminLayout.vue` 第 123-129 行 (sponsor MENU) - Controller: `ry-api/ruoyi-business/.../controller/BizPersonController.java` (`/sponsorList`, `/sponsorImport`, `/sponsorImportTemplate`) - Service: `ry-api/ruoyi-business/.../service/impl/BizPersonServiceImpl.java` - Mapper 接口: `ry-api/ruoyi-business/.../mapper/BizPersonMapper.java` - Mapper XML: `ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml` - 实体: `BizPerson.java`, `BizOrg.java`, `BizPersonImportVO.java` - 数据库配置: `ry-api/ruoyi-admin/src/main/resources/application-druid.yml` - 库结构 (旧,仅供参考): `ry0808_mysql_dump.sql` (与现行 DB 存在差异 — biz_org / biz_person 部分字段未在此 dump) - 原型菜单入口: `proto/html/sponsor.html` (sponsor 角色工作台布局) - 原型组件: `proto/html/components/sponsor-people.html` ← **本次对比基准**