图书馆管理系统 2.后台系统管理模块编写

后端

1.实体类编写

用户实体类

package jkw.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 用户
 */
@Data
public class Admin implements Serializable {
    @TableId
    private Integer aid;
    private String username;//姓名
    private String password;//密码
    private String phoneNum;//手机号

    private String email;//邮箱

    private String adminImg;//头像

    private boolean status; // 状态 true可用 false禁用
    @TableField(exist = false) // 不是数据库的字段
    private List<Role> roles; // 角色集合
}

角色实体类

package jkw.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 角色
 */
@Data
public class Role implements Serializable {
    @TableId
    private Integer rid;
    private String roleName; // 角色名
    private String roleDesc; // 角色介绍
    @TableField(exist = false) // 不是数据库的字段
    private List<Permission> permissions;// 权限集合
}

权限实体类

package jkw.pojo;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.io.Serializable;

/**
 * 权限
 */
@Data
public class Permission implements Serializable {
    @TableId
    private Integer pid;
    private String permissionName; //名称
    private String permissionDesc;//详情
}

2.数据库编写

数据库版本

这里采用的是mysql5.7,编码为utf-8mb4

用户表

DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
  `aid` int(32) NOT NULL AUTO_INCREMENT,
  `email` varchar(50) DEFAULT NULL,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `phoneNum` varchar(20) DEFAULT NULL,
  `status` tinyint(1) DEFAULT NULL,
  `adminImg` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`aid`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

角色表

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `rid` int(32) NOT NULL AUTO_INCREMENT,
  `roleName` varchar(50) DEFAULT NULL,
  `roleDesc` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

权限表

DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `pid` int(32) NOT NULL AUTO_INCREMENT,
  `permissionName` varchar(50) DEFAULT NULL,
  `permissionDesc` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
  `rid` varchar(32) DEFAULT NULL,
  `pid` varchar(32) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

关联表

