11. RBAC权限管理从零到一实现

一、后端功能开源

1. 项目结构

代码均已放到system包下
在这里插入图片描述

2. sql脚本

脚本放与resources/sql目录下cloud.sql也已提交

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80036 (8.0.36-0ubuntu0.22.04.1)
 Source Host           : localhost:3306
 Source Schema         : cloud

 Target Server Type    : MySQL
 Target Server Version : 80036 (8.0.36-0ubuntu0.22.04.1)
 File Encoding         : 65001

 Date: 02/06/2024 18:30:47
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_file
-- ----------------------------
DROP TABLE IF EXISTS `sys_file`;
CREATE TABLE `sys_file` (
  `file_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL,
  `file_type` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '文件类型(1:WORD,2:EXCEL,3:PPT,4:PDF,5:TXT)',
  `file_size` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '文件大小',
  `file_location` varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '文件地址',
  `store_name` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '文件存储名',
  `show_name` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '文件显示名',
  `remark` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '修改人',
  `del_flag` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL COMMENT '删除标志',
  PRIMARY KEY (`file_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin ROW_FORMAT=DYNAMIC COMMENT='附件';

-- ----------------------------
-- Records of sys_file
-- ----------------------------
BEGIN;
COMMIT;

-- ----------------------------
-- Table structure for sys_module
-- ----------------------------
DROP TABLE IF EXISTS `sys_module`;
CREATE TABLE `sys_module` (
  `module_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '菜单ID',
  `module_name` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '菜单名',
  `module_url` varchar(258) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '菜单链接',
  `module_code` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '菜单编码',
  `sort_code` int DEFAULT NULL COMMENT '排序码',
  `parent_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '父菜单ID',
  `parent_name` varchar(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '父菜单名',
  PRIMARY KEY (`module_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='菜单资源表';

-- ----------------------------
-- Records of sys_module
-- ----------------------------
BEGIN;
INSERT INTO `sys_module` (`module_id`, `module_name`, `module_url`, `module_code`, `sort_code`, `parent_id`, `parent_name`) VALUES ('001', '算法小生', NULL, NULL, 1, '-1', NULL);
INSERT INTO `sys_module` (`module_id`, `module_name`, `module_url`, `module_code`, `sort_code`, `parent_id`, `parent_name`) VALUES ('1797210739841884160', '机构管理', '/org', 'org', 1, '001', '算法小生');
INSERT INTO `sys_module` (`module_id`, `module_name`, `module_url`, `module_code`, `sort_code`, `parent_id`, `parent_name`) VALUES ('1797210846234599424', '菜单管理', '/module', 'module', 2, '001', '算法小生');
INSERT INTO `sys_module` (`module_id`, `module_name`, `module_url`, `module_code`, `sort_code`, `parent_id`, `parent_name`) VALUES ('1797210920687689728', '用户管理', '/user', 'user', 3, '001', '算法小生');
INSERT INTO `sys_module` (`module_id`, `module_name`, `module_url`, `module_code`, `sort_code`, `parent_id`, `parent_name`) VALUES ('1797211013025292288', '菜单管理', '/role', 'role', 4, '001', '算法小生');
COMMIT;

-- ----------------------------
-- Table structure for sys_org
-- ----------------------------
DROP TABLE IF EXISTS `sys_org`;
CREATE TABLE `sys_org` (
  `org_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '机构ID',
  `org_code` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '机构编码',
  `org_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '机构名称',
  `parent_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '父机构ID',
  `parent_code` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '父机构编码',
  `parent_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '父机构名称',
  `sort_code` int DEFAULT NULL COMMENT '排序码',
  `remark` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '备注',
  `del_flag` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '删除标记 0正常 1删除',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`org_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='机构信息表';

-- ----------------------------
-- Records of sys_org
-- ----------------------------
BEGIN;
INSERT INTO `sys_org` (`org_id`, `org_code`, `org_name`, `parent_id`, `parent_code`, `parent_name`, `sort_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES ('1797211572465754112', '001.001', '欢迎关注我', NULL, '001', '算法小生', 1, '', '0', '2024-06-02 18:20:04', 'admin', NULL, NULL);
INSERT INTO `sys_org` (`org_id`, `org_code`, `org_name`, `parent_id`, `parent_code`, `parent_name`, `sort_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES ('4037a607-de4a-11e7-b521-4437e6437701', '001', '算法小生', '-1', '-1', NULL, 1, '', '0', '2023-08-01 13:31:21', 'admin', NULL, NULL);
COMMIT;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `role_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色ID',
  `role_name` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '角色名',
  `remark` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='角色信息表';

-- ----------------------------
-- Records of sys_role
-- ----------------------------
BEGIN;
INSERT INTO `sys_role` (`role_id`, `role_name`, `remark`) VALUES ('admin', '管理员', '各组织机构管理员');
INSERT INTO `sys_role` (`role_id`, `role_name`, `remark`) VALUES ('normal', '一般用户', '普通用户');
INSERT INTO `sys_role` (`role_id`, `role_name`, `remark`) VALUES ('super-admin', '超级管理员', '	系统管理员,全部管理功能,不能删除!!');
COMMIT;

-- ----------------------------
-- Table structure for sys_role_module
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_module`;
CREATE TABLE `sys_role_module` (
  `role_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色ID',
  `module_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '菜单ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='角色与菜单资源关系表';

-- ----------------------------
-- Records of sys_role_module
-- ----------------------------
BEGIN;
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('super-admin', '001');
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('super-admin', '1797210739841884160');
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('super-admin', '1797210846234599424');
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('super-admin', '1797210920687689728');
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('super-admin', '1797211013025292288');
INSERT INTO `sys_role_module` (`role_id`, `module_id`) VALUES ('normal', '1797210846234599424');
COMMIT;

-- ----------------------------
-- Table structure for usr_user
-- ----------------------------
DROP TABLE IF EXISTS `usr_user`;
CREATE TABLE `usr_user` (
  `user_id` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户ID',
  `username` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '用户名',
  `account` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '用户账号',
  `password` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '密码',
  `org_code` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '机构编码',
  `role_id` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '角色ID',
  `department` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '部门',
  `title` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '职称',
  `employee_number` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '工号',
  `state` varchar(2) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '状态 0启用 1禁用',
  `phone_number` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '手机号码',
  `gender` varchar(2) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '性别 0男 1女',
  `del_flag` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '删除标记 0正常 1删除',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='用户信息表';

-- ----------------------------
-- Records of usr_user
-- ----------------------------
BEGIN;
INSERT INTO `usr_user` (`user_id`, `username`, `account`, `password`, `org_code`, `role_id`, `department`, `title`, `employee_number`, `state`, `phone_number`, `gender`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES ('1', '超级管理员', 'admin', 'c4ca4238a0b923820dcc509a6f75849b', '001', 'super-admin', NULL, NULL, NULL, '0', '1234567990', '', '0', '2023-07-24 16:18:36', 'admin', '2024-06-02 18:02:49', 'admin');
INSERT INTO `usr_user` (`user_id`, `username`, `account`, `password`, `org_code`, `role_id`, `department`, `title`, `employee_number`, `state`, `phone_number`, `gender`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES ('1797211702321405952', 'shen', 'shen', 'e10adc3949ba59abbe56e057f20f883e', '001.001', 'normal', NULL, NULL, NULL, '0', NULL, NULL, '0', '2024-06-02 18:20:35', 'admin', '2024-06-02 18:28:03', 'admin');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
3. 核心代码

菜单模块服务实现类

package online.shenjian.cloud.api.system.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import online.shenjian.cloud.client.cloud.dto.system.module.ModuleDto;
import online.shenjian.cloud.client.cloud.dto.system.module.ModuleQueryDto;
import online.shenjian.cloud.client.cloud.dto.system.module.ModuleTreeDto;
import online.shenjian.cloud.api.system.mapper.ModuleMapper;
import online.shenjian.cloud.api.system.mapper.RoleModuleMapper;
import online.shenjian.cloud.api.system.model.Module;
import online.shenjian.cloud.api.system.model.RoleModule;
import online.shenjian.cloud.api.system.service.ModuleService;
import online.shenjian.cloud.api.utils.TreeUtils;
import io.micrometer.common.util.StringUtils;
import online.shenjian.cloud.common.utils.CommonDtoUtils;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author shenjian
 * @since 2023/8/1
 */
@Service
public class ModuleServiceImpl implements ModuleService {

    private ModuleMapper moduleMapper;
    private RoleModuleMapper roleModuleMapper;

    public ModuleServiceImpl(ModuleMapper moduleMapper, RoleModuleMapper roleModuleMapper) {
        this.moduleMapper = moduleMapper;
        this.roleModuleMapper = roleModuleMapper;
    }

    @Override
    public List<ModuleTreeDto> initModuleInfoTree() {
        QueryWrapper<Module> queryWrapper = new QueryWrapper<>();
        List<Module> moduleList = moduleMapper.selectList(queryWrapper);

        List<ModuleTreeDto> moduleTreeDtoList = TreeUtils.listModuleTree(moduleList, "-1");
        return moduleTreeDtoList;
    }

    @Override
    public IPage<ModuleDto> listModule(ModuleQueryDto moduleQueryDto) {
        IPage<Module> page = new Page<>(moduleQueryDto.getPageNumber(), moduleQueryDto.getPageSize());
        QueryWrapper<Module> queryWrapper = new QueryWrapper<>();
        // 查询该组织机构下所有信息
        queryWrapper.likeRight("parent_id", moduleQueryDto.getParentId());
        if (StringUtils.isNotBlank(moduleQueryDto.getModuleName())) {
            queryWrapper.like("module_name", moduleQueryDto.getModuleName());
        }
        queryWrapper.orderByAsc("sort_code");
        IPage<Module> iPage = moduleMapper.selectPage(page, queryWrapper);
        List<ModuleDto> patientDtoList = CommonDtoUtils.transformList(iPage.getRecords(), ModuleDto.class);
        IPage<ModuleDto> resultPage = new Page<>(iPage.getCurrent(), iPage.getSize(), iPage.getTotal());
        resultPage.setRecords(patientDtoList);
        return resultPage;
    }

    @Override
    public Boolean saveModule(ModuleDto moduleDto) {
        Module module = CommonDtoUtils.transform(moduleDto, Module.class);
        module.setModuleId(IdUtil.getSnowflakeNextIdStr());
        moduleMapper.insert(module);
        return true;
    }

    @Override
    public void deleteModule(String moduleId) {
        if (StringUtils.isBlank(moduleId)) {
            return ;
        }
        Module module = new Module();
        module.setModuleId(moduleId);
        moduleMapper.deleteById(module);

        QueryWrapper<RoleModule> roleModuleQueryWrapper = new QueryWrapper<>();
        roleModuleQueryWrapper.eq("module_id", moduleId);
        roleModuleMapper.delete(roleModuleQueryWrapper);
    }

    @Override
    public Boolean updateModule(ModuleDto moduleDto) {
        if (StringUtils.isBlank(moduleDto.getModuleId())) {
            return false;
        }
        Module module = CommonDtoUtils.transform(moduleDto, Module.class);
        moduleMapper.updateById(module);
        return true;
    }
}
package online.shenjian.cloud.api.system.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import online.shenjian.cloud.client.cloud.dto.system.module.ModuleDto;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 菜单信息加强版Mapper
 *
 * @author shenjian
 * @since 2023/8/10
 */
@Repository
public interface ModulePlusMapper extends BaseMapper<ModuleDto> {

    String querySql = "SELECT " +
            "               m.module_name, m.module_code, rm.role_id " +
            "          FROM " +
            "               sys_module AS m " +
            "               LEFT JOIN sys_role_module AS rm ON m.module_id = rm.module_id";
    String wrapperSql = "SELECT * FROM ( " + querySql + " ) AS q ${ew.customSqlSegment}";

    @Select(wrapperSql)
    List<ModuleDto> list(@Param("ew") Wrapper queryWrapper);
}
4. 代码访问链接
https://github.com/SJshenjian/cloud
https://gitee.com/SJshenjian/cloud

喜欢的话麻烦点个star ~ 或 在公众号 算法小生打个赏~

下一篇文章将开源RBAC相关的管理页面

二、前端页面开源

1. 效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 核心代码

vue.config.js

let ip = process.env.VUE_APP_IP
let outputDir = process.env.OUTPUT_DIR
let assetsDir = process.env.ASSETS_DIR
let indexPath = process.env.INDEX_PATH

const version = new Date().getTime();

const CompressionWebpackPlugin = require('compression-webpack-plugin')
const TerserWebpackPlugin = require("terser-webpack-plugin");
const isProduction = process.env.NODE_ENV === 'production'
const path = require('path')
const webpack = require("webpack");

module.exports = {
  lintOnSave: false, // 关闭eslint
  publicPath:'./',
  outputDir: outputDir, // 输出文件目录
  assetsDir: assetsDir,
  indexPath: indexPath,
  // 去除生产环境的productionSourceMap
  productionSourceMap: false,
  chainWebpack: config => {
    // webpack 会默认给commonChunk打进chunk-vendors,所以需要对webpack的配置进行delete
    config.optimization.delete('splitChunks')
    // prefetch,此插件是用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容
    config.plugins.delete("prefetch")
    // if (!isProduction) {
    //   config.plugin('webpack-bundle-analyzer')
    //       .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
    // }
  },
  css: {
    loaderOptions:{
      sass:{
        additionalData:'@import "@/styles/themes.scss";'
      }
    },
    // 是否使用css分离插件 ExtractTextPlugin
    extract: {
      // 修改打包后css文件名   // css打包文件,添加时间戳
      filename: `assert/css/str[name].[chunkhash].${version}.css`,
      chunkFilename: `assert/css/str[name].[chunkhash].${version}.css`
    }
  },
  configureWebpack: {
    output: isProduction ?  { // 输出重构  打包编译后的 文件名称  【模块名称.版本号.时间戳】
      filename: `assert/js/str[name].[chunkhash].${version}.js`,
      chunkFilename: `assert/js/str[id].[chunkhash].${version}.js`
    } : {},
    // 开启分离js
    optimization: isProduction ? {
      runtimeChunk: 'single',
      splitChunks: {
        // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
        chunks: 'all',
        maxInitialRequests: Infinity,
        // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
        minSize: 1000 * 60,
        // 表示按需加载文件时,并行请求的最大数目。默认为5。
        maxAsyncRequests: 5,
        // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
        automaticNameDelimiter: '~',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
              // 排除node_modules 然后吧 @ 替换为空 ,考虑到服务器的兼容
              const match = module.context && module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/);
              const packageName = match ? match[1] : 'default-package-name';
              return `npm.${packageName.replace('@', '')}`
            }
          }
        }
      }
    } : {},
    // 取消webpack警告的性能提示
    performance: isProduction ? {
      hints: 'warning',
      //入口起点的最大体积
      maxEntrypointSize: 1000 * 500,
      //生成文件的最大体积
      maxAssetSize: 1000 * 1000,
      //只给出 js 文件的性能提示
      assetFilter: function (assetFilename) {
        return assetFilename.endsWith('.js');
      }
    } : {},
    plugins: isProduction ? [
      // 只打包改变的文件 用于根据模块的相对路径生成 hash 作为模块 id, 一般用于生产环境
      new webpack.ids.HashedModuleIdsPlugin({
        context: __dirname,
        hashFunction: 'sha256',
        hashDigest: 'hex',
        hashDigestLength: 20,
      }),
      // nginx也要开启压缩
      new CompressionWebpackPlugin({
        algorithm: 'gzip',
        test: /\.(js|css)$/,// 匹配文件名
        threshold: 10000, // 对超过10k的数据压缩
        deleteOriginalAssets: false, // 不删除源文件
        minRatio: 0.8 // 压缩比
      }),
      new TerserWebpackPlugin ()
    ]: [],
    module: {
      rules: [
        {
          test: path.resolve(__dirname, 'node_modules/leader-line/'),
          use: [
            {
              // 解决leader-line报错问题
              loader: 'skeleton-loader',
              options: {
                procedure: content => `${content}export default LeaderLine`
              }
            }
          ]
        }
      ]
    }
  },
  // 配置代理
  devServer:{
    port:8089,
    open: true,
    proxy:{
      '/api':{
        target:'http://' + ip,
        changeOrigin: true,
        pathRewrite:{
          '^/api':''   //请求的时候使用这个api就可以
        }
      }
    }
  }
}

用户新增页面

<!--
 * 用户创建
 * @author shenjian
 * @since 2023/08/8
 -->
<template>
  <div class="user-add">
    <el-dialog class="new-dialog"
               v-model="myAddFlag" :width="width" align-center
               title="用户管理 新增" :show-close="false" destroy-on-close
               :close-on-click-modal="false">
      <el-form :model="form" :rules="formRules" ref="form" label-width="100px">
        <el-row>
          <el-col :span="10">
            <el-form-item prop="orgCode" label="所属机构:">
              <OrgTreeSelect v-model="form.orgCode" :tree="tree" style="width: 100%"></OrgTreeSelect>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item prop="username" label="用户姓名:">
              <el-input v-model="form.username" placeholder="请输入用户姓名"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="10">
            <el-form-item prop="account" label="登录帐号:">
              <el-input v-model="form.account" placeholder="请输入登录帐号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item prop="phoneNumber" label="手机号码:">
              <el-input v-model="form.phoneNumber" placeholder="请输入手机号码"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="10">
            <el-form-item prop="roleId" label="用户角色:">
              <el-select v-model="form.roleId" placeholder="请选择用户角色" style="width:100%">
                <el-option v-for="item in roleList" size="small" :key="item.roleId" :label="item.roleName" :value="item.roleId"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item prop="employeeNumber" label="工号:">
              <el-input v-model="form.employeeNumber" placeholder="请输入工号"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="10">
            <el-form-item prop="department" label="科室:">
              <el-input v-model="form.department" placeholder="请输入科室"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item prop="title" label="职称:">
              <el-input v-model="form.title" placeholder="请输入职称"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="20">
            <el-form-item prop="state" label="状态:" required>
              <el-radio-group v-model="form.state"  style="width:100%">
                <el-radio :label="'0'">启用</el-radio>
                <el-radio :label="'1'">禁用</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
      <span class="dialog-footer">
        <el-button :icon="Close" @click="cancel">取消</el-button>
        <el-button type="primary" :icon="Check" @click="save('form')">确定</el-button>
      </span>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import {Check, Close} from "@element-plus/icons-vue";
import {isMobile} from "@/util/validate";
import OrgTreeSelect from "@/components/tree/OrgTreeSelect.vue";
import {ElMessageBox} from "element-plus";

export default {
  name: 'addUser',
  components: {OrgTreeSelect},
  computed: {
    Check() {
      return Check
    },
    Close() {
      return Close
    }
  },
  props: ['addFlag', 'currentTreeNode'],
  created() {
    this.listTree()
    this.listRole()
  },
  data () {
    let orgCodeValidator = (ruler, value, callback) => {
      if (this.$isEmpty(value)) {
        callback(new Error('请选择所属机构!'))
      } else {
        callback()
      }
    };
    let roleIdValidator = (ruler, value, callback) => {
      if (this.$isEmpty(value)) {
        callback(new Error('请选择用户角色!'))
      } else {
        callback()
      }
    };
    let usernameValidator = (ruler, value, callback) => {
      if (this.$isEmpty(value)) {
        callback(new Error('请输入用户姓名!'))
      } else {
        callback()
      }
    };
    let accountValidator = (ruler, value, callback) => {
      if (this.$isEmpty(value)) {
        callback(new Error('请输入登录账号!'))
      } else {
        callback()
      }
    };
    let phoneValidator = (ruler, value, callback) => {
      if (this.$isEmpty(value)) {
        callback(new Error('请输入手机号码!'))
      } else if (!isMobile(value)) {
        callback(new Error('手机号码格式错误,请重新输入!'))
      } else {
        callback()
      }
    };
    return {
      myAddFlag: false,
      roleList: [],
      tree: [],
      form: {
        orgCode: '',
        username: '',
        account: '',
        phoneNumber: '',
        roleId: '',
        employeeNumber: '',
        department: '',
        title: '',
        state: '0'
      },
      width: this.$isMobileDevice ? '80%' : '50%',
      formRules: {
        orgCode: [
          {validator: orgCodeValidator, trigger: ['blur', 'change'], required: true}
        ],
        username: [
          {validator: usernameValidator, trigger: 'blur', required: true}
        ],
        account: [
          {validator: accountValidator, trigger: 'blur', required: true}
        ],
        roleId: [
          {validator: roleIdValidator, trigger: ['blur', 'change'], required: true}
        ],
        phoneNumber: [
          {validator: phoneValidator, trigger: 'blur', required: true}
        ],
      },
    }
  },
  watch: {
    addFlag: {
      handler(newVal, oldVal) {
        this.myAddFlag = newVal
      },
      immediate: true,
    },
    currentTreeNode: {
      handler(newVal, oldVal) {
        this.form.parentName = newVal['label']
        this.form.parentCode = newVal['userCode']
      },
      immediate: true,
    }
  },
  methods: {
    // 取消
    cancel() {
      this.form = {
        orgCode: '',
        username: '',
        account: '',
        phoneNumber: '',
        roleId: '',
        employeeNumber: '',
        department: '',
        title: '',
        state: '0'
      },
      this.$emit("updateAddFlag", false)
    },
    listRole() {
      const self = this
      self.$http.post('/role/list', {pageNumber: 1, pageSize: 30}, 'apiUrl').then(res => {
        self.roleList = res.records
      })
    },
    listTree() {
      const self = this
      self.$http.post('/org/initOrgInfoTree', {}, 'apiUrl').then(res => {
        self.tree = res
      })
    },
    updateCurrentTreeNode(val) {
      this.form.orgCode = val.orgCode
    },
    // 保存
    save (formName) {
      const self = this
      self.$refs[formName].validate(async (valid) => {
        if (valid) {
          self.$http.post('/user/save', self.form, 'apiUrl', {body: 'json'}).then((res) => {
            if (res === false || res === undefined) {
              return false
            }
            ElMessageBox.alert(res, '提示', {
              confirmButtonText: '确认',
            })
            this.cancel()
            this.$emit('listData', '')
          })
        }
      })
    }
  }
}
</script>

<style>
.new-dialog .el-dialog__footer {
  background-color: #daebfb;
  padding-bottom: 10px;
}
.new-dialog .el-dialog__title {
  color: white
}

.new-dialog .el-dialog__header {
  background-color: #132EBE;
  margin-right: 0px;
}

</style>

角色列表页面

<!--
 * 角色列表
 * @author shenjian
 * @since 2023/08/07
 -->
<template>
  <div class="role">
    <el-row>
      <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
        <el-row>
          <el-col :xs="24" :sm="24" :md="24" :lg="18" :xl="14">
            <el-form :inline="true" style="height: 20px; margin: 20px 20px 20px 20px">
              <el-form-item label="角色名称:">
                <el-input v-model="query.roleName" placeholder="请输入角色名称" clearable></el-input>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" :icon="Search" style="margin-left: 20px;" @click="listData">查询</el-button>
              </el-form-item>
            </el-form>
          </el-col>
        </el-row>
        <el-row style="margin: 20px 20px 20px 20px">
          <el-col :xs="19" :sm="19" :md="19" :lg="20" :xl="21">
            <el-row>
              <el-col :xs="4" :sm="4" :md="4" :lg="4" :xl="4">
                <span style="font-weight: bolder">角色管理</span>
              </el-col>
              <el-col :xs="20" :sm="20" :md="20" :lg="20" :xl="20" style="float: right;">
                <el-button type="primary" size="small" class="iconfont icon-quanxianyuechi-xianxing" style="margin-left: 20px; font-size: 12px; float: right" @click="handleAuth">
                  <span style="margin-left: 4px;">权限分配</span></el-button>
                <el-button type="primary" size="small" :icon="Delete" style="margin-left: 20px; float: right" @click="handleDelete">删除</el-button>
                <el-button type="primary" size="small" :icon="Edit" style="margin-left: 20px; float: right" @click="handleEdit">编辑</el-button>
                <el-button type="primary" size="small" :icon="Plus" style="margin-left: 20px; float: right" @click="handleAdd">增加</el-button>
              </el-col>
            </el-row>
            <el-row style="margin-top: 10px">
              <el-table :data="dataList" @current-change="handleCurrentChange" border highlight-current-row :header-cell-style="setRowClass" :cell-style="{ textAlign: 'center' }" stripe>
                <el-table-column prop="roleId" label="角色编码"> </el-table-column>
                <el-table-column prop="roleName" label="角色名称"> </el-table-column>
                <el-table-column prop="status" label="状态">
                  <template v-slot="scope">{{ scope.row.status === '0' ? '不使用' : '使用' }}</template>
                </el-table-column>
                <el-table-column prop="category" label="是否评估机构">
                  <template v-slot="scope">{{ scope.row.category === '0' ? '否' : '是' }}</template>
                </el-table-column>
                <el-table-column prop="remark" label="备注"></el-table-column>
              </el-table>
            </el-row>
            <el-row style="float:right; margin-top: 15px;">
              <pagination
                  background
                  @pagination="updatePageInfo"
                  :page-size="page.pageSize"
                  v-model:current-page="page.pageNumber"
                  :total="page.total"
              ></pagination>
            </el-row>
          </el-col>
          <el-col :xs="5" :sm="5" :md="5" :lg="4" :xl="3">
            <el-row style="font-weight: bolder; margin-left: 20px">菜单树</el-row>
            <el-row style="margin-top: 10px; margin-left: 20px">
              <div class="checkbox-tree">
                <el-tree :data="tree" :indent="50" show-checkbox :default-checked-keys="checkedKeys" node-key="moduleId" ref="tree">
                  <template #default="{ node }">
                    <span class="custom-tree-node">
                       <i v-if="node.isLeaf && node.isLeaf === true" class="iconfont icon-24gl-fileEmpty" ref="icon" style="font-size: 20px; color: #A4B3D6; cursor: pointer;"></i>
                       <i v-else class="iconfont icon-wenjianjia-guan_folder-close" style="font-size: 20px; color: #EBD47B; cursor: pointer"></i>
                      <span style="margin-left: 5px">{{ node.label }}</span>
                    </span>
                  </template>
                </el-tree>
              </div>
            </el-row>
          </el-col>
        </el-row>
      </el-col>
    </el-row>
  </div>
  <div>
    <add-role v-if="visible.add" :addFlag="visible.add" :currentTreeNode="currentTreeNode" @updateAddFlag="updateAddFlag" @listData="listData"></add-role>
    <edit-role v-if="visible.edit" :editFlag="visible.edit" :currentRow="currentRow" @updateEditFlag="updateEditFlag" @listData="listData"></edit-role>
  </div>
</template>
<script>
import {Delete, Edit, Plus, Search} from "@element-plus/icons-vue";
import Pagination from "@/components/pagination/Index.vue";
import AddRole from "@/views/system/role/Add.vue";
import {ElMessageBox} from "element-plus";
import EditRole from "@/views/system/role/Edit.vue";

export default {
  name: 'role',
  computed: {
    Delete() {
      return Delete
    },
    Edit() {
      return Edit
    },
    Plus() {
      return Plus
    },
    Search() {
      return Search
    }
  },
  components: {EditRole, AddRole, Pagination},
  data() {
    return {
      tree: [],
      dataList: [],
      // 查询条件
      query: {
        roleName: '',
      },
      // 选中树节点信息
      currentTreeNode: '',
      // 表格选中行信息
      currentRow: '',
      visible: {
        add: false,
        edit: false
      },
      checkedKeys: [],
      page: {
        pageNumber: 1,
        pageSize: 10,
        total: 10
      }
    }
  },
  created() {
    this.listData()
  },
  methods: {
    listData(ignoreTree) {
      if (!ignoreTree) {
        this.listTree()
      }
      const self = this
      self.currentRow = ''
      self.query['pageNumber'] = self.page.pageNumber
      self.query['pageSize'] = self.page.pageSize
      self.$http.post('/role/list', self.query, 'apiUrl').then(res => {
        self.dataList = res.records
        self.page.total = res.total
      })
    },
    listTree() {
      const self = this
      self.$http.post('/module/initModuleInfoTree', {}, 'apiUrl').then(res => {
        self.tree = res
      })
    },
    handleCurrentChange (val) {
      if (val) {
        this.currentRow = val
        this.getCheckedKey()
      }
    },
    setRowClass () {
     return {background: '#F3F3F3', textAlign: 'center', color: 'black'}
    },
    updatePageInfo (data) {
      const self = this
      self.page.pageNumber = data.pageNumber
      self.page.pageSize = data.pageSize
      self.listData()
    },
    updateCurrentTreeNode(val) {
      this.currentTreeNode = val;
      this.listData(true)
    },
    // 更新新建对话框展示
    updateAddFlag() {
      this.visible.add = false
    },
    updateEditFlag() {
      this.visible.edit = false
    },
    handleAdd() {
      const self = this
      self.visible.add = true
    },
    handleEdit() {
      const self = this
      if (self.currentRow === '') {
        self.$message.warning('请选择一条需要编辑的数据!')
        return
      }
      self.visible.edit = true
    },
    // 权限分配
    handleAuth() {
      const self = this
      if (self.currentRow === '') {
        self.$message.warning('请选择需要分配权限的数据!')
        return
      }
      self.saveRoleAuth()
    },
    getCheckedKey () {
      const self = this
      self.$http.get('/role/getRoleModule', {roleId: self.currentRow.roleId}, 'apiUrl').then(res => {
        if (res === undefined) {
          self.$refs.tree.setCheckedKeys([])
        } else {
          self.$refs.tree.setCheckedKeys(res)
        }
      })
    },
    saveRoleAuth () {
      const self = this
      let keys = self.$refs.tree.getCheckedKeys()
      // 与言语不同,不可以选择父节点否则组件显示存在问题
      // const parentKeys = self.$refs.tree.getHalfCheckedKeys()
      // keys = keys.concat(parentKeys)
      const data = {
        moduleIds: keys,
        roleId: self.currentRow.roleId
      }
      self.$confirm('确认为' + self.currentRow.roleName + '授权吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        self.$http.post('/role/auth', data, 'apiUrl', {body: 'json'}).then((res) => {
          if (res === false || res === undefined) {
            return false
          }
          self.$message({
            message: '授权成功',
            type: 'success'
          })
        })
      }).catch(() => {
        console.log("")
      })
    },
    handleDelete() {
      const self = this
      if (self.currentRow === '') {
        self.$message.warning('请选择需要删除的数据!')
        return
      }
      ElMessageBox.confirm(
          '确定要删除[' + self.currentRow.roleName + ']吗?',
          '提示',
          {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
          }
      ).then(() => {
        self.$http.get('/role/delete', {'roleId': self.currentRow.roleId}, 'apiUrl', {body: 'json'}).then((res) => {
          if (res === false || res === undefined) {
            return false
          }
          self.$message({
            message: '删除成功!',
            type: 'success'
          })
          this.listData()
        })
      }).catch(() => {
        console.log("")
      })
    }
  }
}
</script>
<style>

</style>
3. 源码获取

前端页面已提交至git 或者关注公众号算法小生加群交流

https://github.com/SJshenjian/cloud-web

默认用户名密码admin 1

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沈健_算法小生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值