当前位置: 首页 > news >正文

SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:权限管理(三)

目录

一、前言

二、后端开发及调整

1.新增实体(表)

2.DTO类调整

1.Resp类

 2.Req类

3.用户管理

4.角色管理

5.菜单管理

6.配置调整

1.优化JwtUtils工具类

2.优化JwtInterceptor拦截器

三、前端开发及调整

1.用户管理

1.1.页面功能(user.vue)

1.2.请求js调整(user.js)

2.角色管理

1.1.页面功能(role.vue)

1.2.请求js调整(role.js)

3.菜单管理

1.1.页面功能(menu.vue)

1.2.请求js调整(menu.js)

4.配置调整

5.首页布局

1.1.菜单动态化显示

1.2.首页统计信息展示

6.其他调整

1.标签调整

四、权限管理逻辑

1.用户-角色-菜单

2.权限管理功能规则 

五、附:源码

1.源码下载地址

六、结语

一、前言

此文章在上次调整的基础上开发后端管理系统必备的权限管理功能,具体功能包含用户管理、角色管理、菜单管理,并对页面整体布局进行了调整。

此项目是在我上一个文章的后续开发, 需要的同学可以关注一下,文章链接如下:SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:整体布局、架构调整(二)

(注:源码我会在文章结尾提供gitee连接,需要的同学可以去自行下载)

二、后端开发及调整

1.新增实体(表)

1.角色表(role >> RoleEntity.java)

-- role
CREATE TABLE `role` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`role_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',`role_key` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色标识',`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`update_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',`del_flag` int(10) unsigned zerofill DEFAULT '0000000000' COMMENT '删除标识0未删除,1已删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'-- RoleEntity.java
/*** 角色表* @TableName role*/
@Data
public class RoleEntity extends BaseEntity {/*** 主键*/private Integer id;/*** 角色名称*/private String roleName;/*** 角色标识*/private String roleKey;/*** 备注*/private String remark;
}

2.用户-角色表(user_role >> UserRoleEntity.java)

-- user_role
CREATE TABLE `user_role` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` int NOT NULL COMMENT '用户ID',`role_id` int NOT NULL COMMENT '角色ID',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`update_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',`del_flag` int(10) unsigned zerofill DEFAULT '0000000000' COMMENT '删除标识0未删除,1已删除',PRIMARY KEY (`id`) USING BTREE,KEY `idx_user_id` (`user_id`) USING BTREE,KEY `idx_role_id` (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户角色关联表'-- UserRoleEntity.java/*** 角色菜单关联表* @TableName role_menu*/
@Data
public class RoleMenuEntity extends BaseEntity {/*** 主键*/private Integer id;/*** 角色ID*/private Integer roleId;/*** 菜单ID*/private Integer menuId;}

 3.菜单表(menu >> MenuEntity.java)

-- menu
CREATE TABLE `menu` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`menu_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',`parent_id` int DEFAULT '0' COMMENT '父菜单ID',`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由路径',`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '组件路径',`perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限标识',`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '图标',`sort` int DEFAULT '0' COMMENT '排序',`visible` tinyint(1) DEFAULT '1' COMMENT '是否显示(0隐藏,1显示)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`update_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',`del_flag` int(10) unsigned zerofill DEFAULT '0000000000' COMMENT '删除标识0未删除,1已删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单表'-- MenuEntity.java/*** 菜单表* @TableName menu*/
@Data
public class MenuEntity extends BaseEntity {/*** 主键*/private Integer id;/*** 菜单名称*/private String menuName;/*** 父菜单ID*/private Integer parentId;/*** 路由路径*/private String path;/*** 组件路径*/private String component;/*** 权限标识*/private String perms;/*** 图标*/private String icon;/*** 排序*/private Integer sort;/*** 是否显示(0隐藏,1显示)*/private Integer visible;}

4. 角色-菜单表(role_menu >> RoleMenuEntity.java)

-- role_menu
CREATE TABLE `role_menu` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`role_id` int NOT NULL COMMENT '角色ID',`menu_id` int NOT NULL COMMENT '菜单ID',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`update_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',`del_flag` int(10) unsigned zerofill DEFAULT '0000000000' COMMENT '删除标识0未删除,1已删除',PRIMARY KEY (`id`) USING BTREE,KEY `idx_role_id` (`role_id`) USING BTREE,KEY `idx_menu_id` (`menu_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色菜单关联表'-- RoleMenuEntity.java/*** 角色菜单关联表* @TableName role_menu*/
@Data
public class RoleMenuEntity extends BaseEntity {/*** 主键*/private Integer id;/*** 角色ID*/private Integer roleId;/*** 菜单ID*/private Integer menuId;}

5. 权限管理表关联

2.DTO类调整

1.Resp类

 1.调整菜单返回树结构类TreeDataResp .java

package org.wal.userdemo.DTO.resp;import lombok.Data;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Data
public class TreeDataResp {/*** 主键*/private Integer id;/*** 菜单名称*/private String menuName;/*** 父菜单ID*/private Integer parentId;/*** 路由路径*/private String path;/*** 组件路径*/private String component;/*** 权限标识*/private String perms;/*** 图标*/private String icon;/*** 排序*/private Integer sort;/*** 是否显示(0隐藏,1显示)*/private Integer visible;/*** 子菜单*/private List<TreeDataResp> children;public static List<TreeDataResp> buildTree(List<TreeDataResp> menus) {Map<Integer, TreeDataResp> menuMap = new HashMap<>();menus.forEach(menu -> menuMap.put(menu.getId(), menu));List<TreeDataResp> rootMenus = new ArrayList<>();menus.forEach(menu -> {Integer parentId = menu.getParentId();if (parentId == null || parentId == 0) {rootMenus.add(menu);} else {TreeDataResp parent = menuMap.get(parentId);if (parent != null) {if (parent.getChildren() == null) {parent.setChildren(new ArrayList<>());}parent.getChildren().add(menu);}}});return rootMenus;}
}

2.新增用户信息返回类UserDataResp .java

package org.wal.userdemo.DTO.resp;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.wal.userdemo.entity.MenuEntity;
import org.wal.userdemo.entity.RoleEntity;import java.util.Date;
import java.util.List;@Data
public class UserDataResp {/*** id 主键*/private Integer id;/*** name 姓名*/private String name;/*** age 年龄*/private Integer age;/*** birthday 生日*/@JsonFormat(pattern = "yyyy-MM-dd")@DateTimeFormat(pattern = "yyyy-MM-dd")private Date birthday;/*** 创建时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;/*** 创建人*/private String createBy;/*** 修改时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;/*** 修改人*/private String updateBy;/*** 删除标记0未删除1已删除(逻辑删除)*/private Integer delFlag;/*** 角色列表*/private List<RoleEntity> roles;/*** 菜单列表*/private List<TreeDataResp> menus;
}
 2.Req类

1.新增查询用户请求类QueryUserReq .java

package org.wal.userdemo.DTO.req;import lombok.Data;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;/*** 查询用户参数*/
@ToString(callSuper = true)
@Data
public class QueryUserReq extends PageReq{/*** 姓名*/private String name;/*** 生日*/@DateTimeFormat(pattern = "yyyy-MM-dd")private Date birthday;}

2.新增查询角色请求类QueryRoleReq.java

package org.wal.userdemo.DTO.req;import lombok.Data;@Data
public class QueryRoleReq extends PageReq{private String roleName;private String roleKey;
}

3.新增查询菜单请求类QueryMenuReq.java

package org.wal.userdemo.DTO.req;public class QueryMenuReq{private String menuName;
}

3.用户管理

1.新增分配角色接口、重置密码接口,部分接口添加用户校验、微调。

1.1 UserController.java

   /*** 给用户分配角色* @param userId* @param roleIds* @return Integer*/@PostMapping("/assignRole")public Result<Integer> assignRole(@RequestParam Integer userId, @RequestBody List<Integer> roleIds) {return userService.assignRole(userId, roleIds) > 0 ? Result.success() : Result.error("分配角色失败");}/*** 重置密码* @param id*/@PostMapping("/resetPassword")public Result<Integer> resetPassword(@RequestParam Integer id) {return userService.resetPassword(id) > 0 ? Result.success() : Result.error("重置密码失败");}

1.2 UserServiceImpl.java

    @Overridepublic Integer resetPassword(Integer id) {return userMapper.resetPassword( id);}
