SpringBoot + Vue前后端项目分离后台管理系统 笔记 (以及错误总结) 第一篇 前端笔记

707 阅读3分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

学习视频的来源(链接)

B站的 博主 ps (讲的很不错)

前端的学习笔记记录

安装前端的Vue 环境 ,新建 Vue 项目

安装node.js

官网 地址 在这里插入图片描述 安装后傻瓜操作就可以 检查是否安装完成 (命令) 下面是我的版本 在这里插入图片描述 出现上面的版本信息说明安装成功

安装vue 的环境

命令

# 安装淘宝的 npm 也可以不安装,不过安装了国内的镜像会快
npm install -g cnpm --registry=https://registry.npm.taobao.org
# vue - cli 安装依赖包
cnpm install --g vue-cli
# 打开 vue 的可视化 管理界面 这个可以用可视化创建 vue 的项目,也可以不用 ,我用的命令行创建 vue 的项目
vue ui

创建 vue 的环境

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

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 剩下的一直下一步就可以了 在项目的目录下 运行就可以看到 在这里插入图片描述 在这里插入图片描述

设置在IDEA 里面输入命令访问 在这里插入图片描述 把项目导入到 IDEA 在这里插入图片描述

在这里插入图片描述

我的另一篇安装Vue 的博客 Vue 笔记

安装 element-ui,axios、qs、mockjs

#  安装 element-ui
cnpm install element-ui --save
#  安装 axios
cnpm install axios --save
# 安装 cnpm install qs --save
cnpm install qs --save
# 安装 mockjs 
cnpm install mockjs --save-dev
  1. axios:一个基于 promise 的 HTTP 库,ajax
  2. qs:查询参数序列化和解析库
  3. mockjs:为我们生成随机数据的工具

**Mockjs ** 在 src 目录下创建 mock.js 文件,用来编写随机的api 现在还没有和后端交互,所以先用假的数据 同时在main.js 中引入这个文件 在这里插入图片描述 main.js 文件

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
//引入mock数据,关闭则注释该行
require("./mock") 
Vue.config.productionTip = false;
new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

引入 element-ui ,axios 然后我们打开项目src目录下的main.js,引入element-ui依赖 引入axios。

import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
import axios from 'axios'
Vue.use(Element)

页面路由

登录和首页的创建

Router WebApp 的链路路径管理系统,就是建立起url 和页面之间的映射关系 主要在 src\router\index.js 就是来配置路由的 创建我们的登录页面和首页页面 在这里插入图片描述

在路由中心配置 url 与Vue 页面的映射关系 可以参考原来默认的写法 ./src/router/index.js

import Vue from "vue";
import VueRouter from "vue-router";
import Login from "../views/Login";
Vue.use(VueRouter);
const routes = [
  {
    path: "/",
    name: "Home",
    component: () => import('../views/Home.vue'),
  },
  {
    path: "/login",
    name: "Login",
    component: Login
  },
];
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
});

export default router;

修改完 index.js 页面后 我们 启动vue 的项目

npm run serve

启动完成后访问 localhost:8081/login 页面发现页面如下所示 ,出现了 Home | About

在这里插入图片描述 原因: 新建的vue 项目没有其他配置,默认就是一个单页面应用,就是说这个应用是由一个外壳页面,和多个页面,组成在跳转的时候没有离开外壳的页面,这个的外壳压面就是App.vue 登录的页面就是一个片段而已,我们应该修改我们的 App.vue 页面 在这里插入图片描述 修改 后的App.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>
<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

**修改完成后在次查看 ** 在这里插入图片描述

登录开发

登录的开发流程

需要去element-ui 上找到 表单的组件,简单的登录页面,但是登录页面的验证码需要与后台进行交互 主要登录与后台交互的有两个 1,获取登录的验证码 2,提交登录表单完成登录 由于还没有写后端的代码,所以先在我们的mock.js 里写数据,完成交互。开发api 交互的过程 1,打开登录的界面 2,动态加载登录验证码,前后端分离的项目,我们不在使用session 进行交互,所以后端警用session 后端可以随机生成一个验证码的同时生成一个随机码,把随机码作为 key,验证码为value 保存到redis 中,然后把随机码和验证码图片的Base64 字符串码发送到前端 3,前端提交用户名,密码,验证码,还有随机码 4,后台验证是否正确 大概的流程图 在这里插入图片描述

登录的页面

<template>

  <el-row type="flex" class="row-bg" justify="center">
    <el-col :xl="6" :lg="7">
      <h2>欢迎来到fjj 管理系统</h2>
      <el-image :src="require('@/assets/img.png')" style="height: 180px; width: 180px;"></el-image>
      <p>扫码二维码,添加个人微信</p>

    </el-col>
    <el-col :span="1">
      <el-divider direction="vertical"></el-divider>
    </el-col>
    <el-col :span="6" :lg="7">
      <el-form :model="loginForm" :rules="rules" ref="loginForm" label-width="80px">
        <el-form-item label="用户名" prop="username" style="width: 380px;">
          <el-input v-model="loginForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password"  style="width: 380px;">
          <el-input v-model="loginForm.password" type="password"></el-input>
        </el-form-item>
        <el-form-item label="验证码" prop="code"  style="width: 380px;">
          <el-input v-model="loginForm.code"  style="width: 172px; float: left" maxlength="5"></el-input>
          <el-image :src="captchaImg" class="captchaImg" @click="getCaptcha"></el-image>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="submitForm('loginForm')">立即创建</el-button>
          <el-button @click="resetForm('loginForm')">重置</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script>
export default {
  name: "Login",
  data () {
    return {
      loginForm: {
        username: '',
        password: '',
        code: '',
        token: ''
      },
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' }
        ],
        code: [
          { required: true, message: '请输入验证码', trigger: 'blur' },
          { min: 5, max: 5, message: '长度为 5 个字符', trigger: 'blur' }
        ]
      },
      captchaImg: null

    }
  },
  methods: {
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$axios.post('/login?' ,this.loginForm).then(res => {
           
          })
        } else {
          console.log('错误的提交')
          return false
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
    }
  }
}
</script>

<style scoped>
.el-row {
  /*background-color: #fafafa;*/
  height: 100%;
  display: flex;
  align-items: center;
  text-align: center;
  justify-content: center;
  margin-top: 10%;
}

.el-divider {
  height: 200px;
}

.captchaImg {
  float: left;
  margin-left: 8px;
  border-radius: 4px;
}
</style>

效果 我们的验证码还没有显示出来效果 这个图片二维码是在这里引入的 在这里插入图片描述

在这里插入图片描述

验证码

此时我们的验证码还没有显示出来,没有与后台交互先用mock 做验证码 在这里插入图片描述

先在data 里面设置为 null 在这里插入图片描述 ** getCaptcha () 的方法。调用创建图片的方法**

    getCaptcha () {
      this.$axios.post('/captcha').then(res => {
        this.loginForm.token = res.data.data.token
        this.captchaImg = res.data.data.captchaImg
      })
    }

在这里插入图片描述 创建完成之后不要忘记使用这个方法 在下面调用这个方法 在这里插入图片描述 **代码 ,只放了export default 的内容 **

