1 创建项目

1.1 Vue 2.0

cmd d:\html\test    	创建一个文件夹放vue项目

vue init webpack test 	创建项目

image.png

cd test         进入刚刚创建的项目

npm run dev   	启动项目

1.2 Vue 3.0

cmd d:\html\test    创建一个文件夹放vue项目

vue create test     创建项目

根据下面图片选择配置:空格是选择,回车是确认!!
image.png

cd test         进入刚刚创建的项目

npm run serve   启动项目

2 导入插件

2.1 Element-UI 插件

npm install --save element-ui

在main.js 里面引用element-ui 组件

//引用element-ui 以及样式
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

// 安装ElementUI 配置全局
Vue.use(ElementUI, {size: 'small'});

2.2 Axios

npm install --save axios

2.2.1 简单

创建utils文件在api里面封装自己需要 get post put delete请求

import axios from 'axios'

let base = '';

export const postRequest = (url, params) => {
    return axios({
        method: 'post',
        url: `${base}${url}`,
        data: params
    })
};

//传递json的put请求
export const putRequest = (url, params) => {
    return axios({
        method: 'put',
        url: `${base}${url}`,
        data: params
    })
}
//传递json的get请求
export const getRequest = (url, params) => {
    return axios({
        method: 'get',
        url: `${base}${url}`,
        data: params
    })
}
//传递json的delete请求
export const deleteRequest = (url, params) => {
    return axios({
        method: 'delete',
        url: `${base}${url}`,
        data: params
    })
}

import {postRequest} from "./utils/api";
import {putRequest} from "./utils/api";
import {getRequest} from "./utils/api";
import {deleteRequest} from "./utils/api";

// 全局方法挂载
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;

2.2.2 封装

创建utils文件夹 request.js

import axios from 'axios'

//请求超时时间
axios.defaults.timeout = 10000;
//设置请求头以json格式发送到后端
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
    
export default axios

创建api文件夹 login.js
data为post请求的携带信息
params是get请求携带的参数

import request from '@/utils/request'

//登录
export function login(data) {
    return request({
        url: '/auth/login',
        method: 'post',
        data: data
    })
}

//注册
export function register(data) {
    return request({
        url: '/auth/register',
        method: 'post',
        data: data
    })
}

export function fetchList(query) {
    return request({
        url: '/admin/user/page',
        method: 'get',
        params: query
    })
}

export function addObj(obj) {
    return request({
        url: '/admin/user',
        method: 'post',
        data: obj
    })
}

2.3 Vuex 状态管理模式

npm install vuex --save

创建store文件

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
    state: {},
    //同步执行操作
    mutations: {},
    //异步执行操作
    actions: {},
    modules: {}
})
import store from './store'

new Vue({
  router,
  store,//引用
  render: h => h(App)
}).$mount('#app');

2.4 Scss

npm install node-sass --save
npm install sass-loader --save
npm install scss --save
npm install scss-loader --save

引入过程中会提示有高危漏洞使用命令:

npm audit fix

npm install

错误解决:
image.png
检查代码中并无写错的地方
其实涉及到这个问题,就是版本原因了,我安装的 scss-loader 版本太高,卸载安装低版本即可
卸载:npm uninstall 名字比如:sass

npm uninstall --save sass-loader // 卸载
npm i -D sass-loader@8.x // 安装 
npm uninstall --save node-sass // 卸载
npm i node-sass@4.14.1 // 安装

2.5 图标

我们使用了 Font Awesome 的图标做为菜单图标,使用前先安装 Font Awesome

npm install font-awesome

导入 Font Awesome (main.js)

import 'font-awesome/css/font-awesome.min.css'

3 配置全局响应拦截器(业务逻辑错误)

image.png

import {Message} from 'element-ui'
import router from '../router'

//响应拦截器
axios.interceptors.response.use(success=>{
  //业务逻辑错误
  if (success.status && success.status == 200) {
    //500 业务逻辑错误,401 未登录,403 权限错误
    if (success.data.code==500||success.data.code==401||success.data.code==403){
      Message.error({message:success.data.msg});
      return;
    }
    if (success.data.message) {
      Message.success({message:success.data.msg});
    }
  }
  return success.data;
}, error =>{
  //504 服务器有问题,404 页面找不到
  if (error.response.code==504||error.response.code==404){
    Message.error({message:'服务器没有了'});
  }else if (error.response.code==403){
    Message.error({message:'权限不足,请联系管理员!'})
  }else if (error.response.code==401){
    Message.error({message:'尚未登录,请登录'})
    router.replace('/');
  }else{
    if (error.response.data.message) {
      Message.error({message:error.response.data.msg});
    }else{
      Message.error({message:'未知错误!'});
    }
  }
  return;
});

4 登录页面

4.1 配置跨越

创建vue.config.js文件
image.png

let proxyObj = {}//代理对象

proxyObj['/'] = {//代理路径
    //websocket
    ws: true,
    //目标地址
    target: 'http://localhost:8082',
    // target: 'http://47.115.143.129:8082',
    //发送请求头中host会设置成target
    changeOrigin: true,// 开启跨域
    //不重写请求地址
    pathReWrite:{
        '^/': '/'
    }
};

module.exports = {
    assetsDir: 'static', // 静态资源保存路径
    outputDir: 'medicine-ui', // 打包后生成的文件夹
    lintOnSave: false,
    productionSourceMap: false, // 取消错误日志
    runtimeCompiler: true, // 实时编译
    devServer: {
        open: true,
        host: 'localhost',
        port: 80,
        proxy: proxyObj //代理
    }
};

4.2 创建Login.vue

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">后台管理系统</h3>
      <el-form-item prop="userName">
        <el-input v-model="loginForm.userName"
                  type="text"
                  auto-complete="false"
            placeholder="账号">
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="loginForm.password"
                  type="password"
                  auto-complete="false"
            placeholder="密码">
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaOnOff">
        <el-input v-model="loginForm.code"
                  auto-complete="false"
                  placeholder="验证码"
                  style="width: 63%">
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="updateCaptcha" class="login-code-img"/>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="medium" type="primary" style="width:100%;">
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © 2018-2022 admin All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>

export default {
  name: "Login",
  data() {
    return {
      codeUrl: '/captcha?time=' + new Date(),
      loginForm: {
        userName: "admin",
        password: "123456",
        rememberMe: false,
        code: ""
      },
      loginRules: {//错误提示信息
        userName: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
        password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
      },
      loading: false,
      // 验证码开关
      captchaOnOff: true,
      // 注册开关
      register: false
    };
  },
  methods: {
    //获取验证码
    updateCaptcha() {
      this.codeUrl = '/captcha?time=' + new Date();
    }
  }
};
</script>

<style rel="stylesheet/scss" lang="scss">
.login {
  display: flex;
  position: fixed;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}

