mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	新增:新增系统管理/用户管理(列表、查看详情、新增、修改、删除、导出)
This commit is contained in:
		| @@ -26,7 +26,6 @@ import org.springframework.data.domain.Sort; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfo; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; | ||||
|   | ||||
| @@ -32,4 +32,9 @@ public class Constants { | ||||
|      * 超级管理员角色编码 | ||||
|      */ | ||||
|     public static final String ADMIN_ROLE_CODE = "admin"; | ||||
|  | ||||
|     /** | ||||
|      * 默认密码 | ||||
|      */ | ||||
|     public static final String DEFAULT_PASSWORD = "123456"; | ||||
| } | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.monitor.model.entity.LogDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -35,6 +35,7 @@ import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.service.CommonUserService; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.ReflectUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| @@ -46,7 +47,6 @@ import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.query.SystemLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.*; | ||||
| import top.charles7c.cnadmin.monitor.service.LogService; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
|  * 系统日志业务实现类 | ||||
| @@ -60,7 +60,7 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
| public class LogServiceImpl implements LogService { | ||||
|  | ||||
|     private final LogMapper logMapper; | ||||
|     private final UserService userService; | ||||
|     private final CommonUserService commonUserService; | ||||
|  | ||||
|     @Async | ||||
|     @EventListener | ||||
| @@ -84,7 +84,7 @@ public class LogServiceImpl implements LogService { | ||||
|  | ||||
|         // 填充数据(如果是查询个人操作日志,只查询一次用户信息即可) | ||||
|         if (query.getUid() != null) { | ||||
|             String nickname = ExceptionUtils.exToNull(() -> userService.getById(query.getUid()).getNickname()); | ||||
|             String nickname = ExceptionUtils.exToNull(() -> commonUserService.getNicknameById(query.getUid())); | ||||
|             pageDataVO.getList().forEach(o -> o.setCreateUserString(nickname)); | ||||
|         } else { | ||||
|             pageDataVO.getList().forEach(this::fill); | ||||
| @@ -153,6 +153,6 @@ public class LogServiceImpl implements LogService { | ||||
|         if (createUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         logVO.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getById(createUser)).getNickname()); | ||||
|         logVO.setCreateUserString(ExceptionUtils.exToNull(() -> commonUserService.getNicknameById(createUser))); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.DeptDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.MenuDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.RoleDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserRoleDO; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -65,16 +65,16 @@ public class UserDO extends BaseDO { | ||||
|      */ | ||||
|     private GenderEnum gender; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     private String phone; | ||||
|  | ||||
|     /** | ||||
|      * 邮箱 | ||||
|      */ | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     private String phone; | ||||
|  | ||||
|     /** | ||||
|      * 头像地址 | ||||
|      */ | ||||
|   | ||||
| @@ -19,6 +19,7 @@ package top.charles7c.cnadmin.system.model.entity; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
|  | ||||
| @@ -29,6 +30,7 @@ import com.baomidou.mybatisplus.annotation.TableName; | ||||
|  * @since 2023/2/13 23:13 | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @TableName("sys_user_role") | ||||
| public class UserRoleDO implements Serializable { | ||||
|  | ||||
| @@ -43,4 +45,9 @@ public class UserRoleDO implements Serializable { | ||||
|      * 角色 ID | ||||
|      */ | ||||
|     private Long roleId; | ||||
|  | ||||
|     public UserRoleDO(Long userId, Long roleId) { | ||||
|         this.userId = userId; | ||||
|         this.roleId = roleId; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.query; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.springdoc.api.annotations.ParameterObject; | ||||
| import org.springframework.format.annotation.DateTimeFormat; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.annotation.Query; | ||||
|  | ||||
| /** | ||||
|  * 用户查询条件 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:01 | ||||
|  */ | ||||
| @Data | ||||
| @ParameterObject | ||||
| @Schema(description = "用户查询条件") | ||||
| public class UserQuery implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @Schema(description = "用户名") | ||||
|     @Query(blurry = "username,nickname,email,phone") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 状态(1启用 2禁用) | ||||
|      */ | ||||
|     @Schema(description = "状态(1启用 2禁用)") | ||||
|     @Query | ||||
|     private Integer status; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @Schema(description = "创建时间") | ||||
|     @Query(type = Query.Type.BETWEEN) | ||||
|     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||||
|     private List<Date> createTime; | ||||
|  | ||||
|     /** | ||||
|      * 部门 ID | ||||
|      */ | ||||
|     @Schema(description = "部门 ID") | ||||
|     @Query | ||||
|     private Long deptId; | ||||
| } | ||||
| @@ -0,0 +1,117 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.request; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.validation.constraints.NotBlank; | ||||
| import javax.validation.constraints.NotNull; | ||||
| import javax.validation.constraints.Null; | ||||
| import javax.validation.constraints.Pattern; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| import cn.hutool.core.lang.RegexPool; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseRequest; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||
|  | ||||
| /** | ||||
|  * 创建或修改用户信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:03 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "创建或修改用户信息") | ||||
| public class UserRequest extends BaseRequest { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户 ID | ||||
|      */ | ||||
|     @Schema(description = "角色 ID") | ||||
|     @Null(message = "新增时,ID 必须为空", groups = Create.class) | ||||
|     @NotNull(message = "修改时,ID 不能为空", groups = Update.class) | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @Schema(description = "用户名") | ||||
|     @NotBlank(message = "用户名不能为空") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 昵称 | ||||
|      */ | ||||
|     @Schema(description = "昵称") | ||||
|     @Length(max = 32, message = "昵称长度不能超过 {max} 个字符") | ||||
|     private String nickname; | ||||
|  | ||||
|     /** | ||||
|      * 性别(0未知 1男 2女) | ||||
|      */ | ||||
|     @Schema(description = "性别(0未知 1男 2女)", type = "Integer", allowableValues = {"0", "1", "2"}) | ||||
|     @NotNull(message = "性别非法") | ||||
|     private GenderEnum gender; | ||||
|  | ||||
|     /** | ||||
|      * 邮箱 | ||||
|      */ | ||||
|     @Schema(description = "邮箱") | ||||
|     @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     @Schema(description = "手机号码") | ||||
|     @Pattern(regexp = RegexPool.MOBILE, message = "手机号码格式错误") | ||||
|     private String phone; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述") | ||||
|     @Length(max = 200, message = "描述长度不能超过 {max} 个字符") | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 状态(1启用 2禁用) | ||||
|      */ | ||||
|     @Schema(description = "状态(1启用 2禁用)", type = "Integer", allowableValues = {"1", "2"}) | ||||
|     private DisEnableStatusEnum status; | ||||
|  | ||||
|     /** | ||||
|      * 部门 ID | ||||
|      */ | ||||
|     @Schema(description = "部门 ID") | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 角色 ID 列表 | ||||
|      */ | ||||
|     @Schema(description = "角色 ID 列表") | ||||
|     private List<Long> roleIds; | ||||
| } | ||||
| @@ -0,0 +1,134 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.vo; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseDetailVO; | ||||
| import top.charles7c.cnadmin.common.config.easyexcel.ExcelBaseEnumConverter; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||
|  | ||||
| /** | ||||
|  * 用户详情信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:11 | ||||
|  */ | ||||
| @Data | ||||
| @ExcelIgnoreUnannotated | ||||
| @Schema(description = "用户详情信息") | ||||
| public class UserDetailVO extends BaseDetailVO { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户 ID | ||||
|      */ | ||||
|     @Schema(description = "用户 ID") | ||||
|     @ExcelProperty(value = "用户ID") | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @Schema(description = "用户名") | ||||
|     @ExcelProperty(value = "用户名") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 昵称 | ||||
|      */ | ||||
|     @Schema(description = "昵称") | ||||
|     @ExcelProperty(value = "昵称") | ||||
|     private String nickname; | ||||
|  | ||||
|     /** | ||||
|      * 性别(0未知 1男 2女) | ||||
|      */ | ||||
|     @Schema(description = "性别(0未知 1男 2女)") | ||||
|     @ExcelProperty(value = "性别", converter = ExcelBaseEnumConverter.class) | ||||
|     private GenderEnum gender; | ||||
|  | ||||
|     /** | ||||
|      * 邮箱 | ||||
|      */ | ||||
|     @Schema(description = "邮箱") | ||||
|     @ExcelProperty(value = "邮箱") | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     @Schema(description = "手机号码") | ||||
|     @ExcelProperty(value = "手机号码") | ||||
|     private String phone; | ||||
|  | ||||
|     /** | ||||
|      * 头像地址 | ||||
|      */ | ||||
|     @Schema(description = "头像地址") | ||||
|     @ExcelProperty(value = "头像地址") | ||||
|     private String avatar; | ||||
|  | ||||
|     /** | ||||
|      * 状态(1启用 2禁用) | ||||
|      */ | ||||
|     @Schema(description = "状态(1启用 2禁用)") | ||||
|     @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class) | ||||
|     private DisEnableStatusEnum status; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述") | ||||
|     @ExcelProperty(value = "描述") | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 角色 ID 列表 | ||||
|      */ | ||||
|     @Schema(description = "角色 ID 列表") | ||||
|     private List<Long> roleIds; | ||||
|  | ||||
|     /** | ||||
|      * 所属角色 | ||||
|      */ | ||||
|     @Schema(description = "所属角色") | ||||
|     @ExcelProperty(value = "所属角色") | ||||
|     private String roleNames; | ||||
|  | ||||
|     /** | ||||
|      * 部门 ID | ||||
|      */ | ||||
|     @Schema(description = "部门 ID") | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 所属部门 | ||||
|      */ | ||||
|     @Schema(description = "所属部门") | ||||
|     @ExcelProperty(value = "所属部门") | ||||
|     private String deptName; | ||||
| } | ||||
| @@ -0,0 +1,119 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.vo; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.experimental.Accessors; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
|  | ||||
| import cn.hutool.core.util.DesensitizedUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseVO; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
|  | ||||
| /** | ||||
|  * 用户信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:08 | ||||
|  */ | ||||
| @Data | ||||
| @Accessors(chain = true) | ||||
| @Schema(description = "用户信息") | ||||
| public class UserVO extends BaseVO { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户 ID | ||||
|      */ | ||||
|     @Schema(description = "用户 ID") | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @Schema(description = "用户名") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 昵称 | ||||
|      */ | ||||
|     @Schema(description = "昵称") | ||||
|     private String nickname; | ||||
|  | ||||
|     /** | ||||
|      * 性别(0未知 1男 2女) | ||||
|      */ | ||||
|     @Schema(description = "性别(0未知 1男 2女)") | ||||
|     private GenderEnum gender; | ||||
|  | ||||
|     /** | ||||
|      * 邮箱 | ||||
|      */ | ||||
|     @Schema(description = "邮箱") | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     @Schema(description = "手机号码") | ||||
|     private String phone; | ||||
|  | ||||
|     /** | ||||
|      * 头像地址 | ||||
|      */ | ||||
|     @Schema(description = "头像地址") | ||||
|     private String avatar; | ||||
|  | ||||
|     /** | ||||
|      * 状态(1启用 2禁用) | ||||
|      */ | ||||
|     @Schema(description = "状态(1启用 2禁用)") | ||||
|     private DisEnableStatusEnum status; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述") | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 是否禁用修改 | ||||
|      */ | ||||
|     @JsonInclude(JsonInclude.Include.NON_NULL) | ||||
|     private Boolean disabled; | ||||
|  | ||||
|     public Boolean getDisabled() { | ||||
|         if (userId.equals(LoginHelper.getUserId())) { | ||||
|             return true; | ||||
|         } | ||||
|         return disabled; | ||||
|     } | ||||
|  | ||||
|     public String getPhone() { | ||||
|         if (phone == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return DesensitizedUtil.mobilePhone(phone); | ||||
|     } | ||||
| } | ||||
| @@ -16,6 +16,10 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseService; | ||||
| import top.charles7c.cnadmin.system.model.query.RoleQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.RoleRequest; | ||||
| @@ -28,4 +32,23 @@ import top.charles7c.cnadmin.system.model.vo.RoleVO; | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/8 23:15 | ||||
|  */ | ||||
| public interface RoleService extends BaseService<RoleVO, RoleDetailVO, RoleQuery, RoleRequest> {} | ||||
| public interface RoleService extends BaseService<RoleVO, RoleDetailVO, RoleQuery, RoleRequest> { | ||||
|  | ||||
|     /** | ||||
|      * 构建树 | ||||
|      * | ||||
|      * @param list | ||||
|      *            原始列表数据 | ||||
|      * @return 树列表 | ||||
|      */ | ||||
|     List<Tree<Long>> buildTree(List<RoleVO> list); | ||||
|  | ||||
|     /** | ||||
|      * 根据角色 ID 列表查询 | ||||
|      * | ||||
|      * @param roleIds | ||||
|      *            角色 ID 列表 | ||||
|      * @return 角色名称列表 | ||||
|      */ | ||||
|     List<String> listRoleNamesByRoleIds(List<Long> roleIds); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,56 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 用户和角色业务接口 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:30 | ||||
|  */ | ||||
| public interface UserRoleService { | ||||
|  | ||||
|     /** | ||||
|      * 保存 | ||||
|      * | ||||
|      * @param roleIds | ||||
|      *            角色 ID 列表 | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      */ | ||||
|     void save(List<Long> roleIds, Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 根据角色 ID 列表查询 | ||||
|      * | ||||
|      * @param roleIds | ||||
|      *            角色 ID 列表 | ||||
|      * @return 总记录数 | ||||
|      */ | ||||
|     Long countByRoleIds(List<Long> roleIds); | ||||
|  | ||||
|     /** | ||||
|      * 根据用户 ID 查询 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @return 角色 ID 列表 | ||||
|      */ | ||||
|     List<Long> listRoleIdsByUserId(Long userId); | ||||
| } | ||||
| @@ -20,7 +20,12 @@ import java.util.List; | ||||
|  | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseService; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||
| import top.charles7c.cnadmin.system.model.query.UserQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.UserRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserDetailVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserVO; | ||||
|  | ||||
| /** | ||||
|  * 用户业务接口 | ||||
| @@ -28,7 +33,7 @@ import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/21 21:48 | ||||
|  */ | ||||
| public interface UserService { | ||||
| public interface UserService extends BaseService<UserVO, UserDetailVO, UserQuery, UserRequest> { | ||||
|  | ||||
|     /** | ||||
|      * 根据用户名查询 | ||||
| @@ -50,14 +55,6 @@ public interface UserService { | ||||
|      */ | ||||
|     String uploadAvatar(MultipartFile avatar, Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 修改信息 | ||||
|      * | ||||
|      * @param user | ||||
|      *            用户信息 | ||||
|      */ | ||||
|     void update(UserDO user); | ||||
|  | ||||
|     /** | ||||
|      * 修改密码 | ||||
|      * | ||||
| @@ -82,15 +79,6 @@ public interface UserService { | ||||
|      */ | ||||
|     void updateEmail(String newEmail, String currentPassword, Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     UserDO getById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 根据部门 ID 列表查询 | ||||
|      * | ||||
|   | ||||
| @@ -21,6 +21,8 @@ import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
| @@ -54,7 +56,8 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
| public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO, DeptDetailVO, DeptQuery, DeptRequest> | ||||
|     implements DeptService { | ||||
|  | ||||
|     private final UserService userService; | ||||
|     @Resource | ||||
|     private UserService userService; | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|   | ||||
| @@ -16,17 +16,24 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; | ||||
| import top.charles7c.cnadmin.common.consts.Constants; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.util.TreeUtils; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.RoleMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.RoleDO; | ||||
| @@ -51,7 +58,8 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, | ||||
|     private final RoleMenuService roleMenuService; | ||||
|     private final RoleDeptService roleDeptService; | ||||
|     private final MenuService menuService; | ||||
|     private final UserService userService; | ||||
|     @Resource | ||||
|     private UserService userService; | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
| @@ -122,4 +130,22 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, | ||||
|             detailVO.setDeptIds(roleDeptService.listDeptIdByRoleId(roleId)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Tree<Long>> buildTree(List<RoleVO> list) { | ||||
|         return TreeUtils.build(list, (r, tree) -> { | ||||
|             tree.setId(r.getRoleId()); | ||||
|             tree.setName(r.getRoleName()); | ||||
|             tree.setWeight(r.getRoleSort()); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> listRoleNamesByRoleIds(List<Long> roleIds) { | ||||
|         List<RoleDO> roleList = super.lambdaQuery().select(RoleDO::getRoleName).in(RoleDO::getRoleId, roleIds).list(); | ||||
|         if (CollUtil.isEmpty(roleList)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return roleList.stream().map(RoleDO::getRoleName).collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,74 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.system.mapper.UserRoleMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserRoleDO; | ||||
| import top.charles7c.cnadmin.system.service.UserRoleService; | ||||
|  | ||||
| /** | ||||
|  * 用户和角色业务实现类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:30 | ||||
|  */ | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class UserRoleServiceImpl implements UserRoleService { | ||||
|  | ||||
|     private final UserRoleMapper userRoleMapper; | ||||
|  | ||||
|     @Override | ||||
|     public void save(List<Long> roleIds, Long userId) { | ||||
|         if (CollUtil.isEmpty(roleIds)) { | ||||
|             return; | ||||
|         } | ||||
|         // 删除原有关联 | ||||
|         userRoleMapper.delete(Wrappers.<UserRoleDO>lambdaQuery().eq(UserRoleDO::getUserId, userId)); | ||||
|         // 保存最新关联 | ||||
|         List<UserRoleDO> userRoleList = | ||||
|             roleIds.stream().map(roleId -> new UserRoleDO(userId, roleId)).collect(Collectors.toList()); | ||||
|         userRoleMapper.insertBatch(userRoleList); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Long countByRoleIds(List<Long> roleIds) { | ||||
|         return userRoleMapper.selectCount(Wrappers.<UserRoleDO>lambdaQuery().in(UserRoleDO::getRoleId, roleIds)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Long> listRoleIdsByUserId(Long userId) { | ||||
|         List<UserRoleDO> userRoleList = userRoleMapper.selectList( | ||||
|             Wrappers.<UserRoleDO>lambdaQuery().select(UserRoleDO::getRoleId).eq(UserRoleDO::getUserId, userId)); | ||||
|         if (CollUtil.isEmpty(userRoleList)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return userRoleList.stream().map(UserRoleDO::getRoleId).collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
| @@ -20,32 +20,39 @@ import java.io.File; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.io.file.FileNameUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; | ||||
| import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties; | ||||
| import top.charles7c.cnadmin.common.consts.Constants; | ||||
| import top.charles7c.cnadmin.common.consts.FileConstants; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.model.dto.LoginUser; | ||||
| import top.charles7c.cnadmin.common.service.CommonUserService; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.FileUtils; | ||||
| import top.charles7c.cnadmin.common.util.SecureUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.UserMapper; | ||||
| import top.charles7c.cnadmin.system.mapper.UserRoleMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserRoleDO; | ||||
| import top.charles7c.cnadmin.system.model.query.UserQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.UserRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserDetailVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserVO; | ||||
| import top.charles7c.cnadmin.system.service.DeptService; | ||||
| import top.charles7c.cnadmin.system.service.RoleService; | ||||
| import top.charles7c.cnadmin.system.service.UserRoleService; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
| @@ -56,15 +63,76 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
|  */ | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class UserServiceImpl implements UserService, CommonUserService { | ||||
| public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO, UserDetailVO, UserQuery, UserRequest> | ||||
|     implements UserService, CommonUserService { | ||||
|  | ||||
|     private final UserMapper userMapper; | ||||
|     private final UserRoleMapper userRoleMapper; | ||||
|     private final UserRoleService userRoleService; | ||||
|     private final LocalStorageProperties localStorageProperties; | ||||
|     @Resource | ||||
|     private RoleService roleService; | ||||
|     @Resource | ||||
|     private DeptService deptService; | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public Long add(UserRequest request) { | ||||
|         String username = request.getUsername(); | ||||
|         boolean isExist = this.checkNameExists(username, request.getUserId()); | ||||
|         CheckUtils.throwIf(() -> isExist, String.format("新增失败,'%s'已存在", username)); | ||||
|  | ||||
|         // 新增用户 | ||||
|         request.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         Long userId = super.add(request); | ||||
|         super.lambdaUpdate() | ||||
|             .set(UserDO::getPassword, SecureUtils.md5Salt(Constants.DEFAULT_PASSWORD, userId.toString())) | ||||
|             .set(UserDO::getPwdResetTime, LocalDateTime.now()).eq(UserDO::getUserId, userId).update(); | ||||
|         // 保存用户和角色关联 | ||||
|         userRoleService.save(request.getRoleIds(), userId); | ||||
|         return userId; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void update(UserRequest request) { | ||||
|         String username = request.getUsername(); | ||||
|         boolean isExist = this.checkNameExists(username, request.getUserId()); | ||||
|         CheckUtils.throwIf(() -> isExist, String.format("修改失败,'%s'已存在", username)); | ||||
|  | ||||
|         // 更新用户 | ||||
|         super.update(request); | ||||
|         Long userId = request.getUserId(); | ||||
|         // 保存用户和角色关联 | ||||
|         userRoleService.save(request.getRoleIds(), userId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查名称是否存在 | ||||
|      * | ||||
|      * @param name | ||||
|      *            名称 | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return 是否存在 | ||||
|      */ | ||||
|     private boolean checkNameExists(String name, Long id) { | ||||
|         return super.lambdaQuery().eq(UserDO::getUsername, name).ne(id != null, UserDO::getUserId, id).exists(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void fillDetail(Object detailObj) { | ||||
|         super.fillDetail(detailObj); | ||||
|         if (detailObj instanceof UserDetailVO) { | ||||
|             UserDetailVO detailVO = (UserDetailVO)detailObj; | ||||
|             detailVO.setDeptName(ExceptionUtils.exToNull(() -> deptService.get(detailVO.getDeptId()).getDeptName())); | ||||
|             List<Long> roleIds = userRoleService.listRoleIdsByUserId(detailVO.getUserId()); | ||||
|             detailVO.setRoleIds(roleIds); | ||||
|             detailVO.setRoleNames(String.join(",", roleService.listRoleNamesByRoleIds(roleIds))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserDO getByUsername(String username) { | ||||
|         return userMapper.selectOne(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, username)); | ||||
|         return super.lambdaQuery().eq(UserDO::getUsername, username).one(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -86,8 +154,7 @@ public class UserServiceImpl implements UserService, CommonUserService { | ||||
|  | ||||
|         // 更新用户头像 | ||||
|         String newAvatar = newAvatarFile.getName(); | ||||
|         userMapper.update(null, | ||||
|             new LambdaUpdateWrapper<UserDO>().set(UserDO::getAvatar, newAvatar).eq(UserDO::getUserId, userId)); | ||||
|         super.lambdaUpdate().set(UserDO::getAvatar, newAvatar).eq(UserDO::getUserId, userId).update(); | ||||
|  | ||||
|         // 删除原头像 | ||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||
| @@ -102,18 +169,6 @@ public class UserServiceImpl implements UserService, CommonUserService { | ||||
|         return newAvatar; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void update(UserDO user) { | ||||
|         userMapper.updateById(user); | ||||
|  | ||||
|         // 更新登录用户信息 | ||||
|         UserDO userDO = this.getById(user.getUserId()); | ||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||
|         BeanUtil.copyProperties(userDO, loginUser); | ||||
|         LoginHelper.updateLoginUser(loginUser); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void updatePassword(String oldPassword, String newPassword, Long userId) { | ||||
| @@ -123,10 +178,8 @@ public class UserServiceImpl implements UserService, CommonUserService { | ||||
|  | ||||
|         // 更新密码和密码重置时间 | ||||
|         LocalDateTime now = LocalDateTime.now(); | ||||
|         userMapper.update(null, | ||||
|             new LambdaUpdateWrapper<UserDO>() | ||||
|                 .set(UserDO::getPassword, SecureUtils.md5Salt(newPassword, userId.toString())) | ||||
|                 .set(UserDO::getPwdResetTime, now).eq(UserDO::getUserId, userId)); | ||||
|         super.lambdaUpdate().set(UserDO::getPassword, SecureUtils.md5Salt(newPassword, userId.toString())) | ||||
|             .set(UserDO::getPwdResetTime, now).eq(UserDO::getUserId, userId).update(); | ||||
|  | ||||
|         // 更新登录用户信息 | ||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||
| @@ -140,13 +193,12 @@ public class UserServiceImpl implements UserService, CommonUserService { | ||||
|         UserDO userDO = this.getById(userId); | ||||
|         CheckUtils.throwIfNotEqual(SecureUtils.md5Salt(currentPassword, userId.toString()), userDO.getPassword(), | ||||
|             "当前密码错误"); | ||||
|         Long count = userMapper.selectCount(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getEmail, newEmail)); | ||||
|         Long count = super.lambdaQuery().eq(UserDO::getEmail, newEmail).count(); | ||||
|         CheckUtils.throwIf(() -> count > 0, "邮箱已绑定其他账号,请更换其他邮箱"); | ||||
|         CheckUtils.throwIfEqual(newEmail, userDO.getEmail(), "新邮箱不能与当前邮箱相同"); | ||||
|  | ||||
|         // 更新邮箱 | ||||
|         userMapper.update(null, | ||||
|             new LambdaUpdateWrapper<UserDO>().set(UserDO::getEmail, newEmail).eq(UserDO::getUserId, userId)); | ||||
|         super.lambdaUpdate().set(UserDO::getEmail, newEmail).eq(UserDO::getUserId, userId).update(); | ||||
|  | ||||
|         // 更新登录用户信息 | ||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||
| @@ -154,25 +206,18 @@ public class UserServiceImpl implements UserService, CommonUserService { | ||||
|         LoginHelper.updateLoginUser(loginUser); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserDO getById(Long userId) { | ||||
|         UserDO userDO = userMapper.selectById(userId); | ||||
|         CheckUtils.throwIfNull(userDO, String.format("ID为 [%s] 的用户已不存在", userId)); | ||||
|         return userDO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Long countByDeptIds(List<Long> deptIds) { | ||||
|         return userMapper.selectCount(Wrappers.<UserDO>lambdaQuery().in(UserDO::getDeptId, deptIds)); | ||||
|         return super.lambdaQuery().in(UserDO::getDeptId, deptIds).count(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Long countByRoleIds(List<Long> roleIds) { | ||||
|         return userRoleMapper.selectCount(Wrappers.<UserRoleDO>lambdaQuery().in(UserRoleDO::getRoleId, roleIds)); | ||||
|         return userRoleService.countByRoleIds(roleIds); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getNicknameById(Long userId) { | ||||
|         return this.getById(userId).getNickname(); | ||||
|         return super.getById(userId).getNickname(); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								continew-admin-ui/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								continew-admin-ui/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -75,6 +75,7 @@ declare module '@vue/runtime-core' { | ||||
|     ATag: typeof import('@arco-design/web-vue')['Tag'] | ||||
|     ATextarea: typeof import('@arco-design/web-vue')['Textarea'] | ||||
|     ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] | ||||
|     ATree: typeof import('@arco-design/web-vue')['Tree'] | ||||
|     ATreeSelect: typeof import('@arco-design/web-vue')['TreeSelect'] | ||||
|     ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph'] | ||||
|     ATypographyText: typeof import('@arco-design/web-vue')['TypographyText'] | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import axios from 'axios'; | ||||
| import qs from 'query-string'; | ||||
| import { DeptParam } from '@/api/system/dept'; | ||||
| import { MenuParam } from '@/api/system/menu'; | ||||
| import { RoleParam } from '@/api/system/role'; | ||||
| import { TreeNodeData } from '@arco-design/web-vue'; | ||||
|  | ||||
| export function listDeptTree(params: DeptParam) { | ||||
| @@ -21,3 +22,12 @@ export function listMenuTree(params: MenuParam) { | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function listRoleTree(params: RoleParam) { | ||||
|   return axios.get<TreeNodeData[]>('/common/tree/role', { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -36,7 +36,7 @@ export function getDept(id: number) { | ||||
|   return axios.get<DeptRecord>(`${BASE_URL}/${id}`); | ||||
| } | ||||
|  | ||||
| export function createDept(req: DeptRecord) { | ||||
| export function addDept(req: DeptRecord) { | ||||
|   return axios.post(BASE_URL, req); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,7 @@ export function getMenu(id: number) { | ||||
|   return axios.get<MenuRecord>(`${BASE_URL}/${id}`); | ||||
| } | ||||
|  | ||||
| export function createMenu(req: MenuRecord) { | ||||
| export function addMenu(req: MenuRecord) { | ||||
|   return axios.post(BASE_URL, req); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,9 +23,9 @@ export interface RoleRecord { | ||||
| export interface RoleParam { | ||||
|   roleName?: string; | ||||
|   status?: number; | ||||
|   page: number; | ||||
|   size: number; | ||||
|   sort: Array<string>; | ||||
|   page?: number; | ||||
|   size?: number; | ||||
|   sort?: Array<string>; | ||||
| } | ||||
|  | ||||
| export interface RoleListRes { | ||||
| @@ -46,7 +46,7 @@ export function getRole(id: number) { | ||||
|   return axios.get<RoleRecord>(`${BASE_URL}/${id}`); | ||||
| } | ||||
|  | ||||
| export function createRole(req: RoleRecord) { | ||||
| export function addRole(req: RoleRecord) { | ||||
|   return axios.post(BASE_URL, req); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										63
									
								
								continew-admin-ui/src/api/system/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								continew-admin-ui/src/api/system/user.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| import axios from 'axios'; | ||||
| import qs from 'query-string'; | ||||
|  | ||||
| const BASE_URL = '/system/user'; | ||||
|  | ||||
| export interface UserRecord { | ||||
|   userId?: number; | ||||
|   username: string; | ||||
|   nickname: string; | ||||
|   gender: number; | ||||
|   email?: string; | ||||
|   phone?: string; | ||||
|   description?: string; | ||||
|   roleIds?: Array<number>; | ||||
|   deptId?: number; | ||||
|   status?: number; | ||||
|   createUserString?: string; | ||||
|   createTime?: string; | ||||
|   updateUserString?: string; | ||||
|   updateTime?: string; | ||||
|   deptName?: string; | ||||
|   roleNames?: Array<string>; | ||||
|   disabled?: boolean; | ||||
| } | ||||
|  | ||||
| export interface UserParam { | ||||
|   username?: string; | ||||
|   status?: number; | ||||
|   createTime?: Array<string>; | ||||
|   page?: number; | ||||
|   size?: number; | ||||
|   sort?: Array<string>; | ||||
| } | ||||
|  | ||||
| export interface UserListRes { | ||||
|   list: UserRecord[]; | ||||
|   total: number; | ||||
| } | ||||
|  | ||||
| export function listUser(params: UserParam) { | ||||
|   return axios.get<UserListRes>(`${BASE_URL}`, { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function getUser(id: number) { | ||||
|   return axios.get<UserRecord>(`${BASE_URL}/${id}`); | ||||
| } | ||||
|  | ||||
| export function addUser(req: UserRecord) { | ||||
|   return axios.post(BASE_URL, req); | ||||
| } | ||||
|  | ||||
| export function updateUser(req: UserRecord) { | ||||
|   return axios.put(BASE_URL, req); | ||||
| } | ||||
|  | ||||
| export function deleteUser(ids: number | Array<number>) { | ||||
|   return axios.delete(`${BASE_URL}/${ids}`); | ||||
| } | ||||
| @@ -147,7 +147,7 @@ | ||||
|             :size="32" | ||||
|             :style="{ marginRight: '8px', cursor: 'pointer' }" | ||||
|           > | ||||
|             <img alt="avatar" :src="getAvatar(loginStore)" /> | ||||
|             <img alt="avatar" :src="getAvatar(loginStore.avatar, loginStore.gender)" /> | ||||
|           </a-avatar> | ||||
|           <template #content> | ||||
|             <a-doption> | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import localeWorkplace from '@/views/dashboard/workplace/locale/en-US'; | ||||
|  | ||||
| import localeUser from '@/views/system/user/locale/en-US'; | ||||
| import localeRole from '@/views/system/role/locale/en-US'; | ||||
| import localeMenu from '@/views/system/menu/locale/en-US'; | ||||
| import localeDept from '@/views/system/dept/locale/en-US'; | ||||
| @@ -50,6 +51,7 @@ export default { | ||||
|  | ||||
|   ...localeWorkplace, | ||||
|  | ||||
|   ...localeUser, | ||||
|   ...localeRole, | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN'; | ||||
|  | ||||
| import localeUser from '@/views/system/user/locale/zh-CN'; | ||||
| import localeRole from '@/views/system/role/locale/zh-CN'; | ||||
| import localeMenu from '@/views/system/menu/locale/zh-CN'; | ||||
| import localeDept from '@/views/system/dept/locale/zh-CN'; | ||||
| @@ -50,6 +51,7 @@ export default { | ||||
|  | ||||
|   ...localeWorkplace, | ||||
|  | ||||
|   ...localeUser, | ||||
|   ...localeRole, | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   | ||||
| @@ -12,6 +12,16 @@ const System: AppRouteRecordRaw = { | ||||
|     order: 1, | ||||
|   }, | ||||
|   children: [ | ||||
|     { | ||||
|       path: '/system/user', | ||||
|       name: 'User', | ||||
|       component: () => import('@/views/system/user/index.vue'), | ||||
|       meta: { | ||||
|         locale: 'menu.system.user.list', | ||||
|         requiresAuth: true, | ||||
|         roles: ['*'], | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       path: '/system/role', | ||||
|       name: 'Role', | ||||
|   | ||||
| @@ -1,20 +1,17 @@ | ||||
| import { UserState } from '@/store/modules/login/types'; | ||||
| import Unknown from '../assets/images/avatar/unknown.png'; | ||||
| import Male from '../assets/images/avatar/male.png'; | ||||
| import Female from '../assets/images/avatar/female.png'; | ||||
|  | ||||
| export default function getAvatar(loginStore: UserState) { | ||||
|   const userAvatar = loginStore.avatar; | ||||
|   if (userAvatar) { | ||||
| export default function getAvatar(avatar: string | undefined, gender: number | undefined) { | ||||
|   if (avatar) { | ||||
|     const baseUrl = import.meta.env.VITE_API_BASE_URL; | ||||
|     return `${baseUrl}/avatar/${userAvatar}`; | ||||
|     return `${baseUrl}/avatar/${avatar}`; | ||||
|   } | ||||
|  | ||||
|   const userGender = loginStore.gender; | ||||
|   if (userGender === 1) { | ||||
|   if (gender === 1) { | ||||
|     return Male; | ||||
|   } | ||||
|   if (userGender === 2) { | ||||
|   if (gender === 2) { | ||||
|     return Female; | ||||
|   } | ||||
|   return Unknown; | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|         <div v-if="userInfo"> | ||||
|           <a-space :size="12"> | ||||
|             <a-avatar :size="24"> | ||||
|               <img :src="getAvatar(userInfo)" /> | ||||
|               <img :src="getAvatar(userInfo.avatar, userInfo.gender)" /> | ||||
|             </a-avatar> | ||||
|             <a-typography-text> | ||||
|               {{ userInfo.nickname }} {{ $t('monitor.studioPreview.studio') }} | ||||
|   | ||||
| @@ -89,7 +89,7 @@ | ||||
|       <a-drawer | ||||
|         title="日志详情" | ||||
|         :visible="visible" | ||||
|         :width="570" | ||||
|         :width="580" | ||||
|         :footer="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
| @@ -233,7 +233,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { getCurrentInstance, ref, toRefs, reactive, } from 'vue'; | ||||
|   import { getCurrentInstance, ref, toRefs, reactive } from 'vue'; | ||||
|   import { | ||||
|     SystemLogParam, | ||||
|     SystemLogRecord, | ||||
|   | ||||
| @@ -210,7 +210,7 @@ | ||||
|       <a-drawer | ||||
|         title="部门详情" | ||||
|         :visible="detailVisible" | ||||
|         :width="570" | ||||
|         :width="580" | ||||
|         :footer="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
| @@ -292,7 +292,7 @@ | ||||
|     DeptParam, | ||||
|     listDept, | ||||
|     getDept, | ||||
|     createDept, | ||||
|     addDept, | ||||
|     updateDept, | ||||
|     deleteDept, | ||||
|   } from '@/api/system/dept'; | ||||
| @@ -431,7 +431,7 @@ | ||||
|             proxy.$message.success(res.msg); | ||||
|           }); | ||||
|         } else { | ||||
|           createDept(form.value).then((res) => { | ||||
|           addDept(form.value).then((res) => { | ||||
|             handleCancel(); | ||||
|             getList(); | ||||
|             proxy.$message.success(res.msg); | ||||
|   | ||||
| @@ -345,7 +345,7 @@ | ||||
|     MenuParam, | ||||
|     listMenu, | ||||
|     getMenu, | ||||
|     createMenu, | ||||
|     addMenu, | ||||
|     updateMenu, | ||||
|     deleteMenu, | ||||
|   } from '@/api/system/menu'; | ||||
| @@ -482,7 +482,7 @@ | ||||
|             proxy.$message.success(res.msg); | ||||
|           }); | ||||
|         } else { | ||||
|           createMenu(form.value).then((res) => { | ||||
|           addMenu(form.value).then((res) => { | ||||
|             handleCancel(); | ||||
|             getList(); | ||||
|             proxy.$message.success(res.msg); | ||||
|   | ||||
| @@ -181,7 +181,7 @@ | ||||
|       <a-drawer | ||||
|         :title="title" | ||||
|         :visible="visible" | ||||
|         :width="570" | ||||
|         :width="580" | ||||
|         :mask-closable="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
| @@ -275,7 +275,7 @@ | ||||
|       <a-drawer | ||||
|         title="角色详情" | ||||
|         :visible="detailVisible" | ||||
|         :width="570" | ||||
|         :width="580" | ||||
|         :footer="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
| @@ -384,7 +384,7 @@ | ||||
|     RoleParam, | ||||
|     listRole, | ||||
|     getRole, | ||||
|     createRole, | ||||
|     addRole, | ||||
|     updateRole, | ||||
|     deleteRole, | ||||
|   } from '@/api/system/role'; | ||||
| @@ -621,7 +621,7 @@ | ||||
|         } else { | ||||
|           form.value.menuIds = getMenuAllCheckedKeys(); | ||||
|           form.value.deptIds = getDeptAllCheckedKeys(); | ||||
|           createRole(form.value).then((res) => { | ||||
|           addRole(form.value).then((res) => { | ||||
|             handleCancel(); | ||||
|             getList(); | ||||
|             proxy.$message.success(res.msg); | ||||
|   | ||||
| @@ -12,7 +12,11 @@ | ||||
|         <template #upload-button> | ||||
|           <a-avatar :size="100" class="info-avatar"> | ||||
|             <template #trigger-icon><icon-camera /></template> | ||||
|             <img v-if="avatarList.length" :src="avatarList[0].url" :alt="$t('userCenter.panel.avatar')" /> | ||||
|             <img | ||||
|               v-if="avatarList.length" | ||||
|               :src="avatarList[0].url" | ||||
|               :alt="$t('userCenter.panel.avatar')" | ||||
|             /> | ||||
|           </a-avatar> | ||||
|         </template> | ||||
|       </a-upload> | ||||
| @@ -32,22 +36,33 @@ | ||||
|         align="right" | ||||
|         layout="inline-horizontal" | ||||
|       > | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.nickname')">{{ loginStore.nickname }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.nickname')">{{ | ||||
|           loginStore.nickname | ||||
|         }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.gender')"> | ||||
|           <div v-if="loginStore.gender === 1"> | ||||
|             {{ $t('userCenter.panel.male') }} | ||||
|             <icon-man style="color: #19BBF1" /> | ||||
|             <icon-man style="color: #19bbf1" /> | ||||
|           </div> | ||||
|           <div v-else-if="loginStore.gender === 2"> | ||||
|             {{ $t('userCenter.panel.female') }} | ||||
|             <icon-woman style="color: #FA7FA9" /> | ||||
|             <icon-woman style="color: #fa7fa9" /> | ||||
|           </div> | ||||
|           <div v-else>{{ $t('userCenter.panel.unknown') }}</div> | ||||
|         </a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.phone')">{{ loginStore.phone }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.email')">{{ loginStore.email }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.deptName')">{{ loginStore.deptName }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.registrationDate')">{{ loginStore.registrationDate }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.phone')">{{ | ||||
|           loginStore.phone | ||||
|         }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.email')">{{ | ||||
|           loginStore.email | ||||
|         }}</a-descriptions-item> | ||||
|         <a-descriptions-item :label="$t('userCenter.panel.label.deptName')">{{ | ||||
|           loginStore.deptName | ||||
|         }}</a-descriptions-item> | ||||
|         <a-descriptions-item | ||||
|           :label="$t('userCenter.panel.label.registrationDate')" | ||||
|           >{{ loginStore.registrationDate }}</a-descriptions-item | ||||
|         > | ||||
|       </a-descriptions> | ||||
|     </a-space> | ||||
|   </a-card> | ||||
| @@ -66,7 +81,7 @@ | ||||
|   const avatar = { | ||||
|     uid: '-2', | ||||
|     name: 'avatar.png', | ||||
|     url: getAvatar(loginStore), | ||||
|     url: getAvatar(loginStore.avatar, loginStore.gender), | ||||
|   }; | ||||
|   const avatarList = ref<FileItem[]>([avatar]); | ||||
|  | ||||
| @@ -88,11 +103,13 @@ | ||||
|       onProgress(20); | ||||
|       const formData = new FormData(); | ||||
|       formData.append(name as string, fileItem.file as Blob); | ||||
|       uploadAvatar(formData).then((res) => { | ||||
|       uploadAvatar(formData) | ||||
|         .then((res) => { | ||||
|           onSuccess(res); | ||||
|           loginStore.avatar = res.data.avatar; | ||||
|           proxy.$message.success(res.msg); | ||||
|       }).catch((error) => { | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|           onError(error); | ||||
|         }); | ||||
|     })(); | ||||
| @@ -109,7 +126,10 @@ | ||||
|    * @param fileItemList 文件列表 | ||||
|    * @param currentFile 当前文件 | ||||
|    */ | ||||
|   const handleAvatarChange = (fileItemList: FileItem[], currentFile: FileItem) => { | ||||
|   const handleAvatarChange = ( | ||||
|     fileItemList: FileItem[], | ||||
|     currentFile: FileItem | ||||
|   ) => { | ||||
|     avatarList.value = [currentFile]; | ||||
|   }; | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										804
									
								
								continew-admin-ui/src/views/system/user/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										804
									
								
								continew-admin-ui/src/views/system/user/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,804 @@ | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|     <Breadcrumb :items="['menu.system', 'menu.system.user.list']" /> | ||||
|     <a-card class="general-card" :title="$t('menu.system.user.list')"> | ||||
|       <a-row> | ||||
|         <a-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4" style="margin-right: 12px"> | ||||
|           <a-input-search | ||||
|             v-model="deptName" | ||||
|             placeholder="输入部门名称搜索" | ||||
|             style="margin-bottom: 8px; max-width: 240px" | ||||
|             allow-clear | ||||
|           /> | ||||
|           <a-tree | ||||
|             ref="deptTreeRef" | ||||
|             :data="deptTree" | ||||
|             default-expand-all | ||||
|             show-line | ||||
|             @select="handleSelectNode" | ||||
|           /> | ||||
|         </a-col> | ||||
|         <a-col :xs="15" :sm="18" :md="19" :lg="20" :xl="19"> | ||||
|           <!-- 头部区域 --> | ||||
|           <div class="header"> | ||||
|             <!-- 搜索栏 --> | ||||
|             <div v-if="showQuery" class="header-query"> | ||||
|               <a-form ref="queryRef" :model="queryParams" layout="inline"> | ||||
|                 <a-form-item field="username" hide-label> | ||||
|                   <a-input | ||||
|                     v-model="queryParams.username" | ||||
|                     placeholder="输入用户名搜索" | ||||
|                     allow-clear | ||||
|                     style="width: 150px" | ||||
|                     @press-enter="handleQuery" | ||||
|                   /> | ||||
|                 </a-form-item> | ||||
|                 <a-form-item field="status" hide-label> | ||||
|                   <a-select | ||||
|                     v-model="queryParams.status" | ||||
|                     :options="statusOptions" | ||||
|                     placeholder="状态搜索" | ||||
|                     allow-clear | ||||
|                     style="width: 150px" | ||||
|                   /> | ||||
|                 </a-form-item> | ||||
|                 <a-form-item field="createTime" hide-label> | ||||
|                   <date-range-picker v-model="queryParams.createTime" /> | ||||
|                 </a-form-item> | ||||
|                 <a-form-item hide-label> | ||||
|                   <a-space> | ||||
|                     <a-button type="primary" @click="handleQuery"> | ||||
|                       <template #icon><icon-search /></template>查询 | ||||
|                     </a-button> | ||||
|                     <a-button @click="resetQuery"> | ||||
|                       <template #icon><icon-refresh /></template>重置 | ||||
|                     </a-button> | ||||
|                   </a-space> | ||||
|                 </a-form-item> | ||||
|               </a-form> | ||||
|             </div> | ||||
|             <!-- 操作栏 --> | ||||
|             <div class="header-operation"> | ||||
|               <a-row> | ||||
|                 <a-col :span="12"> | ||||
|                   <a-space> | ||||
|                     <a-button type="primary" @click="toCreate"> | ||||
|                       <template #icon><icon-plus /></template>新增 | ||||
|                     </a-button> | ||||
|                     <a-button | ||||
|                       type="primary" | ||||
|                       status="success" | ||||
|                       :disabled="single" | ||||
|                       :title="single ? '请选择一条要修改的数据' : ''" | ||||
|                       @click="toUpdate(ids[0])" | ||||
|                     > | ||||
|                       <template #icon><icon-edit /></template>修改 | ||||
|                     </a-button> | ||||
|                     <a-button | ||||
|                       type="primary" | ||||
|                       status="danger" | ||||
|                       :disabled="multiple" | ||||
|                       :title="multiple ? '请选择要删除的数据' : ''" | ||||
|                       @click="handleBatchDelete" | ||||
|                     > | ||||
|                       <template #icon><icon-delete /></template>删除 | ||||
|                     </a-button> | ||||
|                     <a-button | ||||
|                       :loading="exportLoading" | ||||
|                       type="primary" | ||||
|                       status="warning" | ||||
|                       @click="handleExport" | ||||
|                     > | ||||
|                       <template #icon><icon-download /></template>导出 | ||||
|                     </a-button> | ||||
|                   </a-space> | ||||
|                 </a-col> | ||||
|                 <a-col :span="12"> | ||||
|                   <right-toolbar | ||||
|                     v-model:show-query="showQuery" | ||||
|                     @refresh="getList" | ||||
|                   /> | ||||
|                 </a-col> | ||||
|               </a-row> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <!-- 列表区域 --> | ||||
|           <a-table | ||||
|             ref="tableRef" | ||||
|             :data="userList" | ||||
|             :row-selection="{ | ||||
|               type: 'checkbox', | ||||
|               showCheckedAll: true, | ||||
|               onlyCurrent: false, | ||||
|             }" | ||||
|             :pagination="{ | ||||
|               showTotal: true, | ||||
|               showPageSize: true, | ||||
|               total: total, | ||||
|               current: queryParams.page, | ||||
|             }" | ||||
|             row-key="userId" | ||||
|             :bordered="false" | ||||
|             :stripe="true" | ||||
|             :loading="loading" | ||||
|             size="large" | ||||
|             :scroll="{ | ||||
|               x: '120%', | ||||
|             }" | ||||
|             @page-change="handlePageChange" | ||||
|             @page-size-change="handlePageSizeChange" | ||||
|             @selection-change="handleSelectionChange" | ||||
|           > | ||||
|             <template #columns> | ||||
|               <a-table-column title="ID" data-index="userId" /> | ||||
|               <a-table-column title="用户名"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   <a-link @click="toDetail(record.userId)">{{ | ||||
|                     record.username | ||||
|                   }}</a-link> | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column title="昵称" data-index="nickname" :width="120" /> | ||||
|               <a-table-column title="性别"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   <span v-if="record.gender === 1">男</span> | ||||
|                   <span v-else-if="record.gender === 2">女</span> | ||||
|                   <span v-else>未知</span> | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column title="头像" align="center"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   <a-avatar> | ||||
|                     <img :src="getAvatar(record.avatar, record.gender)" alt="头像" /> | ||||
|                   </a-avatar> | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column title="联系方式" :width="175"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   {{ record.email }}<br v-if="record.email && record.phone"> | ||||
|                   {{ record.phone }} | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column title="状态" align="center"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   <a-switch | ||||
|                     v-model="record.status" | ||||
|                     :checked-value="1" | ||||
|                     :unchecked-value="2" | ||||
|                     :disabled="record.disabled" | ||||
|                     @change="handleChangeStatus(record)" | ||||
|                   /> | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column title="描述" data-index="description" ellipsis tooltip /> | ||||
|               <a-table-column title="创建人/创建时间" :width="175"> | ||||
|                 <template #cell="{ record }"> | ||||
|                   {{ record.createUserString }}<br> | ||||
|                   {{ record.createTime }} | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|               <a-table-column | ||||
|                 title="操作" | ||||
|                 align="center" | ||||
|                 fixed="right" | ||||
|                 :width="120" | ||||
|               > | ||||
|                 <template #cell="{ record }"> | ||||
|                   <a-button | ||||
|                     v-permission="['admin']" | ||||
|                     type="text" | ||||
|                     size="small" | ||||
|                     title="修改" | ||||
|                     @click="toUpdate(record.userId)" | ||||
|                   > | ||||
|                     <template #icon><icon-edit /></template>修改 | ||||
|                   </a-button> | ||||
|                   <a-popconfirm | ||||
|                     content="确定要删除当前选中的数据吗?" | ||||
|                     type="warning" | ||||
|                     @ok="handleDelete([record.userId])" | ||||
|                   > | ||||
|                     <a-button | ||||
|                       v-permission="['admin']" | ||||
|                       type="text" | ||||
|                       size="small" | ||||
|                       title="删除" | ||||
|                       :disabled="record.disabled" | ||||
|                     > | ||||
|                       <template #icon><icon-delete /></template>删除 | ||||
|                     </a-button> | ||||
|                   </a-popconfirm> | ||||
|                 </template> | ||||
|               </a-table-column> | ||||
|             </template> | ||||
|           </a-table> | ||||
|         </a-col> | ||||
|       </a-row> | ||||
|  | ||||
|       <!-- 表单区域 --> | ||||
|       <a-modal | ||||
|         :title="title" | ||||
|         :visible="visible" | ||||
|         :width="580" | ||||
|         :mask-closable="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
|         @ok="handleOk" | ||||
|         @cancel="handleCancel" | ||||
|       > | ||||
|         <a-form | ||||
|           ref="formRef" | ||||
|           :model="form" | ||||
|           :rules="rules" | ||||
|           :label-col-style="{ width: '84px' }" | ||||
|           size="large" | ||||
|           layout="inline" | ||||
|         > | ||||
|           <a-form-item label="用户名" field="username"> | ||||
|             <a-input | ||||
|               v-model="form.username" | ||||
|               placeholder="请输入用户名" | ||||
|               style="width: 162px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="昵称" field="nickname"> | ||||
|             <a-input | ||||
|               v-model="form.nickname" | ||||
|               placeholder="请输入昵称" | ||||
|               style="width: 162px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="邮箱" field="email"> | ||||
|             <a-input | ||||
|               v-model="form.email" | ||||
|               placeholder="请输入邮箱" | ||||
|               style="width: 162px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="手机号码" field="phone"> | ||||
|             <a-input | ||||
|               v-model="form.phone" | ||||
|               placeholder="请输入手机号码" | ||||
|               style="width: 162px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="性别" field="gender"> | ||||
|             <a-radio-group v-model="form.gender"> | ||||
|               <a-radio :value="1">男</a-radio> | ||||
|               <a-radio :value="2">女</a-radio> | ||||
|               <a-radio :value="0" disabled>未知</a-radio> | ||||
|             </a-radio-group> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="所属角色" field="roleIds"> | ||||
|             <a-select | ||||
|               v-model="form.roleIds" | ||||
|               :options="roleOptions" | ||||
|               :field-names="{ | ||||
|                 label: 'title', | ||||
|                 value: 'key', | ||||
|               }" | ||||
|               placeholder="请选择所属角色" | ||||
|               :loading="roleLoading" | ||||
|               multiple | ||||
|               allow-clear | ||||
|               :allow-search="{ retainInputValue: true }" | ||||
|               style="width: 416px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="所属部门" field="deptId"> | ||||
|             <a-tree-select | ||||
|               v-model="form.deptId" | ||||
|               :data="deptOptions" | ||||
|               placeholder="请选择所属部门" | ||||
|               allow-clear | ||||
|               allow-search | ||||
|               :filter-tree-node="filterDeptOptions" | ||||
|               style="width: 416px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="描述" field="description"> | ||||
|             <a-textarea | ||||
|               v-model="form.description" | ||||
|               :max-length="200" | ||||
|               placeholder="请输入描述" | ||||
|               :auto-size="{ | ||||
|                 minRows: 3, | ||||
|               }" | ||||
|               show-word-limit | ||||
|               style="width: 416px" | ||||
|             /> | ||||
|           </a-form-item> | ||||
|         </a-form> | ||||
|       </a-modal> | ||||
|  | ||||
|       <!-- 详情区域 --> | ||||
|       <a-drawer | ||||
|         title="用户详情" | ||||
|         :visible="detailVisible" | ||||
|         :width="580" | ||||
|         :footer="false" | ||||
|         unmount-on-close | ||||
|         render-to-body | ||||
|         @cancel="handleDetailCancel" | ||||
|       > | ||||
|         <a-descriptions title="基础信息" :column="2" bordered size="large"> | ||||
|           <a-descriptions-item label="用户名"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.username }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="昵称"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.nickname }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="性别"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else> | ||||
|               <span v-if="user.gender === 1">男</span> | ||||
|               <span v-else>女</span> | ||||
|             </span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="状态"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else> | ||||
|               <a-tag v-if="user.status === 1" color="green">启用</a-tag> | ||||
|               <a-tag v-else color="red">禁用</a-tag> | ||||
|             </span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="邮箱"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.email || '无' }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="手机号码"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.phone || '无' }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="所属角色"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.roleNames }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="所属部门"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.deptName }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="创建人"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.createUserString }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="创建时间"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.createTime }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="修改人"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.updateUserString }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="修改时间"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.updateTime }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="描述"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ user.description }}</span> | ||||
|           </a-descriptions-item> | ||||
|         </a-descriptions> | ||||
|       </a-drawer> | ||||
|     </a-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { getCurrentInstance, ref, toRefs, reactive, watch } from 'vue'; | ||||
|   import { SelectOptionData, TreeNodeData } from '@arco-design/web-vue'; | ||||
|   import { | ||||
|     UserRecord, | ||||
|     UserParam, | ||||
|     listUser, | ||||
|     getUser, | ||||
|     addUser, | ||||
|     updateUser, | ||||
|     deleteUser, | ||||
|   } from '@/api/system/user'; | ||||
|   import { listRoleTree, listDeptTree } from '@/api/common'; | ||||
|   import getAvatar from '@/utils/avatar'; | ||||
|  | ||||
|   const { proxy } = getCurrentInstance() as any; | ||||
|  | ||||
|   const userList = ref<UserRecord[]>([]); | ||||
|   const user = ref<UserRecord>({ | ||||
|     username: '', | ||||
|     nickname: '', | ||||
|     gender: 1, | ||||
|     email: undefined, | ||||
|     phone: undefined, | ||||
|     status: 1, | ||||
|     createUserString: '', | ||||
|     createTime: '', | ||||
|     updateUserString: '', | ||||
|     updateTime: '', | ||||
|     description: '', | ||||
|     roleIds: undefined, | ||||
|     deptId: undefined, | ||||
|   }); | ||||
|   const total = ref(0); | ||||
|   const ids = ref<Array<number>>([]); | ||||
|   const title = ref(''); | ||||
|   const single = ref(true); | ||||
|   const multiple = ref(true); | ||||
|   const showQuery = ref(true); | ||||
|   const loading = ref(false); | ||||
|   const detailLoading = ref(false); | ||||
|   const exportLoading = ref(false); | ||||
|   const visible = ref(false); | ||||
|   const detailVisible = ref(false); | ||||
|   const statusOptions = ref<SelectOptionData[]>([ | ||||
|     { label: '启用', value: 1 }, | ||||
|     { label: '禁用', value: 2 }, | ||||
|   ]); | ||||
|   const roleLoading = ref(false); | ||||
|   const deptLoading = ref(false); | ||||
|   const roleOptions = ref<TreeNodeData[]>([]); | ||||
|   const deptOptions = ref<TreeNodeData[]>([]); | ||||
|   const deptTree = ref<TreeNodeData[]>([]); | ||||
|   const deptName = ref(''); | ||||
|  | ||||
|   const data = reactive({ | ||||
|     // 查询参数 | ||||
|     queryParams: { | ||||
|       username: undefined, | ||||
|       status: undefined, | ||||
|       createTime: undefined, | ||||
|       deptId: undefined, | ||||
|       page: 1, | ||||
|       size: 10, | ||||
|       sort: ['createTime,desc'], | ||||
|     }, | ||||
|     // 表单数据 | ||||
|     form: {} as UserRecord, | ||||
|     // 表单验证规则 | ||||
|     rules: { | ||||
|       username: [{ required: true, message: '请输入用户名' }], | ||||
|       nickname: [{ required: true, message: '请输入昵称' }], | ||||
|       roleIds: [{ required: true, message: '请选择所属角色' }], | ||||
|       deptId: [{ required: true, message: '请选择所属部门' }], | ||||
|     }, | ||||
|   }); | ||||
|   const { queryParams, form, rules } = toRefs(data); | ||||
|  | ||||
|   /** | ||||
|    * 查询部门树 | ||||
|    * | ||||
|    * @param name 名称 | ||||
|    */ | ||||
|   const getDeptTree = (name: string) => { | ||||
|     listDeptTree({ deptName: name }).then((res) => { | ||||
|       deptTree.value = res.data; | ||||
|       setTimeout(() => { | ||||
|         proxy.$refs.deptTreeRef.expandAll(); | ||||
|       }, 0); | ||||
|     }); | ||||
|   }; | ||||
|   getDeptTree(''); | ||||
|   watch(deptName, (val) => { | ||||
|     getDeptTree(val); | ||||
|   }); | ||||
|  | ||||
|   /** | ||||
|    * 查询列表 | ||||
|    * | ||||
|    * @param params 查询参数 | ||||
|    */ | ||||
|   const getList = (params: UserParam = { ...queryParams.value }) => { | ||||
|     loading.value = true; | ||||
|     listUser(params) | ||||
|       .then((res) => { | ||||
|         userList.value = res.data.list; | ||||
|         total.value = res.data.total; | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         loading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|   getList(); | ||||
|  | ||||
|   /** | ||||
|    * 打开新增对话框 | ||||
|    */ | ||||
|   const toCreate = () => { | ||||
|     reset(); | ||||
|     getRoleOptions(); | ||||
|     getDeptOptions(); | ||||
|     title.value = '新增用户'; | ||||
|     visible.value = true; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 打开修改对话框 | ||||
|    * | ||||
|    * @param id ID | ||||
|    */ | ||||
|   const toUpdate = (id: number) => { | ||||
|     reset(); | ||||
|     getRoleOptions(); | ||||
|     getDeptOptions(); | ||||
|     getUser(id).then((res) => { | ||||
|       form.value = res.data; | ||||
|       title.value = '修改用户'; | ||||
|       visible.value = true; | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查询角色列表 | ||||
|    */ | ||||
|   const getRoleOptions = () => { | ||||
|     roleLoading.value = true; | ||||
|     listRoleTree({}) | ||||
|       .then((res) => { | ||||
|         roleOptions.value = res.data; | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         roleLoading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查询部门列表 | ||||
|    */ | ||||
|   const getDeptOptions = () => { | ||||
|     deptLoading.value = true; | ||||
|     listDeptTree({}) | ||||
|       .then((res) => { | ||||
|         deptOptions.value = res.data; | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         deptLoading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 重置表单 | ||||
|    */ | ||||
|   const reset = () => { | ||||
|     form.value = { | ||||
|       userId: undefined, | ||||
|       username: '', | ||||
|       nickname: '', | ||||
|       gender: 1, | ||||
|       email: undefined, | ||||
|       phone: undefined, | ||||
|       description: '', | ||||
|       status: 1, | ||||
|       roleIds: [], | ||||
|       deptId: undefined, | ||||
|     }; | ||||
|     proxy.$refs.formRef?.resetFields(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 取消 | ||||
|    */ | ||||
|   const handleCancel = () => { | ||||
|     visible.value = false; | ||||
|     proxy.$refs.formRef.resetFields(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 确定 | ||||
|    */ | ||||
|   const handleOk = () => { | ||||
|     proxy.$refs.formRef.validate((valid: any) => { | ||||
|       if (!valid) { | ||||
|         if (form.value.userId !== undefined) { | ||||
|           updateUser(form.value).then((res) => { | ||||
|             handleCancel(); | ||||
|             getList(); | ||||
|             proxy.$message.success(res.msg); | ||||
|           }); | ||||
|         } else { | ||||
|           addUser(form.value).then((res) => { | ||||
|             handleCancel(); | ||||
|             getList(); | ||||
|             proxy.$message.success(res.msg); | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查看详情 | ||||
|    * | ||||
|    * @param id ID | ||||
|    */ | ||||
|   const toDetail = async (id: number) => { | ||||
|     if (detailLoading.value) return; | ||||
|     detailLoading.value = true; | ||||
|     detailVisible.value = true; | ||||
|     getUser(id) | ||||
|       .then((res) => { | ||||
|         user.value = res.data; | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         detailLoading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 关闭详情 | ||||
|    */ | ||||
|   const handleDetailCancel = () => { | ||||
|     detailVisible.value = false; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 批量删除 | ||||
|    */ | ||||
|   const handleBatchDelete = () => { | ||||
|     if (ids.value.length === 0) { | ||||
|       proxy.$message.info('请选择要删除的数据'); | ||||
|     } else { | ||||
|       proxy.$modal.warning({ | ||||
|         title: '警告', | ||||
|         titleAlign: 'start', | ||||
|         content: '确定要删除当前选中的数据吗?', | ||||
|         hideCancel: false, | ||||
|         onOk: () => { | ||||
|           handleDelete(ids.value); | ||||
|         }, | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 删除 | ||||
|    * | ||||
|    * @param ids ID 列表 | ||||
|    */ | ||||
|   const handleDelete = (ids: Array<number>) => { | ||||
|     deleteUser(ids).then((res) => { | ||||
|       proxy.$message.success(res.msg); | ||||
|       getList(); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 已选择的数据行发生改变时触发 | ||||
|    * | ||||
|    * @param rowKeys ID 列表 | ||||
|    */ | ||||
|   const handleSelectionChange = (rowKeys: Array<any>) => { | ||||
|     ids.value = rowKeys; | ||||
|     single.value = rowKeys.length !== 1; | ||||
|     multiple.value = !rowKeys.length; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 导出 | ||||
|    */ | ||||
|   const handleExport = () => { | ||||
|     if (exportLoading.value) return; | ||||
|     exportLoading.value = true; | ||||
|     proxy | ||||
|       .download('/system/user/export', { ...queryParams.value }, '用户数据') | ||||
|       .finally(() => { | ||||
|         exportLoading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 修改状态 | ||||
|    * | ||||
|    * @param record 记录信息 | ||||
|    */ | ||||
|   const handleChangeStatus = (record: UserRecord) => { | ||||
|     const tip = record.status === 1 ? '启用' : '禁用'; | ||||
|     updateUser(record) | ||||
|       .then(() => { | ||||
|         proxy.$message.success(`${tip}成功`); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         record.status = record.status === 1 ? 2 : 1; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 过滤部门列表 | ||||
|    * | ||||
|    * @param searchValue 搜索值 | ||||
|    * @param nodeData 节点值 | ||||
|    */ | ||||
|   const filterDeptOptions = (searchValue: string, nodeData: TreeNodeData) => { | ||||
|     if (nodeData.title) { | ||||
|       return ( | ||||
|         nodeData.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1 | ||||
|       ); | ||||
|     } | ||||
|     return false; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 根据选中部门查询 | ||||
|    * | ||||
|    * @param keys 选中节点 key | ||||
|    */ | ||||
|   const handleSelectNode = (keys: Array<any>) => { | ||||
|     if (queryParams.value.deptId === keys[0]) { | ||||
|       queryParams.value.deptId = undefined; | ||||
|       // 如已选中,再次点击则取消选中 | ||||
|       proxy.$refs.deptTreeRef.selectNode(keys, false); | ||||
|     } else { | ||||
|       queryParams.value.deptId = keys.length === 1 ? keys[0] : undefined; | ||||
|     } | ||||
|     handleQuery(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查询 | ||||
|    */ | ||||
|   const handleQuery = () => { | ||||
|     getList(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 重置 | ||||
|    */ | ||||
|   const resetQuery = () => { | ||||
|     proxy.$refs.queryRef.resetFields(); | ||||
|     handleQuery(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 切换页码 | ||||
|    * | ||||
|    * @param current 页码 | ||||
|    */ | ||||
|   const handlePageChange = (current: number) => { | ||||
|     queryParams.value.page = current; | ||||
|     getList(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 切换每页条数 | ||||
|    * | ||||
|    * @param pageSize 每页条数 | ||||
|    */ | ||||
|   const handlePageSizeChange = (pageSize: number) => { | ||||
|     queryParams.value.size = pageSize; | ||||
|     getList(); | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
|   export default { | ||||
|     name: 'User', | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"></style> | ||||
							
								
								
									
										3
									
								
								continew-admin-ui/src/views/system/user/locale/en-US.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								continew-admin-ui/src/views/system/user/locale/en-US.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.system.user.list': 'User management', | ||||
| }; | ||||
							
								
								
									
										3
									
								
								continew-admin-ui/src/views/system/user/locale/zh-CN.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								continew-admin-ui/src/views/system/user/locale/zh-CN.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.system.user.list': '用户管理', | ||||
| }; | ||||
| @@ -35,10 +35,13 @@ import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.monitor.annotation.Log; | ||||
| import top.charles7c.cnadmin.system.model.query.DeptQuery; | ||||
| import top.charles7c.cnadmin.system.model.query.MenuQuery; | ||||
| import top.charles7c.cnadmin.system.model.query.RoleQuery; | ||||
| import top.charles7c.cnadmin.system.model.vo.DeptVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.MenuVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.RoleVO; | ||||
| import top.charles7c.cnadmin.system.service.DeptService; | ||||
| import top.charles7c.cnadmin.system.service.MenuService; | ||||
| import top.charles7c.cnadmin.system.service.RoleService; | ||||
|  | ||||
| /** | ||||
|  * 公共 API | ||||
| @@ -54,6 +57,7 @@ public class CommonController { | ||||
|  | ||||
|     private final DeptService deptService; | ||||
|     private final MenuService menuService; | ||||
|     private final RoleService roleService; | ||||
|  | ||||
|     @Log(ignore = true) | ||||
|     @Operation(summary = "查询部门树", description = "查询树结构的部门列表") | ||||
| @@ -72,4 +76,13 @@ public class CommonController { | ||||
|         List<Tree<Long>> treeList = menuService.buildTree(list); | ||||
|         return R.ok(treeList); | ||||
|     } | ||||
|  | ||||
|     @Log(ignore = true) | ||||
|     @Operation(summary = "查询角色树", description = "查询树结构的角色列表") | ||||
|     @GetMapping("/tree/role") | ||||
|     public R<List<Tree<Long>>> listRoleTree(@Validated RoleQuery query, @Validated SortQuery sortQuery) { | ||||
|         List<RoleVO> list = roleService.list(query, sortQuery); | ||||
|         List<Tree<Long>> treeList = roleService.buildTree(list); | ||||
|         return R.ok(treeList); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -38,10 +38,10 @@ import top.charles7c.cnadmin.common.util.RedisUtils; | ||||
| import top.charles7c.cnadmin.common.util.SecureUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| import top.charles7c.cnadmin.common.util.validate.ValidationUtils; | ||||
| import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||
| import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest; | ||||
| import top.charles7c.cnadmin.system.model.request.UpdateEmailRequest; | ||||
| import top.charles7c.cnadmin.system.model.request.UpdatePasswordRequest; | ||||
| import top.charles7c.cnadmin.system.model.request.UserRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.AvatarVO; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| @@ -73,10 +73,10 @@ public class UserCenterController { | ||||
|     @Operation(summary = "修改基础信息", description = "修改用户基础信息") | ||||
|     @PatchMapping("/basic/info") | ||||
|     public R updateBasicInfo(@Validated @RequestBody UpdateBasicInfoRequest updateBasicInfoRequest) { | ||||
|         UserDO userDO = new UserDO(); | ||||
|         userDO.setUserId(LoginHelper.getUserId()); | ||||
|         BeanUtil.copyProperties(updateBasicInfoRequest, userDO); | ||||
|         userService.update(userDO); | ||||
|         UserRequest userRequest = new UserRequest(); | ||||
|         userRequest.setUserId(LoginHelper.getUserId()); | ||||
|         BeanUtil.copyProperties(updateBasicInfoRequest, userRequest); | ||||
|         userService.update(userRequest); | ||||
|         return R.ok("修改成功"); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.webapi.controller.system; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
|  | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.annotation.CrudRequestMapping; | ||||
| import top.charles7c.cnadmin.common.base.BaseController; | ||||
| import top.charles7c.cnadmin.common.base.BaseRequest; | ||||
| import top.charles7c.cnadmin.common.consts.Constants; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.system.model.query.UserQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.UserRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserDetailVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.UserVO; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
|  * 用户管理 API | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/20 21:00 | ||||
|  */ | ||||
| @Tag(name = "用户管理 API") | ||||
| @RestController | ||||
| @CrudRequestMapping("/system/user") | ||||
| public class UserController extends BaseController<UserService, UserVO, UserDetailVO, UserQuery, UserRequest> { | ||||
|  | ||||
|     @Override | ||||
|     protected R<Long> add(@Validated(BaseRequest.Create.class) @RequestBody UserRequest request) { | ||||
|         Long id = baseService.add(request); | ||||
|         return R.ok(String.format("新增成功,请牢记默认密码:%s", Constants.DEFAULT_PASSWORD), id); | ||||
|     } | ||||
| } | ||||
| @@ -161,7 +161,15 @@ spring: | ||||
|     # 配合 Maven Profile 选择不同配置文件进行启动,在 IntelliJ IDEA 右侧 Maven 工具窗口可以快速切换环境 | ||||
|     active: @profiles.active@ | ||||
|   main: | ||||
|     # 允许定义重名的 bean 对象覆盖原有的 bean | ||||
|     allow-bean-definition-overriding: true | ||||
|     # 允许循环依赖 | ||||
|     allow-circular-references: true | ||||
|   ## MVC 配置 | ||||
|   mvc: | ||||
|     format: | ||||
|       # 日期格式化(针对 java.util.Date) | ||||
|       date-time: yyyy-MM-dd HH:mm:ss | ||||
|   ## JSON 配置 | ||||
|   jackson: | ||||
|     # 时区配置 | ||||
|   | ||||
| @@ -42,29 +42,29 @@ INSERT IGNORE INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '系统 | ||||
| INSERT IGNORE INTO `sys_role` VALUES (2, '测试人员', 'test', 5, '系统初始角色', 2, 2, 1, NOW(), 1, NOW()); | ||||
|  | ||||
| -- 初始化默认用户:admin/admin123;test/123456 | ||||
| INSERT IGNORE INTO `sys_user` VALUES (1, 'admin', '超级管理员', '9802815bcc5baae7feb1ae0d0566baf2', 1, '18888888888', 'charles7c@126.com', NULL, '系统初始用户', 1, NOW(), 1, 1, NOW(), 1, NOW()); | ||||
| INSERT IGNORE INTO `sys_user` VALUES (2, 'test', '测试员', '8e114197e1b33783a00542ad67e80516', 0, NULL, NULL, NULL, '系统初始用户', 2, NOW(), 2, 1, NOW(), 1, NOW()); | ||||
| INSERT IGNORE INTO `sys_user` VALUES (1, 'admin', '超级管理员', '9802815bcc5baae7feb1ae0d0566baf2', 1, 'charles7c@126.com', '18888888888', NULL, '系统初始用户', 1, NOW(), 1, 1, NOW(), 1, NOW()); | ||||
| INSERT IGNORE INTO `sys_user` VALUES (2, 'test', '测试员', '8e114197e1b33783a00542ad67e80516', 2, NULL, NULL, NULL, '系统初始用户', 2, NOW(), 2, 1, NOW(), 1, NOW()); | ||||
|  | ||||
| -- 初始化默认角色和菜单关联数据 | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1000); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1010); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1011); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1012); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1013); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1014); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1030); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1031); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1032); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1033); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1034); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1050); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1051); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1052); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1053); | ||||
| INSERT INTO `sys_role_menu` VALUES (2, 1054); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1000); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1010); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1011); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1012); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1013); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1014); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1030); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1031); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1032); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1033); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1034); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1050); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1051); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1052); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1053); | ||||
| INSERT IGNORE INTO `sys_role_menu` VALUES (2, 1054); | ||||
|  | ||||
| -- 初始化默认角色和部门关联数据 | ||||
| INSERT INTO `sys_role_dept` VALUES (2, 5); | ||||
| INSERT IGNORE INTO `sys_role_dept` VALUES (2, 5); | ||||
|  | ||||
| -- 初始化默认用户和角色关联数据 | ||||
| INSERT IGNORE INTO `sys_user_role` VALUES (1, 1); | ||||
|   | ||||
| @@ -78,8 +78,8 @@ CREATE TABLE IF NOT EXISTS `sys_user`  ( | ||||
|     `nickname` varchar(255) DEFAULT NULL COMMENT '昵称', | ||||
|     `password` varchar(255) DEFAULT NULL COMMENT '密码', | ||||
|     `gender` tinyint(1) unsigned DEFAULT 0 COMMENT '性别(0未知 1男 2女)', | ||||
|     `phone` varchar(255) DEFAULT NULL COMMENT '手机号码', | ||||
|     `email` varchar(255) DEFAULT NULL COMMENT '邮箱', | ||||
|     `phone` varchar(255) DEFAULT NULL COMMENT '手机号码', | ||||
|     `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', | ||||
|     `description` varchar(512) DEFAULT NULL COMMENT '描述', | ||||
|     `status` tinyint(1) unsigned DEFAULT 1 COMMENT '状态(1启用 2禁用)', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user