<script>
export default {
  name: "Login",
  data () {
    return {
      loginForm: {
        username: '',
        password: '',
        code: '',
        token: ''
      },
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' }
        ],
        code: [
          { required: true, message: '请输入验证码', trigger: 'blur' },
          { min: 5, max: 5, message: '长度为 5 个字符', trigger: 'blur' }
        ]
      },
      captchaImg: null

    }
  },
  methods: {
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          // eslint-disable-next-line no-unused-vars
          this.$axios.post('/login?' ,this.loginForm).then(res => {

          })
        } else {
          console.log('错误的提交')
          return false
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
    },
    getCaptcha () {
      this.$axios.post('/captcha').then(res => {
        // res 为结果 结果后的data 里面取到我们的 token 值
        // 其实就是我们 后台返回的一个结果,如果正确的话里面会有一个data 的值在
        this.loginForm.token = res.data.data.token
        // 同理拿出来我们的图片
        this.captchaImg = res.data.data.captchaImg
      })
    }
  },
  created() {
    this.getCaptcha()
  }
}
</script>

<style scoped>
.el-row {
  /*background-color: #fafafa;*/
  height: 100%;
  display: flex;
  align-items: center;
  text-align: center;
  justify-content: center;
  margin-top: 10%;
}

.el-divider {
  height: 200px;
}
.captchaImg {
  float: left;
  margin-left: 8px;
  border-radius: 4px;
}
</style>

在我们的mock.js 里面写数据

// 创建放回的对象
const Mock = require('mockjs')
// 获取随机的 Random
const Random = Mock.Random
// 设置返回结果
let Result = {
    code: 200,
    msg: '操作成功',
    data: null
}
// 图片的请求
Mock.mock('/captcha','post',()=> {
    Result.data = {
        token: Random.string(32),
        captchaImg: Random.dataImage('120x40','jikof')
    }
    return Result
})

此时我们的效果,现在的验证码是mock 随机生成 的 与后端交互的时候给成后端的api 接口就可以了 在这里插入图片描述

登录的请求

**由于我们登录的时候要求把我们的后台的数据放在 localStorage 里面要在 ./src/store/index.js 里面写 这样就可以存到localStorage **

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    token: ''
  },
  mutations: {
    SET_TOKEN: (state,token) => {
      state.token = token
      localStorage.setItem("token",token)
    }
  },
  actions: {},
  modules: {},
});

在我们的 Login.vue 里,把我们的jwt 存到 设置登录成功后跳到首页 在这里插入图片描述 一样还是写我们的mock 的请求 测试数据 暂时没有办法把 jwt 给放入,所以测试的时候没有jwt 身份放入,先直接放入,等与后台交互的时候在放入里面 在这里插入图片描述 登录后跳转到首页

在这里插入图片描述 存到了 只是暂时没有与后台交互,所以 暂时是 undefined 在这里插入图片描述

小小的错误记录总结 引入 axios 的错误

这里出现了一个小小的错误记录一下 在这里插入图片描述 可是在 用 axios 请求的时候 报错 在这里插入图片描述 在这里插入图片描述

解决方法 在这里插入图片描述 在Login.vue 里面也更换一下 在这里插入图片描述 在这里插入图片描述 请求就可以发送出去了

设置登录时候的全局 axios

这里要设置全局的原因是因为登录失败,我们是需要弹窗显示错误的,比如验证码错误,用户名或者密码不正确等,不仅仅是这个登录接口,所有的接口调用都会有这个情况,所有我们想做个拦截器,对返回的结果进行分析,如果是异常就直接弹出错误,这样我们就省下来每个接口都写一遍 在src目录下创建一个文件axios.js (与main.js 同级)

import axios from 'axios'
import router from './router'
import Element from 'element-ui'
axios.defaults.baseURL = 'http://localhost:8081'
const request = axios.create({
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    }
})

request.interceptors.request.use(config => {
    config.headers.Authorization = localStorage.getItem('token')
    return config
})

request.interceptors.response.use(
    response => {
        console.log('response ->' + response)

        const res = response.data

        if (res.code === 200) {
            return response
        } else {
            Element.Message.error(!res.msg ? '系统异常' : res.msg)
            return Promise.reject(response.data.msg)
        }
    },
    error => {
        console.log(error)

        if (error.response.data) {
            error.massage = error.response.data.msg
        }

        if (error.response.status === 401) {
            router.push('/login')
        }

        Element.Message.error(error.massage, {duration: 3000})
        return Promise.reject(error)
    }
)

export default request

前置拦截,可以统一为所有权限的请求装配上header 的 token 的信息,后置拦截中,判断status.code 和 error.response.status 如果是401 未登录没权限的就调用登录页面,其他的就直接弹窗显示错误。 我们需要在原来的main.js 更改成我们自己的axios 的js 在这里插入图片描述 测试检查一下,我们先在mock .js 里面输入错误的转态吗 在这里插入图片描述 效果 在这里插入图片描述

后台界面开发

在这里插入图片描述 这里我选择的是

<el-container>
  <el-header>Header</el-header>
  <el-container>
    <el-aside width="200px">Aside</el-aside>
    <el-main>Main</el-main>
  </el-container>
</el-container>

创建一个Index 的页面 这里我们需要 先放在这里,一会可以把公共的抽离出来 记得复制样式 在这里插入图片描述 添加到路由里面 在这里插入图片描述 效果 在这里插入图片描述 添加一个样式设置一下高度 在这里插入图片描述

头部导航栏的设置

Index.vue 页面

<template>
  <el-container>
    <el-aside width="200px">
      <div>菜单栏</div>
    </el-aside>
    <el-container>
      <el-header style="height: 55px;"><Strong>ManHub后台管理系统</Strong>
        <div class="header-avatar block">
          <el-avatar  src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
          <el-dropdown><span class="el-dropdown-link">
            fjj<i class="el-icon-arrow-down el-icon--right"></i>
          </span>
            <el-dropdown-menu slot="dropdown">
              <router-link to="/userCenter">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item @click.native="logout">退出</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
          <el-link href="http://markerhub.com">网站</el-link>
        </div>
      </el-header>
      <el-main>
        <div style="margin: 0 15px;">
          <router-view></router-view>
        </div>
      </el-main>
    </el-container>
  </el-container>
</template>

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

<style scoped>
/*下拉框的css*/
.el-dropdown-link {
  cursor: pointer;
  color: black;
}

.el-icon-arrow-down {
  font-size: 12px;
}

/*设置头部导航的样式*/
.header-avatar {
  float: right;
  width: 210px;
  display: flex;
  justify-content: space-around;
  align-items: center;
  text-align: center;
}

/*导航栏的css*/
.el-container {
  padding: 0vh;
  margin: 0;
  height: 100vh;
}

.el-header {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 200px;
}

.el-main {
  color: #333;
  padding-left: 20px;
}

/*取除a 标签的下划线*/
a {
  text-decoration: none;
}

/*设置 链接滑上去变成小手*/
.el-dropdown-link {
  cursor: pointer;
  color: black;
}

/*设置侧边栏的高度*/
.el-menu-vertical-demo {
  height: 100vh;
}

/*取除a 标签的下划线*/
a {
  text-decoration: none;
}

</style>

效果 在这里插入图片描述

侧边导航栏的设置

**差不多感觉是对的,在 element-ui 上找到菜单栏的组件,添加到Home .vue 里面,但是要做成动态的菜单,所以可以单独的拿出来,新建一个SideMenu.vue ** 在这里插入图片描述