.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code-img {
  height: 38px;
}
</style>

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Home from '../views/Home.vue'


Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Login',
    component: Login,
    hidden: true//隐藏路由
  },
  {
    path: '/register',
    name: '注册',
    component: Register,
    hidden: true//隐藏路由
},
{
    path: '/home',//路径
    name: '控制台',//名字
    redirect: 'console',//重定向路由
    component: Home,//文件地址
    hidden: true,//隐藏路由
    children: [//子级菜单
        {
            path: '/console',
            name: '控制台',
            component: Console
        }
    ]
}
]

const router = new VueRouter({
  // mode: 'history', // 去掉url中的#
  routes
})

export default router

4.3 验证码

4.3.1 前端代码

<el-form-item prop="code" v-if="captchaOnOff">
  <el-input v-model="loginForm.code"
            auto-complete="false"
            placeholder="验证码"
            style="width: 63%">
  </el-input>
  <div class="login-code">
    <img :src="codeUrl" @click="updateCaptcha" class="login-code-img"/>
  </div>
</el-form-item>
//获取验证码
codeUrl: '/captcha?time=' + new Date(),
methods: {
    //获取验证码
    updateCaptcha() {
      this.codeUrl = '/captcha?time=' + new Date();
    }
}

4.3.2 后端代码

<!--google kaptcha依赖-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

        <!--产生随机数-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>

创建RandomUtils类

package com.example.admin.utils;

import java.awt.*;
import java.util.Random;

public class RandomUtils extends  org.apache.commons.lang3.RandomUtils {
    private static final char[] CODE_SEQ = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
            'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
            'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };

    private static final char[] NUMBER_ARRAY = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

    private static Random random = new Random();

    public static String randomString(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(String.valueOf(CODE_SEQ[random.nextInt(CODE_SEQ.length)]));
        }
        return sb.toString();
    }

    public static String randomNumberString(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(String.valueOf(NUMBER_ARRAY[random.nextInt(NUMBER_ARRAY.length)]));
        }
        return sb.toString();
    }

    public static Color randomColor(int fc, int bc) {
        int f = fc;
        int b = bc;
        Random random = new Random();
        if (f > 255) {
            f = 255;
        }
        if (b > 255) {
            b = 255;
        }
        return new Color(f + random.nextInt(b - f), f + random.nextInt(b - f), f + random.nextInt(b - f));
    }

    public static int nextInt(int bound) {
        return random.nextInt(bound);
    }
}

创建Model文件夹 VerifyCode

package com.example.admin.model;

import lombok.Data;

@Data
public class VerifyCode {
    private String code;

    private byte[] imgBytes;

    private long expireTime;
}

创建CaptchaUtils类

package com.example.admin.utils;

import com.example.admin.model.VerifyCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

public class CaptchaUtils {
    private static final Logger logger = LoggerFactory.getLogger(CaptchaUtils.class);

    private static final String[] FONT_TYPES = { "\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53", "\u6977\u4f53", "\u96b6\u4e66" };

    private static final int VALICATE_CODE_LENGTH = 4;

    /**
     * 设置背景颜色及大小,干扰线
     *
     * @param graphics
     * @param width
     * @param height
     */
    private static void fillBackground(Graphics graphics, int width, int height) {
        // 填充背景
        graphics.setColor(Color.WHITE);
        //设置矩形坐标x y 为0
        graphics.fillRect(0, 0, width, height);

        // 加入干扰线条
        for (int i = 0; i < 8; i++) {
            //设置随机颜色算法参数
            graphics.setColor(RandomUtils.randomColor(40, 150));
            Random random = new Random();
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            graphics.drawLine(x, y, x1, y1);
        }
    }

    /**
     * 生成随机字符
     *
     * @param width
     * @param height
     * @param os
     * @return
     * @throws IOException
     */
    public String generate(int width, int height, OutputStream os) throws IOException {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics graphics = image.getGraphics();
        fillBackground(graphics, width, height);
        String randomStr = RandomUtils.randomString(VALICATE_CODE_LENGTH);
        createCharacter(graphics, randomStr);
        graphics.dispose();
        //设置JPEG格式
        ImageIO.write(image, "JPEG", os);
        return randomStr;
    }

    /**
     * 验证码生成
     *
     * @param width
     * @param height
     * @return
     */
    public VerifyCode generate(int width, int height) {
        VerifyCode verifyCode = null;
        try (
                //将流的初始化放到这里就不需要手动关闭流
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ) {
            String code = generate(width, height, baos);
            verifyCode = new VerifyCode();
            verifyCode.setCode(code);
            verifyCode.setImgBytes(baos.toByteArray());
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            verifyCode = null;
        }
        return verifyCode;
    }

    /**
     * 设置字符颜色大小
     *
     * @param g
     * @param randomStr
     */
    private void createCharacter(Graphics g, String randomStr) {
        char[] charArray = randomStr.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            //设置RGB颜色算法参数
            g.setColor(new Color(50 + RandomUtils.nextInt(100),
                    50 + RandomUtils.nextInt(100), 50 + RandomUtils.nextInt(100)));
            //设置字体大小,类型
            g.setFont(new Font(FONT_TYPES[RandomUtils.nextInt(FONT_TYPES.length)], Font.BOLD, 26));
            //设置x y 坐标
            g.drawString(String.valueOf(charArray[i]), 15 * i + 5, 19 + RandomUtils.nextInt(8));
        }
    }
}


创建Controller文件夹 CaptchaController

package com.example.admin.controller;
import com.example.admin.model.VerifyCode;
import com.example.admin.utils.CaptchaUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

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

/**
 * 验证码
 */
@Controller
@Api(tags = "图片验证码")
public class CaptchaController {
    private static final Logger logger = LoggerFactory.getLogger(CaptchaController.class);

    @ApiOperation(value = "验证码")
    @GetMapping("captcha")
    public void verifyCode(HttpServletRequest request, HttpServletResponse response) {
        CaptchaUtils iVerifyCodeGen = new CaptchaUtils();
        try {
            //设置长宽
            VerifyCode verifyCode = iVerifyCodeGen.generate(80, 28);
            String code = verifyCode.getCode();

            //将VerifyCode绑定session
            request.getSession().setAttribute("code", code);
            logger.info("验证码:" + code);
            //设置响应头
            response.setHeader("Pragma", "no-cache");
            //设置响应头
            response.setHeader("Cache-Control", "no-cache");
            //在代理服务器端防止缓冲
            response.setDateHeader("Expires", 0);
            //设置响应内容类型
            response.setContentType("image/jpeg");
            response.getOutputStream().write(verifyCode.getImgBytes());
            response.getOutputStream().flush();
        } catch (IOException e) {
            logger.info("", e);
            e.getStackTrace();
        }
    }
}

image.png

4.4 登录

4.4.1 前端代码

<el-button
          :loading="loading"
          size="medium"
          style="width:100%;"
          type="primary"
          @click="submitLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
methods: {
    //登录事件
    submitLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          console.log(this.loginForm);
          this.loading = true;
          this.postRequest("/login", this.loginForm).then(resp => {
            if (resp) {
              this.loading = false;
              console.log(resp);
              //页面跳转
              let path = this.$route.query.redirect;
              this.$router.replace(
                path == "/" || path == undefined ? "/home" : path
              );
              // this.$router.replace('/home');
            } else {
              this.loading = false;
            }
          });
        } else {
          this.$message.error("请输入所有字段");
          return false;
        }
      });
    }
  }

