Vue+Element-UI入门

1 创建项目

1.1 Vue 2.0

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

vue init webpack test 	创建项目

Vue+Element-UI入门_第1张图片

cd test         进入刚刚创建的项目

npm run dev   	启动项目

1.2 Vue 3.0

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

vue create test     创建项目

根据下面图片选择配置:空格是选择,回车是确认!!
Vue+Element-UI入门_第2张图片

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

错误解决:
Vue+Element-UI入门_第3张图片
检查代码中并无写错的地方
其实涉及到这个问题,就是版本原因了,我安装的 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文件
Vue+Element-UI入门_第4张图片

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







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 前端代码


  
  
  

//获取验证码
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();
        }
    }
}

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 首页







6 侧边栏

6.1 静态菜单栏

6.1.1 页面

创建4个Test.vue页面方便测试
Vue+Element-UI入门_第5张图片

<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 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
表字段:
Vue+Element-UI入门_第6张图片
后端响应菜单栏的格式

[
  {
    "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();
    }
});

	
		
		
			{{children.name}}
		
	




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 分页显示








7.2 CURD






你可能感兴趣的:(vue,vue.js,spring,boot,前端,vue,java)