<template>
  <el-menu class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
    <router-link to="/index">
      <el-menu-item index="Index">
        <template slot="title"><i class="el-icon-s-home"></i> <span slot="title">首页</span></template>
      </el-menu-item>
    </router-link>
    <el-submenu index="1">
      <template slot="title"><i class="el-icon-s-operation"></i> <span>系统管理</span></template>
      <el-menu-item index="1-1">
        <template slot="title"><i class="el-icon-s-custom"></i> <span slot="title">用户管理</span></template>
      </el-menu-item>
      <el-menu-item index="1-2">
        <template slot="title"><i class="el-icon-rank"></i> <span slot="title">角色管理</span></template>
      </el-menu-item>
      <el-menu-item index="1-3">
        <template slot="title"><i class="el-icon-menu"></i> <span slot="title">菜单管理</span></template>
      </el-menu-item>
    </el-submenu>
    <el-submenu index="2">
      <template slot="title"><i class="el-icon-s-tools"></i> <span>系统工具</span></template>
      <el-menu-item index="2-2">
        <template slot="title"><i class="el-icon-s-order"></i> <span slot="title">数字字典</span></template>
      </el-menu-item>
    </el-submenu>
  </el-menu>
</template>

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

<style scoped>
/*设置侧边栏的高度*/
.el-menu-vertical-demo {
  height: 100vh;
}
/*取除a 标签的下划线*/
a {
  text-decoration: none;
}
</style>

此时SideMenu.vue 作为一个组件添加到Home.vue 中,我们需要导入,声明组件,才能使用标签,所以应该在Index.vue 中 声明就可以使用了 在这里插入图片描述

在这里插入图片描述 效果 在这里插入图片描述 把整体的框架移除到Home.vue 里面,在Index 里面只留下中间的 内容 移除完之后的Home.vue

<template>
  <el-container>
    <el-aside width="200px">
      <SideMenu></SideMenu>
    </el-aside>
    <el-container>
      <el-header style="height: 55px;"><Strong>ManHub后台管理系统</Strong>
        <div class="header-avatar block">
          <el-avatar  src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
          <el-dropdown><span class="el-dropdown-link">
            fjj<i class="el-icon-arrow-down el-icon--right"></i>
          </span>
            <el-dropdown-menu slot="dropdown">
              <router-link to="/userCenter">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item @click.native="logout">退出</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
          <el-link href="http://markerhub.com">网站</el-link>
        </div>
      </el-header>
      <el-main>
        <div style="margin: 0 15px;">
          <router-view></router-view>
        </div>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import SideMenu from "./inc/SideMenu";

export default {
  name: "Home",
  components: {SideMenu}
}
</script>

<style scoped>
/*下拉框的css*/
.el-dropdown-link {
  cursor: pointer;
  color: black;
}

.el-icon-arrow-down {
  font-size: 12px;
}

/*设置头部导航的样式*/
.header-avatar {
  float: right;
  width: 210px;
  display: flex;
  justify-content: space-around;
  align-items: center;
  text-align: center;
}

/*导航栏的css*/
.el-container {
  padding: 0vh;
  margin: 0;
  height: 100vh;
}

.el-header {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 200px;
}

.el-main {
  color: #333;
  padding-left: 20px;
}

/*取除a 标签的下划线*/
a {
  text-decoration: none;
}

/*设置 链接滑上去变成小手*/
.el-dropdown-link {
  cursor: pointer;
  color: black;
}

/*设置侧边栏的高度*/
.el-menu-vertical-demo {
  height: 100vh;
}

/*取除a 标签的下划线*/
a {
  text-decoration: none;
}
</style>

移除完之后的index.vue

<template>
<div>中间部分</div>
</template>

<script>

export default {
  name: "Index"

}
</script>

<style scoped>


</style>

这个时候当我们访问Index的时候只有中间的部分没有 整个框架是不行的 在这里插入图片描述 应该把index.vue 作为 Home 的子路由 ,这样当我们访问index 的时候就会显示父级的路由了 第一步 修改路由 在这里插入图片描述 第二步,修改 Home.vue 在这里插入图片描述

加上这句话,这个时候,我们在看效果的时候

在这里插入图片描述

侧边导航栏的路由

新建几个页面,先在views 下新建文件夹,然后再新建vue 页面。 在这里插入图片描述

添加路由到index.js 中 在这里插入图片描述 这个时候我们点击左边的用户管理都还不会点击到页面的链接修改Home 的页面 在这里插入图片描述 效果 在这里插入图片描述

用户登录信息展示,以及退出登录清除 浏览器缓存

管理界面的右上角是被写死的,我们现在登录成功,所以可以通过接口去请求获取到当前的用户信息,这样就可以动态显示用户信息了 Home.vue

在这里插入图片描述 在这里插入图片描述 在 mock.js 里面写请求以及测试数据

// 个人中心的测试数据
Mock.mock('/sys/userInfo','get',() =>{
    Result.data = {
        id: '1',
        username :'冯娇娇',
        avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
    }
    return Result
})

效果 在这里插入图片描述 退出登录的 在这里插入图片描述

mock.js 数据

// 退出登录的
Mock.mock('/logout', 'post', () => {
    return Result
})

个人中心的界面

创建个人中心的vue 在这里插入图片描述

<template>
  <div style="text-align: center;">
    <h2>你好!{{ userInfo.username }} 同学</h2>

    <el-form :model="passForm" status-icon :rules="rules" ref="passForm" label-width="100px">
      <el-form-item label="旧密码" prop="currentPass">
        <el-input type="password" v-model="passForm.currentPass" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="新密码" prop="password">
        <el-input type="password" v-model="passForm.password" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="确认密码" prop="checkPass">
        <el-input type="password" v-model="passForm.checkPass" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('passForm')">提交</el-button>
        <el-button @click="resetForm('passForm')">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'Login',
  data () {
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请再次输入密码'))
      } else if (value !== this.passForm.password) {
        callback(new Error('两次输入密码不一致!'))
      } else {
        callback()
      }
    }
    return {
      userInfo: {

      },
      passForm: {
        password: '',
        checkPass: '',
        currentPass: ''
      },
      rules: {
        password: [
          { required: true, message: '请输入新密码', trigger: 'blur' },
          { min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
        ],
        checkPass: [
          { required: true, validator: validatePass, trigger: 'blur' }
        ],
        currentPass: [
          { required: true, message: '请输入当前密码', trigger: 'blur' }
        ]
      }
    }
  },
  created () {
    this.getUserInfo()
  },
  methods: {
    getUserInfo () {
      this.$axios.get('/sys/userInfo').then(res => {
        this.userInfo = res.data.data
      })
    },
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          const _this = this
          this.$axios.post('/sys/user/updatePass', this.passForm).then(res => {
            _this.$alert(res.data.msg, '提示', {
              confirmButtonText: '确定',
              callback: action => {
                this.$refs[formName].resetFields()
              }
            })
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
    }
  }
}
</script>

<style scoped>
.el-form {
  width: 420px;
  margin: 50px auto;
}
</style>

需要在 Home .vue 里写这句话 在这里插入图片描述 在index 里面设置我们的路由就可以了 在这里插入图片描述 在这里插入图片描述

点击之后 在这里插入图片描述

动态菜单栏开发

上面的代码中,左侧的菜单栏的数据是写死的,在实际情况中不应该是写死的,因为菜单是需要根据登录用户的权限动态显示菜单的,也就是用户看到的菜单栏可能是不一样的,这些数据需要去后端访问获取到 应该把数据简化成一个json 数组数据,然后for 循环展示出来,代码如下 SideMenu.vue