4.4.2 后端代码

package com.example.admin.utils;

import java.util.HashMap;

/**
 * 操作消息提醒
 *
 * @author ruoyi
 */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 方便链式调用
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public AjaxResult put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }


    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, null);
    }
}

package com.example.admin.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "AdminLogin对象", description = "")
public class AdminLogin implements Serializable {
    private static final Long serialVersionUID = -80646425239914972L;

    @ApiModelProperty(value = "账号")
    private String userName;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "验证码")
    private String code;

}

package com.example.admin.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.admin.model.SysUser;
import com.example.admin.service.SysUserService;
import com.example.admin.utils.AjaxResult;
import com.example.admin.vo.AdminLogin;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@RestController
@Api(tags = "用户")
public class LoginController {
    private static final Logger logger = LoggerFactory.getLogger(CaptchaController.class);

    @Resource
    private SysUserService sysUserService;

    @PostMapping("/login")
    @ApiOperation("登录接口")
    public AjaxResult getLogin(@RequestBody AdminLogin adminLogin, HttpServletRequest request){
        //获取session里面的验证码
        String captcha = (String) request.getSession().getAttribute("code");
        if (adminLogin != null && adminLogin.getCode().equals(captcha)) {
            SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("user_name", adminLogin.getUserName()).eq("password", adminLogin.getPassword()));
            if (sysUser != null ) {
                return AjaxResult.success("登录成功!",null);
            }
            return AjaxResult.error("用户名不存在!");
        } else {
            return AjaxResult.error("验证码错误!");
        }
    }
}

5 首页

<template>
<div class="box">
        <el-container>
            <el-aside class="nav-wrap" :width="isCollapse?'66px':'200px'">
                <div style="height: 100%">
                    <el-menu
                            router
                            unique-opened
                            text-color="#fff"
                            :collapse="isCollapse"
                            :collapse-transition="false"
                            active-text-color="#409EFF"
                            style="height: 100%"
                            :default-active="activePath"
                            background-color="#344a5f">
                        <div class="nav-head">
                            <img :src="require('@/assets/images/tp6.jpg')"/>
                            <!--v-show  显示:true/隐藏:false-->
                            <div class="title" v-show="isCollapse?false:true">Admin权限管理系统</div>
                        </div>
                        <el-submenu :index="index+''" v-for="(item,index) in routes" v-if="!item.hidden" :key="index">
                            <template slot="title">
                                <i style="color: #ffffff;margin-right: 5px" :class="item.iconCls"></i>
                                <span>{{item.name}}</span>
                            </template>
                            <el-menu-item :index="children.path"
                                          v-for="(children,indexj) in item.children"
                                          :key="indexj"
                                          @click="saveNavState(children.path)">
                                <i style="color: #ffffff;margin-right: 5px" :class="children.iconCls"></i>
                                {{children.name}}
                            </el-menu-item>
                        </el-submenu>
                    </el-menu>
                </div>
            </el-aside>
            <el-container>
                <el-header class="homeHeader">
                    <el-row style="width: 250px">
                        <el-col :span="4">
                            <div style="font-size: 25px;margin-left: -8px">
                                <i style="cursor: pointer;"
                                   :class="isCollapse?'el-icon-s-unfold':'el-icon-s-fold'"
                                   @click="icons">
                                </i>
                            </div>
                        </el-col>
                        <el-col :span="20">
                            <div style="margin-top: 9px">
                                <el-breadcrumb separator-class="el-icon-arrow-right"
                                               v-if="this.$router.currentRoute.path!='/home'">
                                    <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
                                    <el-breadcrumb-item>{{this.$router.currentRoute.name}}</el-breadcrumb-item>
                                </el-breadcrumb>
                            </div>
                        </el-col>
                    </el-row>
                    <el-row style="width: 250px">
                        <el-col :span="12">
                            <div style="line-height: 56px">
                                <span style="font-size: 25px;">
                                    <el-tooltip class="item" effect="dark" content="刷新" placement="bottom">
                                        <i style="cursor: pointer;margin-right: 8px;" class="el-icon-refresh"
                                           @click="ref"></i>
                                    </el-tooltip>
                                    <el-tooltip class="item" effect="dark" content="全屏" placement="bottom">
                                        <i style="cursor: pointer;margin-right: 8px" class="el-icon-full-screen"></i>
                                    </el-tooltip>
                                    <span class="lock-wrap" @click="lockChange">
                                        <el-tooltip class="item" effect="dark" :content="lockFlag?`点击锁定`:`点击解锁`"
                                                    placement="bottom">
                                            <i style="cursor: pointer;margin-right: 8px" class="el-icon-unlock"></i>
                                        </el-tooltip>
                                    </span>
                                </span>
                            </div>
                        </el-col>
                        <el-col :span="12">
                            <el-dropdown class="userInfo" @command="commandHandler">
                                <span class="nav-head">
                                    <i><img :src="require('@/assets/images/tp6.jpg')"></i>
                                    <span style="margin-left: 2px">
                                    {{user.name}}<i class="el-icon-arrow-down"></i>
                                    </span>
                                </span>
                                <el-dropdown-menu slot="dropdown">
                                    <el-dropdown-item command="userInfo">个人中心</el-dropdown-item>
                                    <el-dropdown-item command="setting">设置</el-dropdown-item>
                                    <el-dropdown-item command="logout">注销登录</el-dropdown-item>
                                </el-dropdown-menu>
                            </el-dropdown>
                        </el-col>
                    </el-row>
                </el-header>
                <el-main class="main-wrap">
                    <router-view class="homeRouterView"/>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

<script>
export default {
  name: "Home",
  data() {
    return {
      //菜单展开/收起
      isCollapse: false,
      breadList: [], // 路由集合
      user: {
        name: "张三",
        userFace: "",
      },
      // 被激活的链接地址
      activePath: "",
      lockFlag: true,
    };
  },
  created() {
    console.log(this.$router.options.routes);
    //初始化菜单激活状态
    this.activePath = window.sessionStorage.getItem("activePath");
  },
  computed: {
    //数据初始化
    routes() {
      return this.$router.options.routes;
    },
  },
  methods: {
    //页面锁定事件
    lockChange() {
      if (this.lockFlag) {
        localStorage.setItem("lockFlag", 0);
        this.lockFlag = false;
        this.$message({
          type: "success",
          message: "页面锁定成功!",
        });
      } else {
        // const uInfo = JSON.parse(localStorage.getItem("userInfo")); // 用户
        this.$prompt("", "请输入密码", {
          inputType: "password",
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          inputValidator: (value) => {
            // let hash = md5(value)//密码
            // return hash.toUpperCase()===uInfo.passwd
          },
          inputErrorMessage: "密码输入不正确",
        })
          .then(({ value }) => {
            this.$message({
              type: "success",
              message: "解锁成功",
            });
            // Message.error({message:'尚未登录,请登录'});
            localStorage.setItem("lockFlag", 1);
            this.lockFlag = true;
          })
          .catch(() => {});
      }
    },
    //刷新页面
    ref() {
      this.$router.go(0);
    },
    //菜单展开/收起
    icons() {
      this.isCollapse = !this.isCollapse;
    },
    // 保存链接的激活状态
    saveNavState(activePath) {
      window.sessionStorage.setItem("activePath", activePath);
      this.activePath = activePath;
    },
    //回调事件
    commandHandler(cmd) {
      if (cmd == "logout") {
        this.$confirm("此操作将注销登录,是否继续?", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }).then(() => {
            //注销
            this.postRequest("/logout");
            //清除用户信息
            window.sessionStorage.removeItem("user");
            window.sessionStorage.removeItem("tokenStr");
            //清空菜单
            this.$store.commit("initRoutes", []);
            //跳转登录页
            this.$router.replace("/");
          }).catch(() => {
            this.$message({
              type: "info",
              message: "已取消操作",
            });
          });
      }
      if (cmd == "userInfo") {
        this.$router.push("/userInfo");
      }
    },
  },
};
</script>