DROP TABLE IF EXISTS `admin_role`;
CREATE TABLE `admin_role` (
  `aid` varchar(32) NOT NULL,
  `rid` varchar(32) NOT NULL,
  PRIMARY KEY (`aid`,`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.数据映射层编写

包接口

用户数据接口

package jkw.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jkw.pojo.Admin;
import jkw.pojo.Permission;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface AdminMapper extends BaseMapper<Admin> {
    // 根据id查询管理员,包括角色和权限
    Admin findById(Integer id);
    // 删除管理员的所有角色
    void deleteAdminAllRole(Integer id);
    // 给管理员添加角色
    void addRoleToAdmin(@Param("aid") Integer aid, @Param("rid") Integer rid);
    // 根据管理员名查询权限
    List<Permission> findAllPermission(String username);
}

用户数据接口映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jkw.mapper.AdminMapper">
    <resultMap id="adminMapper" type="jkw.pojo.Admin">
        <id property="aid" column="aid"></id>
        <result property="username" column="username"></result>
        <result property="email" column="email"></result>
        <result property="phoneNum" column="phoneNum"></result>
        <result property="status" column="status"></result>
        <result property="adminImg" column="adminImg"></result>
        <collection property="roles" column="aid" ofType="jkw.pojo.Role">
            <id property="rid" column="rid"></id>
            <result property="roleName" column="roleName"></result>
            <result property="roleDesc" column="roleDesc"></result>
            <collection property="permissions" column="rid" ofType="jkw.pojo.Permission">
                <id property="pid" column="pid"></id>
                <result property="permissionName" column="permissionName"></result>
                <result property="permissionDesc" column="permissionDesc"></result>
            </collection>
        </collection>
    </resultMap>

    <delete id="deleteAdminAllRole" parameterType="int">
        DELETE
        FROM admin_role
        WHERE aid = #{id}
    </delete>
    <select id="findById" parameterType="int" resultMap="adminMapper">
        SELECT *
        FROM admin
                 LEFT JOIN admin_role
                           ON admin.aid = admin_role.aid
                 LEFT JOIN role
                           ON admin_role.rid = role.rid
                 LEFT JOIN role_permission
                           ON role.rid = role_permission.rid
                 LEFT JOIN permission
                           ON role_permission.pid = permission.pid
        WHERE admin.aid = #{id}
    </select>
    <insert id="addRoleToAdmin">
        INSERT INTO admin_role
        VALUES (#{aid}, #{rid});
    </insert>
    <select id="findAllPermission" resultType="jkw.pojo.Permission" parameterType="string">
        SELECT DISTINCT permission.*
        FROM admin
                 LEFT JOIN admin_role
                           ON admin.aid = admin_role.aid
                 LEFT JOIN role
                           ON admin_role.rid = role.rid
                 LEFT JOIN role_permission
                           ON role.rid = role_permission.rid
                 LEFT JOIN permission
                           ON role_permission.pid = permission.pid
        WHERE admin.username = #{username}
    </select>

</mapper>

角色数据接口

package jkw.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jkw.pojo.Role;
import org.apache.ibatis.annotations.Param;

public interface RoleMapper extends BaseMapper<Role> {
    // 删除角色_权限中间表的相关数据
    void deleteRoleAllPermission(Integer rid);
    // 删除用户_角色表的相关数据
    void deleteRoleAllAdmin(Integer rid);
    // 根据id查询角色,包括权限
    Role findById(Integer id);
    // 给角色添加权限
    void addPermissionToRole(@Param("rid") Integer rid, @Param("pid")Integer pid);
}

角色数据接口映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jkw.mapper.RoleMapper">
    <resultMap id="roleMapper" type="jkw.pojo.Role">
        <id property="rid" column="rid"></id>
        <result property="roleName" column="roleName"></result>
        <result property="roleDesc" column="roleDesc"></result>
        <collection property="permissions" column="rid" ofType="jkw.pojo.Permission">
            <id property="pid" column="pid"></id>
            <result property="permissionName" column="permissionName"></result>
            <result property="permissionDesc" column="permissionDesc"></result>
        </collection>
    </resultMap>
    <delete id="deleteRoleAllPermission" parameterType="int">
        DELETE
        FROM role_permission
        WHERE rid = #{rid}
    </delete>

    <delete id="deleteRoleAllAdmin" parameterType="int">
        DELETE
        FROM admin_role
        where rid = #{rid}
    </delete>
    <select id="findById" parameterType="int" resultMap="roleMapper">
        SELECT *
        FROM role
                 LEFT JOIN role_permission
                           ON role.rid = role_permission.rid
                 LEFT JOIN permission
                           ON role_permission.pid = permission.pid
        WHERE role.rid = #{id}
    </select>
    <insert id="addPermissionToRole">
        INSERT INTO role_permission
        VALUES (#{rid}, #{pid});
    </insert>
</mapper>

权限数据接口

package jkw.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jkw.pojo.Permission;


public interface PermissionMapper extends BaseMapper<Permission> {
    // 删除角色_权限表中的相关数据
    void deletePermissionAllRole(Integer pid);
}

权限数据接口映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jkw.mapper.PermissionMapper">
    <delete id="deletePermissionAllRole" parameterType="int">
        DELETE
        FROM role_permission
        WHERE pid = #{pid}
    </delete>
</mapper>

4.服务层编写

用户服务接口

package jkw.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Admin;
import jkw.pojo.Permission;

import java.util.List;

/**
 * 用户服务层
 */

public interface AdminService {
    // 新增管理员
    void add(Admin admin);
    // 修改管理员
    void update(Admin admin);
    // 删除管理员【自定义】
    void delete(Integer id);
    //修改状态
    void updateStatus(Integer id);
    // 根据id查询管理员(查询用户详情)【自定义】
    Admin findById(Integer id);
    //分页查询管理员
    Page<Admin> search(int page, int size);
    // 修改管理员角色【自定义】
    void updateRoleToAdmin(Integer aid, Integer[] rids);
    // 根据名字查询管理员
    Admin findByName(String username);
    // 根据名字查询管理员所有权限
    List<Permission> findAllPermission(String username);
}

用户服务实现类

package jkw.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.mapper.AdminMapper;
import jkw.pojo.Admin;
import jkw.pojo.Permission;
import jkw.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;

@Service
@Transactional
public class AdminServiceImpl implements AdminService {
    @Autowired
    private AdminMapper adminMapper;

    @Override
    public void add(Admin admin) {
        adminMapper.insert(admin);
        //初始化用户,不然添加后什么角色都没有,会报错
        adminMapper.addRoleToAdmin(admin.getAid(), 2);
    }

    @Override
    public void update(Admin admin) {
        // 如果传来空密码则密码还是原来的密码
        if (!StringUtils.hasText(admin.getPassword())) {
            String password = adminMapper.selectById(admin.getAid()).getPassword();
            admin.setPassword(password);
        }
        adminMapper.updateById(admin);
    }

    @Override
    public void delete(Integer id) {
        // 删除用户的所有角色
        adminMapper.deleteAdminAllRole(id);
        // 删除用户
        adminMapper.deleteById(id);
    }

    @Override
    public void updateStatus(Integer id) {
        Admin admin = adminMapper.selectById(id);
        admin.setStatus(!admin.isStatus());//状态取反
        adminMapper.updateById(admin);
    }

    @Override
    public Admin findById(Integer id) {
        return adminMapper.findById(id);
    }

    @Override
    public Page<Admin> search(int page, int size) {
        return adminMapper.selectPage(new Page<>(page, size), null);
    }

    @Override
    public void updateRoleToAdmin(Integer aid, Integer[] rids) {
        // 删除用户的所有角色
        adminMapper.deleteAdminAllRole(aid);
        // 重新添加管理员角色
        for (Integer rid : rids) {
            adminMapper.addRoleToAdmin(aid, rid);
        }
    }

    @Override
    public Admin findByName(String username) {
        QueryWrapper<Admin> wrapper = new QueryWrapper();
        wrapper.eq("username", username);
        Admin admin = adminMapper.selectOne(wrapper);
        return admin;
    }

    @Override
    public List<Permission> findAllPermission(String username) {
        return adminMapper.findAllPermission(username);
    }
}

角色服务接口

package jkw.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Role;

import java.util.List;

/**
 * 角色服务接口
 */
public interface RoleService {
    // 新增角色
    void add(Role role);
    // 修改角色
    void update(Role role);
    // 删除角色
    void delete(Integer id);
    // 根据id查询角色【自定义】
    Role findById(Integer id);
    // 查询所有角色
    List<Role> findAll();
    // 分页查询角色
    Page<Role> search(int page, int size);
    // 修改角色的权限【自定义】
    void addPermissionToRole(Integer rid, Integer[] pids);
}

角色服务实现类

package jkw.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.mapper.RoleMapper;
import jkw.pojo.Role;
import jkw.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class RoleServiceImpl implements RoleService {
    @Autowired
    private RoleMapper roleMapper;
    @Override
    public void add(Role role) {
        roleMapper.insert(role);
    }

    @Override
    public void update(Role role) {
        roleMapper.updateById(role);
    }

    @Override
    public void delete(Integer id) {
        // 删除角色
        roleMapper.deleteById(id);
        // 删除角色_权限中间表的相关数据
        roleMapper.deleteRoleAllPermission(id);
        // 删除用户_角色中间表的相关数据
        roleMapper.deleteRoleAllAdmin(id);
    }

    @Override
    public Role findById(Integer id) {
        return roleMapper.findById(id);
    }

    @Override
    public List<Role> findAll() {
        return roleMapper.selectList(null);
    }

    @Override
    public Page<Role> search(int page, int size) {
        return roleMapper.selectPage(new Page(page,size),null);
    }

    @Override
    public void addPermissionToRole(Integer rid, Integer[] pids) {
        // 删除角色的所有权限
        roleMapper.deleteRoleAllPermission(rid);
        // 给角色添加权限
        for (Integer pid : pids) {
            roleMapper.addPermissionToRole(rid,pid);
        }
    }
}

权限服务接口

package jkw.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Permission;

import java.util.List;

public interface PermissionService {
    void add(Permission permission);

    void update(Permission permission);

    void delete(Integer id);

    Permission findById(Integer id);

    List<Permission> findAll();

    Page<Permission> search(String search, int page, int size);
}

权限服务实现类 

package jkw.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.mapper.PermissionMapper;
import jkw.pojo.Permission;
import jkw.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
@Service
public class PermissionServiceImpl implements PermissionService {
    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public void add(Permission permission) {
        permissionMapper.insert(permission);
    }

    @Override
    public void update(Permission permission) {
        permissionMapper.updateById(permission);
    }

    @Override
    public void delete(Integer id) {
        // 删除权限
        permissionMapper.deleteById(id);
        // 删除角色_权限表中的相关数据
        permissionMapper.deletePermissionAllRole(id);
    }

    @Override
    public Permission findById(Integer id) {
        return permissionMapper.selectById(id);
    }

    @Override
    public List<Permission> findAll() {
        return permissionMapper.selectList(null);
    }

    @Override
    public Page<Permission> search(String search, int page, int size) {
        QueryWrapper<Permission> queryWrapper = new QueryWrapper<>();
        if (search != null) {
            queryWrapper.like("permissionDesc", search);
        }
        return permissionMapper.selectPage(new Page(page, size), queryWrapper);
    }
}

5.控制层编写

用户控制层

package jkw.controller.back.sys;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Admin;
import jkw.service.AdminService;
import jkw.vo.BaseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

/**
 * back管理员
 */
@RestController
@RequestMapping("/back/admin")
@CrossOrigin
public class AdminController {
    @Autowired
    private AdminService adminService;
    @Autowired
    private PasswordEncoder encoder;

    /**
     * 新增
     *
     * @param admin
     * @return
     */
    @PostMapping("/add")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult add(Admin admin) {
        String password = admin.getPassword();
        password = encoder.encode(password);
        admin.setPassword(password);
        adminService.add(admin);
        return BaseResult.ok();
    }

    /**
     * 修改管理员(设置空密码则还是原来密码)
     *
     * @param admin 管理员
     * @return
     */
    @PostMapping("/update")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult update(Admin admin) {
        String password = admin.getPassword();
        if (StringUtils.hasText(password)) { // 密码不为空加密
            password = encoder.encode(password);
            admin.setPassword(password);
        }
        adminService.update(admin);
        return BaseResult.ok();
    }

    /**
     * 删除
     *
     * @param aid
     * @return
     */
    @DeleteMapping("/delete")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult delete(Integer aid) {
        adminService.delete(aid);
        return BaseResult.ok();
    }

    /**
     * 根据id查询管理员
     *
     * @param aid
     * @return
     */
    @GetMapping("/findById")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult<Admin> findById(Integer aid) {
        Admin admin = adminService.findById(aid);
        return BaseResult.ok(admin);
    }

    /**
     * 分页查询管理员
     *
     * @param page 当前页
     * @param size 每页条数
     * @return
     */
    @GetMapping("/search")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult<Page<Admin>> search(int page, int size) {
        Page<Admin> adminPage = adminService.search(page, size);
        return BaseResult.ok(adminPage);
    }

    /**
     * 修改状态
     *
     * @param aid 管理员id
     * @return
     */
    @PostMapping("/updateStatus")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult updateStatus(Integer aid) {
        adminService.updateStatus(aid);
        return BaseResult.ok();
    }

    /**
     * 修改管理员角色
     *
     * @param aid  管理员id
     * @param rids 角色id
     * @return
     */
    @PostMapping("/updateRole")
    @PreAuthorize("hasAnyAuthority('/admin')")
    public BaseResult updateRoleToAdmin(Integer aid, Integer[] rids) {
        adminService.updateRoleToAdmin(aid, rids);
        return BaseResult.ok();
    }

    /**
     * 获取登录管理员名
     *
     * @return 管理员名
     */
    @GetMapping("/getUsername")
    public BaseResult<String> getUsername() {
        // 1.获取会话对象
        SecurityContext context = SecurityContextHolder.getContext();
        // 2.获取认证对象
        Authentication authentication = context.getAuthentication();
        // 3.获取登录用户信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        String username = userDetails.getUsername();
        return BaseResult.ok(username);
    }

    /**
     * 根据用户名查询管理员
     *
     * @param username
     * @return
     */
    @GetMapping("/findByUsername")
    public BaseResult findByUsername(String username) {
        Admin admin = adminService.findByName(username);
        return BaseResult.ok(admin);
    }
}

角色控制层

package jkw.controller.back.sys;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Role;
import jkw.service.RoleService;
import jkw.vo.BaseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/back/role")
@CrossOrigin
public class RoleController {
    @Autowired
    private RoleService roleService;

    /**
     * 新增角色
     *
     * @param role 角色对象
     * @return 执行结果
     */
    @PostMapping("/add")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult add(Role role) {
        roleService.add(role);
        return BaseResult.ok();
    }


    /**
     * 修改角色
     *
     * @param role 角色对象
     * @return 执行结c果
     */
    @PostMapping("/update")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult update(Role role) {
        roleService.update(role);
        return BaseResult.ok();
    }


    /**
     * 删除角色(包括中间表的管理员、权限)
     *
     * @param rid 角色id
     * @return 执行结果
     */
    @DeleteMapping("/delete")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult delete(Integer rid) {
        roleService.delete(rid);
        return BaseResult.ok();
    }


    /**
     * 根据id查询角色
     *
     * @param rid
     * @return 查询到的角色
     */
    @GetMapping("/findById")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult<Role> findById(Integer rid) {
        Role role = roleService.findById(rid);
        return BaseResult.ok(role);
    }

    /**
     * 查询所有角色
     *
     * @return 查询结果
     */
    @GetMapping("/findAll")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult<List<Role>> findAll() {
        List<Role> all = roleService.findAll();
        return BaseResult.ok(all);
    }

    /**
     * 分页查询角色
     *
     * @param page 页码
     * @param size 每页条数
     * @return 查询结果
     */
    @GetMapping("/search")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult<Page<Role>> search(int page, int size) {
        Page<Role> page1 = roleService.search(page, size);
        return BaseResult.ok(page1);
    }


    /**
     * 修改角色的权限
     *
     * @param rid  角色id
     * @param pids 权限id
     * @return 执行结果
     */
    @PostMapping("/updatePermission")
    @PreAuthorize("hasAnyAuthority('/role')")
    public BaseResult updatePermissionToRole(Integer rid, Integer[] pids) {
        roleService.addPermissionToRole(rid, pids);
        return BaseResult.ok();
    }
}

权限控制层

package jkw.controller.back.sys;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jkw.pojo.Permission;
import jkw.service.PermissionService;
import jkw.vo.BaseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * back_permission权限
 */
@CrossOrigin
@RestController
@RequestMapping("/back/permission")
public class PermissionController {
    @Autowired
    private PermissionService permissionService;

    /**
     * 新增
     *
     * @param permission
     * @return 执行结果
     */
    @PostMapping("/add")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult add(Permission permission) {
        permissionService.add(permission);
        return BaseResult.ok();
    }


    /**
     * 修改
     *
     * @param permission
     * @return 执行结果
     */
    @PostMapping("/update")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult update(Permission permission) {
        permissionService.update(permission);
        return BaseResult.ok();
    }


    /**
     * 删除
     *
     * @param pid id
     * @return 执行结果
     */
    @DeleteMapping("/delete")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult delete(Integer pid) {
        permissionService.delete(pid);
        return BaseResult.ok();
    }


    /**
     * 根据id查询
     *
     * @param pid id
     * @return 查询结果
     */
    @GetMapping("/findById")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult<Permission> findById(Integer pid) {
        Permission permission = permissionService.findById(pid);
        return BaseResult.ok(permission);
    }

    /**
     * 查询所有
     *
     * @return
     */
    @GetMapping("/findAll")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult<List<Permission>> findAll() {
        List<Permission> all = permissionService.findAll();
        return BaseResult.ok(all);
    }

    /**
     * 分页查询
     *
     * @param page 页面
     * @param size 每页条数
     * @return 查询结果
     */
    @GetMapping("/search")
    @PreAuthorize("hasAnyAuthority('/permission')")
    public BaseResult<Page<Permission>> search(String search, int page, int size) {
        Page<Permission> permissionPage = permissionService.search(search, page, size);
        return BaseResult.ok(permissionPage);
    }


}

6.鉴权模块编写

鉴权配置类

package jkw.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * Security配置类
 */

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)// 开启鉴权配置注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    //解决跨域,相关文章http://t.csdnimg.cn/3LYOI
    CorsConfigurationSource configurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许cookies跨域
        corsConfiguration.setAllowCredentials(true);
        // 允许的请求方法
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        // 允许的请求头
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
        corsConfiguration.setMaxAge(3600L);
        //允许跨域访问的站点
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        //对所有URL生效
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义表单登录
        http.formLogin()
                .usernameParameter("username") // 用户名项
                .passwordParameter("password") // 密码项
                .loginProcessingUrl("/back/login") // 登录提交路径
                .successHandler(new MyLoginSuccessHandler()) // 登录成功处理器
                .failureHandler(new MyLoginFailureHandler()); // 登录失败处理器


        // 权限拦截配置
        http.authorizeRequests()
                .antMatchers("/back/login").permitAll() // 登录不需要认证
                .antMatchers("/back/admin/findByUsername").permitAll()
                .antMatchers("/").permitAll()
                .antMatchers("/file/**").permitAll()
                .antMatchers("/front/**").permitAll()
                .anyRequest().authenticated(); // 其余请求都需要认证


        // 退出登录配置
        http.logout()
                .logoutUrl("/back/logout") // 退出登录路径
                .logoutSuccessHandler(new MyLogoutSuccessHandler()) // 登出成功处理器
                .clearAuthentication(true) // 清除认证数据
                .invalidateHttpSession(true); // 清除session


        // 异常处理
        http.exceptionHandling()
                .authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未登录处理器
                .accessDeniedHandler(new MyAccessDeniedHandler()); // 权限不足处理器


        // 关闭csrf防护,取消跨站请求伪造防护
        http.csrf().disable();

        // 开启跨域访问
        http.cors().configurationSource(configurationSource());
//        http.cors();

    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

认证授权逻辑

package jkw.security;


import jkw.pojo.Admin;
import jkw.pojo.Permission;
import jkw.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 认证授权逻辑
 */
@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    private AdminService adminService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.认证
        Admin admin = adminService.findByName(username);
        if (admin == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        if (!admin.isStatus()) {
            throw new UsernameNotFoundException("用户不可用");
        }

        // 2.授权
        List<Permission> permissions = adminService.findAllPermission(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        for (Permission permission : permissions) {
            grantedAuthorities.add(new SimpleGrantedAuthority(permission.getPermissionDesc()));
        }


        // 3.封装为UserDetails对象
        UserDetails userDetails = User.withUsername(admin.getUsername())
                .password(admin.getPassword())
                .authorities(grantedAuthorities)
                .build();


        // 4.返回封装好的UserDetails对象
        return userDetails;
    }
}

权限不足处理器

package jkw.security;

import com.alibaba.fastjson.JSON;
import jkw.vo.BaseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 权限不足处理器
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        BaseResult result = new BaseResult(403, "权限不足", null);
        response.getWriter().write(JSON.toJSONString(result));
    }
}

未登录处理器

package jkw.security;

import com.alibaba.fastjson.JSON;
import jkw.vo.BaseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 未登录处理器
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        BaseResult result = new BaseResult(401, "用户未登录,请登录", null);
        response.getWriter().write(JSON.toJSONString(result));
    }
}



登录失败处理器

package jkw.security;

import com.alibaba.fastjson.JSON;
import jkw.vo.BaseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录失败处理器
 */
public class MyLoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        BaseResult result = new BaseResult(402, "用户名或密码错误", null);
        response.getWriter().write(JSON.toJSONString(result));
    }
}

登录成功处理器

package jkw.security;

import com.alibaba.fastjson.JSON;
import jkw.vo.BaseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录成功处理器
 */
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        BaseResult result = new BaseResult(200, "登录成功", userDetails);
        response.getWriter().write(JSON.toJSONString(result));
    }
}



退出登录成功成功处理器

package jkw.security;

import com.alibaba.fastjson.JSON;
import jkw.vo.BaseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 退出登录成功成功处理器
 */
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        BaseResult result = new BaseResult(200, "注销成功", null);
        response.getWriter().write(JSON.toJSONString(result));
    }
}

前端

1.页面编写

用户页面

<template>
  <div class="data-container">
    <!--添加 start-->
    <div class="data-header">

      <el-button round @click="addHander" class="button" size="large" type="primary">
        <el-icon>
          <DocumentAdd/>
        </el-icon>&nbsp;添加
      </el-button>
    </div>
    <!--添加 end-->
    <div class="data-table">
      <!--表格数据展示 start-->
      <el-table :data="adminList.list" style="width: 1100px;">
        <el-table-column prop="adminImg" label="头像" width="150" align="center">
          <template #default="scope">
            <img :src="scope.row.adminImg" style="height:60px"/>
          </template>
        </el-table-column>
        <el-table-column show-overflow-tooltip prop="username" label="用户名" width="120"></el-table-column>
        <el-table-column prop="email" label="邮箱" width="180" align="center"></el-table-column>
        <el-table-column prop="phoneNum" label="电话号码" width="120" align="center"></el-table-column>
        <el-table-column prop="status" label="用户状态" width="120">
          <!--利用标签帮助状态显示-->
          <template #default="scope">
            <el-tag :type="scope.row.status == true ? 'success' : 'error'">{{
                scope.row.status == true ? '使用' : '禁用'
              }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="角色分配" align="center" width="200">
          <template #default="scope">
            <el-button size="small" type="success" @click="handleDesc(scope.$index, scope.row)">角色</el-button>
            <el-button size="small" type="danger" @click="handleRole(scope.$index, scope.row)">分配</el-button>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200">
          <template #default="scope">
            <el-button size="small" type="primary" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
            <el-button size="small" type="success"
                       @click="handleStatus(scope.$index, scope.row)">状态
            </el-button>
            <el-button size="small" type="danger"
                       @click="handleDelete(scope.$index, scope.row)">删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!--表格数据展示 end-->
      <!--分页 start-->
      <div class="page">
        <el-pagination @current-change="currentChangeHaddler" background layout="prev,pager,next,jumper"
                       :default-page-size="defaultPageSize" :total="totalData"></el-pagination>
      </div>
      <!--分页 end-->
      <!--添加对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogAddVisible" title="添加用户" width="35%" center>
        <el-form :inline="true" :model="addFormInfo" status-icon label-width="120px">
          <el-form-item label="用户名">
            <el-input v-model="addFormInfo.username" placeholder="请输入用户名"></el-input>
          </el-form-item>
          <el-form-item label="密码">
            <el-input v-model="addFormInfo.password" placeholder="请输入密码"></el-input>
          </el-form-item>
          <el-form-item label="邮箱">
            <el-input v-model="addFormInfo.email" placeholder="请输入邮箱"></el-input>
          </el-form-item>
          <el-form-item label="电话号码">
            <el-input v-model="addFormInfo.phoneNum" placeholder="请输入电话号码"></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
                    <span class="dialog-footer">
                        <el-button @click="dialogAddVisible = false">取消</el-button>
                        <el-button type="primary" @click="sureHandler">确定</el-button>
                    </span>
        </template>
      </el-dialog>
      <!--添加对话框 end-->
      <!--编辑对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogEditorVisible" title="编辑用户信息" width="35%" center>
        <el-form :inline="true" :model="editorFormInfo" label-width="120px">
          <el-form-item label="头像">
            <el-input v-model="editorFormInfo.adminImg"></el-input>
          </el-form-item>
          <el-form-item label="用户名">
            <el-input v-model="editorFormInfo.username" placeholder="请输入用户名"></el-input>
          </el-form-item>
          <el-form-item label="密码">
            <el-input v-model="editorFormInfo.password" placeholder="请输入密码"></el-input>
          </el-form-item>
          <el-form-item label="邮箱">
            <el-input v-model="editorFormInfo.email" placeholder="请输入邮箱"></el-input>
          </el-form-item>
          <el-form-item label="电话号码">
            <el-input v-model="editorFormInfo.phoneNum" placeholder="请输入电话号码"></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
                    <span class="dialog-footer">
                        <el-button @click="dialogEditorVisible = false">取消</el-button>
                        <el-button type="primary" @click="sureEditorHandler">确定</el-button>
                    </span>
        </template>
      </el-dialog>
      <!--编辑对话框 end-->
      <!--分配角色对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogRoleVisible" title="分配角色" width="35%" center>
        <el-table ref="multipleTable" :data="allRoles" row-key="rid" @selection-change="onSelectionChange">
          <el-table-column type="selection" :reserve-selection="true"/>
          <el-table-column label="角色名" prop="roleName"/>
          <el-table-column label="角色描述" prop="roleDesc"/>
        </el-table>
        <template #footer>
                    <span class="dialog-footer">
                        <el-button @click="dialogRoleVisible = false">取消</el-button>
                        <el-button type="primary" @click="sureRoleHandler">确定</el-button>
                    </span>
        </template>
      </el-dialog>
      <!--分配角色对话框 end-->
      <!--详情对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogDescVisible" title="管理员详情" width="35%" center>
        <el-tag v-for="role in descInfo.list" :key="role.rid" size="large" effect="light" class="mx-1" round
                :type="success">
          {{ role.roleName }}:[{{ role.roleDesc }}]
        </el-tag>
        <template #footer>
                    <span class="dialog-footer">
                        <el-button @click="dialogDescVisible = false">关闭</el-button>
                    </span>
        </template>
      </el-dialog>
      <!--详情对话框 end-->
    </div>
  </div>
</template>
<script setup>
import sysAxios from "@/api/sys.js"
import {onMounted, reactive, ref} from "vue";
//初始化管理员查询数据
const adminList = reactive({
  list: []
})
//初始化总条数
const totalData = ref(0)
//当前页
const currentPage = ref(1)
//初始化分页显示条数
const defaultPageSize = ref(10)
//添加添加对话框控制器
const dialogAddVisible = ref(false)
//初始化添加对话框状态
const addFormInfo = reactive({
  username: "",
  email: "",
  phoneNum: "",
  password: "",
})
//编辑对话框控制器
const dialogEditorVisible = ref(false)
//初始化编辑对话框状态
const editorFormInfo = reactive({
  aid: '',
  username: "",
  email: "",
  phoneNum: "",
  password: "",
  status: '',
  adminImg: ''
})
//分配角色对话框控制器
const dialogRoleVisible = ref(false)
//初始化角色列表
const allRoles = ref([])
const adminRoles = ref([])
const multipleTable = ref(null)
let rids = []
const adminId = ref(0)
//详情对话框控制器
const dialogDescVisible = ref(false)
//初始化详情对话框
const descInfo = reactive({
  list: []
})
/**
 * 网路请求:分页查询
 *  */
const http = () => {
  sysAxios.admin_search(
      {
        page: currentPage.value,
        size: defaultPageSize.value
      }
  ).then(res => {
    if (res.data.code == 200) {
      adminList.list = res.data.data.records
      totalData.value = res.data.data.total
    }
  })
}
onMounted(() => {
  http()
})
/**
 * 分页
 */
const currentChangeHaddler = (val) => {
  currentPage.value = val
  http()
}
/**
 * 添加对话框弹出事件
 */
const addHander = () => {
  dialogAddVisible.value = true
}
/**
 * 添加对话框 确定事件
 */
const sureHandler = () => {
  sysAxios.admin_add({
    username: addFormInfo.username,
    email: addFormInfo.email,
    phoneNum: addFormInfo.phoneNum,
    password: addFormInfo.password,
  }).then(res => {
    if (res.data.code == 200) {
      dialogAddVisible.value = false
      http()
    }
  })
}
/**
 * 编辑对话框 弹出事件
 *  */
const handleEdit = (index, row) => {
  dialogEditorVisible.value = true
  sysAxios.admin_findById({
    aid: row.aid
  }).then(res => {
    if (res.data.code == 200) {
      editorFormInfo.aid = res.data.data.aid;
      editorFormInfo.username = res.data.data.username;
      editorFormInfo.password = '';
      editorFormInfo.email = res.data.data.email;
      editorFormInfo.phoneNum = res.data.data.phoneNum;
      editorFormInfo.status = res.data.data.status;
      editorFormInfo.adminImg = res.data.data.adminImg;
    }
  })

}
/**
 * 编辑对话框 确定事件
 */
const sureEditorHandler = () => {
  sysAxios.admin_update({
    aid: editorFormInfo.aid,
    username: editorFormInfo.username,
    email: editorFormInfo.email,
    phoneNum: editorFormInfo.phoneNum,
    password: editorFormInfo.password,
    status: editorFormInfo.status,
    adminImg: editorFormInfo.adminImg,
  }).then(res => {
    if (res.data.code == 200) {
      dialogEditorVisible.value = false
      http()
    }
  })
}
/**
 * 修改用户状态
 */
const handleStatus = (index, row) => {
  sysAxios.admin_updateStatus({
    aid: row.aid
  }).then(res => {
    if (res.data.code == 200) {
      http()
    }
  })
}
/**删除 */
const handleDelete = (index, row) => {
  ElMessageBox.confirm(
      '确定删除此用户么',
      '删除',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    sysAxios.admin_delete({
      aid: row.aid
    }).then(res => {
      if (res.data.code == 200) {
        ElMessage({
          type: 'success',
          message: "删除成功!!!",
        })
        http()
      } else {
        ElMessage({
          type: 'error',
          message: res.data.message,
        })
      }
    })
  }).catch(error => {
    ElMessage({
      type: 'info',
      message: "取消删除",
    })
  })


}
/**
 * 分配角色对话框 弹出事件
 */
const handleRole = (index, row) => {
  dialogRoleVisible.value = true
  sysAxios.admin_findById({
    aid: row.aid
  }).then(res => {
    if (res.data.code == 200) {
      console.log(res.data)
      adminRoles.value = res.data.data.roles
      adminId.value = res.data.data.aid
    }
  })
  sysAxios.role_findAll().then(res => {
    if (res.data.code == 200) {
      allRoles.value = res.data.data
    }
  })

}

const onSelectionChange = (keys) => {
  rids = []
  keys.map(item => {
    rids.push(item.rid)
  })
}
/**
 * 分配角色对话框 确定事件
 */
const sureRoleHandler = () => {
  dialogRoleVisible.value = false;
  sysAxios.admin_updateRole({
    aid: adminId.value,
    rids: rids
  }).then(res => {
    if (res.data.code == 200) {
      http()
    }
  })

}
/**
 * 详情对话框弹出事件
 */
const handleDesc = (index, row) => {
  dialogDescVisible.value = true
  sysAxios.admin_findById({
    aid: row.aid
  }).then(res => {
    if (res.data.code == 200) {
      descInfo.list = res.data.data.roles
    }
  })

}
</script>
<style scoped lang="scss">
.data-container {
  background-image: -webkit-gradient(linear, 0% 0%, 100% 100%,color-stop(0, rgb(50, 153, 152)),color-stop(1, rgb(108, 201, 156)));
  background-image: -webkit-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: linear-gradient(to bottom right,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: -ms-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  height: 800px;

.data-header {
  padding: 20px;

.input {
  width: 300px;
}
}

.data-table {
  padding: 20px;
}

.page {
  position: fixed;
  right: 10px;
  bottom: 10px;
}
}

</style>

角色页面

<template>
  <div class="data-container">
    <div class="data-header">
      <el-button round @click="addHander" class="button" size="large" type="primary">
        <el-icon>
          <DocumentAdd/>
        </el-icon>&nbsp;添加
      </el-button>
    </div>
    <div class="data-table">
      <!--表格数据展示 start-->
      <el-table :data="roleList.list" style="width: 830px;">
        <el-table-column prop="roleName" label="名称" width="200"></el-table-column>
        <el-table-column prop="roleDesc" label="角色详情" width="220"></el-table-column>
        <el-table-column label="操作" align="center" width="200">
          <template #default="scope">
            <el-button size="small" type="success" @click="handleDesc(scope.$index, scope.row)">详情</el-button>
            <el-button size="small" type="danger" @click="handlePermission(scope.$index, scope.row)">分配权限</el-button>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200">
          <template #default="scope">
            <el-button size="small" type="primary" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
            <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!--表格数据展示 end-->
      <!--分页 start-->
      <div class="page">
        <el-pagination @current-change="currentChangeHaddler" background layout="prev,pager,next,jumper"
                       default-page-size="defaultPageSize" :total="totalData"></el-pagination>
      </div>
      <!--分页 end-->
      <!--添加对话框 start-->
      <el-dialog draggable v-model="dialogAddVisible" title="添加角色" width="35%" center>
        <el-form :inline="true" :model="addFormInfo" status-icon label-width="120px">
          <el-form-item label="名称">
            <el-input v-model="addFormInfo.roleName" placeholder="请输入名称"></el-input>
          </el-form-item>
          <el-form-item label="介绍">
            <el-input v-model="addFormInfo.roleDesc" placeholder="请输入介绍"></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogAddVisible = false">取消</el-button>
                    <el-button type="primary" @click="sureHandler">确定</el-button>
                </span>
        </template>
      </el-dialog>
      <!--添加对话框 end-->
      <!--编辑对话框 start-->
      <!--destroy-on-close:每次关闭对话框时直接销毁对话框,没有缓存-->
      <el-dialog draggable destroy-on-close v-model="dialogEditorVisible" title="编辑角色" width="35%" center>
        <el-form :inline="true" :model="editorFormInfo">
          <el-form-item label="名称">
            <el-input v-model="editorFormInfo.roleName" placeholder="请输入名称"></el-input>
          </el-form-item>
          <el-form-item label="介绍">
            <el-input v-model="editorFormInfo.roleDesc" placeholder="请输入介绍"></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogEditorVisible = false">取消</el-button>
                    <el-button type="primary" @click="sureEditorHandler">确定</el-button>
                </span>
        </template>
      </el-dialog>
      <!--编辑对话框 end-->
      <!--分配权限对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogPermissionVisible" title="分配权限" width="35%" center>
        <el-table ref="multipleTable" :data="allPermissions" row-key="rid" @selection-change="onSelectionChange">
          <el-table-column type="selection" :reserve-selection="true"/>
          <el-table-column label="权限名" prop="permissionName"/>
          <el-table-column label="权限url" prop="permissionDesc"/>
        </el-table>
        <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogPermissionVisible = false">取消</el-button>
                    <el-button type="primary" @click="surePermissionHandler">确定</el-button>
                </span>
        </template>
      </el-dialog>
      <!--分配权限对话框 end-->
      <!--详情对话框 start-->
      <el-dialog draggable destroy-on-close v-model="dialogDescVisible" title="角色详情" width="35%" center>
        <el-tag v-for="permission in descInfo.list" :key="permission.pid" size="large" effect="light" class="mx-1" round
                :type="success">
          {{ permission.permissionName }}:[{{ permission.permissionDesc }}]
        </el-tag>
        <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogDescVisible = false">关闭</el-button>
                </span>
        </template>
      </el-dialog>
      <!--详情对话框 end-->
    </div>
  </div>
</template>

<script setup>
import sysAxios from "@/api/sys.js"
import {onMounted, reactive, ref} from "vue";
//初始化角色查询数据
const roleList = reactive({
  list: []
})
//初始化总条数
const totalData = ref(0)
//当前页
const currentPage = ref(1)
//初始化分页显示条数
const defaultPageSize = ref(10)
//添加添加对话框控制器
const dialogAddVisible = ref(false)
//初始化添加对话框状态
const addFormInfo = reactive({
  roleName: "",
  roleDesc: "",
})
//编辑对话框控制器
const dialogEditorVisible = ref(false)
//初始化编辑对话框状态
const editorFormInfo = reactive({
  rid: '',
  roleName: "",
  roleDesc: "",
})
//分配权限对话框控制器
const dialogPermissionVisible = ref(false)
//详情对话框控制器
const dialogDescVisible = ref(false)
//初始化详情对话框
const descInfo = reactive({
  list: []
})
//初始化角色列表
const allPermissions = ref([])
const rolePermissions = ref([])
const multipleTable = ref(null)
let pids = []
const roleId = ref(0)
/**
 * 网路请求:分页查询
 *  */
const http = () => {
  sysAxios.role_search({
    page: currentPage.value,
    size: defaultPageSize.value
  }).then(res => {
    if (res.data.code == 200) {
      roleList.list = res.data.data.records
      totalData.value = res.data.data.total
    }
  })
}
onMounted(() => {
  http()
})
/**
 * 分页
 */
const currentChangeHaddler = (val) => {
  currentPage.value = val
  http()
}
/**
 * 添加对话框弹出事件
 */
const addHander = () => {
  dialogAddVisible.value = true
}
/**
 * 添加对话框 确定事件
 */
const sureHandler = () => {
  sysAxios.role_add({
    roleName: addFormInfo.roleName,
    roleDesc: addFormInfo.roleDesc,
  }).then(res => {
    if (res.data.code == 200) {
      dialogAddVisible.value = false
      http()
    }
  })
}
/**
 * 编辑对话框 弹出事件
 *  */
const handleEdit = (index, row) => {
  dialogEditorVisible.value = true
  sysAxios.role_findById({
    rid: row.rid
  }).then(res => {
    if (res.data.code == 200) {
      editorFormInfo.rid = res.data.data.rid;
      editorFormInfo.roleName = res.data.data.roleName;
      editorFormInfo.roleDesc = res.data.data.roleDesc;
    }
  })

}
/**
 * 编辑对话框 确定事件
 */
const sureEditorHandler = () => {
  sysAxios.role_update({
    rid: editorFormInfo.rid,
    roleName: editorFormInfo.roleName,
    roleDesc: editorFormInfo.roleDesc,
  }).then(res => {
    if (res.data.code == 200) {
      dialogEditorVisible.value = false
      http()
    }
  })
}
/**删除 */
const handleDelete = (index, row) => {
  ElMessageBox.confirm(
      '确定删除此角色么',
      '删除',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    //确认删除
    sysAxios.role_delete({
      rid: row.rid
    }).then(res => {
      if (res.data.code == 200) {
        ElMessage({
          type: 'success',
          message: "删除成功!!!",
        })
        http()
      } else {
        ElMessage({
          type: 'error',
          message: res.data.message,
        })
      }
    })
  }).catch(error => {
    ElMessage({
      type: 'info',
      message: "取消删除",
    })
  })


}
/**
 * 分配权限对话框 弹出事件
 */
const handlePermission = (index, row) => {
  dialogPermissionVisible.value = true
  sysAxios.role_findById({
    rid: row.rid
  }).then(res => {
    if (res.data.code == 200) {
      rolePermissions.value = res.data.data.permissions
      roleId.value = res.data.data.rid
    }
  })
  sysAxios.permission_findAll().then(res => {
    if (res.data.code == 200) {
      allPermissions.value = res.data.data
    }
  })


}
const onSelectionChange = (keys) => {
  pids = []
  keys.map(item => {
    pids.push(item.pid)
  })
}
/**
 * 分配角色对话框 确定事件
 */
const surePermissionHandler = () => {
  dialogPermissionVisible.value = false;
  sysAxios.role_updatePermission({
    rid: roleId.value,
    pids: pids
  }).then(res => {
    if (res.data.code == 200) {
      http()
    }
  })

}
/**
 * 详情对话框弹出事件
 */
const handleDesc = (index, row) => {
  dialogDescVisible.value = true
  sysAxios.role_findById({
    rid: row.rid
  }).then(res => {
    if (res.data.code == 200) {
      descInfo.list = res.data.data.permissions
    }
  })

}
</script>
<style scoped lang="scss">
.data-container {
  background-image: -webkit-gradient(linear, 0% 0%, 100% 100%,color-stop(0, rgb(50, 153, 152)),color-stop(1, rgb(108, 201, 156)));
  background-image: -webkit-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: linear-gradient(to bottom right,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: -ms-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  height: 800px;


  .data-header {
    padding: 20px;

    .input {
      width: 300px;
    }
  }

  .data-table {
    padding: 20px;
  }

  .page {
    position: fixed;
    right: 10px;
    bottom: 10px;
  }
}

</style>

权限页面

<template>
  <div class="data-container">
    <!--添加 start-->
    <div class="data-header">
      <el-input class="input" @keyup.enter="searchHandle" v-model="searchInfo" size="large"
                placeholder="请输入关键字"></el-input>
      <el-button @click="searchHandle" class="button" size="large" type="primary" plain>搜索</el-button>
      <el-button round @click="addHander" size="large" type="primary">
        <el-icon>
          <DocumentAdd/>
        </el-icon>
        <span>新增</span>
      </el-button>
    </div>
    <!--添加 end-->
    <!--表格数据展示 start-->
    <div class="data-table">
      <el-table :data="dataList.list" style="width: 700px;">
        <el-table-column label="权限名" prop="permissionName" align="center"></el-table-column>
        <el-table-column label="权限详情" prop="permissionDesc" align="center"></el-table-column>
        <el-table-column label="操作" align="center" width="220">
          <template #default="scope">
            <el-button size="small" type="primary" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
            <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!--分页 start-->
      <div class="page">
        <el-pagination background
                       layout="prev,pager,next,jumper"
                       :default-page-size="defaultPageSize"
                       :total="totalData"
                       @current-change="currentChangeHaddler"></el-pagination>
      </div>
      <!--分页 end-->
    </div>
    <!--表格数据展示 end-->
    <!--添加对话框 start-->
    <el-dialog draggable destroy-on-close v-model="dialogAddVisible" title="添加" width="35%" center>
      <el-form inline :model="addFormInfo" label-width="150px">
        <el-form-item label="权限名">
          <el-input v-model="addFormInfo.permissionName"></el-input>
        </el-form-item>
        <el-form-item label="权限详情">
          <el-input v-model="addFormInfo.permissionDesc"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogAddVisible = false">取消</el-button>
                <el-button type="primary" @click="sureHandler">确定</el-button>
            </span>
      </template>
    </el-dialog>
    <!--添加对话框 end-->
    <!--编辑对话框 start-->
    <!--destroy-on-close:每次关闭对话框时直接销毁对话框,没有缓存-->
    <el-dialog
        draggable
        destroy-on-close
        v-model="dialogEditorVisible"
        title="编辑"
        width="35%"
        center>
      <el-form inline :model="editorFormInfo" label-width="150px">
        <el-form-item label="权限名">
          <el-input v-model="editorFormInfo.permissionName"></el-input>
        </el-form-item>
        <el-form-item label="权限详情">
          <el-input v-model="editorFormInfo.permissionDesc"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogEditorVisible = false">取消</el-button>
                <el-button type="primary" @click="sureEditorHandler">确定</el-button>
            </span>
      </template>
    </el-dialog>
    <!--编辑对话框 end-->
  </div>
</template>

<script setup>
import sysAxios from "@/api/sys.js"
import {onMounted, reactive, ref} from "vue";
import {ElMessage} from "element-plus";
//初始化分页查询数据
const dataList = reactive({
  list: []
})
//初始化总条数
const totalData = ref(0)
//当前页
const currentPage = ref(1)
//初始化分页显示条数
const defaultPageSize = ref(10)
//搜索初始化状态
const searchInfo = ref("")
//添加添加对话框控制器
const dialogAddVisible = ref(false)
//初始化添加对话框状态
const addFormInfo = reactive({
  permissionName: "",
  permissionDesc: "",
})
//编辑对话框控制器
const dialogEditorVisible = ref(false)
//初始化编辑对话框状态
const editorFormInfo = reactive({
  pid: '',
  permissionName: "",
  permissionDesc: "",
})
/**
 * 网路请求:分页查询
 *  */
const http = () => {
  sysAxios.permission_search({
    search: searchInfo.value,
    page: currentPage.value,
    size: defaultPageSize.value
  }).then(res => {
    if (res.data.code == 200) {
      dataList.list = res.data.data.records
      totalData.value = res.data.data.total
    } else {
      ElMessage.error(res.data.message)
    }

  })
}
onMounted(() => {
  http()
})
/**
 * 分页
 */
const currentChangeHaddler = (nowPage) => {
  currentPage.value = nowPage
  http()
}
/**
 * 搜索按钮
 */
const searchHandle = () => {
  http()
}
/**
 * 添加对话框弹出事件
 */
const addHander = () => {
  dialogAddVisible.value = true
}
/**
 * 添加对话框 确定事件
 */
const sureHandler = () => {
  sysAxios.permission_add({
    permissionName: addFormInfo.permissionName,
    permissionDesc: addFormInfo.permissionDesc,
  }).then(res => {
    if (res.data.code == 200) {
      dialogAddVisible.value = false
      http()
    } else {
      ElMessage.error(res.data.message)
    }
  })
}
/**
 * 编辑对话框 弹出事件
 *  */
const handleEdit = (index, row) => {

  dialogEditorVisible.value = true
  sysAxios.permission_findById({
    pid: row.pid
  }).then(res => {
    if (res.data.code == 200) {
      editorFormInfo.pid = res.data.data.pid;
      editorFormInfo.permissionName = res.data.data.permissionName;
      editorFormInfo.permissionDesc = res.data.data.permissionDesc;
    } else {
      ElMessage.error(res.data.data.message)
    }
  })

}
/**
 * 编辑对话框 确定事件
 */
const sureEditorHandler = () => {
  sysAxios.permission_update({
    pid: editorFormInfo.pid,
    permissionName: editorFormInfo.permissionName,
    permissionDesc: editorFormInfo.permissionDesc,
  }).then(res => {
    if (res.data.code == 200) {
      dialogEditorVisible.value = false
      http()
    } else {
      ElMessage.error(res.data.message)
    }
  })
}
/**删除 */
const handleDelete = (index, row) => {
  ElMessageBox.confirm(
      '确定删除么',
      '删除',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    //确认删除
    sysAxios.permission_delete({
      pid: row.pid
    }).then(res => {
      if (res.data.code == 200) {
        ElMessage({
          type: 'success',
          message: "删除成功!!!",
        })
        http()
      } else {
        ElMessage({
          type: 'error',
          message: res.data.message,
        })
      }
    })
  }).catch(error => {
    ElMessage({
      type: 'info',
      message: "取消删除",
    })
  })


}
</script>
<style scoped lang="scss">
.data-container {
  background-image: -webkit-gradient(linear, 0% 0%, 100% 100%,color-stop(0, rgb(50, 153, 152)),color-stop(1, rgb(108, 201, 156)));
  background-image: -webkit-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: linear-gradient(to bottom right,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  background-image: -ms-linear-gradient(top left,rgb(50, 153, 152) 0%,rgb(108, 201, 156) 100%);
  height: 800px;


  .data-header {
    padding: 20px;

    .input {
      width: 300px;
    }
  }

  .data-table {
    padding: 20px;
  }

  .page {
    position: fixed;
    right: 10px;
    bottom: 10px;
  }
}


</style>

2.路由编写

import {createRouter, createWebHashHistory} from "vue-router"
import Layout from '@/views/Layout.vue'
import {useDataStore} from "@/stores/dataStore";

const routes = [
    {
        //最常见404匹配规则:没有找到对应路径地址
        path: "/:pathMath(.*)*",
        name: "404",
        component: () => import("../views/home/404.vue"),
        meta: {
            key: "404"
        }
    },
    {
        path: "/home/login",
        name: "login",
        component: () => import("../views/home/Login.vue"),
    },
    {
        //顶级路由
        path: '/',
        name: 'layout',
        redirect: '/home/login',
        component: Layout,
        children: [

            {
                path: "/sys/admin",
                name: 'sys_admin',
                component: () => import("../views/sys/Admin.vue"),
                meta: {
                    requiresAuth: true,
                    key: "管理员"
                }
            },
            {
                path: "/sys/role",
                name: 'sys_role',
                component: () => import("../views/sys/Role.vue"),
                meta: {
                    requiresAuth: true,
                    key: "角色"
                }
            },
            {
                path: "/sys/permission",
                name: 'sys_permission',
                component: () => import("../views/sys/Permission.vue"),
                meta: {
                    requiresAuth: true,
                    key: "权限"
                }
            },
            {
                path: "/book/book",
                name: 'book_book',
                component: () => import("../views/book/Book.vue"),
                meta: {
                    requiresAuth: true,
                    key: "图书"
                }
            },
            {
                path: "/book/bookCategory",
                name: 'book_bookCategory',
                component: () => import("../views/book/BookCategory.vue"),
                meta: {
                    requiresAuth: true,
                    key: "图书分类"
                }
            },
            {
                path: "/book/bookPublisher",
                name: 'book_bookPublisher',
                component: () => import("../views/book/BookPublisher.vue"),
                meta: {
                    requiresAuth: true,
                    key: "出版社"
                }
            },
        ]
    },
]

const router = createRouter({
    routes,
    history: createWebHashHistory(import.meta.env.BASE_URL)
})
//前置导航守卫
// router.beforeEach((to, from, next) => {
//         if (to.meta.requiresAuth) {
//             const dataStore = useDataStore()
//             if (!dataStore.isLogin) {
//                 next({path: "/home/login"})
//             } else {
//                 next()
//             }
//         } else {
//             next()
//         }
//     }
// )


//后置导航
router.afterEach((to, from) => {
    //存储路径(解决页面刷新后 菜单高亮与面包屑不匹配)
    localStorage.setItem("active", to.path)
    //若meta中的key存在,则修改当前面包屑
    if (to.meta.key) {
        const dataStore = useDataStore()
        dataStore.breadcrumb = to.meta.key
    }
})
export default router;

3.侧边栏编写

<template>
  <div class="slider-navs" :style="{ width: dataStore.isCollapse ? '64px' : '210px' }">
    <div v-if="dataStore.toggleStore" class="logo">{{ dataStore.isCollapse ? '图书馆' : "图书馆管理系统后台" }}</div>
    <el-menu
        unique-opened
        router
        :default-active="active"
        :collapse="dataStore.isCollapse"
    >
      <el-sub-menu index="1" v-if="dataStore.islogin">
        <template #title>
          <el-icon>
            <Tools/>
          </el-icon>
          <span style="font-size: 17px;">系统管理</span>
        </template>
        <el-menu-item index="/sys/admin">
          <van-icon
              name="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA7VJREFUWEfFlk2IFEcUx/+ve1eRRAyKJOQDFA+aNURwBYMhsCYz3VOjXgIJHowf+HVQknV6VgkJZCUQlJme7MWLRlZFL3pQgm5N92zYwSUxB41oohCiIYQEBU+7Ioi69UL3zsjMbs90dS5b0Kf61//96r3qV0WY4UEzHB+JAfrs8mYFToGxAsDbAK4Fn0F0uuBlRpNuSBtgzwfDC2abTw8TsL1NkM9dXxxKAqENkLOkJCATa878jVvJfhGrqwm0AHJ2eTMxn9Q0fUQw3in69m86ei0Ax5I/AlijY1jTFFxf7NfRxwLs6r7aOXfBg4cAZusYhhqC53oivlyhNGb02uWVJnNw0pOMcdcX83QWxALkLO8Ngvpbx6xBc8v1xVs6a2IBAhPHKv8L8Ks6hrUSnHA9sU1HrwkgvwewQccw1DDtdCuZ73T0WgB569JqhvGzjiEIP7ieSGlpdQ5h3Shnya8I6G9nzMADMHaUKiLImNbQykDdqTd18U2TzG9BsKe6M3DcfKK+LFTX3deKXBMlAqgbO2Koi5/RchC6DMZN6uCbBZm9myRwXZsIoC878go/ebyMDZ4DRXPYVGTAHJsAxg1+Oj5BnWMdTONF336kCxMLkLe895nUZ2B0A3itjfE11xer8inZUxwW1bquTwwtaZedtgCOLY8Fh0pzN7uJkGLGRwz0l3xxMJeWW4kwCELLvtASIG/Ls4GZRvB/iHCFFB9hoq8ZeG/aGkbVrYi1UV6RAI4tT4HxSVxwBn4p+aI7/EUJC8HYAuDFFutypHAdJr1Q9DKXWh7CnDWUJ1AhLnjY8GD0Ayq40Z73hyD9iujogJe5t88uzzegDoCp8Wq+0qE6xOHh9Fjg0ZSBvOUtZqg/dYKHGoKnQHsN5j8mgXC+5IsPp653rKH7AL0cahgflyriXGQGnHR5B4iPaQNMBg13H2SBQANFP7NvGkBajoDQUz+cjfNNGXBqwiQAgLGKodZPloEqrp+xpmdA3ghf0BGHsRnAkncALNEFCHZEhEVgbH2eUoW1jX0g6AtsYKTB84zri03RJbDk40RPL+ZBBBcQNR2y38E4SIx7bKALwKcAljYA7HZ9cTQawJaDjbuJywQBo8xchsIF6qR5rPBT5BpGdcLEtoGy+GvqfFMJcmmZJcIRAIvigjfN12rrWDJ4ii8P5xhVUNi+50bVvmUf6O05/5LZOWsjk/EuMV5vC0K4DaJfYajLrszerjew+q8WPmTYOMTAyVJFnIjyir2MEmXif4hnHOA/TxJTMNnGhbcAAAAASUVORK5CYII="></van-icon>&nbsp;
          <span>用户</span>
        </el-menu-item>
        <el-menu-item index="/sys/role">
          <van-icon
              name="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA81JREFUWEfVV11oHFUYPefubuoGCtGSH4rFPkQfIkoftNEXKYVChRYptSvWzZJkY/rQhgoVf0A0Cv6hUg1tEbU7skmlNCBFCUKg+KAIWvJSTKD0RWiMu5tqopUq7s49ssE2Ozuzu7NBKJ2Xebjnfud857v3+2aIm/zwJvPj1hTQ5TibEUGPJR40whyMZnPJwbm1uNm0Ax3jziuURv1knFK0lC7sfzrfjJCmBNQmv06pCy5bdl7p6/slrIiaAjrGT+6FuJXAVkIzRUWPRo37A4SN9YKLXHGHUhl3D4BpGc0UkunpoH2BAgIy/YhATsDLDTMjFmT1OMnvKrEEE7nUwGT1fp+AzonMLlh8WQkUcYDCdgBPNBQAIJ8aZGc2Iw+WWHARe6C6PH4BWecLQLs9AoCdIA5TeDSMAMr2iub7amy5PIW+gVe9zlShOrOZHIBOD0gYkUE3hMNhBMT+sRuKLebXALs/z6UG99YXMJ752X/QdJoy34g63lCAcNk1sd6Iigt+rKbyqfSuBg74SwDhsgzTlAJPspdIU4Q5IWgqoFd8nE8NDNcVUOuuSzwCYx+j+Eh9F/ikqDspvLOmM9B+yrnbuLoA4DZPAOGSgPdIfFhLgMgZAC9SOgugtQr3t43w/sWnBi7VdaC8WMsFAq9Z0tZoxbMulYpYnAWxKUz2Kw2rVjaBIoTf3ZI2RWJ8F4CnlgAegvQ8yD1hyesK6Mw6RwE94w+mLaZkb1ck8nXlmsvYRoPScJA7ot1T6Bsql8X3+BxoH/+k28A8C+GAp1ZEFlafikwCGAyI9SNgX4d4H8j9ADavYlQizUvuumvHFxMH/ww8A+2Os8VElYZwqCp4xpKTRuoP1YqlaWvNIcbYQWtHqvYswmIs1mpPzCeGfrtRgo7xzDCFtwG0XScn8C3AN0UNQfDVtWFDAiZFvE+LO2BwDMJdN/aUBxb0XKEvfYpdWWefoDNVzeQtgOVrlADQFYKsNkR6wzUtxyIqfgZgm8d+2d7y1CqPzYcrMs8C+CnU6A2tjLMsKaEoyq18VYR0zjM2BVyMQEkLng8dOyyQ+GAFWjXQPAJIvmCte5U0jYdOWOJV3JKoEYoTvlvQlT3ZD3A3wNOW6AnudM0z+nYIO0QeMdCsjPkqn+w/5+sDXROZHlmeAXTv/0BZGeK8vfrX9sWDNfpAJbLNcdpaou42WO4guA9EexNi/gCwBGD5v3fzH6XVZN1jY+uurF8fj0aj8Ygpxlk0cRo3Dpo4DK+5RSy3tJaW5ufmlzE6apsQe4v+mjWTYSNsU39GjYKtZf1fR6mSeBOA3XoAAAAASUVORK5CYII="></van-icon>&nbsp;
          <span>角色</span>
        </el-menu-item>
        <el-menu-item index="/sys/permission">
          <van-icon
              name="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABHJJREFUWEe9l21sU1UYx//P7dZuaap7E9zSwVRw6kQhalxvR5ifVCKSqWMooS06X6IzUYdsMSoGZyKiQZMpi0LmENAwaN0IwS+6BraRSYCMF6G3i5ME6zpGW9jmNtj6mHtHoe06e1vE8605z/P//845z3N6LiHJUerozbiE8UYEgx7txHiNc1nRUDJSlEySyeF6iRhVAM1T8glHGdxwsKzw60T1VAMUO04VaFhTHgRXEOiBWEYMPkwk7BFY+L79qTskNTBXAcwOaR0zbmfAQ0TDysI4mMaAkUFGAkrVCIZiGHAS4zwRBhgYAHgmQLmDo0P60Usj7W6b+b3JzbsyRLvrM4DeSsQk0djB0SHn2OXRUiZ87raIb0YAmOzSJgJeSVRUbXzI/ErNbJEsYmXUDri3AbxCrWAicRHmk4k7JatYEbkDu6VWIixJRDgUq6dhPC38gFw6C4EZXtyKzuBCnOYixDCXD36vZBGfiD6CtkQLTRYoom58mLImJndnX16gVrshI3qSCU63RXzkugEMNIg6TTXy6cwUgMPe7EFcOGd4J3szRkgfMf+fAYjCfqzWfKSIHwqa0Bosgwf5eGGgOpAdOKmsfK9+OX7UW24MQIXwHSo02xTxb4JV2DexRDnztLG/Sj8dmKxnKfU+bMj8+MYDtATLUT+8XOnzOy8fw9v+2v8XoKMvL9A2bsrQ8Qhmj/fg/rEuBcCZvhjbDVVqdsC1j0CPqW3D8Orv8Br9Of7jmbFy12Y1wJMyK3KK8ZNkEx+PuoikZgDPqAEIP/v2/nzfLb5jWdF5sul2w6tKDUxpQ2CX2yqWRwI4pEYwbGoA6lOeRx79iQ7vLH+Ov1tZeat+BW4KBmDgizikLcFp3XwMkyG2HNG3ksW0KgLAbHfVM+g1NQD21Eexv/8230zfEWXlsvkefQK3OONLySYqhRH2byjJvVIzHcBCwYmlwi5kkg8n+g2+Gb6jWV6NEV1pixIzB8Cg9W6rSWmTawC7JRsIjdMBVApfYbGmBQe8+X7dhbOZq3Mm+z+ZQYRKl0XcEgEg/xDtUi+AgnDRu+g3lGma8RB1IlTtZ1Lmoi7ri2S85ZyLQ6PaXM/LD/49BcDscL/BzBvDlUMV39Fn9OcEjmf+nP4kerV3o0u3KFmAeskqvh5KjngTivaeGQD/CvDsUIAMYDz3SyAncDKjW1eM+pvfT9YYIBogmihxrSxxxQSYPAbXiwBdfd3OH245kT74x73y3CntAvSk3pM0ADPXuG3mT8IFYr6KRbu0A8CzMR8TSdujWbKKy6LTp32Wz9txpE3+Y0neLyIzpvmUIgxPMe7sTNePYDMDz10fBDVJVtO0N2zcD5PCrQfXMPP6JCDOM9G7boup4d9y4wLIyYVNHUuZUQuiYpUgdgiaddLKh7vjxasCUEQ+aEuZW6CtJSL5Co185F1z8RBznctm3hTPeNo2jJc4p6lzARFVE0d9QxC2jk8Idb+vKnbH04jbhmoE5jQeKCWNZq3A0IGw0WUR5fdEwuMfvbLVMJxAex8AAAAASUVORK5CYII="></van-icon>&nbsp;
          <span>权限</span>
        </el-menu-item>
      </el-sub-menu>

      <el-sub-menu index="2" v-if="dataStore.islogin">
        <template #title>
          <el-icon>
            <Notebook/>
          </el-icon>
          <span style="font-size: 17px;">图书管理</span>
        </template>
        <el-menu-item index="/book/book">
          <van-icon
              name="data:image/png;base64,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"></van-icon>
          &nbsp;<span>图书</span>
        </el-menu-item>
        <el-menu-item index="/book/bookCategory">
          <van-icon
              name="data:image/png;base64,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"></van-icon>
          &nbsp;<span>分类</span>
        </el-menu-item>
        <el-menu-item index="/book/bookPublisher">
          <van-icon
              name="data:image/png;base64,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"></van-icon>
          &nbsp;<span>出版社</span>
        </el-menu-item>
      </el-sub-menu>


    </el-menu>
  </div>
</template>
<script setup>

import {ref} from 'vue';
import {useDataStore} from "@/stores/dataStore"

const dataStore = useDataStore()

const active = ref("/")
if (localStorage.getItem("active")) {
  active.value = localStorage.getItem("active")
}
</script>
<style scoped>
.slider-navs {
  background-color: #ffffff;
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  transition: 0.3s ease-in;
}

.logo {
  background-color: #ffffff;
  width: 100%;
  height: 60px;
  font-size: 20px;
  font-weight: 900;
  text-align: center;
  line-height: 60px;
  cursor: pointer;
}


/**el-menu默认样式 */
.el-menu {
  border-right: 0 !important; /*去掉element menu的右侧边框 */
  border-radius: 5px !important;
}

.el-menu-item {
  border-radius: 15px !important;
}

.el-menu-item.is-active {
  font-weight: 900 !important;
  font-size: 18px !important;
  color: #06b4c0 !important;
}

</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/470909.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习 - 选择模型

接着这一篇博客做进一步说明&#xff1a; 机器学习 - 准备数据 PyTorch moduleExplaintorch.nnContains all of the building blocks for computational graphs (essentially a series of computations executed in a particular way). nn 模块为用户提供了丰富的神经网络组件…

【理解机器学习算法】之分类问题的模型评估(ROC-AUC)

ROC曲线&#xff08;接收者操作特性曲线&#xff09;和AUC&#xff08;曲线下面积&#xff09;是在不同阈值设置下&#xff0c;用于分类问题的性能度量工具。下面是它们所代表的含义以及使用方法&#xff1a; ROC曲线 代表含义&#xff1a;ROC曲线是一个图形化的表示&#xf…

反射 Reflection

反射 反射的概念 反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量&#xff0c;构造器&#xff0c;成员方法等等)&#xff0c;并能操作对象的属性及方法。反射在设计模式和框架底层都会用到加载完类之后&#xff0c;在堆中就产生了一个Class类型…

SurfaceFlinger实战dump获取单个Layer图像方案学员改进成果

背景&#xff1a; hi&#xff0c;粉丝朋友们&#xff1a; 在马哥课程的实战实现dump单个图层的发布后&#xff0c;很多学员朋友就纷纷享马哥要了相关源码&#xff0c;相关的链接请参考这里&#xff1a; https://blog.csdn.net/learnframework/article/details/136323076 学员…

前端项目,个人笔记(三)【Vue-cli - api封装-axios使用举例】

目录 前言 1、axios配置与测试 1.1、配置 1.2、测试 2、使用axios案例-渲染header 3、Pinia优化重复请求 3.1、为什么&#xff1f; 3.2、使用Pinia优化代码步骤 步骤一&#xff1a;在main.js中创建 Pinia 实例&#xff0c;并将其作为插件添加到 Vue 应用中 步骤二&am…

【QT+QGIS跨平台编译】之八十四:【QGIS_Gui跨平台编译】—【错误处理:未实例化QgsMapLayer - QgsHighlight】

文章目录 一、未实例化QgsMapLayer二、错误处理 一、未实例化QgsMapLayer 报错信息&#xff1a; 二、错误处理 第31行修改为&#xff1a; #include "qgsmaplayer.h"

【Leetcode】2684. 矩阵中移动的最大次数

文章目录 题目思路代码结果 题目 题目链接&#x1f517; 给你一个下标从 0 开始、大小为 m x n 的矩阵 grid &#xff0c;矩阵由若干 正 整数组成。 你可以从矩阵第一列中的 任一 单元格出发&#xff0c;按以下方式遍历 grid &#xff1a; 从单元格 (row, col) 可以移动到 (…

Flutter-仿携程首页类型切换

效果 唠叨 闲来无事&#xff0c;不小心下载了携程app&#xff0c;还幻想可以去旅游一番&#xff0c;奈何自己运气不好&#xff0c;自从高考时第一次吹空调导致自己拉肚子考试&#xff0c;物理&#xff0c;数学考了一半就交卷&#xff0c;英语2B铅笔除了问题&#xff0c;导致原…

MySQL数据库的基本概念与安装

目录 引言 一、数据库的基本概念 &#xff08;一&#xff09;数据、表与数据库 1.数据(Data) 2.表 3.数据库 &#xff08;二&#xff09;数据库管理系统 &#xff08;三&#xff09;数据库系统 二、数据库的发展 三、主流数据库的介绍 &#xff08;一&#xff09;关…

一命通关深度优先遍历

前言 在解释深度优先遍历之前&#xff0c;我想先让大家去思考一个可能从未想过的问题&#xff1a; 为什么我们在学习基础数据结构的时候&#xff0c;都没有出现dfs和bfs这两个词&#xff0c;而在学习二叉树和图的时候&#xff0c;突然蹦出了深度优先遍历和广度优先遍历这两个…

力扣 第 126 场双周赛 解题报告 | 珂学家 | 贡献法思维场 + 贪心构造 + 0-1背包

前言 整体评价 T3是道好题&#xff0c;一开始思路偏了往按字母前缀和和DP去想了&#xff0c;但是感觉很难下手&#xff0c;后来发现从贡献的角度&#xff0c;其实和位子无关系&#xff0c;只需要贪心即可。 T4也是一道贡献思路题&#xff0c;理清核心的点&#xff0c;就能简单…

Vue项目使用process.env关键字及Vue.config.js配置解决前端跨域问题

1.process.env 是Node.js 中的一个环境 1.打开命令行查看环境: 2.process.env与Vue CLI 项目 Vue Cli 有以下三种运行模式 development 模式用于 vue-cli-service serve test 模式用于 vue-cli-service test:unit production 模式用于 vue-cli-service build 和 vue-cli-se…

每日一练:LeeCode-167. 两数之和 II - 输入有序数组【双指针】

给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 < index2 < numbers.…

如何本地部署1Panel面板

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

YOLOv8改进 | 主干篇 | 利用图像分割网络UNetV2改善图像分割检测性能(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用图像分割网络UNetV2的主干来改进我们的YOLOv8分割模型&#xff08;本文的内容虽然YOLOv8所有的功能的用户都能使用&#xff0c;但是还是建议分割的用户使用&#xff09;&#xff0c;U-Net v2 旨在改进医学图像分割的性能&#xff…

C# 方法(函数)

文章目录 C# 方法&#xff08;函数&#xff09;简单示例程序代码运行效果 值传递和引用传递示例程序 运行效果按输出传递参数运行结果 C# 方法&#xff08;函数&#xff09; 简单示例 程序代码 访问的限制符 using System; using System.Collections.Generic; using Syste…

GO-初识包管理

初识包管理&#xff0c;知道项目中文件和文件夹之间的关系 输出&#xff0c;代码&#xff0c;在go编译器运行时会显示在屏幕中 初识数据类型 整型&#xff0c;数字。例如&#xff1a;1、2、3、4 字符串类型&#xff0c;表示文本信息的。例如:“张三”“李四” 布尔类型&#x…

Day51-Nginx多实例知识与大厂企业级实战

Day51-Nginx多实例知识与大厂企业级实战 1. 什么是nginx多实例&#xff1f;2. 为什么要用多实例&#xff1f;3. 大厂数百个业务项目&#xff0c;如何来管理&#xff1f;4. 大厂上百项目web分用户解决方案4.1 编译nginx环境实践&#xff1a;4.2 zuma实例(利用普通用户权限将不同…

Arduino 开发 野火ESP8266模块的串口通信

一、硬件连接 Arduino ESP8266 串口通信是一个常见的任务&#xff0c;允许通过串行接口与其他设备或计算机进行通信。 连接硬件&#xff1a;将野火ESP8266模块通过USB连接到电脑。注意电源为3.3V。 选择开发板和端口&#xff0c;在Arduino IDE中&#xff0c;选择ESP8266开发板…

图神经网络实战(5)——常用图数据集

图神经网络实战&#xff08;5&#xff09;——常用图数据集 0. 前言0. 图数据集介绍2. Cora 数据集3. Facebook Page-Page 数据集小结系列链接 0. 前言 图数据集往往比单纯的连接集合更丰富&#xff0c;节点和边也可以具有表示分数、颜色、单词等的特征。在输入数据中包含这些…