<template>
  <el-menu class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
    <router-link to="/index">
      <el-menu-item index="Index">
        <template slot="title"><i class="el-icon-s-home"></i> <span slot="title">首页</span></template>
      </el-menu-item>
    </router-link>
    <el-submenu :index="menu.name" v-for="menu in menuList" :key="menu.name">
      <template slot="title">
        <i :class="menu.icon"></i>
        <span>{{menu.title}}</span>
      </template>
      <router-link :to="item.path" v-for="item in menu.children" :key="item.path">
        <el-menu-item :index="item.name" @click="selectMenu(item)">
          <template slot="title">
            <i :class="item.icon"></i>
            <span slot="title">{{item.title}}</span>
          </template>
        </el-menu-item>
      </router-link>
    </el-submenu>

  </el-menu>
</template>

<script>
export default {
  name: "SideMenu",
  data() {
    return {
      menuList: [{
        name: 'SysManga',
        title: '系统管理',
        icon: 'el-icon-s-operation',
        path: '',
        component: '',
        children: [{
          name: 'SysUser',
          title: '用户管理',
          icon: 'el-icon-s-custom',
          path: '/user',
          children: []
        }]
      }, {
        name: 'SysTools',
        title: '系统工具',
        icon: 'el-icon-s-tools',
        path: '',
        children: [{
          name: 'SysDict',
          title: '数字字典',
          icon: 'el-icon-s-order',
          path: '/sys/dicts',
          children: []
        },]
      }],
    }
  }
}
</script>

<style scoped>
/*设置侧边栏的高度*/
.el-menu-vertical-demo {
  height: 100vh;
}

/*取除a 标签的下划线*/
a {
  text-decoration: none;
}
</style>

可以看到,用 for 循环显示数据,那么这样变动菜单栏的时候只需要修改data 中的menuList 即可,效果和之前的完全一样。现在menuList 的数据我们是直接写到页面data 上的,一般我们是要请求后端的,所以需要定义一个mock 的接口,应为是动态的菜单,我们也要考虑到权限问题,所以我们请求数据的时候一般出来动态菜单,还要权限的数据,比如菜单的添加,删除是否有权限,是否能显示等。。 Mock.js

Mock.mock('/sys/menu/nav', 'get', () => {
// 菜单json
 let nav = [     
  {      
     name: 'SysManga',
      ...
       },
     {       
       name: 'SysTools',    
            ...    
              }   
          ]
          // 权限数据
          let authoritys = ['SysUser', "SysUser:save"]
          Result.data = {} 
          Result.data.nav = nav  
          Result.data.authoritys = authoritys  
          return Result       

定义好导航菜单的接口,应该在登录成功完成之后调用,但是并不是每一次打开都需要登录,也就是浏览器已经存储到用户token 的时候我们不需要再去登录了所以我们不能放在登录完成的方法里了。 这里需要考虑一个问题,就是导航菜单的路由问题,当我们点击菜单之后路由到那个页面是需要在 router 中声明 解决方案: 动态渲染,把加载到导航菜单数据动态绑定路由 把加载菜单数据这个动作放在 router.js 中,Router 有个前缀拦截,就是在路由到页面之前我们可以做一些判断或者加载数据

在router.js中添加一下代码: src/router/index.js

// 动态导航栏的
router.beforeEach((to, from, next) => {
    let hasRoute = store.state.menus.hasRoute
    const token = localStorage.getItem('token')
    if (to.path === '/login') {
        next()
    } else if (!token) {
        next({ path: '/login' })
    }
    if (token && !hasRoute) {
        axios.get('/sys/menu/nav', {
            headers: {
                Authorization: localStorage.getItem('token')
            }
        }).then(res => {
            console.log(res.data.data)
            store.commit('setMenuList', res.data.data.nav)
            store.commit('setPermList', res.data.data.authoritys)
            // 动态绑定路由
            const newRoutes = router.options.routes
            res.data.data.nav.forEach(menu => {
                if (menu.children) {
                    menu.children.forEach(e => {
                        const route = menuToRoute(e)
                        if (route) {
                            newRoutes[0].children.push(route)
                        }
                    })
                }
            })
            console.log(newRoutes)
            console.log('newRoutes')
            router.addRoutes(newRoutes)
            hasRoute = true
            store.commit('changeRouteStatus', hasRoute)
        })
    }

    next()
})
const menuToRoute = (menu) => {
    if (!menu.component) {
        return null
    }
    // 复制到属性
    const route = {
        path: menu.path,
        name: menu.name,
        meta: {
            icon: menu.icon,
            title: menu.title
        }
    }
    route.component = () => import('@/views/' + menu.component + '.vue')
    console.log(route)
    console.log('route')
    return route
}
export default router

可以看到,我们通过menuToRoute 就是把menu 数据 转换成路由对象,然后router.addRoutes(newRoutes)动态添加路由对象同时上面的menu对象中,有个menu.component,这个就是连接对应的组件,我们需要添加上去,比如说/sys/users链接对应到component(sys/User)。同时上面的menu对象中,有个menu.component,这个就是连接对应的组件,我们需要添加上去,比如说/sys/users链接对应到component(sys/User)。

 // 菜单的json
    const nav = [{
        name: 'SysManga',
        title: '系统管理',
        icon: 'el-icon-s-operation',
        component: '',
        path: '',
        children: [
            {
                title: '用户管理',
                icon: 'el-icon-s-custom',
                path: '/sys/users',
                name: 'SysUser',
                component: 'sys/User',
                children: []
            },
            {
                name: 'SysRole',
                title: '角色管理',
                icon: 'el-icon-rank',
                path: '/sys/roles',
                component: 'sys/Role',
                children: []
            },
            {
                name: 'SysMenu',
                title: '菜单管理',
                icon: 'el-icon-menu',
                path: '/sys/menus',
                component: 'sys/Menu',
                children: []
            }
        ]
    }, {
        name: 'SysTools',
        title: '系统工具',
        icon: 'el-icon-s-tools',
        path: '',
        component: '',
        children: [
            {
                title: '数字字典',
                icon: 'el-icon-s-order',
                path: '/sys/dicts',
                component: '',
                children: []
            }]
    }]

同时上面router中我们还通过判断是否登录页面,是否有token等判断提前判断是否能加载菜单,同时还做了个开关hasRoute来动态判断是否已经加载过菜单。还需要在store中定义几个方法用于存储数据,我们定义一个menu模块,所以在store中新建文件夹modules,然后新建menus.js

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

Vue.use(Vuex)
export default {
    state: {      // 菜单栏数据   
        menuList: [],
        // 权限数据  
        permList: [],
        hasRoute: false
    }, mutations: {
        changeRouteStatus(state, hasRoute) {
            state.hasRoute = hasRoute
            sessionStorage.setItem("hasRoute", hasRoute)
        }, setMenuList(state, menus) {
            state.menuList = menus
        }, setPermList(state, authoritys) {
            state.permList = authoritys
        }
    }
}

记得在store中import这个模块,然后添加到modules:src/store/index.js

import menus from "./modules/menus"
...
export default new Vuex.Store({  
...  modules:
 {    menus  }})

这样我们菜单的数据就可以加载了,然后再SideMenu.vue中直接获取store中的menuList数据即可显示菜单出来了。src/views/inc/SideMenu.vue

data() {   
 return {   
      menuList: this.$store.state.menus.menuList, 
         }}

效果如下 在这里插入图片描述

动态标签页开发

1,当我们点击导航菜单,上方会添加一个对应的标签,注意不能重复添加,发现已存在标签直接切换到这标签就可以 2,删除当前标签的时候会自动切换到前一个标签页 3,点击标签的时候会调整到对应的内容页中 我们先和左侧菜单一样单独定义一个组件Tabs.vue放在views/inc文件夹内: src/views/inc/Tabs.vue

<template>
  <el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTab">
    <el-tab-pane
      v-for="(item) in editableTabs"
      :key="item.name"
      :label="item.title"
      :name="item.name"
    >
    </el-tab-pane>
  </el-tabs>
</template>

<script>
export default {
  name: 'Tabs',
  data () {
    return {
      editableTabsValue: this.$store.state.menus.editableTabsValue,
      tabIndex: 2
    }
  },
  computed: {
    editableTabs: {
      get () {
        return this.$store.state.menus.editableTabs
      },
      set (val) {
        this.$store.state.menus.editableTabs = val
      }
    }
  },
  methods: {
    removeTab (targetName) {
      const tabs = this.editableTabs
      let activeName = this.editableTabsValue
      if (targetName === 'Index') {
        return
      }
      if (activeName === targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name === targetName) {
            const nextTab = tabs[index + 1] || tabs[index - 1]
            if (nextTab) {
              activeName = nextTab.name
            }
          }
        })
      }
      this.editableTabsValue = activeName
      this.editableTabs = tabs.filter(tab => tab.name !== targetName)
      this.$router.push({ name: activeName })
    },
    clickTab (target) {
      this.$router.push({ name: target.name })
    }
  }
}
</script>