<style lang="scss">
    .box {
        margin: -8px;
        padding: 0;
        height: 100%;
    }

    /*---- 侧边栏 start ----*/
    .nav-wrap {
        top: 0;
        left: 0;
        height: 100vh;
    }

    //头部背景
    .nav-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 15px;
        box-sizing: border-box;
        height: 60px;
    }

    //头部字体大小
    .nav-head .title {
        font-size: 14px;
        color: #fff;
    }

    //log
    .nav-head img {
        width: 48px;
        height: 48px;
        border-radius: 24px;
        margin-left: -8px;
    }

    //二级菜单背景色
    .el-menu-item {
        /*background-color: rgb(31,45,61) !important;*/
    }

    //鼠标悬浮背景色
    .el-menu-item:hover {
        outline: 0 !important;
        color: #409EFF !important;
    }

    //点击选择背景色
    .el-menu-item.is-active {
        color: #409EFF !important;
        background: rgb(31, 45, 61) !important;
    }

    /*---- 侧边栏 end ----*/

    /*---- 头部 start ----*/
    .homeHeader {
        -webkit-box-shadow: 0 3px 16px 0 rgba(0, 0, 0, .1);
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 15px;
        box-sizing: border-box;
    }

    .homeHeader .userInfo {
        cursor: pointer;
    }

    /*---- 头部 end ----*/

    /*---- 内容 start ----*/
    .main-wrap {
        border: 20px solid #f7f7f7;
        border-bottom: none;
        -webkit-box-sizing: border-box
    }

    .homeRouterView {
        /*margin-top: 10px;*/
    }

    /*---- 内容 end ----*/

</style>

6 侧边栏

6.1 静态菜单栏

6.1.1 页面

创建4个Test.vue页面方便测试
image.png

<template>
    <div>test1</div>
</template>

<script>
    export default {
        name: "test1"
    }
</script>

<style scoped>

</style>

6.1.2 路由

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/views/Login'
import Home from '@/views/Home'
import Index from '@/views/Index'
import Test1 from '@/views/Test1'
import Test2 from '@/views/Test2'
import Test3 from '@/views/Test3'
import Test4 from '@/views/Test4'


Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login,
      hidden: true
    },
    {
      path: '/home',//路径
      name: '控制台',//名字
      redirect: 'console',//重定向路由
      component: Home,//文件地址
      hidden: true,//隐藏路由
      children: [//子级菜单
        {
          path: '/console',
          name: '控制台',
          component: Index,
        }
      ]
    },
    {
      path: '/home',
      name: '商品管理',
      component: Home,
      children: [
        {
          path: '/test1',
          name: '商品列表',
          component: Test1,
        },
        {
          path: '/test2',
          name: '订单列表',
          component: Test2
        }
      ]
    },
    {
      path: '/home',
      name: '系统管理',
      component: Home,
      children: [
        {
          path: '/test3',
          name: '用户管理',
          component: Test3,
        },
        {
          path: '/test4',
          name: '角色管理',
          component: Test4
        }
      ]
    }
  ]
})

6.1.3 主页

我们需要频繁添加菜单选项的时候会发现操作的步骤比较重复,因此我们可以将菜单和路由数据统一起来,将路由数据动态渲染到菜单上。

<template>
    <div class="box">
      <el-container>
          <el-aside class="nav-wrap">
              <div style="height: 100%">
                    <el-menu
                            router
                            unique-opened
                            text-color="#fff"
                            :collapse-transition="false"
                            active-text-color="#409EFF"
                            style="height: 100%"
                            background-color="#344a5f">
                        <div class="nav-head">
                            <div class="title">Admin权限管理系统</div>
                        </div>
                        <el-submenu :index="index+''" v-for="(item,index) in routes" v-if="!item.hidden" :key="index">
                            <template slot="title">
                                <span>{{item.name}}</span>
                            </template>
                            <el-menu-item :index="children.path" v-for="(children,indexj) in item.children" :key="indexj">
                                {{children.name}}
                            </el-menu-item>
                        </el-submenu>
                    </el-menu>
                </div>
            </el-aside>
            <el-container>
                <el-header class="homeHeader">
                    <h2>管理系统</h2>
                </el-header>
                <el-main class="main-wrap">
                    <router-view/>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

<script>
export default {
    name: "Home",
    created() {
        console.log(this.$router.options.routes)
    },
    computed: {//数据初始化
        routes() {
            return this.$router.options.routes;
        }
    },
    methods: {
        
    },
};
</script>

<style lang="scss">
    .box {
        margin: -8px;
        padding: 0;
        height: 100%;
    }

    /*---- 侧边栏 start ----*/
    .nav-wrap {
        top: 0;
        left: 0;
        height: 100vh;
    }

    //头部背景
    .nav-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 15px;
        box-sizing: border-box;
        height: 60px;
    }

    //头部字体大小
    .nav-head .title {
        font-size: 14px;
        color: #fff;
    }

    //log
    .nav-head img {
        width: 48px;
        height: 48px;
        border-radius: 24px;
        margin-left: -8px;
    }

    //鼠标悬浮背景色
    .el-menu-item:hover {
        outline: 0 !important;
        color: #409EFF !important;
    }

    //点击选择背景色
    .el-menu-item.is-active {
        color: #409EFF !important;
        background: rgb(31, 45, 61) !important;
    }
    /*---- 侧边栏 end ----*/

    /*---- 头部 start ----*/
    .homeHeader {
        -webkit-box-shadow: 0 3px 16px 0 rgba(0, 0, 0, .1);
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 15px;
        box-sizing: border-box;
    }

    .homeHeader .userInfo {
        cursor: pointer;
    }
    /*---- 头部 end ----*/

    /*---- 内容 start ----*/
    .main-wrap {
        border: 20px solid #f7f7f7;
        border-bottom: none;
        -webkit-box-sizing: border-box
    }
    /*---- 内容 end ----*/

</style>


router :是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转。我们可以取消之前的 select 事件。

