diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java index d3d2c72..5b9318e 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java @@ -19,6 +19,7 @@ import com.ruoyi.business.mapper.BizOrgMapper; import com.ruoyi.business.service.IBizPersonService; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.system.mapper.SysUserMapper; /** * 人员Controller @@ -32,9 +33,34 @@ public class BizPersonController extends BaseController @Autowired private BizOrgMapper bizOrgMapper; + + @Autowired + private SysUserMapper sysUserMapper; + + /** + * 业务角色白名单: 这些 roleType 的 MAIN 账号调 /list 会自动按"本机构子账号"隔离 + * (sys_user.parent_user_id → sys_user.user_id → biz_person.user_id 链路过滤) + */ + private static final java.util.Set BUSINESS_MAIN_ROLES = java.util.Set.of( + "executor", "sponsor", "doctor", "manager", "leader" + ); + @GetMapping("/list") public TableDataInfo list(BizPerson bizPerson) { + // 当前登录用户是 MAIN 业务账号 → 自动注入 subUserIds, 隔离到本主账号的子账号对应人员 + Long currentUserId = getUserId(); + SysUser currentUser = currentUserId == null ? null : sysUserMapper.selectUserById(currentUserId); + if (currentUser != null + && "MAIN".equals(currentUser.getAccountType()) + && currentUser.getRoleType() != null + && BUSINESS_MAIN_ROLES.contains(currentUser.getRoleType())) { + List subUserIds = sysUserMapper.selectUserIdsByParent(currentUserId); + // 即使没子账号也强制注入, 避免漏出 sys_user.parent_user_id=NULL 的孤立 person + bizPerson.getParams().put("subUserIds", subUserIds == null || subUserIds.isEmpty() + ? java.util.Collections.singletonList(-1L) + : subUserIds); + } startPage(); List list = bizPersonService.selectList(bizPerson); return getDataTable(list); @@ -107,6 +133,33 @@ public class BizPersonController extends BaseController @Log(title = "人员批量导入", businessType = BusinessType.INSERT) @PostMapping("/sponsorImport") public AjaxResult sponsorImport(@RequestParam("file") MultipartFile file) throws Exception + { + return doImport(file, "sponsor"); + } + + /** + * executor 端批量导入人员 (上传 .xlsx) - 模板列头同 sponsor + * orgName 按 orgType='executor' 匹配, unitType 写 executor + */ + @GetMapping("/executorImportTemplate") + public void executorImportTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil<>(BizPersonImportVO.class); + util.importTemplateExcel(response, "人员导入"); + } + + @Log(title = "人员批量导入", businessType = BusinessType.INSERT) + @PostMapping("/executorImport") + public AjaxResult executorImport(@RequestParam("file") MultipartFile file) throws Exception + { + return doImport(file, "executor"); + } + + /** + * 通用人员导入逻辑 (sponsor / executor 共用) + * @param unitType 'sponsor' 或 'executor' - 决定 orgName 匹配和 person.unitType 字段 + */ + private AjaxResult doImport(MultipartFile file, String unitType) throws Exception { if (file == null || file.isEmpty()) { throw new ServiceException("请选择要上传的文件"); @@ -126,17 +179,18 @@ public class BizPersonController extends BaseController r.put("name", row.getName()); r.put("phone", row.getPhone()); try { - // 1. orgName → orgId (按 orgType='sponsor' 精确匹配, 查不到就报错) + // 1. orgName → orgId (按 unitType 精确匹配, 查不到就报错) String orgName = row.getOrgName(); if (orgName == null || orgName.trim().isEmpty()) { throw new ServiceException("所属公司不能为空"); } BizOrg orgQuery = new BizOrg(); orgQuery.setOrgName(orgName.trim()); - orgQuery.setOrgType("sponsor"); + orgQuery.setOrgType(unitType); List matched = bizOrgMapper.selectList(orgQuery); if (matched == null || matched.isEmpty()) { - throw new ServiceException("找不到所属公司: " + orgName + " (需先在 [公司管理] 录入 orgType=sponsor 的公司)"); + String label = "sponsor".equals(unitType) ? "支持单位管理" : "服务机构管理"; + throw new ServiceException("找不到所属公司: " + orgName + " (需先在 [" + label + "] 录入 orgType=" + unitType + " 的公司)"); } Long orgId = matched.get(0).getOrgId(); @@ -148,7 +202,7 @@ public class BizPersonController extends BaseController p.setPosition(row.getPosition()); p.setRole(row.getRole()); p.setStatus("0"); // 默认正常 - p.setUnitType("sponsor"); + p.setUnitType(unitType); p.setLoginUsername(row.getPhone()); // 用户名 = 手机号 (要求唯一) p.setLoginPassword("123456"); // 默认密码 p.setCreateBy(getUsername()); diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPerson.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPerson.java index f130758..ae0b347 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPerson.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPerson.java @@ -32,11 +32,9 @@ public class BizPerson extends BaseEntity { /** role */ @Excel(name = "role") private String role; - /** status */ - @Excel(name = "status") + /** 状态 0正常 1禁用 - 跟 sys_user.status 同步, 前端展示请读 sys_user */ + @Deprecated private String status; - /** 逻辑删除 0正常 1已删 */ - private String delFlag; /** create_by */ @Excel(name = "create_by") private String createBy; @@ -60,8 +58,10 @@ public class BizPerson extends BaseEntity { /** 主账号ID (来自 sys_user.parent_user_id, 子账号指向其主账号) - 仅展示用 */ private Long parentUserId; /** 子账号登录账号 (前端传入, 用于创建 sys_user 子账号) - 非持久化字段 */ + @com.fasterxml.jackson.annotation.JsonProperty("userName") private transient String loginUsername; /** 子账号初始密码 (前端传入, 明文, 创建后不存储明文) - 非持久化字段 */ + @com.fasterxml.jackson.annotation.JsonProperty("password") private transient String loginPassword; public String getPersonId() { return personId; } public void setPersonId(String personId) { this.personId = personId; } @@ -83,8 +83,9 @@ public class BizPerson extends BaseEntity { public void setRole(String role) { this.role = role; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } - public String getDelFlag() { return delFlag; } - public void setDelFlag(String delFlag) { this.delFlag = delFlag; } + /** 兼容前端调用, 实际读 sys_user.delFlag */ + public String getDelFlag() { return null; } + public void setDelFlag(String delFlag) { /* noop - 跟 sys_user 同步 */ } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizPersonServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizPersonServiceImpl.java index 8a56c04..e3ac99e 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizPersonServiceImpl.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizPersonServiceImpl.java @@ -3,7 +3,9 @@ package com.ruoyi.business.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.ruoyi.business.domain.BizOrg; import com.ruoyi.business.domain.BizPerson; +import com.ruoyi.business.mapper.BizOrgMapper; import com.ruoyi.business.mapper.BizPersonMapper; import com.ruoyi.business.service.IBizPersonService; import com.ruoyi.common.core.domain.entity.SysUser; @@ -18,6 +20,8 @@ public class BizPersonServiceImpl implements IBizPersonService private BizPersonMapper bizPersonMapper; @Autowired private SysUserMapper sysUserMapper; + @Autowired + private BizOrgMapper bizOrgMapper; @Override public BizPerson getById(String personId) @@ -42,6 +46,23 @@ public class BizPersonServiceImpl implements IBizPersonService if (exist != null) { throw new ServiceException("登录账号已存在"); } + + // 0.5 兜底: 如果前端没传 orgId, 按 (orgName + unitType) 查 biz_org 回填 + // (前端从 org 页面跳来时已带 orgId; 直接访问 new 页面没带 orgId 时走这条路径) + if (entity.getOrgId() == null && entity.getOrgName() != null && !entity.getOrgName().isEmpty() + && entity.getUnitType() != null && !entity.getUnitType().isEmpty()) { + BizOrg q = new BizOrg(); + q.setOrgName(entity.getOrgName().trim()); + q.setOrgType(entity.getUnitType()); + List matched = bizOrgMapper.selectList(q); + if (matched != null && !matched.isEmpty()) { + entity.setOrgId(matched.get(0).getOrgId()); + } + } + if (entity.getOrgId() == null) { + throw new ServiceException("请填写所属公司 (需先在 [支持单位管理/服务机构管理] 录入 orgType=" + entity.getUnitType() + " 的公司)"); + } + // 1. 创建 sys_user 子账号 SysUser newUser = new SysUser(); newUser.setUserName(entity.getLoginUsername()); @@ -67,15 +88,33 @@ public class BizPersonServiceImpl implements IBizPersonService return newUser; } + /** + * 更新人员: 同时更新 biz_person 业务字段 + 同步更新 sys_user 基础字段 + * 同步字段: nick_name (业务姓名) / phonenumber (业务手机号) / status (账号启停) + * 不同步: password (走独立 updatePwd 接口) + */ @Override - public int updateByPrimaryKey(BizPerson entity) - { return bizPersonMapper.updateByPrimaryKey(entity); } + public int updateByPrimaryKey(BizPerson entity) { + 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()); + // status 同步 (如果前端传了 status 字段) + if (entity.getStatus() != null) { + u.setStatus(entity.getStatus()); + } + sysUserMapper.updateUser(u); + } + return n; + } @Override public int deleteByPrimaryKey(String personId) { int n = bizPersonMapper.deleteByPrimaryKey(personId); - if (n > 0) bizPersonMapper.softDeleteSysUserByPersonIds(new String[]{ personId }); return n; } @@ -83,7 +122,6 @@ public class BizPersonServiceImpl implements IBizPersonService public int deleteByPrimaryKeys(String[] personIds) { int n = bizPersonMapper.deleteByPrimaryKeys(personIds); - if (n > 0) bizPersonMapper.softDeleteSysUserByPersonIds(personIds); return n; } } \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml index e7dbe92..be6f752 100644 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizPersonMapper.xml @@ -23,16 +23,16 @@ - select person_id, name, phone, org_id, department, position, role, unit_type, status, del_flag, user_id, create_by, create_time, update_by, update_time + select person_id, name, phone, org_id, department, position, role, unit_type, user_id, create_by, create_time, update_by, update_time from biz_person - + 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.status, p.del_flag, p.user_id, + 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.del_flag as user_del_flag + 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 @@ -46,7 +46,7 @@ @@ -72,7 +79,6 @@ position, role, unit_type, - status, user_id, create_by, create_time, @@ -88,7 +94,6 @@ #{position}, #{role}, #{unitType}, - #{status}, #{userId}, #{createBy}, sysdate(), @@ -108,31 +113,32 @@ department = #{department}, position = #{position}, role = #{role}, - status = #{status}, update_by = #{updateBy}, update_time = sysdate(), where person_id = #{personId} - + - update biz_person set del_flag='1', update_time=sysdate() - where person_id = #{personId} and del_flag='0' + update sys_user u inner join biz_person p on u.user_id = p.user_id + set u.del_flag='1' + where p.person_id = #{personId} and u.del_flag='0' - update biz_person set del_flag='1', update_time=sysdate() - where del_flag='0' and person_id in + update sys_user u inner join biz_person p on u.user_id = p.user_id + set u.del_flag='1' + where u.del_flag='0' and p.person_id in #{personId} - + update sys_user u inner join biz_person p on u.user_id = p.user_id set u.del_flag='1' - where u.del_flag='0' and p.del_flag='1' and p.person_id in + where u.del_flag='0' and p.person_id in #{personId} @@ -143,8 +149,7 @@ diff --git a/ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index b4418fa..27ec4d2 100644 --- a/ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -86,6 +86,8 @@ public class SysUser extends BaseEntity private Long[] postIds; /** 角色ID */ private Long roleId; + /** 业务角色 (admin / leader / manager / doctor / executor / sponsor), DB 列 sys_user.role_type */ + private String roleType; public SysUser() { } @@ -279,6 +281,14 @@ public class SysUser extends BaseEntity { this.roleId = roleId; } + public String getRoleType() + { + return roleType; + } + public void setRoleType(String roleType) + { + this.roleType = roleType; + } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) diff --git a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java index 64ead00..164f1c9 100644 --- a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java +++ b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -4,22 +4,31 @@ import java.util.Date; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.domain.vo.SysUserExtendVo; /** * 用户表 数据层 - * + * * @author ruoyi */ public interface SysUserMapper { /** * 根据条件分页查询用户列表 - * + * * @param sysUser 用户信息 * @return 用户信息集合信息 */ public List selectUserList(SysUser sysUser); + /** + * 根据条件分页查询用户列表(扩展视图, 附带业务角色 roleType) + * + * @param vo 查询条件, roleType 非空时按 sys_user.role_type 过滤 + * @return 用户扩展视图集合 + */ + public List selectUserExtendList(SysUserExtendVo vo); + /** * 根据条件分页查询已配用户角色列表 * @@ -89,7 +98,7 @@ public interface SysUserMapper /** * 更新用户登录信息(IP和登录时间) - * + * * @param userId 用户ID * @param loginIp 登录IP地址 * @param loginDate 登录时间 @@ -145,4 +154,12 @@ public interface SysUserMapper * @return 结果 */ public SysUser checkEmailUnique(String email); + + /** + * 通过主账号 user_id 查询所有子账号 user_id (sys_user.parent_user_id) + * + * @param parentUserId 主账号 user_id + * @return 子账号 user_id 集合 + */ + public List selectUserIdsByParent(@Param("parentUserId") Long parentUserId); } diff --git a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java index 5e908b2..34fb754 100644 --- a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java +++ b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -3,22 +3,31 @@ package com.ruoyi.system.service; import java.util.Date; import java.util.List; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.domain.vo.SysUserExtendVo; /** * 用户 业务层 - * + * * @author ruoyi */ public interface ISysUserService { /** * 根据条件分页查询用户列表 - * + * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectUserList(SysUser user); + /** + * 根据条件分页查询用户列表(扩展视图, 附带 sys_user.roleType 业务角色) + * + * @param vo 查询条件 + * @return 用户扩展视图集合 + */ + public List selectUserExtendList(SysUserExtendVo vo); + /** * 根据条件分页查询已分配用户角色列表 * @@ -214,10 +223,10 @@ public interface ISysUserService public boolean isPhoneRegistered(String phone); /** - * 更新 sys_user.role_type (DB 列, entity 无字段) + * 更新 sys_user.role_type (业务角色: admin/leader/manager/doctor/executor/sponsor) * * @param userId 用户ID - * @param roleType 角色类型 (admin/leader/manager/doctor/executor/sponsor) + * @param roleType 角色类型 * @return 影响行数 */ public int updateRoleType(Long userId, String roleType); diff --git a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index d0982ad..63e0164 100644 --- a/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -23,6 +23,7 @@ import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.domain.SysUserPost; import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.domain.vo.SysUserExtendVo; import com.ruoyi.system.mapper.SysPostMapper; import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.mapper.SysUserMapper; @@ -68,7 +69,7 @@ public class SysUserServiceImpl implements ISysUserService /** * 根据条件分页查询用户列表 - * + * * @param user 用户信息 * @return 用户信息集合信息 */ @@ -79,6 +80,19 @@ public class SysUserServiceImpl implements ISysUserService return userMapper.selectUserList(user); } + /** + * 根据条件分页查询用户列表(扩展视图, 附带 sys_user.roleType 业务角色) + * + * @param vo 查询条件 + * @return 用户扩展视图集合 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserExtendList(SysUserExtendVo vo) + { + return userMapper.selectUserExtendList(vo); + } + /** * 根据条件分页查询已分配用户角色列表 * diff --git a/ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index b7ab75f..71df528 100644 --- a/ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -48,9 +49,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + - select u.user_id, u.dept_id, u.user_name, u.nick_name, u.account_type, u.parent_user_id, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.account_type, u.parent_user_id, u.role_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status from sys_user u @@ -87,6 +93,43 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ${params.dataScope} + + + + select user_id from sys_user where parent_user_id = #{parentUserId} and del_flag = '0' + + \ No newline at end of file diff --git a/ry-vue3/src/utils/roleMap.js b/ry-vue3/src/utils/roleMap.js new file mode 100644 index 0000000..0439f0b --- /dev/null +++ b/ry-vue3/src/utils/roleMap.js @@ -0,0 +1,23 @@ +/** + * biz_person.role enum key → 中文 label 翻译 + * 跟 sys_user.role_type 模式一致: DB 存 enum key, UI 显示中文 + * + * 适用场景: + * - executor/People.vue 列表/详情显示 role + * - 各处 NewPerson 表单 el-option (label=中文, value=key) + * - 查询条件 role=key + */ +export const PERSON_ROLE_LABEL = { + meetingExecutor: '会议执行', + supervisor: '监察员' +} + +/** + * 翻译 biz_person.role enum key → 中文 label + * @param {string} key + * @returns {string} 没匹配到时返回原 key, 避免显示 'undefined' + */ +export function translatePersonRole(key) { + if (!key) return '' + return PERSON_ROLE_LABEL[key] || key +} \ No newline at end of file diff --git a/ry-vue3/src/views/admin/ExecutorPersonNew.vue b/ry-vue3/src/views/admin/ExecutorPersonNew.vue new file mode 100644 index 0000000..3c6a50a --- /dev/null +++ b/ry-vue3/src/views/admin/ExecutorPersonNew.vue @@ -0,0 +1,173 @@ + + + + + \ No newline at end of file diff --git a/ry-vue3/src/views/admin/SponsorPersonNew.vue b/ry-vue3/src/views/admin/SponsorPersonNew.vue new file mode 100644 index 0000000..528a0e4 --- /dev/null +++ b/ry-vue3/src/views/admin/SponsorPersonNew.vue @@ -0,0 +1,173 @@ + + + + + \ No newline at end of file diff --git a/ry-vue3/src/views/auth/Login.vue b/ry-vue3/src/views/auth/Login.vue index a14b4eb..d339116 100644 --- a/ry-vue3/src/views/auth/Login.vue +++ b/ry-vue3/src/views/auth/Login.vue @@ -154,12 +154,15 @@ async function onSubmit() { loading.value = true try { const res = await login({ username: form.username, password: form.password, code: form.code || '', uuid: form.uuid || '' }) - const role = autoRole(form.username) + // username 推断仅作兜底, 真实 role 以 /getInfo 返回的 sys_user.role_type 为准 + const fallbackRole = autoRole(form.username) userStore.setToken(res.token) - // /login 接口只返回 token; 调 /getInfo 拿完整 user (含 accountType/parentUserId, 让 isMain/isSub 判断正确) + // /login 接口只返回 token; 调 /getInfo 拿完整 user (含 accountType/parentUserId/roleType, 让 isMain/isSub/role 都从后端真实值取) + let role = fallbackRole try { const info = await getInfo() const u = info.user || {} + role = u.roleType || fallbackRole userStore.setUser({ userId: u.userId, userName: u.userName || form.username, @@ -176,13 +179,13 @@ async function onSubmit() { nickName: form.username, accountType: 'MAIN', parentUserId: null, - role + role: fallbackRole }) } ElMessage.success(`欢迎,${form.username}(${userTypes.find(u => u.value === role)?.name || role})`) router.replace(roleHome[role] || '/leader/home') } catch (e) { - ElMessage.error(e?.msg || '登录失败') + // 错误提示已由 utils/request.js 拦截器统一弹 (ElMessage.error), 这里只刷新验证码 loadCaptcha() } finally { loading.value = false diff --git a/ry-vue3/src/views/executor/NewPerson.vue b/ry-vue3/src/views/executor/NewPerson.vue new file mode 100644 index 0000000..0c1f4c7 --- /dev/null +++ b/ry-vue3/src/views/executor/NewPerson.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/ry-vue3/src/views/executor/People.vue b/ry-vue3/src/views/executor/People.vue index c7959d2..a77e179 100644 --- a/ry-vue3/src/views/executor/People.vue +++ b/ry-vue3/src/views/executor/People.vue @@ -17,7 +17,7 @@
- 新建人员 + 新建人员 批量导入 *批量导入模板与列表表项一致
@@ -31,7 +31,7 @@ @@ -42,7 +42,7 @@ @@ -54,29 +54,8 @@ @current-change="loadList" @size-change="loadList" /> - - - - - - - - - - - - - - - - *新建时,管理员只能新建监察员 - - - - + + @@ -105,7 +84,7 @@ {{ view.orgName }} {{ view.department }} {{ view.position }} - {{ view.role || '监察员' }} + {{ translatePersonRole(view.role) }} {{ view.status || '正常' }} @@ -115,18 +94,18 @@ + + \ No newline at end of file diff --git a/ry-vue3/src/views/manager/ManagerProjectDetail.vue b/ry-vue3/src/views/manager/ManagerProjectDetail.vue index 70e61fc..e95d26f 100644 --- a/ry-vue3/src/views/manager/ManagerProjectDetail.vue +++ b/ry-vue3/src/views/manager/ManagerProjectDetail.vue @@ -281,7 +281,7 @@ async function openAddMonitor() { addMonitorOpen.value = true if (!monitorCandidates.value.length) { try { - const resp = await bizList('person', { pageNum: 1, pageSize: 100, role: '监察员', unitType: 'sponsor' }) + const resp = await bizList('person', { pageNum: 1, pageSize: 100, role: 'supervisor', unitType: 'sponsor' }) monitorCandidates.value = (resp && resp.rows) || ((resp && resp.data) && (resp && resp.data).rows) || (resp && resp.data) || [] } catch (e) { monitorCandidates.value = [] } } @@ -393,7 +393,7 @@ onMounted(async () => { .section-title { font-size: 14px; font-weight: 600; color: #262626; margin: 16px 0 12px; - padding-left: 8px; border-left: 3px solid var(--brand-primary, #1890ff); + padding-left: 8px; border-left: 3px solid var(--brand-primary); } /* 项目信息: 11 字段, 一列竖排 (el-form + 文本, 无分隔线) */ @@ -407,7 +407,7 @@ onMounted(async () => { font-family: ui-monospace, "Courier New", monospace; } .op-link { - color: var(--brand-primary, #1890ff); text-decoration: none; + color: var(--brand-primary); text-decoration: none; font-weight: 600; cursor: pointer; font-size: 15px; } .op-link:hover { text-decoration: underline; } @@ -435,5 +435,5 @@ onMounted(async () => { .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; } /* 评分 */ -.score-num { font-size: 18px; color: var(--brand-primary, #1890ff); font-weight: 700; margin-right: 6px; } +.score-num { font-size: 18px; color: var(--brand-primary); font-weight: 700; margin-right: 6px; } diff --git a/ry-vue3/src/views/manager/SponsorPersonNew.vue b/ry-vue3/src/views/manager/SponsorPersonNew.vue new file mode 100644 index 0000000..d1a8d07 --- /dev/null +++ b/ry-vue3/src/views/manager/SponsorPersonNew.vue @@ -0,0 +1,172 @@ + + + + + \ No newline at end of file diff --git a/ry-vue3/src/views/sponsor/NewPerson.vue b/ry-vue3/src/views/sponsor/NewPerson.vue index 9a180a0..1b40703 100644 --- a/ry-vue3/src/views/sponsor/NewPerson.vue +++ b/ry-vue3/src/views/sponsor/NewPerson.vue @@ -42,7 +42,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -111,7 +111,7 @@ const form = reactive({ orgName: '', department: '', position: '', - role: '监察员', + role: 'supervisor', unitType: 'sponsor', status: '0', // 子账号登录信息 (后端会创建 sys_user SUB,parent_user_id=主账号) diff --git a/ry-vue3/src/views/sponsor/Projects.vue b/ry-vue3/src/views/sponsor/Projects.vue index bb67ace..a33a279 100644 --- a/ry-vue3/src/views/sponsor/Projects.vue +++ b/ry-vue3/src/views/sponsor/Projects.vue @@ -246,8 +246,8 @@ async function load() { async function loadPersons() { try { // sponsor 团队下"监察员"角色 + 已绑 sys_user 的子账号 - const r = await bizList('person', { pageNum: 1, pageSize: 100, unitType: 'sponsor', role: '监察员' }) - personList.value = (r.rows || []).filter(p => p.userId) + const r = await bizList('person', { pageNum: 1, pageSize: 100, unitType: 'sponsor', role: 'supervisor' }) + personList.value = ((r.data && r.data.rows) || r.rows || []).filter(p => p.userId) } catch (e) { personList.value = [] } } diff --git a/ry-vue3/src/views/sponsor/SponsorProjects.vue b/ry-vue3/src/views/sponsor/SponsorProjects.vue index 6ea51c9..b919fe3 100644 --- a/ry-vue3/src/views/sponsor/SponsorProjects.vue +++ b/ry-vue3/src/views/sponsor/SponsorProjects.vue @@ -288,7 +288,7 @@ const candidateMonitors = ref([]) async function loadMonitors() { // 业务隔离: 仅查 login 主账号创建的人员 (parent_user_id = mainUserId) const mainUid = userStore.id - const res = await listBizPerson({ parentUserId: mainUid, role: '监察员', pageNum: 1, pageSize: 100 }) + const res = await listBizPerson({ parentUserId: mainUid, role: 'supervisor', pageNum: 1, pageSize: 100 }) allMonitors.value = res.rows || [] candidateMonitors.value = allMonitors.value.slice() } diff --git a/sql/migration_unify_role_type_2026_08_16.sql b/sql/migration_unify_role_type_2026_08_16.sql new file mode 100644 index 0000000..5690ecc --- /dev/null +++ b/sql/migration_unify_role_type_2026_08_16.sql @@ -0,0 +1,45 @@ +-- ============================================================================ +-- 2026-08-16 重构: 删除 biz_user_role_bind 表, 业务角色统一存 sys_user.role_type +-- +-- 背景: +-- 原架构在两个地方存储业务角色 (admin/leader/manager/doctor/executor/sponsor): +-- 1. sys_user.role_type 列 (RuoYi 标准列, 只被 updateRoleType 写, 从未读) +-- 2. biz_user_role_bind 表 (自建, sys_user 的 1:N 关系设计, 但 replaceUserRoleType 实际是 delete+insert 强制单绑定) +-- - 三个注册入口不一致: 专家/admin 创建走 sys_user.role_type, 执行单位走 biz_user_role_bind, 赞助方无实现 +-- - /admin/users 查询 LEFT JOIN biz_user_role_bind, 因此专家显示空 role_type +-- - biz_user_role_bind 的 unit_id/unit_type 列始终为 NULL, 是死代码 +-- +-- 重构目标: 统一到 sys_user.role_type, 删 biz_user_role_bind +-- 改动: +-- 1. sys_user 表确保 role_type 列存在 (RuoYi 标准列, 已存在) +-- 2. 从 biz_user_role_bind 同步 role_type 到 sys_user.role_type (兜底已有数据) +-- 3. DROP TABLE biz_user_role_bind +-- ============================================================================ + +-- 1. 确保 sys_user.role_type 列存在 (RuoYi 标准列通常在 ruoyi-mysql.sql 已建, 这里兜底) +-- 生产环境如果已存在会报错, 用 IF NOT EXISTS 容错 +SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'sys_user' AND column_name = 'role_type'); +SET @sql = IF(@col_exists = 0, + 'ALTER TABLE sys_user ADD COLUMN role_type VARCHAR(20) DEFAULT NULL COMMENT ''业务角色 admin/leader/manager/doctor/executor/sponsor''', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 2. 同步 biz_user_role_bind 的 role_type 到 sys_user.role_type +-- 一个 user 在 biz_user_role_bind 里最多 1 条 (replaceUserRoleType 强制), 用 MAX(role_type) 兜底 +UPDATE sys_user u +LEFT JOIN ( + SELECT user_id, MAX(role_type) AS role_type + FROM biz_user_role_bind + GROUP BY user_id +) b ON b.user_id = u.user_id +SET u.role_type = b.role_type +WHERE u.role_type IS NULL AND b.role_type IS NOT NULL; + +-- 3. DROP biz_user_role_bind +DROP TABLE IF EXISTS biz_user_role_bind; + +-- 4. 验证 +SELECT user_id, user_name, role_type FROM sys_user WHERE role_type IS NOT NULL ORDER BY user_id LIMIT 20;