<style scoped>

</style>

computed 表示当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值,其他clickTab removeTab 的逻辑就比较简单,特别是removeTab 注意考虑多种情况就可以,修改meun.js 添加editableTabsValue 和 editableTabs 然后把首页作为默认显示的页面 src/store/modules/menus.js

  state: {
    // 菜单栏数据
    menuList: [],
    // 权限数据
    permList: [],
    hasRoute: false,
    editableTabsValue: 'Index',
    editableTabs: [{
      title: '首页',
      name: 'Index'
    }]
  },

然后再Home.vue中引入我们Tabs.vue这个组件

# 引入组件import Tabs from "./inc/Tabs"
# 声明组件
components: {   
SideMenu, Tabs
},
<el-main> 
  # 使用组件 
    <Tabs></Tabs>   
    <div style="margin: 0 15px;">  
        <router-view></router-view> 
          </div>
          </el-main>

现在的效果 在这里插入图片描述 完成了第一步之后,现在我们需要点击菜单导航,然后tabs 列表中添加tab 标签页,那么我们应该修改sideMeun.vue 页面 在这里插入图片描述 因为tabs标签列表我们是存储在store中的,因此我们需要commit提交事件,因此我们在menu.js中添加addTabs方法: 在这里插入图片描述 刷新浏览器之后链接/sys/users不变,内容不变,但是Tab却不见了,所以我们需要修补一下,当用户是直接通过输入链接形式打开页面的时候我们也能根据链接自动添加激活指定的tab。那么在哪里添加这个回显的方法呢?router中?其实可以,只不过我们需要做判断,因为每次点击导航都会触发router。有没有更简便的方法?有的!因为刷新或者打开页面都是一次性的行为,所以我们可以在更高层的App.vue中做这个回显动作,具体如下: 在这里插入图片描述

上面代码可以看到,除了login页面,其他页面都会触发addTabs方法,这样我们就可以添加tab和激活tab了。

个人中心

个人中心的页面

创建 UserCenCenter.vue 在这里插入图片描述

<template>
  <div style="text-align: center;">
    <h2>你好!{{ userInfo.username }} 同学</h2>

    <el-form :model="passForm" status-icon :rules="rules" ref="passForm" label-width="100px">
      <el-form-item label="旧密码" prop="currentPass">
        <el-input type="password" v-model="passForm.currentPass" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="新密码" prop="password">
        <el-input type="password" v-model="passForm.password" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="确认密码" prop="checkPass">
        <el-input type="password" v-model="passForm.checkPass" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('passForm')">提交</el-button>
        <el-button @click="resetForm('passForm')">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'Login',
  data () {
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请再次输入密码'))
      } else if (value !== this.passForm.password) {
        callback(new Error('两次输入密码不一致!'))
      } else {
        callback()
      }
    }
    return {
      userInfo: {

      },
      passForm: {
        password: '',
        checkPass: '',
        currentPass: ''
      },
      rules: {
        password: [
          { required: true, message: '请输入新密码', trigger: 'blur' },
          { min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
        ],
        checkPass: [
          { required: true, validator: validatePass, trigger: 'blur' }
        ],
        currentPass: [
          { required: true, message: '请输入当前密码', trigger: 'blur' }
        ]
      }
    }
  },
  created () {
    this.getUserInfo()
  },
  methods: {
    getUserInfo () {
      this.$axios.get('/sys/userInfo').then(res => {
        this.userInfo = res.data.data
      })
    },
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          const _this = this
          this.$axios.post('/sys/user/updatePass', this.passForm).then(res => {
            _this.$alert(res.data.msg, '提示', {
              confirmButtonText: '确定',
              callback: action => {
                this.$refs[formName].resetFields()
              }
            })
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
    }
  }
}
</script>

<style scoped>
.el-form {
  width: 420px;
  margin: 50px auto;
}
</style>

菜单界面

菜单页面

在这里插入图片描述

菜单管理我们用到了Table表格组件的树形结构数据,我们只需要根据例子自己组装数据,就可以自动显示出来了 在这里插入图片描述 这里原来应该是一个 树形结构 但是elemenui 没有就加了 - 具体代码 src/views/sys/Menu.vue

<template>
  <div>
    <el-form :inline="true" :model="formInline" ref="editForm" class="demo-form-inline">
      <el-form-item>
        <el-button type="primary" @click="dialogVisible = true">新增</el-button>
      </el-form-item>
    </el-form>
    <el-table
    :data="tableData"
    style="width: 100%;margin-bottom: 20px;"
    row-key="id"
    border
    stripe
    default-expand-all
    :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
    <el-table-column
      prop="name"
      label="名称"
      sortable
      width="180">
    </el-table-column>
    <el-table-column
      prop="perm"
      label="权限编码"
      sortable
      width="180">
    </el-table-column>
    <el-table-column
      prop="icon"
      label="图标">
    </el-table-column>
      <el-table-column
        prop="type"
        label="类型">
        <template slot-scope="scope">
          <el-tag size="small" v-if="scope.row.type === 0">目录</el-tag>
          <el-tag size="small" v-else-if="scope.row.type === 1" type="success">菜单</el-tag>
          <el-tag size="small" v-else-if="scope.row.type === 2" type="info">按钮</el-tag>
        </template>
      </el-table-column>
      <el-table-column
        prop="path"
        label="菜单URL">
      </el-table-column>
      <el-table-column
        prop="component"
        label="菜单组件">
      </el-table-column>
      <el-table-column
        prop="orderNum"
        label="排序号">
      </el-table-column>
      <el-table-column
        prop="statu"
        label="状态">
        <template slot-scope="scope">
   <el-tag size="small" v-if="scope.row.statu === 1" type="success">正常</el-tag>
   <el-tag size="small" v-else-if="scope.row.statu === 0" type="danger">禁用</el-tag>
        </template>
      </el-table-column>
      <el-table-column
        prop="icon"
        label="操作">

        <template slot-scope="scope">
          <el-button type="text" @click="editHandle(scope.row.id)">编辑</el-button>
          <el-divider direction="vertical"></el-divider>

          <template>
            <el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row.id)">
              <el-button type="text" slot="reference"  >删除</el-button>
            </el-popconfirm>
          </template>

        </template>
      </el-table-column>

  </el-table>