6.2 动态菜单栏

6.2.1 前端代码

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
表字段:
image.png
后端响应菜单栏的格式

[
  {
    "mid": 1,
    "url": "/",
    "path": "/home",
    "component": "Home",
    "name": "商品管理",
    "iconcls": null,
    "keepalive": null,
    "requireauth": true,
    "parentid": 0,
    "enabled": true,
    "children": [
      {
        "mid": 9,
        "url": null,
        "path": "/product/product",
        "component": "product/product/list",
        "name": "商品列表",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 1,
        "enabled": true,
        "children": null
      },
      {
        "mid": 10,
        "url": null,
        "path": "/product/order",
        "component": "product/order/list",
        "name": "订单列表",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 1,
        "enabled": true,
        "children": null
      }
    ]
  },
  {
    "mid": 2,
    "url": "/",
    "path": "/home",
    "component": "Home",
    "name": "会员管理",
    "iconcls": null,
    "keepalive": null,
    "requireauth": true,
    "parentid": 0,
    "enabled": true,
    "children": [
      {
        "mid": 11,
        "url": null,
        "path": "/member/grade",
        "component": "menber/grade",
        "name": "会员等级",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 2,
        "enabled": true,
        "children": null
      }
    ]
  },
  {
    "mid": 3,
    "url": "/",
    "path": "/home",
    "component": "Home",
    "name": "系统管理",
    "iconcls": "el-icon-setting",
    "keepalive": null,
    "requireauth": true,
    "parentid": 0,
    "enabled": true,
    "children": [
      {
        "mid": 5,
        "url": null,
        "path": "/system/user",
        "component": "system/user/index",
        "name": "用户管理",
        "iconcls": "el-icon-user",
        "keepalive": null,
        "requireauth": true,
        "parentid": 3,
        "enabled": true,
        "children": null
      },
      {
        "mid": 6,
        "url": null,
        "path": "/system/role",
        "component": "system/role/index",
        "name": "角色管理",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 3,
        "enabled": true,
        "children": null
      },
      {
        "mid": 7,
        "url": null,
        "path": "/system/menu",
        "component": "system/menu/index",
        "name": "菜单栏管理",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 3,
        "enabled": true,
        "children": null
      }
    ]
  },
  {
    "mid": 4,
    "url": "/",
    "path": "/home",
    "component": "Home",
    "name": "系统工具",
    "iconcls": "el-icon-s-tools",
    "keepalive": null,
    "requireauth": true,
    "parentid": 0,
    "enabled": true,
    "children": [
      {
        "mid": 8,
        "url": null,
        "path": "/tools/logs",
        "component": "tools/logs/index",
        "name": "日志管理",
        "iconcls": null,
        "keepalive": null,
        "requireauth": true,
        "parentid": 4,
        "enabled": true,
        "children": null
      }
    ]
  }
]

在 src 目录下创建一个名为 store 的目录并新建一个名为 index.js 文件用来配置 Vuex。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
    	routes:[]
    },
    //同步执行操作
    mutations: {
    	//初始化
    	initRoutes(state, data) {
            state.routes = data;
    	}
    },
    //异步执行操作
    actions: {},
    modules: {}
})
state全局state对象,用于保存所有组件的公共数据
getters监听state值的最新状态(计算属性)
actions异步执行mutations方法
mutations唯一可以改变state值的方法(同步执行)

修改 main.js 增加刚才配置的 store/index.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

//引用element-ui 以及样式
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

import {postRequest} from "./utils/api";
import {putRequest} from "./utils/api";
import {getRequest} from "./utils/api";
import {deleteRequest} from "./utils/api";

//插件形式使用请求
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;


// 安装ElementUI 配置全局
Vue.use(ElementUI, {size: 'small'});

Vue.config.productionTip = false;

new Vue({
  router,
  store,//引用
  render: h => h(App)
}).$mount('#app');

封装菜单请求工具类
后端接口返回的数据中 component 的值为String,我们需要将其转换为前端所需的对象并且我们需要将数据放入到路由的配置里。所以我们需要封装菜单请求工具类实现我们的需求。

import {getRequest} from "./api";

export const initMenu = (router, store) => {
    if (store.state.routes.length > 0) {
        return;
    }
    //查询菜单栏
    getRequest('/system/menu/list').then(data => {
        if (data) {
            //格式化Router
            let fmtRoutes = formatRoutes(data);
            //添加到router
            router.addRoutes(fmtRoutes);
            //将数据存入vuex
            store.commit('initRoutes', fmtRoutes);
        }
    })
};

export const formatRoutes = (routes) => {
    let fmtRoutes = [];
    routes.forEach(router => {
        let {
            path,
            component,
            name,
            iconCls,
            children,
        } = router;
        if (children && children instanceof Array) {
            //递归
            children = formatRoutes(children);
        }
        let fmRouter = {
            path: path,
            name: name,
            iconCls: iconCls,
            children: children,
            //这里注意了,数据库里的路径要对页面的路径,不能出错了
            component:()=>import(`../views/${component}.vue`)
            // component(resolve) {
                // if (component.startsWith("Home")) {
                //     require(['../views/' + component + '.vue'], resolve);
                // } else if (component.startsWith('system')) {
                //     require(['../views/' + component + '.vue'], resolve);
                // } else if (component.startsWith('tools')) {
                //     require(['../views/' + component + '.vue'], resolve);
                // } else if (component.startsWith('product')) {
                //     require(['../views/' + component + '.vue'], resolve);
                // }
            // }
        };
        fmtRoutes.push(fmRouter);
    });
    return fmtRoutes;
};

导航守卫
菜单数据在用户点击刷新按钮时可能出现丢失的情况,解决办法

  1. 每个页面添加初始化菜单的方法,这显然很麻烦 。
  2. 路由导航守卫 。

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的,单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入离开的导航守卫。我们可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Home from '../views/Home'
import Console from '../views/console/index'

Vue.use(VueRouter);

const routes = [
    {
        path: '/',
        name: 'Login',
        component: Login,
        hidden: true,//隐藏路由
    }, {
        path: '/home',//路径
        name: 'Home',//名字
        redirect: 'console',//重定向路由
        component: Home,//文件地址
        hidden: true,//隐藏路由
        children: [//子级菜单
            {
                path: '/console',
                name: '控制台',
                component: Console,
            }
        ]
    }
]

const router = new VueRouter({
    routes
})

export default router

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve 完之前一直处于 等待中
每个守卫方法接收三个参数:

