Files
guoju0808/_self/executor_people.md
T
郭庆泰andClaude e59b512e4b fix(person): 子账号继承主账号 role_type + Login 不再硬编 leader
问题: sponsor 主账号新建的子账号 role_type 没写入,
      被 sys_user.role_type DEFAULT 'executor' 覆盖,
      导致子账号进入 executor 视角;另外 Login.vue
      autoRole/短信登录都硬编 'leader' 兜底, getInfo
      失败时静默跳 /leader/home。

修复:
- BizPersonServiceImpl.insert 读主账号 SysUser,
  把 mainUser.getRoleType() 写到新子账号
  (SysUserMapper.xml insertUser 已支持 role_type 列)
- Login.vue: 短信登录 fallbackRole 改 '',
  autoRole 不匹配返回 '', afterLogin 无 role 跳 /login
  (不再 fallback /leader/home)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 21:35:51 +08:00

22 KiB

executor / People — 端到端页面技术审查

审查日期: 2026-08-18 审查范围: 前端 → 后端 → 数据库 → 原型 审查目标: ry-vue3/src/views/executor/People.vue


0. 四跳定位

文件 / 位置 命中
① 角色菜单 ry-vue3/src/layout/AdminLayout.vue:119 { path: '/executor/people', title: '人员管理', icon: User, requireMain: true }
② 路由 ry-vue3/src/router/index.js:101-110 父路由 meta: { role: 'executor' } → 子路由 { path: 'people', name: 'executor-people', component: () => import('@/views/executor/People.vue'), meta: { title: '人员管理' } }
③ 组件 ry-vue3/src/views/executor/People.vue 审查目标 (1-171 行)
④ 原型 proto/html/components/people.html proto/html/executor.html:206data-page="components/people.html"

角色定义: AdminLayout.vue:65executor: '执行人'


1. 主要功能与可见性

1.1 主要功能

功能 前端入口 后端接口 数据表
列表 + 筛选 + 分页 loadList() (onMounted) GET /business/person/executorList biz_person + LEFT JOIN biz_org, sys_user
新建人员 goNew() → 跳 /executor/people/new (另页 NewPerson.vue) biz_person + sys_user (新建 SUB 子账号)
编辑人员 goEdit(row) → 跳 /executor/people/edit/:id PUT /business/person biz_person + 同步 sys_user
查看详情 onView(row) (本地 dialog) GET /business/person/{id} 同列表
启用 / 禁用 onToggleStatus(row, action) PUT /business/person (传 status + userId) biz_person (无字段,status transient) + 同步 sys_user.status
批量导入 submitImport() (dialog) POST /business/person/executorImport (本页面未调用,UI 占位) biz_person + sys_user

1.2 可见性三层过滤