<!--    新增对话框-->
    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="600px"
      :before-close="handleClose">
      <el-form :model="editForm" :rules="editFormRules" ref="editForm" label-width="100px" class="demo-editForm">
        <el-form-item label="上级菜单" prop="parentId">
          <el-select v-model="editForm.parentId" placeholder="请选择上级菜单">
      <template v-for="item in tableData">
        <el-option :label="item.name" :value="item.id"></el-option>
        <template v-for="chid in item.children">
          <el-option :label="chid.name" :value="chid.id"></el-option>
          <span>{{ '-  ' + chid.name}}</span>
        </template>
      </template>
          </el-select>
        </el-form-item>
        <el-form-item label="菜单名称" prop="name" label-width="100px">
          <el-input v-model="editForm.name" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="权限编码" prop="perms" label-width="100px">
          <el-input v-model="editForm.perms" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="图标" prop="icon" label-width="100px">
          <el-input v-model="editForm.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="菜单URL" prop="path" label-width="100px">
          <el-input v-model="editForm.path" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="菜单组件" prop="component" label-width="100px">
          <el-input v-model="editForm.component" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="类型" prop="type" label-width="100px">
          <el-radio-group v-model="editForm.type">
            <el-radio :label=0>目录</el-radio>
            <el-radio :label=1>菜单</el-radio>
            <el-radio :label=2>按钮</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="状态" prop="statu" label-width="100px">
          <el-radio-group v-model="editForm.statu">
            <el-radio :label=0>禁用</el-radio>
            <el-radio :label=1>正常</el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item label="排序号" prop="orderNum" label-width="100px">
          <el-input-number v-model="editForm.orderNum" :min="1" label="排序号">1</el-input-number>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="submitForm('editForm')">立即创建</el-button>
          <el-button @click="resetForm('editForm')">重置</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>
  </div>
</template>

<script>