to: Route即将要进入的目标路由对象。
from: Route当前导航正要离开的路由。
next: Function一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
next()进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed (确认的)。
next(false)中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。next(‘/’) 或者 next({ path: ‘/’ }) : 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace:true 、 name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或router.push 中的选项。
next(error)(2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

router.beforeEach((to, from, next)=>{
    if (to.path=='/'){
        next()
    } else {
        initMenu(router,store);
        next();
    }
});
<el-menu router unique-opened>
	<el-submenu :index="index+''" v-for="(item,index) in routes" v-if="!item.hidden" :key="index">
		<template slot="title">
			<i :class="item.iconCls" style="color: #1accff;margin-right: 5px"></i>
			<span>{{item.name}}</span>
		</template>
		<el-menu-item :index="children.path" v-for="(children,indexj) in item.children" :key="indexj">
			{{children.name}}
		</el-menu-item>
	</el-submenu>
</el-menu>


<script>
    export default {
        name: "Home",
        data() {
            return {
                isCollapse: false
            };
        },
        computed: {//数据初始化
            routes() {
                return this.$store.state.routes;
            }
        }
    }
</script>

6.2.2 后端代码

package com.example.admin.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.api.ApiController;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.admin.model.SysMenu;
import com.example.admin.service.SysMenuService;
import com.example.admin.utils.AjaxResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;

/**
 * (SysMenu)表控制层
 *
 * @author makejava
 * @since 2022-04-08 16:58:26
 */
@RestController
@RequestMapping("sysMenu")
public class SysMenuController extends ApiController {
    /**
     * 服务对象
     */
    @Resource
    private SysMenuService sysMenuService;

    @GetMapping("/getMenu")
    public AjaxResult getMenu(){
        List<SysMenu> menu = sysMenuService.getMenu();
//        System.out.println(JSON.toJSON(menu));
        if (!menu.isEmpty()){
            return AjaxResult.success(menu);
        }
        return AjaxResult.error();
    }
}
package com.example.admin.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin.model.SysMenu;

import java.util.List;

/**
 * (SysMenu)表服务接口
 *
 * @author makejava
 * @since 2022-04-08 16:58:27
 */
public interface SysMenuService extends IService<SysMenu> {

    List<SysMenu> getMenu();
}


package com.example.admin.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin.dao.SysMenuDao;
import com.example.admin.model.SysMenu;
import com.example.admin.service.SysMenuService;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * (SysMenu)表服务实现类
 *
 * @author makejava
 * @since 2022-04-08 16:58:27
 */
@Service("sysMenuService")
public class SysMenuServiceImpl extends ServiceImpl<SysMenuDao, SysMenu> implements SysMenuService {

    @Resource
    private SysMenuDao sysMenuDao;

    @Override
    public List<SysMenu> getMenu() {
        List<SysMenu> sysMenus = sysMenuDao.selectList(null);
        //获取父节点
        List<SysMenu> collect = sysMenus.stream().filter(m -> m.getParentid() == 0).map(
                (m) -> {
                    m.setChildList(getChildrens(m, sysMenus));
                    return m;
                }
        ).collect(Collectors.toList());
        return collect;
    }

    /**
     * 递归查询子节点
     *
     * @param root 根节点
     * @param all  所有节点
     * @return 根节点信息
     */
    private List<SysMenu> getChildrens(SysMenu root, List<SysMenu> all) {
        List<SysMenu> children = all.stream().filter(m -> {
            return Objects.equals(m.getParentid(), root.getMid());
        }).map(
                (m) -> {
                    m.setChildList(getChildrens(m, all));
                    return m;
                }
        ).collect(Collectors.toList());
        return children;
    }
}


7 数据表格

7.1 分页显示

<template>
<div class="userContainer">
  <el-form
           :model="userForm"
           ref="userForm">
    <h3 class="logonTitle">个人信息</h3>
    <span style="margin-left: 160px" class="el-dropdown-links">
      <el-upload
                 action="http://medicine-img.oss-cn-shanghai.aliyuncs.com"
                 :data="dataObj"
                 list-type="picture"
                 :multiple="false" :show-file-list="showFileList"
                 :file-list="fileList"
                 :before-upload="beforeUpload"
                 :on-remove="handleRemove"
                 :on-success="handleUploadSuccess"
                 :on-preview="handlePreview">
        <img title="点击修改头像"  :src="valueUrl"/>
  </el-upload>
      <!--                <SingleUpload v-model="userForm.avatar"></SingleUpload>-->
  </span>
    
    <el-row>
      <el-col :span="13">
        <el-form-item style="width: 180px" label="账号" prop="name">
          <el-input v-model="userForm.name" :disabled="true"></el-input>
  </el-form-item>
  </el-col>
      <el-col :span="11">
        <el-form-item label="真实姓名" prop="nickName">
          <el-input v-model="userForm.nickName" :disabled="true"></el-input>
  </el-form-item>
  </el-col>
  </el-row>
    <!--<el-form-item label="密码" prop="password">-->
    <!--<el-input type="password" v-model="userForm.password"></el-input>-->
    <!--</el-form-item>-->
    <el-form-item label="性别" prop="sex">
      <el-radio-group v-model="userForm.sex" style="margin-top: 8px;margin-left: 28px">
        <el-radio v-model="userForm.sex" :label="1">男</el-radio>
        <el-radio v-model="userForm.sex" :label="0">女</el-radio>
  </el-radio-group>
      
  </el-form-item>
    <el-form-item label="邮箱" prop="email"  :rules="[
                                                   { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
                                                   ]">
      <el-input type="email" v-model="userForm.email"></el-input>
  </el-form-item>
    <el-form-item label="手机号码" prop="mobile">
      <el-input v-model="userForm.mobile"></el-input>
  </el-form-item>
    <el-form-item label="过敏史" prop="allergy">
      <el-input type="textarea"
                v-model="userForm.allergy"></el-input>
  </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(userForm)">修改</el-button>
      <el-button type="success" @click="submitFormss">修改密码</el-button>
      <el-button @click="resetForm()">重置</el-button>
  </el-form-item>
  </el-form>
  <el-dialog
             :title="title"
             :visible.sync="dialogVisible"
             :modal="model"
             width="40%"
             >
    <div>
      <el-form :model="ruleForm" :rules="rules" status-icon  ref="ruleForm" label-width="100px" class="demo-ruleForm">
        <el-form-item label="原始密码" prop="pass">
          <el-input type="password" v-model="ruleForm.pass" show-password  autocomplete="off"></el-input>
  </el-form-item>
        <el-form-item label="新密码" prop="checkPass">
          <el-input type="password" v-model="ruleForm.checkPass"  show-password  autocomplete="off"></el-input>
  </el-form-item>
        <el-form-item label="确认密码" prop="pass2">
          <el-input type="password" v-model="ruleForm.pass2"   show-password autocomplete="off"></el-input>
  </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm1('ruleForm')" >提交</el-button>
          <el-button @click="resetForm1('ruleForm')">重置</el-button>
          <el-button @click="resetForm12('ruleForm')">取消</el-button>
  </el-form-item>
  </el-form>
  </div>
  </el-dialog>
  
  </div>
</template>

<script>
  import {getUUID, policy} from "../../components/upload/policy";
  export default {
    name: "UserMessage",
    props: {
      value: String
    },
    data() {
      var checkAge = (rule, value, callback) => {
        // console.log(value.length);
        if (!value) {
          return callback(new Error('原始密码不能为空'));
        }
        if (value.length < 5) {
          callback(new Error('密码必须大于6位数'));
        } else {
          callback();
        }
      };
      var validatePass = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请输入密码'));
        }
        callback();
        
      };
      var validatePass2 = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请再次输入密码'));
        } else if (value !== this.ruleForm.checkPass) {
          callback(new Error('两次输入密码不一致!'));
        } else {
          callback();
        }
      };
      return {
        ruleForm: {
          pass: '',
          checkPass: '',
          pass2: ''
        },
        rules: {
          checkPass: [
            { validator: validatePass }
          ],
          pass2: [
            { validator: validatePass2 }
          ],
          pass: [
            { validator: checkAge }
          ]
        },
        token: this.getToken(),
        userForm: {
          id:'56',
          name:'',//账号
          nickName: '',//真实姓名
          password: '',//密码
          email: '',//邮箱
          mobile: '',//电话
          sex: 1,//性别
          allergy: '',//过敏史
          avatar:''
        },
        valueUrl:'',
        title: '',//弹出层标题
        dialogVisible: false, //是否弹出,弹出层
        model: false,     //取消弹出层遮掩层
        dataObj: {
          policy: '',
          signature: '',
          key: '',
          ossaccessKeyId: '',
          dir: '',
          host: '',
          // callback:'',
        },
      }
    },
    computed: {
      imageUrl() {
        return this.value;
      },
      imageName() {
        if (this.value != null && this.value !== '') {
          return this.value.substr(this.value.lastIndexOf("/") + 1);
        } else {
          return null;
        }
      },
      fileList() {
        return [{
          name: this.imageName,
          url: this.imageUrl
        }]
      },
      showFileList: {
        get: function () {
          return this.value !== null && this.value !== ''&& this.value!==undefined;
        },
        set: function (newValue) {
        }
      }
    },
    mounted(){
      this.userGr();
    },
    methods:{
      emitInput(val) {
        this.$emit('input', val)
      },
      handleRemove(file, fileList) {
        this.emitInput('');
      },
      handlePreview(file) {
        this.dialogVisible = true;
      },
      beforeUpload(file) {
        let _self = this;
        return new Promise((resolve, reject) => {
          policy().then(response => {
            console.log(response)
            _self.dataObj.policy = response.obj.policy;
            _self.dataObj.signature = response.obj.signature;
            _self.dataObj.ossaccessKeyId = response.obj.accessid;
            _self.dataObj.key = response.obj.dir + '/'+getUUID()+'_${filename}';
            _self.dataObj.dir = response.obj.dir;
            _self.dataObj.host = response.obj.host;
            resolve(true)
          }).catch(err => {
            reject(false)
          })
        })
      },
      handleUploadSuccess(res, file) {
        console.log("上传成功...")
        this.showFileList = true;
        this.fileList.pop();
        this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
        this.emitInput(this.fileList[0].url);
      },
      submitForm1(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            this.postRequest('/passwrods?password='+this.ruleForm.pass+'&passwrod1='+this.ruleForm.pass2+'&id='+this.userForm.id).then(resp => {
              if (resp.data.code==200){
                window.sessionStorage.removeItem("token");
                this.getToken();
                
              }else {
                this.$message({
                  type: 'error',
                  message: resp.data.message
                });
              }
              
            });
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      res(){
        this.getToken();
      },
      resetForm1(formName) {
        this.$refs[formName].resetFields();
      },
      resetForm12(formName) {
        this.dialogVisible = false;
        this.$refs[formName].resetFields();
      },
      submitForm(row){
        var sReg = /[_a-zA-Z\d\-\.]+@[_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+$/;
        if (sReg.test(row.email)) {
          this.$confirm('此操作将修改个人信息, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            this.postRequest('/sysUser/upSysuser',row).then(resp => {
              const tokenStr = resp.data.obj;
              this.userForm=resp.data.obj;
              window.sessionStorage.setItem('user', JSON.stringify(tokenStr));
              this.$message({
                type: 'success',
                message: '修改成功!'
              });
            });
            
          }).catch(() => {
            this.$message({
              type: 'info',
              message: '已取消修改'
            });
          });
        }
        return false;
      },
      resetForm(){
        this.userForm.email='';
        this.userForm.mobile='';
        this.userForm.allergy='';
      },
      userGr(){
        var loginInfo=JSON.parse(window.sessionStorage.getItem("user"));
        var uRL = window.sessionStorage.getItem("avatar");
        this.userForm=loginInfo;
        this.valueUrl = uRL;
      },
      submitFormss(){
        this.title = '修改密码';
        this.dialogVisible = true;
      }
      ,
    }
  }