/*** 检查用户是否存在** @param username* @return Integer*/@Overridepublic Integer checkUserExist(String username) {return userMapper.checkUserExist(username);}/*** 分配角色** @param userId* @param roleIds* @return Integer*/@Override@Transactionalpublic Integer assignRole(Integer userId, List<Integer> roleIds) {//删除用户-角色中间表数据Integer result = userRoleMapper.deleteByUserId(userId);if (roleIds != null && !roleIds.isEmpty()) {//分配角色return userRoleMapper.assignRole(userId, roleIds);}return result;}

1.3UserMapper.xml

   <update id="resetPassword" parameterType="Integer">update user set password = '123456' where id = #{id};</update><select id="checkUserExist" resultType="java.lang.Integer" parameterType="String">select count(*)from userwhere name = #{username}and del_flag = 0;</select>

 1.4UserRoleMapper.xml

    <insert id="assignRole">insert into user_role(user_id,role_id,create_time,create_by,del_flag) values<foreach collection="roleIds" item="roleId" separator=",">(#{userId},#{roleId},NOW(),'王',0)</foreach>;</insert>

1.5优化登录角色查询结果UserServiceImpl.java

    /*** 获取用户信息** @return UserEntity*/@Overridepublic UserDataResp getUserById(Integer id) {UserEntity user = userMapper.getUserById(id);UserDataResp resp = BeanUtils.copyAs(user, UserDataResp.class);//查询用户角色List<RoleEntity> roles = roleMapper.getRoleListByUserId(id);resp.setRoles(roles);//查询用户角色的菜单List<MenuEntity> menus = menuMapper.getMenuListByUserId(id);List<TreeDataResp> treeDataResp = BeanUtils.copyAsList(menus, TreeDataResp.class);List<TreeDataResp> menuTree =TreeDataResp.buildTree(treeDataResp);resp.setMenus(menuTree);return resp;}

4.角色管理

1.新增用户管理相关接口

1.1RoleController.java

  @Autowiredprivate RoleService roleService;/*** 查询角色列表** @param queryRoleReq* @return List<RoleEntity>*/@PostMapping("/getRoleList")public Result<RoleEntity> getRoleList(@RequestBody QueryRoleReq queryRoleReq) {List<RoleEntity> roleList = roleService.getRoleList(queryRoleReq);int total = roleService.getRoleCount(queryRoleReq);return Result.page(roleList, total);}@GetMapping("/getAllsRoleList")public Result<?> getAllsRoleList() {List<RoleEntity> roleList = roleService.getAllsRoleList();return Result.success(roleList);}/*** 根据id查询角色** @param id* @return*/@GetMapping("/getRoleById")public Result<?> getRoleById(@RequestParam Integer id) {return Result.success(roleService.getRoleById(id));}/*** 添加角色** @param roleEntity* @return Integer*/@PostMapping("/addRole")public Result<?> addRole(@RequestBody RoleEntity roleEntity) {Integer result = roleService.checkRoleExist(roleEntity.getRoleName());if (result > 0) return Result.error("角色已存在");return roleService.addRole(roleEntity) > 0 ? Result.success() : Result.error("新增角色失败");}/*** 修改角色** @param roleEntity* @return Integer*/@PostMapping("/updateRole")public Result<?> updateRole(@RequestBody RoleEntity roleEntity) {Integer result = roleService.checkRoleExist(roleEntity.getRoleName());if (result > 0) return Result.error("角色已存在");return roleService.updateRole(roleEntity) > 0 ? Result.success() : Result.error("修改角色失败");}/*** 删除角色** @param id* @return Integer*/@PostMapping("/deleteRole")public Result<?> deleteRole(@RequestParam Integer id) {return roleService.deleteRole(id) > 0 ? Result.success() : Result.error("删除角色失败");}/*** 给角色分配菜单** @param roleId* @param menuIds* @return Integer*/@PostMapping("/assignMenu")public Result<?> assignMenu(@RequestParam Integer roleId, @RequestBody List<Integer> menuIds) {return roleService.assignMenu(roleId, menuIds) > 0 ? Result.success() : Result.error("分配菜单失败");}

1.2RoleServiceImpl.java

@Autowiredprivate RoleMapper roleMapper;@Autowiredprivate MenuMapper menuMapper;@Autowiredprivate RoleMenuMapper roleMenuMapper;/*** 获取角色列表** @param queryRoleReq* @return*/@Overridepublic List<RoleEntity> getRoleList(QueryRoleReq queryRoleReq) {return roleMapper.getRoleList(queryRoleReq);}/*** 获取角色列表数量** @param queryRoleReq* @return*/@Overridepublic Integer getRoleCount(QueryRoleReq queryRoleReq) {return roleMapper.getRoleCount(queryRoleReq);}/*** 获取角色详情** @param id* @return*/@Overridepublic RoleDataResp getRoleById(Integer id) {RoleEntity roleEntity = roleMapper.getRoleById(id);//查询角色对应的菜单列表RoleDataResp resp = BeanUtils.copyAs(roleEntity, RoleDataResp.class);resp.setMenus(menuMapper.getMenuListByRoleId(id));return resp;}/*** 检查角色是否存在** @param roleName* @return*/@Overridepublic Integer checkRoleExist(String roleName) {return roleMapper.checkRoleExist(roleName);}/*** 添加角色** @param roleEntity* @return*/@Overridepublic Integer addRole(RoleEntity roleEntity) {return roleMapper.addRole(roleEntity);}/*** 修改角色** @param roleEntity* @return*/@Overridepublic Integer updateRole(RoleEntity roleEntity) {return roleMapper.updateRole(roleEntity);}/*** 删除角色** @param id* @return*/@Overridepublic Integer deleteRole(Integer id) {return roleMapper.deleteRole(id);}/*** 给角色分配菜单* @param roleId* @param menuIds* @return  Integer*/@Override@Transactionalpublic Integer assignMenu(Integer roleId, List<Integer> menuIds) {//删除角色-菜单中间表数据Integer result =roleMenuMapper.deleteByRoleId(roleId);//分配菜单if(menuIds != null && !menuIds.isEmpty()){return roleMenuMapper.assignMenu(roleId, menuIds);}return result;}/*** 获取所有角色列表* @return*/@Overridepublic List<RoleEntity> getAllsRoleList() {return roleMapper.getAllsRoleList();}

1.3RoleMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.wal.userdemo.mapper.RoleMapper"><resultMap id="BaseResultMap" type="org.wal.userdemo.entity.RoleEntity"><id property="id" column="id" /><result property="roleName" column="role_name" /><result property="roleKey" column="role_key" /><result property="remark" column="remark" /><result property="createTime" column="create_time" /><result property="createBy" column="create_by" /><result property="updateTime" column="update_time" /><result property="updateBy" column="update_by" /><result property="delFlag" column="del_flag" /></resultMap><select id="getRoleList" resultMap="BaseResultMap" parameterType="org.wal.userdemo.DTO.req.QueryRoleReq">select * from role<where><if test="roleName != null and roleName != ''">and role_name like concat('%',#{roleName},'%')</if><if test="roleKey != null and roleKey != ''">and role_key like concat('%',#{roleKey},'%')</if>and del_flag = 0</where>order by idlimit #{page},#{limit};</select><select id="getRoleCount" resultType="java.lang.Integer" parameterType="org.wal.userdemo.DTO.req.QueryRoleReq">select count(*) from role<where><if test="roleName != null and roleName != ''">and role_name like concat('%',#{roleName},'%')</if><if test="roleKey != null and roleKey != ''">and role_key like concat('%',#{roleKey},'%')</if>and del_flag = 0</where>;</select><select id="getRoleById" resultMap="BaseResultMap">select * from role where id = #{id} and del_flag = 0;</select><select id="checkRoleExist" resultType="java.lang.Integer" parameterType="String">select count(*) from role where role_name = #{roleName} and del_flag = 0;</select><select id="getRoleListByUserId" resultMap="BaseResultMap" parameterType="Integer">select r.* from role rleft join user_role ur on r.id = ur.role_idwhere ur.user_id = #{userId} and r.del_flag = 0;</select><select id="getAllsRoleList" resultMap="BaseResultMap">select * from role where del_flag = 0 ;</select><insert id="addRole" parameterType="org.wal.userdemo.entity.RoleEntity">insert into role<trim prefix="(" suffixOverrides=", " suffix=")"><if test="roleName != null">role_name,</if><if test="roleKey != null">role_key,</if><if test="remark != null">remark,</if><if test="createTime != null">create_time,</if><if test="createBy != null">create_by,</if><if test="updateTime != null">update_time,</if><if test="updateBy != null">update_by,</if><if test="delFlag != null">del_flag,</if></trim>values<trim prefix="(" suffixOverrides=", " suffix=")"><if test="roleName != null">#{roleName},</if><if test="roleKey != null">#{roleKey},</if><if test="remark != null">#{remark},</if><if test="createTime != null">#{createTime},</if><if test="createBy != null">#{createBy},</if><if test="updateTime != null">#{updateTime},</if><if test="updateBy != null">#{updateBy},</if></trim>;</insert><update id="updateRole" parameterType="org.wal.userdemo.entity.RoleEntity">update role<set><if test="roleName != null">role_name = #{roleName},</if><if test="roleKey != null">role_key = #{roleKey},</if><if test="remark != null">remark = #{remark},</if><if test="updateTime != null">update_time = #{updateTime},</if><if test="updateBy != null">update_by = #{updateBy},</if><if test="delFlag != null">del_flag = #{delFlag},</if></set>;</update><delete id="deleteRole">update role set del_flag = 1 where id = #{id};</delete></mapper>

1.4RoleMenuMapper.xml

  <delete id="deleteByRoleId" parameterType="Integer">delete from role_menu where role_id = #{roleId};</delete><update id="assignMenu">insert into role_menu(role_id, menu_id, create_time, create_by, del_flag) values<foreach item="item" index="index" collection="menuIds" separator=",">(#{roleId}, #{item}, now(), '王',0)</foreach>;</update>

5.菜单管理

1.新增菜单管理相关接口、动态化显示前端菜单

1.1MenuController.java

package org.wal.userdemo.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.wal.userdemo.DTO.req.QueryMenuReq;
import org.wal.userdemo.DTO.resp.TreeDataResp;
import org.wal.userdemo.entity.MenuEntity;
import org.wal.userdemo.mapper.MenuMapper;
import org.wal.userdemo.service.MenuService;
import org.wal.userdemo.utils.Result;import java.util.List;@RestController
@RequestMapping("/api/menu")
public class MenuController {@Autowiredprivate MenuService menuService;@Autowiredprivate MenuMapper menuMapper;@GetMapping("/getAllMenuList")public Result<List<TreeDataResp>> getAllMenuList() {return Result.success(menuService.getAllMenuList());}@PostMapping("/getMenuList")public Result<?> getMenuList(@RequestBody QueryMenuReq queryMenuReq) {List<TreeDataResp> menuList = menuService.getMenuList(queryMenuReq);return Result.success(menuList);}@GetMapping("/getMenuListByUserId")public Result<?> getMenuListByUserId(@RequestParam Integer userId) {return Result.success(menuService.getMenuListByUserId(userId));}@GetMapping("getMenuById")public Result<?> getMenuById(@RequestParam Integer id) {return Result.success(menuService.getMenuById(id));}@PostMapping("addMenu")public Result<?> addMenu(@RequestBody MenuEntity menu) {Integer num = menuService.addMenu(menu);return num > 0 ? Result.success() : num == -1 ? Result.error("菜单名称/路由/标识有重复项") : Result.error("添加失败");}@PostMapping("updateMenu")public Result<?> updateMenu(@RequestBody MenuEntity menu) {Integer num = menuService.updateMenu(menu);return num > 0 ? Result.success() : num == -1 ? Result.error("菜单名称/路由/标识有重复项") : Result.error("修改失败");}@PostMapping("deleteMenu")public Result<?> deleteMenu(@RequestParam Integer id) {//校验菜单是否有用户在用Integer count = menuMapper.checkMenuForUser(id);if(count > 0){return Result.error("菜单有 "+count+" 个用户在使用,不能删除");}return menuService.deleteMenu(id) > 0 ? Result.success() : Result.error("删除失败");}
}

 1.2MenuServiceImpl.java

package org.wal.userdemo.service.impl;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wal.userdemo.DTO.req.QueryMenuReq;
import org.wal.userdemo.DTO.resp.TreeDataResp;
import org.wal.userdemo.entity.MenuEntity;
import org.wal.userdemo.entity.RoleMenuEntity;
import org.wal.userdemo.mapper.MenuMapper;
import org.wal.userdemo.mapper.RoleMenuMapper;
import org.wal.userdemo.service.MenuService;
import org.wal.userdemo.service.RoleService;
import org.wal.userdemo.utils.BeanUtils;import java.util.*;@Service
public class MenuServiceImpl implements MenuService {@Autowiredprivate MenuMapper menuMapper;@Autowiredprivate RoleService roleService;@Autowiredprivate RoleMenuMapper roleMenuMapper;/*** 获取菜单列表** @return*/@Overridepublic List<TreeDataResp> getAllMenuList() {List<MenuEntity> menuList = menuMapper.getAllMenuList();List<TreeDataResp> treeDataRespList = BeanUtils.copyAsList(menuList, TreeDataResp.class);return buildMenuTree(treeDataRespList);}@Overridepublic List<TreeDataResp> getMenuList(QueryMenuReq queryMenuReq) {List<MenuEntity> menuList = menuMapper.getMenuList(queryMenuReq);List<TreeDataResp> treeDataRespList = BeanUtils.copyAsList(menuList, TreeDataResp.class);return buildMenuTree(treeDataRespList);}@Overridepublic List<TreeDataResp> getMenuListByUserId(Integer userId) {List<MenuEntity> menuList = menuMapper.getMenuListByUserId(userId);List<TreeDataResp> treeDataRespList = BeanUtils.copyAsList(menuList, TreeDataResp.class);return buildMenuTree(treeDataRespList);}@Overridepublic MenuEntity getMenuById(Integer id) {return menuMapper.getMenuById(id);}@Override@Transactionalpublic Integer addMenu(MenuEntity menu) {List<MenuEntity> repeatList = menuMapper.checkMenu(menu);if (!repeatList.isEmpty()) {return -1;}return menuMapper.addMenu(menu);}@Override@Transactionalpublic Integer updateMenu(MenuEntity menu) {List<MenuEntity> repeatList = menuMapper.checkMenu(menu);if (!repeatList.isEmpty()) {return -1;}MenuEntity source = menuMapper.getMenuById(menu.getId());//只修改了菜单部分属性,未修改父级菜单if (source.getParentId() == menu.getParentId()) {//不做处理} else {//修改了父级菜单//批量新增role-menu表(新增挪到新父菜单的数据,不管挪到那个父菜单下,角色都有该子菜单的权限)List<RoleMenuEntity> insertRoleMenuList = new ArrayList<>();List<RoleMenuEntity> roleMenuList = roleMenuMapper.getRoleMenuListByMenuId(menu.getId());for (RoleMenuEntity item : roleMenuList) {RoleMenuEntity roleMenuEntity = new RoleMenuEntity();roleMenuEntity.setRoleId(item.getRoleId());roleMenuEntity.setMenuId(menu.getParentId());insertRoleMenuList.add(roleMenuEntity);}roleMenuMapper.insertRoleMenuBatch(insertRoleMenuList);}return menuMapper.updateMenu(menu);}@Overridepublic Integer deleteMenu(Integer id) {return menuMapper.deleteMenu(id);}/*** 构建菜单树** @param menus* @return*/public List<TreeDataResp> buildMenuTree(List<TreeDataResp> menus) {Map<Integer, TreeDataResp> menuMap = new HashMap<>();menus.forEach(menu -> menuMap.put(menu.getId(), menu));List<TreeDataResp> rootMenus = new ArrayList<>();menus.forEach(menu -> {Integer parentId = menu.getParentId();if (parentId == null || parentId == 0) {rootMenus.add(menu);} else {TreeDataResp parent = menuMap.get(parentId);if (parent != null) {if (parent.getChildren() == null) {parent.setChildren(new ArrayList<>());}parent.getChildren().add(menu);}}});return rootMenus;}}

1.3MenuMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.wal.userdemo.mapper.MenuMapper"><resultMap id="BaseResultMap" type="org.wal.userdemo.entity.MenuEntity"><id property="id" column="id" /><result property="menuName" column="menu_name" /><result property="parentId" column="parent_id" /><result property="path" column="path" /><result property="component" column="component" /><result property="perms" column="perms" /><result property="icon" column="icon" /><result property="sort" column="sort" /><result property="visible" column="visible" /><result property="createTime" column="create_time" /><result property="createBy" column="create_by" /><result property="updateTime" column="update_time" /><result property="updateBy" column="update_by" /><result property="delFlag" column="del_flag" /></resultMap><select id="getAllMenuList" resultMap="BaseResultMap">SELECT * FROM menu WHERE del_flag = 0 ORDER BY parent_id, sort;</select><select id="getMenuListByRoleId" resultMap="BaseResultMap" parameterType="Integer">select m.* from menu mleft join role_menu rm on m.id = rm.menu_idwhere rm.role_id = #{roleId} and m.del_flag = 0;</select><select id="getMenuListByUserId" resultMap="BaseResultMap" parameterType="Integer">select m.* from menu mleft join role_menu rm on m.id = rm.menu_idleft join user_role ur on rm.role_id = ur.role_idwhere ur.user_id = #{userId} and m.del_flag = 0 group by m.id;</select><select id="getMenuList" resultMap="BaseResultMap" parameterType="org.wal.userdemo.DTO.req.QueryMenuReq">SELECT * FROM menu<where><if test="menuName != null">AND menu_name LIKE CONCAT('%',#{menuName},'%')</if>and del_flag = 0</where>ORDER BY parent_id, sort;</select><insert id="addMenu" parameterType="org.wal.userdemo.entity.MenuEntity">INSERT INTO menu<trim prefix="(" suffix=")" suffixOverrides=","><if test="menuName != null">menu_name,</if><if test="parentId != null">parent_id,</if><if test="path != null">path,</if><if test="component != null">component,</if><if test="perms != null">perms,</if><if test="icon != null">icon,</if><if test="sort != null">sort,</if><if test="visible != null">visible,</if><if test="createTime != null">create_time,</if><if test="createBy != null">create_by,</if><if test="delFlag != null">del_flag,</if></trim>VALUES<trim prefix="(" suffix=")" suffixOverrides=","><if test="menuName != null">#{menuName},</if><if test="parentId != null">#{parentId},</if><if test="path != null">#{path},</if><if test="component != null">#{component},</if><if test="perms != null">#{perms},</if><if test="icon != null">#{icon},</if><if test="sort != null">#{sort},</if><if test="visible != null">#{visible},</if><if test="createTime != null">#{createTime},</if><if test="createBy != null">#{createBy},</if><if test="delFlag != null">#{delFlag},</if></trim>;</insert><update id="updateMenu" parameterType="org.wal.userdemo.entity.MenuEntity">UPDATE menu<set><if test="menuName != null">menu_name = #{menuName},</if><if test="parentId != null">parent_id = #{parentId},</if><if test="path != null">path = #{path},</if><if test="component != null">component = #{component},</if><if test="perms != null">perms = #{perms},</if><if test="icon != null">icon = #{icon},</if><if test="sort != null">sort = #{sort},</if><if test="visible != null">visible = #{visible},</if><if test="updateTime != null">update_time = #{updateTime},</if><if test="updateBy != null">update_by = #{updateBy},</if><if test="delFlag != null">del_flag = #{delFlag}</if></set>WHERE id = #{id};</update><delete id="deleteMenu" parameterType="Integer">DELETE FROM menu WHERE id = #{id};</delete><select id="checkMenu" parameterType="org.wal.userdemo.entity.MenuEntity" resultMap="BaseResultMap">SELECT *FROM menuWHERE del_flag = 0and (menu_name = #{menuName}or path = #{path}or component = #{component}or perms = #{perms})<if test="id != null">and id != #{id}</if></select><select id="checkMenuForUser" resultType="java.lang.Integer">SELECT count(ur.user_id)FROM menu mLEFT JOIN role_menu rm ON m.id = rm.menu_idLEFT JOIN role r ON rm.role_id = r.idLEFT JOIN user_role ur ON r.id = ur.role_idwhere m.id = #{id}</select><select id="getMenuById" resultMap="BaseResultMap">select * from menu where id = #{id} and del_flag = 0;</select></mapper>

 1.4RoleMenuMapper.xml

    <insert id="insertRoleMenuBatch" parameterType="list">insert into role_menu(role_id, menu_id, create_time, create_by, del_flag) values<foreach item="item" index="index" collection="list" separator=",">(#{item.roleId}, #{item.menuId}, now(), '王',0)</foreach>;</insert><select id="getRoleMenuListByMenuId" resultMap="BaseResultMap" parameterType="Integer">select id,role_id,menu_id from role_menu where menu_id = #{menuId} ORDER BY role_id;</select>

6.配置调整

1.优化JwtUtils工具类

用户ID 作为JWT的主题比 用户名称 更合适、更合理,上下文调用也方便。

/*** 生成 JWT 令牌。** @param userId 用户id,作为 JWT 的 subject 字段* @return 返回生成的 JWT 字符串*/public static String generateToken(Integer userId) {return Jwts.builder()// 设置 JWT 的主题(通常为用户标识).setSubject(userId.toString())// 设置 JWT 的过期时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))// 使用 HS512 算法签名,并指定密钥.signWith(SIGNING_KEY)// 构建并返回紧凑格式的 JWT 字符串.compact();}/*** 从 JWT 令牌中解析出用户ID。** @param token 需要解析的 JWT 字符串* @return 解析出的用户ID(subject)* @throws JwtException 如果 token 无效或签名不匹配会抛出异常*/public static String parseUserId(String token) {return Jwts.parser()// 设置签名验证所使用的密钥.setSigningKey(SIGNING_KEY)// 解析并验证 JWT 令牌.parseClaimsJws(token)// 获取 JWT 中的负载(claims),并提取 subject(用户名).getBody().getSubject();}
2.优化JwtInterceptor拦截器

  2.1JwtInterceptor.java(解析token中userId用户上下文调用)

   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);try {String userId = JwtUtil.parseUserId(token);// 可以将 username 存入 request 或 SecurityContextlog.info("用户 {} 使用正确的token访问了后端接口", userId);return true;} catch (JwtException e) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效 Token");return false;}} else {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "缺少 Token");return false;}}

(注:避免篇幅过长,省略了service类和mapper类的接口,需要可直接下载源代码) 

三、前端开发及调整

(权限管理相关页面统一挪到view/permission/下,请求js统一挪到api/permission/下)

1.用户管理

1.1.页面功能(user.vue)
<template><div><!-- 查询条件 --><el-form :inline="true" label-position="right" label-width="80px" :model="queryForm" class="query-border-container"><el-row :gutter="20" justify="center"><!-- 姓名 --><el-col :span="7"><el-form-item label="姓名"><el-input v-model="queryForm.name" placeholder="请输入姓名"></el-input></el-form-item></el-col><!-- 出生日期 --><el-col :span="7"><el-form-item label="出生日期"><el-date-picker v-model="queryForm.birthday" type="date" placeholder="选择日期"style="width: 100%;"></el-date-picker></el-form-item></el-col><!-- 按钮组 --><el-col :span="7"><el-form-item><div style="display: flex; gap: 10px;"><el-button type="primary" @click="onQuery" size="small">查询</el-button><el-button @click="onReset" size="small">重置</el-button></div></el-form-item></el-col></el-row><el-row :gutter="20" justify="center"><el-col :span="7"><div style="display: flex; gap: 10px;"><el-button type="primary" size="small" @click="handleAdd()">新增</el-button></div></el-col></el-row></el-form><!-- 用户列表 --><el-table :data="tableData" style="width: 100%;" class="table-border-container" max-height="480"v-loading="loading"><el-table-column type="index" label="序号" width="100" align="center"><template #default="scope">{{ (queryForm.page - 1) * queryForm.limit + scope.$index + 1 }}</template></el-table-column><el-table-column prop="name" label="姓名" width="180" align="center"></el-table-column><el-table-column prop="age" label="年龄" width="180" align="center"></el-table-column><el-table-column prop="birthday" label="出生日期" width="180" align="center"></el-table-column><el-table-column label="操作" width="350" align="center"><template #default="scope"><el-button type="primary" size="small" v-if="scope.row.name != 'admin'"@click="handleEdit(scope.$index, scope.row)">修改</el-button><el-button type="danger" size="small" v-if="scope.row.name != 'admin'"@click="handleDelete(scope.$index, scope.row)">删除</el-button><el-button type="primary" size="small" @click="setRoles(scope.row)">分配角色</el-button><el-button type="primary" size="small" @click="resetPassword(scope.row)">重置密码</el-button></template></el-table-column></el-table><!-- 分页 --><el-pagination background layout="total,sizes,prev, pager, next" :total="total" @size-change="handleSizeChange"@current-change="handleCurrentChange" :page-size.sync="queryForm.limit" :page-sizes="[10, 20, 50, 100]"class="page-border-container"></el-pagination><!-- 添加用户 and 修改用户 --><el-dialog title="用户信息" :visible.sync="showUser" width="600px" append-to-body><el-form ref="form" :model="form" label-width="80px" center="false" :rules="rules"><el-row><el-col :span="12"><el-form-item label="用户名" prop="name"><el-input v-model="form.name"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="出生日期" prop="birthday"><el-date-picker v-model="form.birthday" type="date" placeholder="选择日期"style="width: 100%;"></el-date-picker></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="用户年龄" prop="age"><el-input v-model="form.age" disabled></el-input></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="密码" prop="password"><el-input v-model="form.password"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="确认密码" prop="password2"><el-input v-model="form.password2"></el-input></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer" style="text-align: center;"><el-button type="primary" size="small" @click="submit()">保存</el-button><el-button @click="close()" size="small">取消</el-button></div></el-dialog><!-- 给用户分配角色穿梭框 --><el-dialog title="分配角色" :visible.sync="showRoleDialog" width="800px" append-to-body><div style="text-align: left"><el-transfer v-model="selectedRoles" filterable :data="roleList" :render-content="renderRole":titles="['未拥有角色', '已有角色']" :button-texts="['移除', '添加']":format="{ noChecked: '${total}', hasChecked: '${checked}/${total}' }"></el-transfer></div><!-- 按钮区域 --><div slot="footer" class="dialog-footer" style="text-align: center;"><el-button size="small" @click="resetRoles">重置</el-button><el-button type="primary" size="small" @click="saveRoles">保存</el-button><el-button size="small" @click="closeRoleDialog">取消</el-button></div></el-dialog></div></template><script>
import { getUserList, updateUser, getUserById, addUser, assignRole, deleteUser, resetPassword } from '@/api/permission/user';
import { getAllsRoleList } from '@/api/permission/role'
export default {name: 'userView',data() {return {tableData: [],queryForm: {page: 1,limit: 10,username: '',birthday: '',},total: 0,loading: false,showUser: false,form: {},//是否禁用 【暂时不需要】isDisabled: true,rules: {name: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 3, max: 12, message: '长度在 3 到 12 个字符', trigger: 'blur' },{pattern: /^[^\s]+$/,message: '用户名不能包含空格',trigger: 'blur'}, {validator: (rule, value, callback) => {const forbiddenNames = ['null', 'undefined', 'root', 'system', 'admin'];if (forbiddenNames.includes(value.toLowerCase())) {callback(new Error(`用户名不能是 ${value}`));} else if (value && value.toLowerCase().includes('admin')) {callback(new Error('用户名不能包含 "admin"'));} else {callback();}},trigger: 'blur'}],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 6, max: 10, message: '长度在 6 到 10 个字符', trigger: 'blur' },{pattern: /^(?=.*[A-Za-z])(?=.*\d)|^[A-Za-z]+$|^\d+$/,message: '只能包含大小写字母和数字,或纯字母、纯数字',trigger: 'blur'}],password2: [{ required: true, message: '请再次输入密码', trigger: 'blur' },{validator: (rule, value, callback) => {if (value !== this.form.password) {callback(new Error('两次输入的密码不一致'));} else {callback();}},trigger: 'blur'}]},//分配角色区showRoleDialog: false,     // 控制角色弹窗显示selectedRoles: [],         // 当前选中的角色 ID 数组roleList: [],              //所有角色列表userId: '',                //当前分配角色的用户ID(暂定这样)//原始角色副本列表roleListCopy: [],};},watch: {'form.birthday': function (newVal) {if (newVal) {const age = this.calculateAge(newVal);this.$set(this.form, 'age', age); // 使用 Vue.set 确保响应式更新}}},created() {this.getUserList();},methods: {// 获取用户列表getUserList() {this.loading = true;getUserList(this.queryForm).then(res => {if (res.data.code == 200) {this.tableData = res.data.data;this.total = res.data.total;this.$message.success("获取用户列表成功!");} else {this.$message.error("获取用户列表失败!");}}).finally(() => {this.loading = false;});},resetPassword(row) {resetPassword(row.id).then(res => {if (res.data.code == 200) {this.$message.success("重置密码成功,默认密码是:123456");localStorage.removeItem("token");this.$router.push("/login");} else {this.$message.error("重置密码失败!");}});},// 查询onQuery() {this.getUserList();},// 重置表单并查询onReset() {this.queryForm = {page: 1,limit: 10,name: '',birthday: '',};this.getUserList();},//分页器改变handleSizeChange(val) {this.queryForm.limit = val;this.getUserList();},//改变页码handleCurrentChange(val) {this.queryForm.page = val;this.getUserList();},//修改按钮 - 打开修改模态框handleEdit(index, row) {getUserById(row.id).then(res => {if (res.data.code == 200) {this.form = res.data.data;this.showUser = true;} else {this.$message.error("获取用户信息失败!");}});// console.log(index, row);// this.$message.success('编辑成功');},//新增按钮 -打开新增框handleAdd() {this.form = {};this.showUser = true;},//给用户分配角色setRoles(row) {getUserById(row.id).then(res => {this.userId = row.id;if (res.data.code == 200) {this.selectedRoles = res.data.data.roles.map(item => item.id) || [];//保存一份角色列表,用于重置操作this.roleListCopy = [...this.selectedRoles];} else {this.$message.error("获取用户角色信息失败!");}});getAllsRoleList().then(res => {if (res.data.code == 200) {this.roleList = res.data.data.map(item => ({key: item.id,label: item.roleName}));} else {this.$message.error("获取所有角色信息失败!");}});this.showRoleDialog = true;},// 渲染穿梭框内容(可自定义格式)renderRole(h, option) {return h('span', {}, `${option.label}`);},// 重置按钮:清空所有选择resetRoles() {this.selectedRoles = [...this.roleListCopy];},// 保存按钮-【分配角色】saveRoles() {console.log('this.selectedRoles', this.selectedRoles);assignRole({ userId: this.userId }, this.selectedRoles).then(res => {if (res.data.code === 200) {this.$message.success("分配角色成功!");} else {this.$message.error("分配角色失败!");}});this.userId = null;this.showRoleDialog = false; // 关闭弹窗},// 取消按钮:关闭弹窗closeRoleDialog() {this.userId = null;this.selectedRoles = [];this.showRoleDialog = false;},//保存按钮-【新增、修改】submit() {this.$refs.form.validate(valid => {if (valid) {this.loading = true;if (this.form.id) {//修改updateUser(this.form).then(res => {if (res.data.code == 200) {this.$message.success("更新用户信息成功!");this.showUser = false;this.getUserList();this.form = {};} else {this.$message.error(res.data.message);}});} else {//新增addUser(this.form).then(res => {if (res.data.code == 200) {this.$message.success("添加用户成功!");this.showUser = false;this.getUserList();} else {this.$message.error(res.data.message);}});}}this.loading = false;});},//删除按钮handleDelete(index, row) {this.$confirm('确认删除用户:' + row.name + '吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {deleteUser(row.id).then(res => {if (res.data.code == 200) {this.$message.success("删除用户成功!");this.getUserList();} else {this.$message.error(res.data.message);}});}).catch(() => {this.$message({type: 'info',message: '已取消删除'});});},close() {this.showUser = false;this.form = {};},calculateAge(birthday) {const today = new Date();const birthDate = new Date(birthday);let age = today.getFullYear() - birthDate.getFullYear();const m = today.getMonth() - birthDate.getMonth();if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {age--;}return age;},},
};
</script>
<style scoped></style>
1.2.请求js调整(user.js)
import request from '@/utils/request';/*** 查询用户列表(分页)* @param {Object} params - 请求参数,如 page, limit 等*/
export function getUserList(params) {return request({url: '/user/getUserList',method: 'post',data : params,});
}
/*** 修改用户信息* @param {Object} data - 请求参数*/
export function updateUser(data) {return request({url: '/user/updateUser',method: 'post',data : data,});
}
/*** 获取用户信息* @param {Object} id - 用户ID*/
export function getUserById(id) {return request({url: '/user/getUserById',method: 'get',params : {'id': id},});
}
/*** 新增用户* @param {Object} data - 添加用户信息*/
export function addUser(data) {return request({url: '/user/addUser',method: 'post',data : data,});
}
/*** 给用户分配角色* */
export function assignRole(params,data) {return request({url: '/user/assignRole',method: 'post',params : params,data : data,});
}
/*** 删除用户* @param {Object} id - 用户ID*/
export function deleteUser(id) {return request({url: '/user/deleteUser',method: 'post',params : {'id':id},});
}
/*** 重置密码* @param {Object} id - 用户ID*/
export function resetPassword(id) {return request({url: '/user/resetPassword',method: 'post',params : {'id':id},});
}

2.角色管理

1.1.页面功能(role.vue)
<template><div><!-- 查询条件 --><el-form :inline="true" label-position="right" label-width="80px" :model="queryForm"class="query-border-container"><el-row :gutter="20" justify="center"><!-- 角色名称 --><el-col :span="7"><el-form-item label="角色名称"><el-input v-model="queryForm.roleName" placeholder="请输入角色名称"></el-input></el-form-item></el-col><!-- 角色标识 --><el-col :span="7"><el-form-item label="角色标识"><el-input v-model="queryForm.roleKey" placeholder="请输入角色标识"></el-input></el-form-item></el-col><!-- 按钮组 --><el-col :span="7"><el-form-item><div style="display: flex; gap: 10px;"><el-button type="primary" @click="onQuery" size="small">查询</el-button><el-button @click="onReset" size="small">重置</el-button></div></el-form-item></el-col></el-row><el-row :gutter="20" justify="center"><el-col :span="7"><div style="display: flex; gap: 10px;"><el-button type="primary" size="small" @click="handleAdd()">新增</el-button></div></el-col></el-row></el-form><!-- 角色列表 --><el-table :data="tableData" style="width: 100%;" class="table-border-container" max-height="480"v-loading="loading"><el-table-column type="index" label="序号" width="100" align="center"><template #default="scope">{{ (queryForm.page - 1) * queryForm.limit + scope.$index + 1 }}</template></el-table-column><el-table-column prop="roleName" label="角色名称" width="180" align="center"></el-table-column><el-table-column prop="roleKey" label="角色标识" width="180" align="center"></el-table-column><el-table-column prop="remark" label="备注" width="180" align="center"></el-table-column><el-table-column prop="createTime" label="创建时间" width="180" align="center"></el-table-column><el-table-column prop="createTime" label="创建时间" width="180" align="center"></el-table-column><el-table-column label="操作" width="270" align="center"><template #default="scope"><el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">修改</el-button><el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)">删除</el-button><el-button type="primary" size="small" @click="setMenus(scope.row)">分配菜单</el-button></template></el-table-column></el-table><!-- 分页 --><el-pagination background layout="total,sizes,prev, pager, next" :total="total" @size-change="handleSizeChange"@current-change="handleCurrentChange" :page-size.sync="queryForm.limit" :page-sizes="[10, 20, 50, 100]"class="page-border-container"></el-pagination><!-- 添加角色 and 修改角色 --><el-dialog title="角色信息" :visible.sync="showRole" width="600px" append-to-body><el-form ref="form" :model="form" label-width="80px" center="false" :rules="rules"><el-row><el-col :span="12"><el-form-item label="角色名称"><el-input v-model="form.roleName"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="角色标识"><el-input v-model="form.roleKey"></el-input></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer" style="text-align: center;"><el-button type="primary" size="small" @click="submit()">保存</el-button><el-button @click="close()" size="small">取消</el-button></div></el-dialog><!-- 给角色分配菜单权限 --><el-dialog title="分配菜单" :visible.sync="showMenuDialog" width="400px" append-to-body><el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input><el-tree ref="menuTree" :data="menuList" node-key="key" show-checkbox default-expand-all:default-checked-keys="selectedMenus" style="text-align: center; height: 300px; max-height: 350px;"width="100%" :filter-node-method="filterNode"><template #default="{ node }"><span>{{ node.label }}</span></template></el-tree><!-- 按钮区域 --><div slot="footer" class="dialog-footer" style="text-align: center;"><el-button size="small" @click="resetMenus">重置</el-button><el-button type="primary" size="small" @click="saveMenus">保存</el-button><el-button size="small" @click="closeMenuDialog">取消</el-button></div></el-dialog></div>
</template><script>import { getRoleList, addRole, updateRole, getRoleById, deleteRole, assignMenu } from '@/api/permission/role'
import { getAllMenuList } from '@/api/permission/menu'
export default {name: 'roleView',data() {return {queryForm: {page: 1,limit: 10,roleName: '',roleKey: ''},tableData: [],total: 0,showRole: false,loading: false,form: {},rules: {roleName: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],roleKey: [{ required: true, message: '请输入角色标识', trigger: 'blur' }]},//tree组件showMenuDialog: false,menuList: [],selectedMenus: [],//操作的roleroleId: '',//用于重置数据menuListCopy: [],filterText: '',};},watch: {filterText(val) {this.$refs.menuTree.filter(val);}},created() {this.getRoleList();this.getAllMenuList();},methods: {getRoleList() {this.loading = true;getRoleList(this.queryForm).then(res => {if (res.data.code === 200) {this.tableData = res.data.data;this.total = res.data.total;this.$message.success("获取角色列表成功!");} else {this.$message.error("获取角色列表失败!");}})this.loading = false;},//保存按钮-【新增、修改】submit() {this.$refs.form.validate(valid => {if (valid) {this.loading = true;if (this.form.id) {//修改updateRole(this.form).then(res => {if (res.data.code == 200) {this.$message.success("更新角色信息成功!");this.showRole = false;this.getRoleList();this.form = {};} else {this.$message.error(res.data.message);}});} else {//新增addRole(this.form).then(res => {if (res.data.code == 200) {this.$message.success("添加角色成功!");this.showRole = false;this.getRoleList();} else {this.$message.error(res.data.message);}});}}this.loading = false;})},handleAdd() {this.form = {};this.showRole = true;},handleEdit(index, row) {getRoleById(row.id).then(res => {if (res.data.code == 200) {this.form = res.data.data;this.showRole = true;} else {this.$message.error("获取角色信息失败!");}});},handleDelete(index, row) {this.$confirm('确认删除角色:' + row.roleName + '吗?', '是否删除', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {deleteRole(row.id).then(res => {if (res.data.code == 200) {this.$message.success("删除角色成功!");this.getRoleList();} else {this.$message.error("删除角色失败!");}});}).catch(() => {});},close() {this.showRole = false;this.form = {};},closeMenuDialog() {this.roleId = '';this.showMenuDialog = false;this.menuListCopy = [];},setMenus(row) {this.roleId = row.id;this.showMenuDialog = true;getRoleById(row.id).then(res => {if (res.data.code == 200) {this.selectedMenus = res.data.data.menus.map(item => item.id);//保存一份菜单列表,用于重置操作this.menuListCopy = [...this.selectedMenus];} else {this.$message.error("获取角色拥有菜单失败!");}});},getAllMenuList() {getAllMenuList().then(res => {if (res.data.code === 200) {this.menuList = this.mapMenuTree(res.data.data);} else {this.$message.error("获取菜单列表失败!");}});},saveMenus() {// 获取所有被选中的节点(包括父节点和子节点)const checkedNodes = this.$refs.menuTree.getCheckedNodes(false, true);// 提取 key(即菜单 id)const menuIds = checkedNodes.map(node => node.key);console.log("menuIds:" + menuIds);// const menuIds = this.$refs.menuTree.getCheckedKeys();assignMenu({ roleId: this.roleId }, menuIds).then(res => {if (res.data.code === 200) {this.$message.success("保存成功!");} else {this.$message.error("保存失败!");}});this.showMenuDialog = false;this.selectedMenus = [];this.menuListCopy = [];this.roleId = null;},// 递归映射菜单树结构mapMenuTree(data) {return data.map(item => ({key: item.id,label: item.menuName,children: Array.isArray(item.children) && item.children.length > 0? this.mapMenuTree(item.children): undefined}));},// 渲染穿梭框内容(可自定义格式)renderMenu(h, option) {return h('span', {}, `${option.label}`);},resetMenus() {this.selectedMenus = [...this.menuListCopy];},// 查询onQuery() {this.getRoleList();},//分页器改变handleSizeChange(val) {this.queryForm.limit = val;this.getRoleList();},//改变页码handleCurrentChange(val) {this.queryForm.page = val;this.getRoleList();},// 重置表单并查询onReset() {this.queryForm = {page: 1,limit: 10,roleName: ''};this.getRoleList();},//筛选菜单树节点filterNode(value, data) {if (!value) return true;return data.label.indexOf(value) !== -1;}}
}
</script>
1.2.请求js调整(role.js)
import request from '@/utils/request';/*** 查询所有角色列表*/
export function getAllsRoleList() {return request({url: '/role/getAllsRoleList',method: 'get',});
}
/*** 获取角色列表* @param {Object} params - 查询参数*/
export function getRoleList(params) {return request({url: '/role/getRoleList',method: 'post',data: params,});
}
/*** 新增角色* @param {Object} params - 新增角色参数*/
export function addRole(params) {return request({url: '/role/addRole',method: 'post',data: params,});
}
/*** 修改角色* @param {Object} params - 修改角色参数*/
export function updateRole(params) {return request({url: '/role/updateRole',method: 'post',data: params,});
}
/*** 删除角色* @param {Number|String} id - 角色 ID*/
export function deleteRole(params) {return request({url: `/role/delete`,method: 'post',params: params,});
}
/*** 获取角色信息* @param {Number|String} id - 角色 ID*/
export function getRoleById(id) {return request({url: `/role/getRoleById`,method: 'get',params : {'id': id},});
}
/*** 给角色分配菜单权限* @param {Object} params - 参数对象* @param {Number|String} params.roleId - 角色 ID*/
export function assignMenu(params,data) {return request({url: `/role/assignMenu`,method: 'post',params: params,data: data,});
}

3.菜单管理

1.1.页面功能(menu.vue)
<template><div><!-- 查询条件 --><el-form :inline="true" label-position="right" label-width="80px" :model="queryForm"class="query-border-container"><el-row :gutter="20" justify="center"><!-- 菜单名称 --><el-col :span="7"><el-form-item label="菜单名称"><el-input v-model="queryForm.menuName" placeholder="请输入菜单名称"></el-input></el-form-item></el-col><!-- 按钮组 --><el-col :span="7"><el-form-item><div style="display: flex; gap: 10px;"><el-button type="primary" @click="onQuery" size="small">查询</el-button><el-button @click="onReset" size="small">重置</el-button></div></el-form-item></el-col></el-row><el-row :gutter="20" justify="center"><el-col :span="7"><div style="display: flex; gap: 10px;"><el-button type="primary" size="small" @click="toggleAll()">展开/折叠</el-button></div></el-col></el-row></el-form><!-- 菜单树形列表 --><el-table :data="tableData" style="width: 100%;" class="table-border-container" max-height="480"v-loading="loading" row-key="id" :tree-props="{ children: 'children' }" :default-expand-all="isExpandAll"ref="menuTableRef"><el-table-column type="index" label="序号" width="100" align="center"></el-table-column><el-table-column prop="menuName" label="菜单名称" width="180" align="center"></el-table-column><el-table-column prop="path" label="路由路径" width="180" align="center"></el-table-column><el-table-column prop="component" label="组件路径" width="180" align="center"></el-table-column><el-table-column prop="perms" label="权限标识" width="80" align="center"></el-table-column><el-table-column prop="icon" label="图标" width="180" align="center"></el-table-column><el-table-column prop="visible" label="显示/隐藏" width="80" align="center"><template #default="scope"><el-switch v-model="scope.row.visible" :active-value="1" :inactive-value="0" :disabled="true"></el-switch></template></el-table-column><el-table-column label="操作" width="270" align="center"><template #default="scope"><el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">修改</el-button><el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)"v-if="scope.row.parentId !== 0">删除</el-button><el-button type="primary" size="small" @click="handleAdd(scope.$index, scope.row)">添加</el-button></template></el-table-column></el-table><!-- 添加菜单 and 修改菜单 --><el-dialog title="菜单信息" :visible.sync="showMenu" width="600px" append-to-body><el-form ref="form" :model="form" label-width="80px" center="false" :rules="rules"><el-row><el-col :span="24"><el-form-item label="上级菜单"><!-- <el-input v-model="form.parentId" ></el-input> --><el-cascader v-model="parentIdCascader" :disabled="menuDisabled" :options="options":props="{ checkStrictly: true }" clearable></el-cascader></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="菜单名称"><el-input v-model="form.menuName"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="路由路径"><el-input v-model="form.path"></el-input></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="组件路径"><el-input v-model="form.component"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="权限标识"><el-input v-model="form.perms"></el-input></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="图标"><el-input v-model="form.icon"></el-input></el-form-item></el-col><el-col :span="12"><el-form-item label="排序"><el-input v-model="form.sort"></el-input></el-form-item></el-col></el-row><el-row><el-col :span="12"><el-form-item label="显示/隐藏"><!-- <el-input v-model="form.visible"></el-input> --><el-switch v-model="form.visible" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer" style="text-align: center;"><el-button type="primary" size="small" @click="submit()">保存</el-button><el-button @click="close()" size="small">取消</el-button></div></el-dialog></div>
</template>
<script>
import { getMenuList, addMenu, updateMenu, deleteMenu, getMenuById } from '@/api/permission/menu'
export default {name: 'menuView',data() {return {queryForm: {menuName: '',},tableData: [],loading: false,menuDisabled: false,showMenu: false,form: {},menuOptions: [],parentIdCascader: [],rules: {menuName: [{ required: true, message: '请输入菜单名称', trigger: 'blur' },],path: [{ required: true, message: '请输入菜单地址', trigger: 'blur' },],component: [{ required: true, message: '请输入菜单图标', trigger: 'blur' },],icon: [{ required: true, message: '请输入菜单图标', trigger: 'blur' },],perms: [{ required: true, message: '请输入权限标识', trigger: 'blur' },],},isExpandAll: true,}},created() {this.getMenuList();},methods: {// 查询onQuery() {this.getMenuList()},// 重置onReset() {this.queryForm = {menuName: '',};this.getMenuList();},getMenuList() {getMenuList(this.queryForm).then(res => {if (res.data.code === 200) {this.tableData = res.data.data || [];// this.total = res.data.total;this.$message.success("获取菜单列表成功!");this.options = this.buildCascaderOptions(this.tableData);} else {this.$message.error("获菜单列表失败!");}});},//菜单列表转换成级联选择器数据buildCascaderOptions(data) {return data.map(item => ({value: item.id,label: item.menuName,children: item.children && item.children.length > 0? this.buildCascaderOptions(item.children): undefined}));},// 递归查找父级路径findParentPath(data, targetId, path = []) {for (const item of data) {const currentPath = [...path, item.value];if (item.value === targetId) {return currentPath;}if (item.children && item.children.length > 0) {const result = this.findParentPath(item.children, targetId, currentPath);if (result) return result;}}return null;},toggleAll() {this.isExpandAll = !this.isExpandAll;this.tableData.forEach(row => {this.$refs.menuTableRef.toggleRowExpansion(row, this.isExpandAll);});},handleAdd(index, row) {// this.form = {};this.form.parentId = row.id;console.table("父菜单id" + row);this.showMenu = true;this.menuDisabled = true;},handleEdit(index, row) {getMenuById(row.id).then(res => {if (res.data.code == 200) {this.form = res.data.data;this.parentIdCascader = this.findParentPath(this.options, this.form.parentId) || [];} else {this.$message.error("获取菜单信息失败!");}});this.showMenu = true;this.menuDisabled = false;},handleDelete(index, row) {this.$confirm('确认删除菜单:' + row.menuName + '吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {deleteMenu(row.id).then(res => {if (res.data.code == 200) {this.getMenuList();this.$message.success("删除成功!");} else {this.$message.error(res.data.message);}});}).catch(() => {this.$message({type: 'info',message: '已取消删除'});});},// 保存菜单submit() {this.$refs.form.validate(valid => {if (valid) {this.loading = true;this.form.parentId = this.parentIdCascader[this.parentIdCascader.length - 1];if (this.form.id) {updateMenu(this.form).then(res => {if (res.data.code === 200) {this.$message.success("修改成功!");this.showMenu = false;this.getMenuList();} else {this.$message.error(res.data.message);}})} else {addMenu(this.form).then(res => {if (res.data.code === 200) {this.$message.success("添加成功!");this.showMenu = false;this.getMenuList();} else {this.$message.error(res.data.message);}})}}this.loading = false;})},close() {this.form = {};this.menuDisabled = false;this.showMenu = false;},}
}
</script>
1.2.请求js调整(menu.js)
import request from '@/utils/request';
/*** 查询所有菜单列表-【树形结构】* */
export function getAllMenuList() {return request({url: '/menu/getAllMenuList',method: 'get',});
}
/*** 查询菜单列表 - 【树形结构】* @param {Object} params - 菜单参数*/
export function getMenuList(data) {return request({url: '/menu/getMenuList',method: 'post',data: data,});
}
/*** 查询登录角色菜单列表* @param {Object} params - 菜单参数*/
export function getMenuListByUserId(userId) {return request({url: '/menu/getMenuListByUserId',method: 'get',params: {'userId': userId},});
}
/*** 新增菜单* @param {Object} data - 菜单信息*/
export function addMenu(data) {return request({url: '/menu/addMenu',method: 'post',data: data,});
}
/*** 修改菜单* @param {Object} data - 菜单信息*/
export function updateMenu(data) {return request({url: '/menu/updateMenu',method: 'post',data: data,});
}
/*** 删除菜单* @param {Number|String} id - 菜单 ID*/
export function deleteMenu(id) {return request({url: `/menu/delete`,method: 'post',params: {'id': id},});
}
/*** 获取菜单* @param {Number|String} id - 菜单 ID*/
export function getMenuById(id) {return request({url: `/menu/getMenuById`,method: 'get',params: {'id': id},});
}

4.配置调整

1.1配置权限管理路由

在router/下新建permission.js如下:

export default [{path: 'user',name: 'user',component: () => import('@/view/permission/user.vue'),meta: { title: '用户管理', requiresAuth: true }},{path: 'role',name: 'role',component: () => import('@/view/permission/role.vue'),meta: { title: '角色管理', requiresAuth: true }},{path: 'menu',name: 'menu',component: () => import('@/view/permission/menu.vue'),meta: { title: '菜单管理', requiresAuth: true }}]

在router/index.js中引入权限管理路由

import permission from './permission.js'......
{path: '/',name: 'Index',component: Index,redirect: '/login', // 默认重定向到 /homechildren: [{path: '/home',name: 'home',component: () => import('@/view/home.vue'),meta: { title: '首页', requiresAuth: true }},// 其他子路由也可以放在这里...permission,]},

5.首页布局

1.1.菜单动态化显示
<template><el-container style="height: 100vh;"><el-main class="login-main"><el-row type="flex" justify="center" align="middle" style="height: 100%;"><el-col :xs="20" :sm="12" :md="8" :lg="6" :xl="4"><el-card class="login-card"><div slot="header" class="login-header"><h2>用户管理平台</h2></div><el-form ref="form" :model="formData" label-width="80px" :rules="rules"><el-form-item label="用户名" prop="username"><el-input v-model="formData.username" placeholder="请输入用户名"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="formData.password" show-password placeholder="请输入密码"></el-input></el-form-item><el-form-item><el-button type="primary" @click="login" style="width: 100%;">登录</el-button></el-form-item></el-form></el-card></el-col></el-row></el-main></el-container>
</template><script>
import { login } from '@/api/login';
const jwtDecode = require('jwt-decode').default;
export default {name: 'UserLogin',data() {return {formData: {username: '',password: ''},rules: {username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],}};},methods: {async login() {try {const res = await login(this.formData);console.log('jwtDecode:', jwtDecode);console.log('res.data.data', res.data.data)if (res.data.code === 200) {const token = res.data.data;const decoded = this.parseJwt(token.trim());console.log('decoded', decoded);localStorage.setItem('token', token);// 跳转到首页 传递用户idthis.$router.push({path: '/home',query: { userId: decoded.sub }});// this.$router.push('/');this.$message.success('登录成功');} else {this.$message.error(res.data.message || '登录失败');}} catch (error) {this.$message.error('请求异常,请检查网络或服务端状态');}},parseJwt(token) {try {const base64Url = token.split('.')[1];const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));return JSON.parse(jsonPayload);} catch (e) {console.error('JWT 解析失败:', e);return null;}}}
};
</script><style scoped>
.login-main {background: linear-gradient(to right, #e0f7fa, #fffde7);/* 柔和渐变背景 */
}.login-card {box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);border-radius: 10px;
}.login-header {text-align: center;margin-bottom: 20px;
}
</style>

解析token获取用户Id,查询用户详细信息(基本信息、角色、菜单等)

//解析前要安装jwt依赖
npm install jwt-decode --save

 有时新版 jwt-decode@4.0.0 在 Vue 2 中有问题,你可以尝试降级:

npm install jwt-decode@3.1.2 --save
1.2.首页统计信息展示

 1.在view/新建home.vue

<template><el-container class="home-page"><!-- 上部:统计数字 --><el-header><el-row :gutter="20" class="stats-header"><el-col :span="6" v-for="(stat, index) in stats" :key="index"><el-card class="chart-container stat-card"><el-statistic :title="stat.title" :value="stat.value":value-style="{ fontSize: '20px', color: '#333' }"><template #suffix v-if="stat.suffix">{{ stat.suffix }}</template></el-statistic></el-card></el-col></el-row></el-header><!-- 中部:图表 --><el-main><el-row :gutter="20" class="chart-section"><el-col :span="12"><el-card class="chart-container"><div slot="header" class="card-header">月度趋势分析</div><div id="bar-chart"></div></el-card></el-col><el-col :span="12"><el-card class="chart-container"><div slot="header" class="card-header">用户角色占比</div><div id="pie-chart"></div></el-card></el-col></el-row></el-main><!-- 下部:项目信息 --><el-footer><el-row :gutter="20" class="info-section"><el-col :span="8"><el-card class="section-card small-card"><div slot="header" class="card-header">项目介绍</div><p>这是一个基于 Vue.js 和 Element Plus 的后台管理系统...</p></el-card></el-col><el-col :span="8"><el-card class="section-card small-card"><div slot="header" class="card-header">所用技术</div><ul><li>Vue.js, Vue Router, Vuex</li><li>Element Plus</li></ul></el-card></el-col><el-col :span="8"><el-card class="section-card small-card"><div slot="header" class="card-header">功能模块</div><ul><li>用户管理</li><li>权限管理</li></ul></el-card></el-col></el-row></el-footer></el-container>
</template><script>
import * as echarts from 'echarts';
export default {name: 'homeView',data() {return {stats: [{ title: '用户总数', value: 12345 },{ title: '本月活跃人数', value: 4567 },{ title: '转化率', value: 68.4, suffix: '%' },{ title: '本月增长人数', value: 987 }]}},mounted() {this.$nextTick(() => {this.initBarChart();this.initPieChart();});},methods: {initBarChart() {const chart = echarts.init(document.getElementById('bar-chart'));const option = {color: ['#a3c4dc', '#b6d7a8', '#f9cb9c', '#ead1dc'], // 浅色系配色tooltip: {trigger: 'axis',axisPointer: { type: 'shadow' }},legend: {data: ['用户总数', '活跃人数', '增长人数', '转化率']},xAxis: {type: 'category',data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']},yAxis: [{ name: '数量', type: 'value' },{ name: '转化率', type: 'value' }],series: [{ name: '用户总数', type: 'bar', data: [12000, 12500, 13000, 13500, 14000, 14500] },{ name: '活跃人数', type: 'bar', data: [4000, 4200, 4500, 4600, 4800, 5000] },{ name: '增长人数', type: 'bar', data: [800, 900, 950, 1000, 1100, 1200] },{name: '转化率', type: 'line', yAxisIndex: 1, data: [65, 66, 67, 68, 69, 70],itemStyle: {borderRadius: 2,borderColor: '#d9d9d9',borderWidth: 1}}]};chart.setOption(option);},initPieChart() {const chart = echarts.init(document.getElementById('pie-chart'));const option = {tooltip: { trigger: 'item' },legend: { show: false },color: ['#a3c4dc', '#b6d7a8', '#f9cb9c', '#ead1dc'],series: [{type: 'pie',radius: '70%',data: [{ value: 120, name: '管理员' },{ value: 80, name: '编辑' },{ value: 150, name: '访客' },{ value: 200, name: '普通用户' }],insideLabel: {show: true},label: {show: true,position: 'outside',fontSize: 16,formatter: '{b}:({d}%)', // 显示名称、数值、百分比rich: {b: (params) => ({ color: params.color }) // 名称颜色跟随扇区颜色},color: 'inherit' // 关键:继承数据项颜色},labelLine: {show: true,length: 15,length2: 25,lineStyle: {width: 1.5,type: 'solid',color: null // 关键:线条颜色继承扇区颜色(inherit在这里不生效所以设置为null,会自动继承)}},roseType: true,itemStyle: {borderRadius: 5,borderColor: '#fff',   // 白色边框(可根据背景调整)borderWidth: 2,        // 边框宽度shadowBlur: 10,shadowColor: 'rgba(0, 0, 0, 0.2)'}}]};chart.setOption(option);}}
};
</script><style scoped>
.home-page {padding: 10px;background-color: #f5f7fa;font-family: 'Segoe UI', sans-serif;
}/* 新增:为 header 添加底部边距 */
.el-header {margin-bottom: 10px !important;
}.stats-header .el-row :deep(.el-col) {padding: 10px;
}.stat-card {background-color: #ffffff;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);padding: 20px;text-align: center;font-size: 18px;height: 80px !important;
}.stat-card strong {font-size: 20px;color: #333;
}.chart-section {margin-top: 20px;
}.info-section {margin-top: 0px;
}.chart-container,
.card-container {background-color: #ffffff;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);padding: 10px;height: 400px;position: relative;
}#bar-chart,
#pie-chart {width: 100%;height: 100%;min-height: 300px;
}.section-card {background-color: #ffffff;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);padding: 15px;font-size: 13px;
}.small-card {font-size: 12px;
}.card-header {font-size: 16px;font-weight: bold;color: #333;
}
</style>

使用echart图表需要安装以下依赖:

npm install echarts --save

 2.在router/index.js中,添加home.vue路由到index下,如下:

  {path: '/',name: 'Index',component: Index,redirect: '/login', // 默认重定向到 /homechildren: [{path: '/home',name: 'home',component: () => import('@/view/home.vue'),meta: { title: '首页', requiresAuth: true }},// 其他子路由也可以放在这里...permission,]},

(注:首页数据由于后端整体功能还需扩展,例如转化率、图表、活跃度等需要日志相关信息,暂定使用假数据,后续会继续开发维护。) 

6.其他调整

1.标签调整

修改项目启动后浏览器显示的标签页名称(修改public/index.html的<title></title>)如下:

<!DOCTYPE html>
<html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1.0"><link rel="icon" href="<%= BASE_URL %>favicon.ico"><!-- <title><%= htmlWebpackPlugin.options.title %></title> --><title>我的管理系统</title></head><body><noscript><strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><!-- built files will be auto injected --></body>
</html>

四、权限管理逻辑

1.用户-角色-菜单

 2.权限管理功能

3.权限管理功能规则 

1.用户-角色是一对多关系,即一个用户可以有多个角色,且不同用户可以有部分相同角色。

2.角色-菜单是一对多关系,即一个角色可以有多个菜单权限,且不同角色可以有部分相同菜单。

3.角色-菜单权限,拥有一个菜单权限,则必然有其父菜单(如果有父菜单)权限,父子联动。

五、附:源码

1.源码下载地址

https://gitee.com/wangaolin/user-demo.git

同学们有需要可以自行下载查看,此文章是dev-vue分支

六、结语

   此次开发总结:

  • 整体流程我做了简单测试(功能测试),未压测、未安测,可能也不需要。
  • 整个项目相对来说还可以,虽然不少地方有些粗糙(还能用),奈何博主能力有限
  • 后续我会继续优化,也欢迎各位同学指出问题,加以改进。

部分调整可能未全发布,多多少少会有漏的,有需要全代码的同学自行下载。 

(注:接定制化开发前后端分离项目,私我) 

http://www.lryc.cn/news/596155.html

相关文章:

  • ucharts 搭配uniapp 自定义x轴文字 实现截取显示
  • redis秒杀之lua脚本
  • 企业微信快捷回复设定方法(提高效率)
  • 如何永久删除安卓设备中的照片(已验证)
  • 大型语言模型(Large Language Models,LLM)
  • REASONING ELICITATION IN LANGUAGE MODELSVIA COUNTERFACTUAL FEEDBACK
  • AWS OpenSearch 搜索排序常见用法
  • 如何加固Endpoint Central服务器的安全?(上)
  • 【运维】SGLang服务器参数配置详解
  • Python趣味算法:折半查找(二分查找)算法终极指南——原理、实现与优化
  • SQL Server 查询优化
  • 电子电气架构 --- 从软件质量看组织转型路径
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 访问鉴权功能实现
  • 5G 智慧矿山监控终端
  • UE5 UI 控件切换器
  • 记录解决问题--使用maven help插件一次性上传所有依赖到离线环境,spring-boot-starter-undertow离线环境缺少依赖
  • Jenkins 多架构并发构建实战
  • gitlab私服搭建
  • wed前端简单解析
  • k8s:离线部署tomcatV11.0.9,报Cannot find /opt/bitnami/tomcat/bin/setclasspath.sh
  • 中国在远程医疗智能化方面有哪些特色发展模式?
  • 公交车客流人数统计管理解决方案:智能化技术与高效运营实践
  • DAY20 奇异值SVD分解
  • 【bug】Yolo11在使用tensorrt推理numpy报错
  • 【数据可视化-70】奶茶店销量数据可视化:打造炫酷黑金风格的可视化大屏
  • 使用qt编写上位机程序,出现串口死掉无法接受数据的bug
  • vue2 webpack 部署二级目录、根目录nginx配置及打包配置调整
  • 【深度解析】从AWS re_Invent 2025看云原生技术发展趋势
  • kafka主题管理详解 - kafka-topics.sh
  • C++ 结构体(struct)与联合体(union)