校验 行为
前端菜单 AdminLayout.vue:119 requireMain: true 仅主账号可见 (子账号在菜单里看不到该入口)
路由守卫 router/index.js:101 meta.role = 'executor' 仅 executor 角色可进入
后端 SQL BizPersonMapper.xml:170-187 selectExecutorList 硬编码 p.unit_type = 'executor' + (u.parent_user_id = #{executorOwnerUid} OR u.user_id = #{executorOwnerUid}),前端绕不开

不能看到本页面的人群:

  • 其他角色映射到自己的同名页: admin / managerrouter/index.js:41-43, 79-81executor-people (实际是"全公司视角",看所有执行方的人员);sponsor 有自己的 sponsor-people (router:119)
  • executor 子账号 — 菜单项 requireMain: true,前端就不渲染

数据隔离机制 (核心):

-- BizPersonMapper.xml:170-187 (selectExecutorList)
where u.del_flag = '0'
  and p.unit_type = 'executor'                                  -- SQL 硬编码,前端改 unit_type 也无效
  and (u.parent_user_id = #{params.executorOwnerUid}           -- 主账号视角: 看到我的子账号人员
       or u.user_id = #{params.executorOwnerUid})               -- 主账号自己也是 person,避免漏自己

⚠️ 注意 executorOwnerUidBizPersonController.java:81-89getUserId() 注入 — 后端取的是当前登录用户,前端无法注入他人 UID。


2. 筛选项 (filter-form)

UI label 控件 绑定字段 后端 SQL 命中表.字段 选项来源 原型对照
姓名 el-input q.name mapper.xml:177 <if test="name != null and name != ''"> and p.name like concat('%', #{name}, '%')</if> biz_person.name (模糊) 用户输入 原型有
手机号 el-input q.phone mapper.xml:178 and p.phone = #{phone} biz_person.phone (精确) 用户输入
所属公司 el-input q.orgName mapper.xml:180 and o.org_name like concat('%', #{orgName}, '%')</if> biz_org.org_name (模糊) 用户输入 ⚠️ 原型 label "工作单位",实现改成 "所属公司"
部门 el-input q.department mapper.xml:181 and p.department = #{department} biz_person.department 用户输入
职务 el-input q.position mapper.xml:182 and p.position = #{position} biz_person.position 用户输入

附注 — 角色 / 状态列:

  • 前端不展示"角色" / "状态"作为筛选项 (但表格里作为列展示),后端 SQL mapper.xml:183-184 提供 <if> 支持,但前端没传参

3. 工具栏按钮 (toolbar)

按钮 功能 接口 涉及表 原型对照
查找 触发列表刷新 无 (本地)
重置 清空筛选 + 回 pageNum=1 无 (本地) ⚠️ 原型无"重置"按钮
新建人员 跳独立页 /executor/people/new (另页) biz_person + sys_user
批量导入 弹 dialog (本页面 UI 占位,未实际上传) (UI 占位) (原型 alert,实现也 alert)

⚠️ P2: downloadTemplate / submitImport 都只 ElMessage.info 占位,没有真实 Excel 下载 / 上传逻辑。BizPersonController.java:152-164 已实现 /executorImportTemplate/executorImport 接口,但前端未对接。


4. 表格 (el-table)

4.1 数据来源

BizPersonMapper.xml:170-187 (selectExecutorList):

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 as user_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 = 'executor'
  and (u.parent_user_id = #{params.executorOwnerUid}
       or u.user_id = #{params.executorOwnerUid})
  [ and p.name like concat('%', #{name}, '%') ]
  [ and p.phone = #{phone} ]
  [ and p.org_id = #{orgId} ]
  [ and o.org_name like concat('%', #{orgName}, '%') ]
  [ and p.department = #{department} ]
  [ and p.position = #{position} ]
  [ and p.role = #{role} ]
  [ and u.status = #{status} ]
order by p.person_id desc

4.2 列映射

列名 row.* DB 表.字段 原型对照
姓名 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 (translatePersonRole 转中文)
状态 row.status sys_user.status (LEFT JOIN)
操作

附注 — status 字段不在 biz_person: BizPerson.java:51-53 标注 status 字段为 transient:

/** 启停状态 '0'/'1' (前端 toggle 用, BizPersonServiceImpl 同步到 sys_user.status) - 非持久化字段 */
@com.fasterxml.jackson.annotation.JsonProperty("status")
private transient String status;

所以表格里的 row.statussys_user.status 通过 LEFT JOIN 进来,不要尝试往 biz_person.status UPDATE(不存在这列)。


5. 行内操作按钮

按钮 触发函数 接口 后端动作 涉及表.字段 原型对照
查看 onView(row) 无 (本地弹 dialog)
编辑 goEdit(row) /executor/people/edit/${row.personId} PUT /business/person (另页) biz_person.* + 同步 sys_user.nick_name/phonenumber/status
禁用 / 恢复 onToggleStatus(row, action) PUT /business/person (本页面直调) BizPersonServiceImpl.updateByPrimaryKey (impl:106-118): 先 updateByPrimaryKey 改 biz_person 业务字段,if (entity.getUserId() != null) 同步 sys_user.status sys_user.status (NOT biz_person.status,后者不存在)

保护逻辑 (People.vue:46-50):

<template v-if="row.userId !== store.user?.userId">
  <el-button v-if="(row.status || '正常') === '正常'" ... >禁用</el-button>
  ...
</template>

主账号的"本人"行不显示禁用按钮 — 防主账号把自己禁用造成锁定。但没禁掉子账号主账号的(主账号不是 executor 自己,这里只针对本主账号自己)

真实落点 — 同步到 sys_user: BizPersonServiceImpl.java:106-118 (updateByPrimaryKey):

int n = bizPersonMapper.updateByPrimaryKey(entity);
if (n > 0 && entity.getUserId() != null) {
    SysUser u = new SysUser();
    u.setUserId(entity.getUserId());
    u.setNickName(entity.getName());
    u.setPhonenumber(entity.getPhone());
    u.setUpdateBy(SecurityUtils.getUsername());
    if (entity.getStatus() != null) {
        u.setStatus(entity.getStatus());
    }
    sysUserMapper.updateUser(u);
}

⚠️ 关键: entity.getUserId() == null 时,不会同步 sys_user.status。前端 People.vue:144 已传 userId


6. Java Entity ↔ 数据库表 一致性

6.1 biz_person DDL (DB 实测 2026-08-18)

CREATE TABLE `biz_person` (
  `person_id`   bigint NOT NULL DEFAULT '0',
  `user_id`     bigint DEFAULT NULL,
  `name`        varchar(50) NOT NULL,
  `phone`       varchar(20) NOT NULL,
  `org_id`      bigint DEFAULT NULL,
  `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`)
)

6.2 biz_org DDL (DB 实测)

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,
  `address`        varchar(500) DEFAULT NULL,
  `tax_no`         varchar(50) DEFAULT NULL,
  `contact_name`   varchar(50) DEFAULT NULL,
  `contact_phone`  varchar(20) DEFAULT NULL,
  `intent_count`   int DEFAULT '0',
  `status`         varchar(20) DEFAULT '0',
  `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`)
)

6.3 BizPerson Entity ↔ biz_person

实体字段 Java 类型 表字段 表类型 一致? 备注
personId String person_id bigint 类型不一致: Java String vs DB bigint — 但 MyBatis resultMap 兼容 (driver 自动转),⚠️ 性能上不建议
name String name varchar(50) NOT NULL
phone String phone varchar(20) NOT NULL 有 uk_person_phone 唯一约束
orgId Long org_id bigint LEFT JOIN biz_org 时填 orgName/orgType
orgName String — (JOIN) 仅展示字段,resultMap 映射到 org_name
orgType String — (JOIN) 仅展示
department String department varchar(100)
position String position varchar(50)
role String role varchar(20)
unitType String unit_type varchar(20)
userId Long user_id bigint 有 uk_person_user_id 唯一约束
status String (transient) — (不存在) 实际写 sys_user.status
accountType String (JOIN) LEFT JOIN sys_user.account_type
parentUserId Long (JOIN) LEFT JOIN sys_user.parent_user_id
loginUsername String (transient) 创建 sys_user 子账号用
loginPassword String (transient) 创建后丢弃
delFlag String (return null) ⚠️ getter 永远返回 null — 软删走 sys_user.del_flag='1'

6.4 修订:之前在 _self/sponsor_people.md 写过的"work_unit 已删除"假设

实测 DB 后: biz_person 表里没有 work_unit 字段,但也没有 biz_person.work_unit 这张废表;目前存"所属公司"的方式是 biz_person.org_id LEFT JOIN biz_org。所以现在的设计是外键指向公司表,比之前假设的"work_unit 字段"更规范


7. 索引检查

7.1 biz_person (DB 实测)

Key 用途覆盖 评估
PRIMARY person_id updateByPrimaryKey WHERE person_id = ?, selectByPrimaryKey OK
uk_person_phone phone (UNIQUE) insert 唯一性,SELECT WHERE phone = ? 精确 OK
uk_person_user_id user_id (UNIQUE) insert 唯一性,JOIN sys_user 关键 OK
idx_person_role role mapper:183 and p.role = #{role},idx_person_org_role 已覆盖 OK (但有冗余)
idx_person_unit_type unit_type selectList:49 and p.unit_type = #{unitType},selectSponsorList/selectExecutorList 硬编码 unit_type='sponsor'/'executor' OK
idx_person_org_id org_id selectByPrimaryKey JOIN,selectList:52 and p.org_id = #{orgId} OK
idx_person_org_role (org_id, role) countAdminByOrgId (impl 中可能) OK

7.2 biz_org

Key 用途覆盖 评估
PRIMARY org_id JOIN OK
uk_org_user_type (user_id, org_type) 主账号唯一公司 OK
idx_org_type_status (org_type, status) OK
idx_org_type_name (org_type, org_name) selectList:53 LIKE '%org_name%'但 LIKE 前缀模糊会走全索引扫描(若 % 开头,索引失效) ⚠️ 暂可接受(org 数量小)
idx_org_user_type_name (user_id, org_type, org_name) OK

7.3 sys_user (已实测,见 _self/sponsor_account.md §7)

Key 评估
PRIMARY user_id
idx_parent_user_id parent_user_id SELECT 子账号
phonenumber 缺(暂可接受,26 行)

⚠️ selectExecutorList 的关键过滤 (u.parent_user_id = X OR u.user_id = X) 走 PRIMARY 主键索引 (u.user_id = X) + idx_parent_user_id,虽然 OR 走全索引扫描但小表 OK;破万后考虑重写为 UNION ALL。


8. 与原型差异 (proto/html/components/people.html)

8.1 实现新增 (原型没有)

  • 分页 (el-pagination) — 原型 8 行静态,实现加分页支持
  • 数据隔离 — 原型无账号概念,实现按主账号/子账号隔离
  • 行内按钮"恢复"(启用) — 原型叫"启用",实现叫"恢复";语义一致
  • 本人禁用按钮隐藏 — 业务逻辑(防主账号自禁用),原型无
  • status 状态列颜色 tag — 扩展
  • 角色列 tag — 原型是普通 span,实现是 el-tag + type

8.2 原型有但实现缺失

  • 顶部 h1 "人员管理" + breadcrumb — 实现只用 class="page-sub" 一行小字"管理执行方人员,分配会务执行角色",无 h1,无面包屑;与项目其它页风格不一致(其它页用 breadcrumb)
  • 复选框列 — 原型表头第一列是 checkbox(全选/单选),实现完全删了。原型注释 (people.html:30) 自承"整合为整列",但实际实现把整列删了
  • 红字提示 *角色:执行方管理员新建人员时,默认只有会议执行的角色 — 原型 people.html:282-284 有提示卡片,实现无 ⚠️

8.3 文字 / 标签差异

原型 实现 评估
筛选项 label "工作单位:" "所属公司" ⚠️ 改了语义,后端 SQL LIKE %org_name% 含义不变,但用户口径不一致
列表列名 "工作单位" "所属公司" ⚠️ 同上
操作按钮文字 "启用" "恢复" 轻微差异,语义等价
表头 无角色/状态标题 color "角色"/"状态"独立列 改进

8.4 总结

  • 方向: 实现做了减法 + 加法 — 删了复选框 / 标题 / 提示,加了分页 / 隔离 / 状态管理
  • 缺失最严重的: 顶部 h1 + breadcrumb(项目其它页都是这个风格);"工作单位"→"所属公司" 是有意的口径调整(后端表名 biz_org.org_name)

9. 表字段冗余 / 设计问题

# 问题 文件 说明
1 BizPerson.status 字段是 transient,前端 toggle 后通过 service 同步到 sys_user.status BizPerson.java:51-53 + BizPersonServiceImpl.java:106-118 字段不持久化,语义跨表,易出 bug — 但前端已规范传 userId,目前安全 ⚠️
2 BizPerson.delFlag getter 永远返回 null BizPerson.java:84-86 设计意图是"软删走 sys_user",getter 留作兼容 — 但保留这个 getter 容易误导
3 BizPerson.personId Java 类型是 String,DB 是 bigint BizPerson.java:13 vs DDL MyBatis 自动转换 OK,但后续若有 personId > 100 这类比较会 ClassCastException ⚠️
4 biz_person.unit_type 默认值 'execution',但 executor 端 SQL 硬编码 unit_type='executor' DDL vs mapper.xml:174 DEFAULT 是历史值,新代码已用 'executor' 字符串;DEFAULT 未统一
5 People.vue:144 调用 bizUpdate('person', ...)userId强依赖,没传则 status 不更新 BizPersonServiceImpl.java:108 这是隐式契约,前端注释 (People.vue:142-143) 已标,但没有任何后端校验(传 null 会 silently noop)
6 selectExecutorList 包含 u.status = ? 过滤条件,前端未用,SQL 永远不过滤 BizPersonMapper.xml:184 是预留扩展 OK,但注意 status 列展示不是用这个条件 — 展示来自 select *(全字段)
7 删除走 sys_user.del_flag='1',业务侧 biz_person 记录永远存在;若要"恢复"还得改 del_flag='0',当前无"恢复已删人员" UI BizPersonMapper.xml:122-145 是设计选择,但产品流程上若有"误删恢复"需求会找不到入口

10. 待修复列表 (按优先级)

# 问题 文件 / 位置 修复建议 严重度
1 顶部缺 h1 + breadcrumb,与项目其它页风格不一致 People.vue:3 加 breadcrumb 首页 / 人员管理 + (可选) h1,跟 manager/executor/Overview 风格统一 P2
2 批量导入 dialog UI 占位,未对接 /executorImport 接口 People.vue:64-80, 154-158 调用 POST /business/person/executorImport + 模板下载 GET /executorImportTemplate;返回的 errorRows 渲染成明细 P2
3 缺原型红字提示 "*角色:执行方管理员新建人员时,默认只有会议执行的角色" People.vue:18-23 batch-bar 在表格上方或表单区补一行提示 P2
4 原型有复选框列(全选/批量操作),实现完全删了 People.vue:26-53 如不需批量操作,可保留一行 checkbox 列+ disabled;若产品决定不做批量,需与原型对齐(在原型文件加 "调整说明" 备注) P2
5 biz_person.unit_type 默认值 'execution' 与 executor SQL 'executor' 不一致 DDL DEFAULT vs mapper.xml 把 DDL DEFAULT 改 'executor' (或同步 sponsor SQL) P3
6 BizPerson.delFlag getter 返回 null 是误导 BizPerson.java:84-86 移除 getter/setter 或改名 getSysUserDelFlag() P3
7 BizPerson.personId 用 String 存 bigint BizPerson.java:13 改为 Long,前端 personId 转 Number P3
8 serviceImpl updateByPrimaryKey 在 userId=null 时 silently noop status 同步 BizPersonServiceImpl.java:108 加日志 warn 或抛异常提示前端 P3
9 "工作单位" → "所属公司" 改名是 SQL 含义,产品口径需统一 前端文案 与产品确认:对外叫"所属公司"还是"工作单位" P3

11. 引用清单

用途 文件 行号
角色菜单 ry-vue3/src/layout/AdminLayout.vue 65, 115-122
路由 ry-vue3/src/router/index.js 101-110
路由守卫 ry-vue3/src/router/index.js 134+
审查目标 ry-vue3/src/views/executor/People.vue 1-171
前端 API 封装 ry-vue3/src/api/business/person.js 24-31 (listExecutorPerson)
后端 Controller ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java 39-62 (BUSINESS_MAIN_ROLES), 81-89 (executorList), 152-164 (executorImport)
后端 Mapper XML ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml 4-22 (resultMap), 24-38 (selectFields), 105-120 (updateByPrimaryKey), 122-145 (deleteByPrimaryKey/Keys), 168-187 (selectExecutorList), 189-194 (countAdminByOrgId)
后端 Service 实现 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizPersonServiceImpl.java 96-98 (SysUser 构造), 106-118 (updateByPrimaryKey + sys_user 同步)
Entity ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPerson.java 9-108 (字段定义 + setter/getter,行 51-53 status 是 transient)
DDL (DB 实测) mysql -h 127.0.0.1 -e 'USE guoju0808; SHOW CREATE TABLE biz_person\G' 2026-08-18
DDL (DB 实测) mysql -h 127.0.0.1 -e 'USE guoju0808; SHOW CREATE TABLE biz_org\G' 2026-08-18
原型 proto/html/components/people.html 全文 434 行 (含详细注释)
原型菜单链 proto/html/executor.html 206 (data-page="components/people.html")
记忆 (角色真相) memory/sys-user-role-type-truth.md role_type 是单一可信源
记忆 (sponsor 同模式) commit 50c2678 executorList 加 executorList 兜底 (sponsor 同模式)