</script>



<style>
  .userContainer {
    border-radius: 15px;
    background-clip: padding-box;
    margin: 40px auto;
    width: 400px;
    padding: 15px 35px 15px 35px;
    background: #fff;
    border: 1px solid #eaeaea;
    box-shadow: 0 0 25px #cac6c6;
  }
  
  .el-dropdown-links {
    display: flex;
    text-align: center;
    line-height: 50px;
  }
  
  .el-dropdown-links img {
    width: 100px;
    height: 100px;
    border-radius: 50px;
  }
  
</style>

7.2 CURD

<template>
<div>
  <div style="display: flex;justify-content: space-between">
    <div>
      <el-input
                v-model="userName"
                @keydown.enter.native="initUser"
                clearable
                @clear="initUser"
                style="width: 300px;margin-right: 10px"
                prefix-icon="el-icon-search"
                placeholder="请输入姓名进行搜索..."/>
      <el-button
                 type="primary"
                 icon="el-icon-search"
                 @click="initUser">搜索
  </el-button>
      <el-button
                 type="primary"
                 icon="el-icon-plus"
                 @click="addClick">添加
  </el-button>
      <el-button
                 @click="deleteClicks"
                 type="danger"
                 icon="el-icon-delete">批量删除
  </el-button>
  </div>
  </div>
  <div style="margin-top: 10px;width: 80%;">
    <el-table
              :data="tableData"
              stripe
              border
              v-loading="loading"
              element-loading-text="正在加载中"
              element-loading-spinner="el-icon-loading"
              element-loading-background="rgba(0, 0, 0, 0.8)"
              style="width: 100%">
      <el-table-column
                       type="selection"
                       width="55">
  </el-table-column>
      <el-table-column
                       prop="uid"
                       label="Id"
                       fixed
                       align="left"
                       width="70">
  </el-table-column>
      <el-table-column
                       prop="uname"
                       label="姓名"
                       align="left"
                       width="120">
  </el-table-column>
      <el-table-column
                       prop="phone"
                       label="手机号码"
                       align="left"
                       width="180">
  </el-table-column>
      <el-table-column
                       prop="mailbox"
                       label="邮箱"
                       align="left"
                       width="180">
  </el-table-column>
      <el-table-column
                       prop="userDate"
                       label="注册时间"
                       align="left"
                       width="240">
  </el-table-column>
      <el-table-column
                       prop="userState"
                       label="状态"
                       align="left"
                       width="110">
        <template slot-scope="scope">
          <span v-if="scope.row.userState ==0">已禁用</span>
          <span v-else-if="scope.row.userState ==1">已启用</span>