export default {
  name: 'Meun',
  data () {
    return {
      dialogVisible: false,
      editForm: [],
      editFormRules: {
        parentId: [
          { required: true, message: '请选择上级菜单', trigger: 'blur' }
        ],
        name: [
          { required: true, message: '请输入名称', trigger: 'blur' }
        ],
        perms: [
          { required: true, message: '请输入权限编码', trigger: 'blur' }
        ],
        type: [
          { required: true, message: '请选择状态', trigger: 'blur' }
        ],
        orderNum: [
          { required: true, message: '请填入排序号', trigger: 'blur' }
        ],
        statu: [
          { required: true, message: '请选择状态', trigger: 'blur' }
        ]
      },
      tableData: []
    }
  },
  created () {
    this.getMenuTree()
  },
  methods: {
    getMenuTree () {
      this.$axios.get('/sys/menu/list').then(res => {
        this.tableData = res.data.data
      })
    },
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$axios.post('/sys/menu/' + (this.editForm.id ? 'update' : 'save'), this.editForm)
            .then(res => {
              this.$message({
                showClose: true,
                message: '恭喜你,操作成功',
                type: 'success',
                onClose: () => {
                  this.getMenuTree()
                }
              })
              this.dialogVisible = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    editHandle (id) {
      this.dialogVisible = true
      this.$axios.get('/sys/menu/info/' + id).then(res => {
        console.log(res.data.data + '99999999')
        this.editForm = res.data.data
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
      this.dialogVisible = false
      this.editForm = {}
    },
    handleClose () {
      this.resetForm('editForm')
    },
    delHandle (id) {
      this.$axios.post('/sys/menu/delete/' + id).then(res => {
        this.$message({
          showClose: true,
          message: '恭喜你,操作成功',
          type: 'success',
          onClose: () => {
            this.getMenuTree()
          }
        })
      })
    }
  }
}
</script>
<style scoped>
</style>

角色管理

角色页面

在这里插入图片描述 角色需要和菜单权限做关联,菜单是个树形结构的,

src/views/sys/Role.vue

<template>
  <div>
    <el-form :inline="true">
      <el-form-item>
        <el-input
          v-model="searchForm.name"
          placeholder="名称"
          clearable
        >
        </el-input>
      </el-form-item>

      <el-form-item>
        <el-button @click="getRoleList">搜索</el-button>
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="dialogVisible = true">新增</el-button>
      </el-form-item>
      <el-form-item>
        <el-popconfirm title="这是确定批量删除吗?" @confirm="delHandle(null)">
          <el-button type="danger" slot="reference" :disabled="delBtlStatu">批量删除</el-button>
        </el-popconfirm>
      </el-form-item>
    </el-form>
    <el-table
      ref="multipleTable"
      :data="tableData"
      tooltip-effect="dark"
      style="width: 100%"
      border
      stripe
      @selection-change="handleSelectionChange">

      <el-table-column
        type="selection"
        width="55">
      </el-table-column>

      <el-table-column
        prop="name"
        label="名称"
        width="120">
      </el-table-column>
      <el-table-column
        prop="code"
        label="唯一编码"
        show-overflow-tooltip>
      </el-table-column>
      <el-table-column
        prop="remark"
        label="描述"
        show-overflow-tooltip>
      </el-table-column>

      <el-table-column
        prop="statu"
        label="状态">
        <template slot-scope="scope">
          <el-tag size="small" v-if="scope.row.statu === 1" type="success">正常</el-tag>
          <el-tag size="small" v-else-if="scope.row.statu === 0" type="danger">禁用</el-tag>
        </template>

      </el-table-column>
      <el-table-column
        prop="icon"
        label="操作">

        <template slot-scope="scope">
          <el-button type="text" @click="permHandle(scope.row.id)">分配权限</el-button>
          <el-divider direction="vertical"></el-divider>

          <el-button type="text" @click="editHandle(scope.row.id)">编辑</el-button>
          <el-divider direction="vertical"></el-divider>

          <template>
            <el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row.id)">
              <el-button type="text" slot="reference">删除</el-button>
            </el-popconfirm>
          </template>

        </template>
      </el-table-column>

    </el-table>

    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      layout="total, sizes, prev, pager, next, jumper"
      :page-sizes="[10, 20, 50, 100]"
      :current-page="current"
      :page-size="size"
      :total="total">
    </el-pagination>

    <!--新增对话框-->
    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="600px"
      :before-close="handleClose">

      <el-form :model="editForm" :rules="editFormRules" ref="editForm" label-width="100px" class="demo-editForm">

        <el-form-item label="角色名称" prop="name" label-width="100px">
          <el-input v-model="editForm.name" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="唯一编码" prop="code" label-width="100px">
          <el-input v-model="editForm.code" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="描述" prop="remark" label-width="100px">
          <el-input v-model="editForm.remark" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="状态" prop="statu" label-width="100px">
          <el-radio-group v-model="editForm.statu">
            <el-radio :label=0>禁用</el-radio>
            <el-radio :label=1>正常</el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="submitForm('editForm')">立即创建</el-button>
          <el-button @click="resetForm('editForm')">重置</el-button>
        </el-form-item>
      </el-form>

    </el-dialog>

    <el-dialog
      title="分配权限"
      :visible.sync="permDialogVisible"
      width="600px">

      <el-form :model="permForm">

        <el-tree
          :data="permTreeData"
          show-checkbox
          ref="permTree"
          :default-expand-all=true
          node-key="id"
          :check-strictly=true
          :props="defaultProps">
        </el-tree>
      </el-form>
      <span slot="footer" class="dialog-footer">
			    <el-button @click="permDialogVisible = false">取 消</el-button>
			    <el-button type="primary" @click="submitPermFormHandle('permForm')">确 定</el-button>
			</span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: 'Role',
  data () {
    return {
      searchForm: {},
      delBtlStatu: true,

      total: 0,
      size: 10,
      current: 1,

      dialogVisible: false,
      editForm: {

      },

      tableData: [],

      editFormRules: {
        name: [
          { required: true, message: '请输入角色名称', trigger: 'blur' }
        ],
        code: [
          { required: true, message: '请输入唯一编码', trigger: 'blur' }
        ],
        statu: [
          { required: true, message: '请选择状态', trigger: 'blur' }
        ]
      },

      multipleSelection: [],

      permDialogVisible: false,
      permForm: {},
      defaultProps: {
        children: 'children',
        label: 'name'
      },
      permTreeData: []
    }
  },
  created () {
    this.getRoleList()

    this.$axios.get('/sys/menu/list').then(res => {
      this.permTreeData = res.data.data
    })
  },
  methods: {
    toggleSelection (rows) {
      if (rows) {
        rows.forEach(row => {
          this.$refs.multipleTable.toggleRowSelection(row)
        })
      } else {
        this.$refs.multipleTable.clearSelection()
      }
    },
    handleSelectionChange (val) {
      console.log('勾选')
      console.log(val)
      this.multipleSelection = val

      this.delBtlStatu = val.length === 0
    },

    handleSizeChange (val) {
      console.log(`每页 ${val} 条`)
      this.size = val
      this.getRoleList()
    },
    handleCurrentChange (val) {
      console.log(`当前页: ${val}`)
      this.current = val
      this.getRoleList()
    },

    resetForm (formName) {
      this.$refs[formName].resetFields()
      this.dialogVisible = false
      this.editForm = {}
    },
    handleClose () {
      this.resetForm('editForm')
    },

    getRoleList () {
      this.$axios.get('/sys/role/list', {
        params: {
          name: this.searchForm.name,
          current: this.current,
          size: this.size
        }
      }).then(res => {
        this.tableData = res.data.data.records
        this.size = res.data.data.size
        this.current = res.data.data.current
        this.total = res.data.data.total
      })
    },

    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$axios.post('/sys/role/' + (this.editForm.id ? 'update' : 'save'), this.editForm)
            .then(res => {
              this.$message({
                showClose: true,
                message: '恭喜你,操作成功',
                type: 'success',
                onClose: () => {
                  this.getRoleList()
                }
              })

              this.dialogVisible = false
              this.resetForm(formName)
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    editHandle (id) {
      this.$axios.get('/sys/role/info/' + id).then(res => {
        this.editForm = res.data.data

        this.dialogVisible = true
      })
    },
    delHandle (id) {
      var ids = []

      if (id) {
        ids.push(id)
      } else {
        this.multipleSelection.forEach(row => {
          ids.push(row.id)
        })
      }

      console.log(ids)

      this.$axios.post('/sys/role/delete', ids).then(res => {
        this.$message({
          showClose: true,
          message: '恭喜你,操作成功',
          type: 'success',
          onClose: () => {
            this.getRoleList()
          }
        })
      })
    },
    permHandle (id) {
      this.permDialogVisible = true

      this.$axios.get('/sys/role/info/' + id).then(res => {
        this.$refs.permTree.setCheckedKeys(res.data.data.menuIds)
        this.permForm = res.data.data
      })
    },

    submitPermFormHandle (formName) {
      var menuIds = this.$refs.permTree.getCheckedKeys()

      console.log(menuIds)

      this.$axios.post('/sys/role/perm/' + this.permForm.id, menuIds).then(res => {
        this.$message({
          showClose: true,
          message: '恭喜你,操作成功',
          type: 'success',
          onClose: () => {
            this.getRoleList()
          }
        })
        this.permDialogVisible = false
        this.resetForm(formName)
      })
    }
  }
}
</script>

<style scoped>
.el-pagination {
  float: right;
  margin-top: 22px;
}
</style>

用户界面

用户页面

在这里插入图片描述用户管理有个操作叫分配角色,和角色添加权限差不多的操作

<template>
  <div>
    <el-form :inline="true">
      <el-form-item>
        <el-input
          v-model="searchForm.username"
          placeholder="用户名"
          clearable
        >
        </el-input>
      </el-form-item>

      <el-form-item>
        <el-button @click="getUserList">搜索</el-button>
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="dialogVisible = true" v-if="hasAuth('sys:user:save')">新增</el-button>
      </el-form-item>
      <el-form-item>
        <el-popconfirm title="这是确定批量删除吗?" @confirm="delHandle(null)">
          <el-button type="danger" slot="reference" :disabled="delBtlStatu" v-if="hasAuth('sys:user:delete')">批量删除</el-button>
        </el-popconfirm>
      </el-form-item>
    </el-form>

    <el-table
      ref="multipleTable"
      :data="tableData"
      tooltip-effect="dark"
      style="width: 100%"
      border
      stripe
      @selection-change="handleSelectionChange">

      <el-table-column
        type="selection"
        width="55">
      </el-table-column>

      <el-table-column
        label="头像"
        width="50">
        <template slot-scope="scope">
          <el-avatar size="small" :src="scope.row.avatar"></el-avatar>
        </template>
      </el-table-column>

      <el-table-column
        prop="username"
        label="用户名"
        width="120">
      </el-table-column>
      <el-table-column
        prop="code"
        label="角色名称">
        <template slot-scope="scope">
          <el-tag size="small" type="info" v-for="item in scope.row.sysRoles">{{item.name}}</el-tag>
        </template>

      </el-table-column>
      <el-table-column
        prop="email"
        label="邮箱">
      </el-table-column>
      <el-table-column
        prop="phone"
        label="手机号">
      </el-table-column>

      <el-table-column
        prop="statu"
        label="状态">
        <template slot-scope="scope">
          <el-tag size="small" v-if="scope.row.statu === 1" type="success">正常</el-tag>
          <el-tag size="small" v-else-if="scope.row.statu === 0" type="danger">禁用</el-tag>
        </template>

      </el-table-column>
      <el-table-column
        prop="created"
        width="200"
        label="创建时间"
      >
      </el-table-column>
      <el-table-column
        prop="icon"
        width="260px"
        label="操作">

        <template slot-scope="scope">
          <el-button type="text" @click="roleHandle(scope.row.id)">分配角色</el-button>
          <el-divider direction="vertical"></el-divider>

          <el-button type="text" @click="repassHandle(scope.row.id, scope.row.username)">重置密码</el-button>
          <el-divider direction="vertical"></el-divider>

          <el-button type="text" @click="editHandle(scope.row.id)">编辑</el-button>
          <el-divider direction="vertical"></el-divider>

          <template>
            <el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row.id)">
              <el-button type="text" slot="reference">删除</el-button>
            </el-popconfirm>
          </template>

        </template>
      </el-table-column>

    </el-table>

    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      layout="total, sizes, prev, pager, next, jumper"
      :page-sizes="[10, 20, 50, 100]"
      :current-page="current"
      :page-size="size"
      :total="total">
    </el-pagination>

    <!--新增对话框-->
    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="600px"
      :before-close="handleClose">

      <el-form :model="editForm" :rules="editFormRules" ref="editForm">
        <el-form-item label="用户名" prop="username" label-width="100px">
          <el-input v-model="editForm.username" autocomplete="off"></el-input>
          <el-alert
            title="初始密码为888888"
            :closable="false"
            type="info"
            style="line-height: 12px;"
          ></el-alert>
        </el-form-item>

        <el-form-item label="邮箱"  prop="email" label-width="100px">
          <el-input v-model="editForm.email" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="手机号"  prop="phone" label-width="100px">
          <el-input v-model="editForm.phone" autocomplete="off"></el-input>
        </el-form-item>

        <el-form-item label="状态"  prop="statu" label-width="100px">
          <el-radio-group v-model="editForm.statu">
            <el-radio :label="0">禁用</el-radio>
            <el-radio :label="1">正常</el-radio>
          </el-radio-group>
        </el-form-item>

      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="resetForm('editForm')">取 消</el-button>
        <el-button type="primary" @click="submitForm('editForm')">确 定</el-button>
      </div>
    </el-dialog>

    <!-- 分配权限对话框 -->
    <el-dialog title="分配角色" :visible.sync="roleDialogFormVisible" width="600px">

      <el-form :model="roleForm">
        <el-tree
          :data="roleTreeData"
          show-checkbox
          ref="roleTree"
          :check-strictly=checkStrictly
          node-key="id"
          :default-expand-all=true
          :props="defaultProps">
        </el-tree>
      </el-form>

      <div slot="footer" class="dialog-footer">
        <el-button @click="roleDialogFormVisible=false">取 消</el-button>
        <el-button type="primary" @click="submitRoleHandle('roleForm')">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: 'SysUser',
  data () {
    return {
      searchForm: {},
      delBtlStatu: true,

      total: 0,
      size: 10,
      current: 1,

      dialogVisible: false,
      editForm: {

      },

      tableData: [],

      editFormRules: {
        username: [
          { required: true, message: '请输入用户名称', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' }
        ],
        statu: [
          { required: true, message: '请选择状态', trigger: 'blur' }
        ]
      },

      multipleSelection: [],

      roleDialogFormVisible: false,
      defaultProps: {
        children: 'children',
        label: 'name'
      },
      roleForm: {},
      roleTreeData: [],
      treeCheckedKeys: [],
      checkStrictly: true

    }
  },
  created () {
    this.getUserList()

    this.$axios.get('/sys/role/list').then(res => {
      this.roleTreeData = res.data.data.records
    })
  },
  methods: {
    toggleSelection (rows) {
      if (rows) {
        rows.forEach(row => {
          this.$refs.multipleTable.toggleRowSelection(row)
        })
      } else {
        this.$refs.multipleTable.clearSelection()
      }
    },
    handleSelectionChange (val) {
      console.log('勾选')
      console.log(val)
      this.multipleSelection = val

      this.delBtlStatu = val.length === 0
    },

    handleSizeChange (val) {
      console.log(`每页 ${val} 条`)
      this.size = val
      this.getUserList()
    },
    handleCurrentChange (val) {
      console.log(`当前页: ${val}`)
      this.current = val
      this.getUserList()
    },

    resetForm (formName) {
      this.$refs[formName].resetFields()
      this.dialogVisible = false
      this.editForm = {}
    },
    handleClose () {
      this.resetForm('editForm')
    },

    getUserList () {
      this.$axios.get('/sys/user/list', {
        params: {
          username: this.searchForm.username,
          current: this.current,
          size: this.size
        }
      }).then(res => {
        this.tableData = res.data.data.records
        this.size = res.data.data.size
        this.current = res.data.data.current
        this.total = res.data.data.total
      })
    },

    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$axios.post('/sys/user/' + (this.editForm.id ? 'update' : 'save'), this.editForm)
            .then(res => {
              this.$message({
                showClose: true,
                message: '恭喜你,操作成功',
                type: 'success',
                onClose: () => {
                  this.getUserList()
                }
              })

              this.dialogVisible = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    editHandle (id) {
      this.$axios.get('/sys/user/info/' + id).then(res => {
        this.editForm = res.data.data

        this.dialogVisible = true
      })
    },
    delHandle (id) {
      var ids = []

      if (id) {
        ids.push(id)
      } else {
        this.multipleSelection.forEach(row => {
          ids.push(row.id)
        })
      }

      console.log(ids)

      this.$axios.post('/sys/user/delete', ids).then(res => {
        this.$message({
          showClose: true,
          message: '恭喜你,操作成功',
          type: 'success',
          onClose: () => {
            this.getUserList()
          }
        })
      })
    },

    roleHandle (id) {
      this.roleDialogFormVisible = true

      this.$axios.get('/sys/user/info/' + id).then(res => {
        this.roleForm = res.data.data

        const roleIds = []
        res.data.data.sysRoles.forEach(row => {
          roleIds.push(row.id)
        })

        this.$refs.roleTree.setCheckedKeys(roleIds)
      })
    },
    submitRoleHandle (formName) {
      var roleIds = this.$refs.roleTree.getCheckedKeys()

      console.log(roleIds)

      this.$axios.post('/sys/user/role/' + this.roleForm.id, roleIds).then(res => {
        this.$message({
          showClose: true,
          message: '恭喜你,操作成功',
          type: 'success',
          onClose: () => {
            this.getUserList()
          }
        })

        this.roleDialogFormVisible = false
      })
    },
    repassHandle (id, username) {
      this.$confirm('将重置用户【' + username + '】的密码, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$axios.post('/sys/user/repass', id).then(res => {
          this.$message({
            showClose: true,
            message: '恭喜你,操作成功',
            type: 'success',
            onClose: () => {
            }
          })
        })
      })
    }
  }
}
</script>

<style scoped>

.el-pagination {
  float: right;
  margin-top: 22px;
}

</style>

按钮权限控制

上面的菜单,角色,用户有的操作,不是每个用户都有的,没有权限的用户我们应该隐藏按钮。 我们再src下面新建一个js文件用于定义一个全局使用的方法:、 src/globalFun.js

import Vue from 'vue'

Vue.mixin({
  methods: {
    hasAuth (perm) {
      var authority = this.$store.state.menus.permList

      return authority.indexOf(perm) > -1
    }
  }
})

在架子啊菜单的时候 要同时架子啊权限数据,现在需要用到权限数据 这里数组,因此我们通过按钮的权限是否在权限列表内就可以了。mixin 的作用是多个组件可以共享数据和方法,在使用mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用,在已有的组件数据和方法进行扩充。在main.js 引入这个文件 src\main.js

import gobal from "./globalFun"

这样全局就可以使用啦,比如我们在新增按钮这里做判断: src/views/sys/Menu.vue

<el-button type="primary" @click="dialogFormVisible = true" v-if="hasAuth('sys:menu:save')">新增</el-button>

通过v-if来判断返回是否为true从而判断是否显示。 效果 当登录的是 test 的时候没有新增的按钮 在这里插入图片描述 admin 的时候是有的 在这里插入图片描述