SpringBoot3+Vue3 前后端分离项目实现基于RBAC权限访问控制-(1)权限管理
SpringBoot3+Vue3 前后端分离项目实现基于RBAC权限访问控制-(2)角色管理
1、SpringBoot 3 后端实现用户管理
1.1 Entity-实体类
1.1.1 User
package com.dragon.springboot3vue3.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Schema(name = "User", description = "用户表")
public class User implements Serializable {
private static final long serialVersionUID=1L;
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
@JsonIgnore
private String password;
@Schema(description = "姓名")
private String name;
@Schema(description = "邮箱")
@Email
private String email;
@Schema(description = "头像地址")
@URL
private String avatarUrl;
@Schema(description = "创建人ID")
@TableField(fill = FieldFill.INSERT)
private String creatorId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime ts;
@Schema(description = "逻辑删除字段 0.正常 1.删除")
@TableLogic
private Integer deleteFlag;
}
1.1.2 UserRole
package com.dragon.springboot3vue3.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 用户角色表
* </p>
*/
@Data
@TableName("user_role")
@Schema(name = "UserRole", description = "用户角色表")
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
@Schema(description = "用户ID")
private String userId;
@Schema(description = "角色ID")
private String roleId;
@Schema(description = "创建人ID")
@TableField(fill = FieldFill.INSERT)
private String creatorId;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime ts;
@Schema(description = "逻辑删除字段 0.正常 1.删除")
private Integer deleteFlag;
}
1.2 UserController
package com.dragon.springboot3vue3.controller;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dragon.springboot3vue3.common.Constant;
import com.dragon.springboot3vue3.controller.dto.entityDto.RegisterOrLoginDto;
import com.dragon.springboot3vue3.controller.dto.entityDto.UpdatePwdDto;
import com.dragon.springboot3vue3.controller.dto.entityDto.UserDto;
import com.dragon.springboot3vue3.controller.dto.pageDto.UserPageDto;
import com.dragon.springboot3vue3.entity.Role;
import com.dragon.springboot3vue3.entity.User;
import com.dragon.springboot3vue3.entity.UserRole;
import com.dragon.springboot3vue3.mapper.UserMapper;
import com.dragon.springboot3vue3.service.IImportExportService;
import com.dragon.springboot3vue3.service.IUserRoleService;
import com.dragon.springboot3vue3.service.IUserService;
import com.dragon.springboot3vue3.utils.StringDTO;
import com.dragon.springboot3vue3.utils.StringIdsDTO;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Tag(name = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
private IUserRoleService userRoleService;
@Autowired
private IImportExportService importExportService;
@Operation(summary = "注册")
@PostMapping("/register")
public SaResult register(@RequestBody @Validated RegisterOrLoginDto registerDto){
User user=userService.lambdaQuery().eq(User::getUsername,registerDto.getUsername()).one();
if(user!=null){
return SaResult.error(Constant.USERNAME_OCCUPIED_MSG).setCode(Constant.USERNAME_OCCUPIED_CODE);
}
user=new User();
BeanUtils.copyProperties(registerDto,user);
// BCrypt.hashpw() 密码加密
user.setPassword(BCrypt.hashpw(registerDto.getPassword(), BCrypt.gensalt()));
userService.save(user);
return SaResult.ok();
}
@Operation(summary = "登录")
@PostMapping("/login")
public SaResult login(@RequestBody @Validated RegisterOrLoginDto loginDto){
User user=userService.lambdaQuery().eq(User::getUsername,loginDto.getUsername()).one();
// BCrypt.checkpw(前端明文,后端密文)
if(user!=null && BCrypt.checkpw(loginDto.getPassword(),user.getPassword())){
// 登录认证
StpUtil.login(user.getId());
// 生成token,token 信息自动存入redis,在yml里配置 sa-token 相关信息
String token = StpUtil.getTokenValue();
// 将用户信息存入 redis
stringRedisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user),1, TimeUnit.DAYS);
return SaResult.ok().setData(token);
}
return SaResult.error(Constant.USERNAME_OR_PASSWORD_ERROR_MSG).setCode(Constant.USERNAME_OR_PASSWORD_ERROR_CODE);
}
@Operation(summary = "注销")
@PostMapping("/logout")
public SaResult logout(){
StpUtil.logout();
return SaResult.ok();
}
@Operation(summary = "分页列表")
@PostMapping("/list")
public SaResult list(@RequestBody UserPageDto pageDto){
// 创建分页对象
Page<UserDto> page=new Page<>(pageDto.getCurrentPage(), pageDto.getPageSize());
// 构造多表查询条件
MPJLambdaWrapper<User> qw=new MPJLambdaWrapper<User>()
.selectAll(User.class)
.selectCollection(Role.class,UserDto::getRoleList)
.leftJoin(UserRole.class,UserRole::getUserId,User::getId)
.leftJoin(Role.class,Role::getId,UserRole::getRoleId)
.like(StringUtils.isNotBlank(pageDto.getUsername()),User::getUsername, pageDto.getUsername())
.like(StringUtils.isNotBlank(pageDto.getName()),User::getName, pageDto.getName())
.like(StringUtils.isNotBlank(pageDto.getEmail()),User::getEmail, pageDto.getEmail())
.orderByDesc(User::getCreateTime);
// 根据查询条件,将结果封装到分页对象
Page<UserDto> response=userMapper.selectJoinPage(page, UserDto.class,qw);
return SaResult.ok().setData(response);
}
@Operation(summary = "新增或更新")
@PostMapping("/saveOrUpdate")
public SaResult saveOrUpdate(@RequestBody @Validated UserDto userDto){
return userService.saveOrUpdate(userDto);
}
@Operation(summary = "删除")
@DeleteMapping("/remove")
public SaResult remove(@RequestBody @Validated StringIdsDTO stringIdsDTO){
userService.removeByIds(stringIdsDTO.getIds());
return SaResult.ok();
}
@Operation(summary = "获取登录用户信息")
@GetMapping("/userInfo")
public SaResult userInfo(){
return SaResult.ok().setData(userService.userInfo());
}
@Operation(summary = "更新用户基本信息")
@PutMapping("/updateUserInfo")
public SaResult updateUserInfo(@RequestBody @Validated User userDto){
User user=userService.getById(StpUtil.getLoginIdAsString());
BeanUtils.copyProperties(userDto,user);
// user 存入 redis
stringRedisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user),1, TimeUnit.DAYS);
userService.saveOrUpdate(user);
return SaResult.ok();
}
@Operation(summary = "更新头像")
@PutMapping("/updateAvatar")
public SaResult updateAvatar(@RequestBody StringDTO stringDTO){
User user = userService.userInfo();
user.setAvatarUrl(stringDTO.getStr());
userService.saveOrUpdate(user);
// user 存入 redis
stringRedisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user),1, TimeUnit.DAYS);
return SaResult.ok().setData(user);
}
@Operation(summary = "修改密码")
@PutMapping("/resetPwd")
public SaResult resetPwd(@RequestBody @Validated UpdatePwdDto updatePwdDto,@RequestHeader(Constant.TOKEN) String token){
String oldPwd = updatePwdDto.getOldPwd();
String newPwd = updatePwdDto.getNewPwd();
String confirmPwd = updatePwdDto.getConfirmPwd();
User user= userService.userInfo();
if(StringUtils.isBlank(oldPwd) || StringUtils.isBlank(newPwd) || StringUtils.isBlank(confirmPwd)){
return SaResult.error(Constant.MISSING_NECESSARY_PARAMETERS_MSG).setCode(Constant.MISSING_NECESSARY_PARAMETERS_CODE);
}
if(!BCrypt.checkpw(oldPwd,user.getPassword())){
return SaResult.error(Constant.ORIGINAL_PASSWORD_ERROR_MSG).setCode(Constant.ORIGINAL_PASSWORD_ERROR_CODE);
}
if(!newPwd.equals(confirmPwd)){
return SaResult.error(Constant.PASSWORD_INCONSISTENCY_MSG).setCode(Constant.PASSWORD_INCONSISTENCY_CODE);
}
user.setPassword(BCrypt.hashpw(newPwd, BCrypt.gensalt()));
userService.saveOrUpdate(user);
stringRedisTemplate.opsForValue().getOperations().delete(token);
return SaResult.ok();
}
@Operation(summary = "用户信息导出")
@GetMapping("/userInfoExport")
public void userInfoExport(HttpServletResponse response) throws Exception {
List<User> list=userService.list();
importExportService.export(list,"用户信息",response);
}
@Operation(summary = "用户信息导入")
@PostMapping("/userInfoImport")
public SaResult userInfoImport(@RequestParam MultipartFile file) throws Exception {
List<User> list = importExportService.imp(file, User.class);
userService.saveBatch(list);
return SaResult.ok();
}
@Operation(summary = "用户总数")
@GetMapping("/count")
public SaResult count(){
return SaResult.ok().setData(userService.count());
}
}
1.3 IUserService & IUserRoleService
package com.dragon.springboot3vue3.service;
import cn.dev33.satoken.util.SaResult;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dragon.springboot3vue3.controller.dto.entityDto.UserDto;
import com.dragon.springboot3vue3.entity.User;
public interface IUserService extends IService<User> {
User userInfo();
SaResult saveOrUpdate(UserDto userDto);
}
package com.dragon.springboot3vue3.service;
import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.entity.UserRole;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户角色表 服务类
* </p>
*/
public interface IUserRoleService extends IService<UserRole> {
// 根据用户id删除用户-角色关联关系
SaResult deleteByUserId(String id);
}
1.4 UserServiceImpl & UserRoleServiceImpl
package com.dragon.springboot3vue3.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dragon.springboot3vue3.common.Constant;
import com.dragon.springboot3vue3.controller.dto.entityDto.UserDto;
import com.dragon.springboot3vue3.entity.User;
import com.dragon.springboot3vue3.entity.UserRole;
import com.dragon.springboot3vue3.mapper.UserMapper;
import com.dragon.springboot3vue3.service.IUserRoleService;
import com.dragon.springboot3vue3.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private IUserRoleService userRoleService;
/**
* 获取 redis 中登录的用户信息
* @return
*/
@Override
public User userInfo() {
String userInfo = stringRedisTemplate.opsForValue().get(StpUtil.getLoginIdAsString());
User user= JSON.parseObject(userInfo,User.class);
return user;
}
/**
* 管理员新增或更新用户
* @param userDto
* @return
*/
@Override
public SaResult saveOrUpdate(UserDto userDto) {
if(StringUtils.isBlank(userDto.getId())){
// 1.新增时查询用户名是否存在
User getUser=this.lambdaQuery().eq(User::getUsername,userDto.getUsername()).one();
if(getUser!=null){
return SaResult.error(Constant.USERNAME_OCCUPIED_MSG).setCode(Constant.USERNAME_OCCUPIED_CODE);
}
// 2.新增用户时设置默认密码
if(StringUtils.isBlank(userDto.getPassword())){
userDto.setPassword(BCrypt.hashpw(Constant.USER_PASSWORD, BCrypt.gensalt()));
}
}
// 3.新增或更新用户
User user=new User();
BeanUtils.copyProperties(userDto,user);
this.saveOrUpdate(user);
// 更新用户-角色关联关系,先要删除关联关系,再更新
if(StringUtils.isNotBlank(userDto.getId())){
userRoleService.deleteByUserId(userDto.getId());
}
// 4.新增或更新用户-角色关联关系
List<UserRole> userRoleList=new ArrayList<>();
User getUserInfo=this.lambdaQuery().eq(User::getUsername,userDto.getUsername()).one();
userDto.getRoleList().forEach(item->{
UserRole userRole=new UserRole();
userRole.setUserId(getUserInfo.getId());
userRole.setRoleId(item.getId());
userRoleList.add(userRole);
});
userRoleService.saveOrUpdateBatch(userRoleList);
return SaResult.ok();
}
}
package com.dragon.springboot3vue3.service.impl;
import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.entity.UserRole;
import com.dragon.springboot3vue3.mapper.UserRoleMapper;
import com.dragon.springboot3vue3.service.IUserRoleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* <p>
* 用户角色表 服务实现类
* </p>
*/
@Service
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements IUserRoleService {
/**
* 根据用户id删除用户-角色关联关系
* @param userId
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public SaResult deleteByUserId(String userId) {
List<UserRole> list = this.lambdaQuery().eq(UserRole::getUserId, userId).list();
this.removeBatchByIds(list);
return SaResult.ok();
}
}
1.5 Swagger 请求后端用户分页数据(包括用户拥有的角色列表)
{
"code": 200,
"msg": "ok",
"data": {
"records": [
{
"id": "f3e7119032075080e92a0b31cac1aca3",
"username": "zzz",
"name": "",
"email": "",
"avatarUrl": "",
"creatorId": "1",
"createTime": "2024-06-14 10:16:18",
"ts": "2024-06-14 10:16:18",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "dd5f700cd9247df52031de9bf8ac7ed0",
"username": "yyy",
"name": "",
"email": "",
"avatarUrl": "",
"creatorId": "0",
"createTime": "2024-06-04 20:16:10",
"ts": "2024-06-04 20:16:10",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "63277e25cf2b0f4d952a2a2c0b5ec435",
"username": "xxx",
"name": "",
"email": "",
"avatarUrl": "",
"creatorId": "0",
"createTime": "2024-06-04 19:56:09",
"ts": "2024-06-04 20:01:17",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "5cdfeea8ba2cdb7af15b50f950e75242",
"username": "vvv",
"name": "",
"email": "",
"avatarUrl": "",
"creatorId": "1",
"createTime": "2024-06-04 18:54:11",
"ts": "2024-06-04 18:54:11",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "cb247a49dc5e9543e13fb64301144156",
"username": "www",
"name": "",
"email": "",
"avatarUrl": "",
"creatorId": "1",
"createTime": "2024-06-04 18:54:11",
"ts": "2024-06-04 18:57:54",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "8027385e5ec77a34403ccf9753373bc0",
"username": "uuu",
"name": "uuu昵称",
"email": "uuu@qq.com",
"avatarUrl": "http://localhost:8080/files/1df758c1617a46538ceb464a479f9fa5.jpg",
"creatorId": "1",
"createTime": "2024-05-19 17:15:08",
"ts": "2024-06-13 19:36:33",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [
{
"id": "3",
"roleName": "店长",
"role": "DZ",
"creatorId": null,
"createTime": "2024-05-19 16:08:00",
"ts": "2024-05-28 15:21:20",
"deleteFlag": 0
}
],
"authList": []
},
{
"id": "0911a653ea11e3284d6ab9eb78b51588",
"username": "ttt",
"name": "ttt昵称",
"email": "ttt@qq.com",
"avatarUrl": "http://localhost:8080/files/7eee9c76b69b4a43b9142604cfcd2696.png",
"creatorId": null,
"createTime": "2024-05-19 16:26:45",
"ts": "2024-06-13 19:35:12",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [
{
"id": "1",
"roleName": "管理员",
"role": "ADMIN",
"creatorId": null,
"createTime": "2024-05-11 13:52:33",
"ts": "2024-06-13 16:29:41",
"deleteFlag": 0
}
],
"authList": []
},
{
"id": "0e4b11a77cead75d2fdb78dfeb6c58c1",
"username": "sss",
"name": null,
"email": null,
"avatarUrl": null,
"creatorId": null,
"createTime": "2024-05-14 11:02:38",
"ts": "2024-05-14 11:02:38",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "56e16f3e121b8cc76396b2951c4753d4",
"username": "loong",
"name": null,
"email": null,
"avatarUrl": null,
"creatorId": null,
"createTime": "2024-05-14 10:57:50",
"ts": "2024-05-14 11:09:38",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
},
{
"id": "c4ea8cfded683689e6757c3bff61dbca",
"username": "rrr",
"name": null,
"email": null,
"avatarUrl": null,
"creatorId": null,
"createTime": "2024-04-22 16:54:43",
"ts": "2024-05-14 11:09:48",
"deleteFlag": 0,
"roleId": null,
"role": null,
"roleIds": [],
"roleList": [],
"authList": []
}
],
"total": 28,
"size": 10,
"current": 1,
"pages": 3
}
}
2、Vue 3 前端实现用户管理
2.1 前端效果
2.2 Vue 3 代码
2.2.1 api -> user.ts(相关接口)
import type { RegisterFormType,LoginFormType } from "@/types"
import request from "@/utils/request";
export default{
register(value:RegisterFormType){
return request.post('/user/register',value);
},
login(value:LoginFormType){
return request.post('/user/login',value);
},
logout(){
return request.post('/user/logout');
},
userInfo(){
return request.get('/user/userInfo');
},
updateUserInfo(value:any){
return request.put('/user/updateUserInfo',value);
},
resetPwd(value:any){
return request.put('/user/resetPwd',value);
},
updateAvatar(url:string){
return request.put('/user/updateAvatar',{ str: url });
},
list(value:any){
return request.post('/user/list',value);
},
saveOrUpdate(value:any){
return request.post('/user/saveOrUpdate',value);
},
remove(ids:any){
return request.delete('/user/remove',{
data:{ ids }
});
},
// 导出的url
exportUrl(){
return 'http://localhost:8080/user/userInfoExport';
},
// 导入的url
importUrl(){
return 'http://localhost:8080/user/userInfoImport';
},
count(){
return request.get('/user/count');
}
}
2.2.2 sys -> user- > index.vue
<template>
<el-card class="container">
<template #header>
<div class="header">
<el-breadcrumb :separator-icon="ArrowRight">
<el-breadcrumb-item :to="{ path: '/home/index' }" class="title">首页</el-breadcrumb-item>
<el-breadcrumb-item class="title">用户管理</el-breadcrumb-item>
</el-breadcrumb>
<div>
<el-button type="primary" @click="addButton">新增用户</el-button>
<el-button type="danger" @click="batchRemove">批量删除</el-button>
</div>
</div>
</template>
<!-- 搜索表单 -->
<el-form inline>
<el-form-item label="用户名">
<el-input v-model="searchModel.username" placeholder="请输入用户名" style="width: 150px" clearable></el-input>
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="searchModel.name" placeholder="请输入昵称" style="width: 150px" clearable></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="searchModel.email" placeholder="请输入邮箱" style="width: 150px" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getUserList">搜索</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
<el-form-item class="importExport">
<el-upload :action="importUrl" :show-file-list="false" accept=".xlsx" :on-success="handleImportSuccess" :on-error="handleImportError" :headers="{'X-Token':useTokenStore().token}">
<el-button :icon="Download" @click="imp" type="primary">导入</el-button>
</el-upload>
<el-button :icon="Upload" @click="exp" type="primary" style="margin-left: 10px;">导出</el-button>
</el-form-item>
</el-form>
<!-- 列表 -->
<el-table :data="userList" border stripe style="width: 100%" height="550" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" />
<el-table-column label="用户名" prop="username"></el-table-column>
<el-table-column label="昵称" prop="name"></el-table-column>
<el-table-column label="邮箱" prop="email"> </el-table-column>
<el-table-column label="头像" prop="avatarUrl" width="100">
<template #default="{ row }">
<el-avatar :size="60" :src="row.avatarUrl" />
</template>
</el-table-column>
<el-table-column label="角色">
<template #default="{ row }">
<el-tag class="tag" v-if="row.roleList!=null" v-for="item in row.roleList">{{ item.roleName }}</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime"></el-table-column>
<el-table-column label="更新时间" prop="ts"> </el-table-column>
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button :icon="Edit" circle plain type="primary" @click="edit(row)"></el-button>
<el-button :icon="Delete" circle plain type="danger" @click="remove(row)"></el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="searchModel.currentPage"
v-model:page-size="searchModel.pageSize"
:page-sizes="[10, 30, 50, 100]"
layout="jumper, total, sizes, prev, pager, next"
:total="searchModel.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
style="margin: 10px 0; justify-content: flex-end"
/>
<!-- 新增和修改的弹窗 -->
<el-dialog v-model="dialogVisible" width="50%" center @close="close">
<template #header>
<h1>{{ title }}</h1>
</template>
<el-form :model="userModel" ref="userModelRef" label-width="120px" :rules="rules">
<el-form-item label="用户名" prop="username">
<el-input v-model="userModel.username" placeholder="请输入用户名"/>
</el-form-item>
<el-form-item label="昵称" prop="name">
<el-input v-model="userModel.name" placeholder="请输入昵称"/>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userModel.email" placeholder="请输入邮箱"/>
</el-form-item>
<el-form-item label="头像" prop="avatar">
<el-upload :action="url" accept=".png,.jpeg,.jpg" :show-file-list="false" :on-success="handleUploadSuccess" :on-error="handleUploadError" >
<!-- <el-image class="img" v-if="avatarUrl" :src="avatarUrl" /> -->
<el-avatar :size="100" v-if="userModel.avatarUrl" :src="userModel.avatarUrl" fit="fill" />
<el-icon v-else><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="角色" prop="roleList">
<!-- value-key 绑定对象要设置的唯一标识符 -->
<el-select placeholder="请选择" v-model="userModel.roleList" value-key="id" multiple>
<el-option v-for="item in roleListData" :key="item.id" :value="item" :label="item.roleName"></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="save">确认</el-button>
</span>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang="ts">
import { ref,reactive,onMounted } from 'vue'
import { Edit,Delete,ArrowRight,Upload,Download,Plus } from '@element-plus/icons-vue'
import userApi from '@/api/user';
import roleApi from '@/api/role';
import { ElMessage, ElMessageBox, type FormRules } from 'element-plus'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import filesApi from '@/api/files'
import { useTokenStore } from '@/stores/token'
// 用户列表
const userList=ref()
// 角色列表
const roleListData=ref()
const userModel=reactive({
id:'',
username:'',
password:'',
name:'',
email:'',
roleList:[],
avatarUrl:''
})
const initUserModel={ ...userModel }
// 批量删除的 id
const ids=ref<string[]>([])
const dialogVisible = ref(false)
// 导出url
const exportUrl=ref()
// 导入url
const importUrl=ref()
// 文件上传url
const url=ref('')
// 新增或编辑
const title=ref();
const userModelRef=ref()
// 分页&搜索模型
const searchModel=reactive({
currentPage:1,
pageSize:10,
total:0,
username:'',
name:'',
email:''
})
const initSearchModel={ ...searchModel }
const rules:FormRules = reactive({
username:[
{ required: true, message:'请输入用户名', trigger: 'blur'},
{ min:3, max:16, message:"用户名长度3-16位", trigger: 'blur'}
],
password:[
{ required: true, message:'请输入密码', trigger: 'blur'},
{ min:6, max:16, message:"密码长度6-16位", trigger: 'blur'}
],
name:[
{ required: true, message:'请输入昵称', trigger: 'blur'},
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
],
roleList:[
{ required: true, message:'请选择角色', trigger: 'blur'},
],
})
// pageSize 变化时触发
const handleSizeChange = (val: number) => {
searchModel.pageSize=val;
getUserList();
}
// currentPage 变化时触发
const handleCurrentChange = (val: number) => {
searchModel.currentPage=val;
getUserList();
}
// 菜单列表
const getUserList= async()=>{
const response= await userApi.list(searchModel);
userList.value=response.data.records;
searchModel.currentPage=response.data.current;
searchModel.pageSize=response.data.size;
searchModel.total=response.data.total;
}
// 获取角色列表
const getRoleList= async()=>{
const response= await roleApi.getAllList();
roleListData.value=response.data;
}
// 新增或编辑保存
const save= async()=>{
console.log(userModel)
userModelRef.value.validate(async(valid:any) => {
if (valid) {
const response=await userApi.saveOrUpdate(userModel) as any;
ElMessage.success(response.msg);
dialogVisible.value=false;
getUserList();
} else {
return false;
}
})
}
// 编辑
const edit= (row:any)=>{
dialogVisible.value=true;
title.value="编辑用户";
// 源对象赋值给目标对象,Object.assign(目标对象, 源对象)
Object.assign(userModel, row);
}
// 新增菜单按钮
const addButton= ()=>{
dialogVisible.value=true;
title.value="添加用户";
}
// 关闭新增、编辑对话框时,清空数据和校验信息
const close= ()=>{
// 清空数据
Object.assign(userModel, initUserModel);
// 清除校验信息
userModelRef.value.clearValidate();
}
// 重置搜索表单
const reset= ()=>{
Object.assign(searchModel, initSearchModel);
getUserList();
}
// 批量删除选择
const handleSelectionChange = (rows: any) => {
ids.value = rows.map((item:any) => item.id);
}
// 批量删除
const batchRemove= ()=>{
if(ids.value.length > 0){
ElMessageBox.confirm(
`是否批量删除?`,
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async() => {
await userApi.remove(ids.value);
ElMessage({ type: 'success',message: '删除成功' });
getUserList();
})
}else{
ElMessage.warning('请选择批量删除项');
}
}
// 单条删除
const remove= async(row:any)=>{
ElMessageBox.confirm(
`是否删除 [ ${row.username} ] 用户?`,
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async() => {
ids.value.push(row.id);
await userApi.remove(ids.value);
ElMessage({ type: 'success', message: '删除成功' });
getUserList();
})
}
// 获取导出url
const getExportUrl= ()=>{
exportUrl.value=userApi.exportUrl();
}
// 获取导入url
const getImportUrl= ()=>{
importUrl.value=userApi.importUrl();
}
// 导出
const exp= ()=>{
getExportUrl();
window.open(exportUrl.value);
}
// 导入
const imp= ()=>{
getImportUrl();
}
// 导入成功处理的事件
const handleImportSuccess= ()=>{
ElMessage.success("导入成功");
// 刷新列表
getUserList();
}
// 导入失败处理的事件
const handleImportError = () => {
ElMessage.error("上传失败,请上传.xlsx文件");
}
// 上传成功处理的事件
const handleUploadSuccess = async(response:any) => {
userModel.avatarUrl=response.data;
}
// 上传失败处理的事件
const handleUploadError = () => {
ElMessage.error("上传失败");
}
// 获取图片上传的url
const getUrl= ()=>{
url.value=filesApi.url();
}
onMounted(()=>{
getUserList();
getRoleList();
getUrl();
})
</script>
<style scoped lang="less">
.container{
height: 100%;
box-sizing: border-box;
}
.header{
display: flex;
align-items: center;
justify-content: space-between;
}
.editor{
width: 100%;
:deep(.ql-editor) {
min-height: 200px;
}
}
.title{
font-size: large;
font-weight: 600;
}
.tag{
margin: 0 5px 0 0;
}
</style>