</template>
</el-table-column>
<el-table-column
                 label="操作"
                 fixed="right"
                 width="200">
  <template slot-scope="scope">
<el-button @click="updateClick(scope.row)" style="padding: 3px" size="mini">编辑</el-button>
<el-button @click="deleteClick(scope.row)" style="padding: 3px" size="mini" type="danger">删除
    </el-button>
  </template>
</el-table-column>
</el-table>
<div style="display: flex;justify-content: flex-end">
  <el-pagination
                 background
                 @current-change="currentChange"
                 @size-change="sizeChange"
                 :page-sizes="[2, 10, 20, 30]"
                 :page-size="2"
                 layout="sizes, prev, pager, next, jumper, ->, total"
                 :total="total">
  </el-pagination>
</div>
</div>
<!--弹出框:添加--->
<div>
  <el-dialog
             :title="title"
             :visible.sync="dialogVisible"
             width="40%">
    <div>
      <el-form ref="userForm" :rules="rules" :model="userForm">
        <el-row>
          <el-col :span="12">
            <el-form-item required label="姓名" prop="uname">
              <el-input style="width: 200px" placeholder="请输入姓名"
                        v-model="userForm.uname"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item required label="密码" prop="upassword">
              <el-input style="width: 200px" placeholder="请输入密码"
                        v-model="userForm.upassword"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="手机号码" prop="phone">
              <el-input style="width: 200px" placeholder="请输入手机号码"
                        v-model="userForm.phone"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item required label="邮箱" prop="mailbox">
              <el-input style="width: 200px" placeholder="请输入邮箱"
                        v-model="userForm.mailbox"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <span slot="footer" class="dialog-footer">
      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
      <el-button size="small" type="primary" @click="doAdd">确 定</el-button>
    </span>
  </el-dialog>
</div>
<!--弹出框:编辑-->
<div>
  <el-dialog
             :title="title"
             :visible.sync="dialogVisibles"
             width="40%">
    <div>
      <el-form ref="userUpdateForm" :rules="rules" :model="userUpdateForm">
        <el-row>
          <el-col :span="12">
            <el-form-item required label="姓名" prop="uname">
              <el-input style="width: 200px" placeholder="请输入姓名"
                        v-model="userUpdateForm.uname"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item required label="密码" prop="upassword">
              <el-input style="width: 200px" placeholder="请输入密码"
                        v-model="userUpdateForm.upassword"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="手机号码" prop="phone">
              <el-input style="width: 200px" placeholder="请输入手机号码"
                        v-model="userUpdateForm.phone"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item required label="邮箱" prop="mailbox">
              <el-input style="width: 200px" placeholder="请输入邮箱"
                        v-model="userUpdateForm.mailbox"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <span slot="footer" class="dialog-footer">
      <el-button size="small" @click="dialogVisibles = false">取 消</el-button>
      <el-button size="small" type="primary" @click="doUpdate">确 定</el-button>
    </span>
  </el-dialog>
</div>
</div>
</template>

<script>
  export default {
    name: "Home",
    data() {
      return {
        tableData: [{//表格数据
          uid: '1',
          uname: 'admin',
          phone: '10086',
          mailbox: '295@qq.com',
          userState: 1,
          userDate: '2021-5-15',
        }],
        currentPage: 1,//第一页
        size: 5,//显示数量
        loading: false,//进度条
        total: 0,//有几条数据
        title: '',//弹出层标题
        dialogVisible: false, //是否弹出,弹出层
        dialogVisibles: false, //是否弹出,弹出层
        userForm: {//添加数据
          uname: '',
          upassword: '',
          phone: '',
          mailbox: '',
        },
        userUpdateForm: {//修改数据
          uid: '',
          uname: '',
          upassword: '',
          phone: '',
          mailbox: '',
        },
        rules: {//验证器
          uname: [{required: true, message: '请输入姓名', trigger: 'blur'}],
          upassword: [{required: true, message: '请输入密码', trigger: 'blur'}],
          phone: [{required: true, message: '请输入电话号码', trigger: 'blur'}],
          mailbox: [{required: true, message: '请输入邮箱地址', trigger: 'blur'}, {
            type: 'email',
            message: '邮箱地址格式不正确',
            trigger: 'blur'
          }],
        },
        userName: '',//搜索框
        
      }
    },
    mounted() {
      //刷新表格数据
      this.initUser();
    },
    methods: {
      //显示页数
      sizeChange(size) {
        this.size = size;
        this.initUser();
      },
      //页数比如:第几页
      currentChange(currentPage) {
        this.currentPage = currentPage;
        this.initUser();
      },
      //编辑
      updateClick(data) {
        // console.log(data);
        this.title = "编辑信息";
        this.dialogVisibles = true;
        this.userUpdateForm = data;
      },
      //确定修改
      doUpdate() {
        // console.log(this.userForm)
        this.$refs['userUpdateForm'].validate(valid => {
          if (valid) {
            this.putRequest('/user/put', this.userUpdateForm).then(resp => {
              if (resp) {
                this.dialogVisibles = false;
                console.log(JSON.stringify(resp.data));
                // this.userForm = {};
              }
            })
          } else {
            this.$message.error('请输入所有字段');
            return false;
          }
        })
      },
      //删除
      deleteClick(data) {
        // console.log(row);
        this.$confirm('此操作将永久删除 ' + data.uname + ', 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.deleteRequest('/user/' + data.uid).then(resp => {
            if (resp) {
              console.log(JSON.stringify(resp.data));
              //重载表格
              this.initUser();
            }
          });
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });
        });
      },
      //添加
      addClick() {
        this.title = "添加信息";
        this.dialogVisible = true;
        this.userForm = {};
        
      },
      //批量删除
      deleteClicks() {
        
      },
      //确定添加
      doAdd() {
        this.$refs['userForm'].validate(valid => {
          if (valid) {
            this.postRequest('/user/addUser/', this.userForm).then(resp => {
              if (resp) {
                this.dialogVisible = false;
                this.initUser();
                console.log(JSON.stringify(resp.data))
              }
            })
          } else {
            this.$message.error('请输入所有字段');
            return false;
          }
        })
      },
      //表格数据
      initUser() {
        this.loading = true;
        // console.log(this.currentPage + "--" + this.size + "--" + this.userName);
        if (this.userName == '') {
          this.getRequest('/user/page/?currentPage=' + this.currentPage + '&size=' + this.size).then(resp => {
            this.loading = false;
            if (resp) {
              // console.log("数据:" + JSON.stringify(resp.data));
              this.tableData = resp.data.data;//表格数据
              this.total = resp.data.total;
            }
          })
        } else {
          this.getRequest('/user/pagelike/?currentPage=' + this.currentPage + '&size=' + this.size + '&name=' + this.userName).then(resp => {
            this.loading = false;
            if (resp) {
              // console.log("数据2:" + JSON.stringify(resp.data));
              this.tableData = resp.data.data;//表格数据
              this.total = resp.data.total;
            }
          })
        }
        
      }
    }
  }
</script>

<style scoped>
  
</style>
Logo

前往低代码交流专区

更多推荐