1.安装VUE项目
第一步:vue-ui
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install --g vue-cli
vue ui
第二步:创建
第三步:详情
第四步:选择手动
第五步:配置
第六步:点击创建项目
第七步:启动项目
第八步:vsCode启动项目
npm run serve
2.安装element-ui
npm install element-ui --save
然后我们打开项目src目录下的main.js,引入element-ui依赖。
import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
Vue.use(Element)
这样我们就可以愉快得在官网上选择组件复制代码到我们项目中直接使用啦。
3.删除不必要的页面
3.1删除/viwes/Home.vue
3.2删除App.vue
4.安装axios、qs、mockjs
4.1安装axios
npm install axios --save
# 然后同样我们在main.js中全局引入axios
import axios from 'axios'
Vue.prototype.$axios = axios //全局使用
4.2安装qs
npm install qs --save
4.3安装mockjs
npm install mockjs --save-dev
5.页面路由
学习视频
5.1删除页面
5.2创建页面
1.创建Login.vue
<template>
<div>登录页面</div>
</template>
<script>
export default {
name: "Login"
}
</script>
<style scoped>
</style>
1.创建Home.vue
<template>
<div>测试主页 </div>
</template>
<script>
export default {
name: 'Home',
}
</script>
<style>
</style>
5.3页面router/index.js
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: Login
}
]
6.登录页面
6.1页面展示
6.2在src/App.vue调整全局样式
我们先调整一下全局的样式
将原页面的style便签删除,更换为一下的style便签内容,下面是调整后的整个页面
<template>
<div id="app">
<router-view/>
</div>
</template>
<style>
html, body, #app {
font-family: 'Helvetica Neue', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'Microsoft Yahei', sans-serif;
height: 100%;
padding: 0;
margin: 0;
font-size: 15px;
}
</style>
6.3src/views/Login.vue
一下页面是写好后的页面,还未发请求前
<template>
<el-row style="height: 100%">
<el-col :xl="6" :lg="7">
<!-- 标题 -->
<h2>欢迎来到VueAdmin管理界面</h2>
<!-- 使用 el-image引入图片 -->
<el-image
:src="require('@/assets/微信图片_20210919105336.jpg')"
style="height: 180px; width: 180px"
></el-image>
<p>公众号:MarkerHup</p>
<p>扫描二维码,回复</p>
</el-col>
<el-col :span="1">
<!-- 分割线 -->
<el-divider direction="vertical"></el-divider>
</el-col>
<el-col :xl="6" :lg="7">
<!--
:model="loginForm" 绑定的表单
:rules="rules" 校验规则
ref="loginForm" 注册实例,可以通过 this.$refs[loginForm]获取
-->
<el-form
:model="loginForm"
:rules="rules"
ref="loginForm"
label-width="80px"
class="demo-loginForm"
>
<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"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="code" style="width: 380px">
<el-input
v-model="loginForm.code"
style="width: 170px; float: left"
></el-input>
<el-image src="" class="captchaImg"></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: "",
},
// 校验
rules: {
userName: [
{required: true, message: "请输入用户名", trigger: "blur"},
{
min: 3,
max: 50,
message: "长度在 3 到 50 个字符",
trigger: "blur",
},
],
passWord: [
{required: true, message: "请输入密码", trigger: "change"},
],
code: [{required: true, message: "请输入验证码", trigger: "change"}],
},
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
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; 文字居中
*/
.el-row {
background-color: #fafafa;
height: 100%;
display: flex;
align-items: center;
text-align: center;
justify-content: center;
}
/*.el-row {*/
/* height: 100%;*/
/* background-color: #fafafa; !*设置背景颜色*!*/
/* display: flex;*/
/* justify-content: center;*/
/* align-items: center;*/
/* text-align: center;*/
/*}*/
/* 分割线的高度 */
.el-divider {
height: 200px;
}
.captchaImg {
/*靠左边*/
float: left;
/*验证码偏移8px*/
margin-left: 8px;
/*圆角*/
border-radius: 4px;
}
</style>
6.4获取验证码
现在没有后端,用mockjs模拟生成验证码
6.4.1安装mockjs
npm install mockjs --save-dev
6.4.2创建mock.js
const Mock = require('mockjs')
const Random = Mock.Random
let Result = {
code: 200,
msg: '操作成功',
data: null
}
Mock.mock('/captcha','get',()=>{
Result.data = {
UUID: Random.string(32),
captchaImg: Random.dataImage('120x40','p7n5w')
}
return Result
})
6.4.3注册
require("./mock.js")
6.4.4login页面(重要)
getCaptCha() {
this.$axios.get('/captcha').then(res => {
debugger;
this.loginForm.UUID = res.data.data.token;
this.captchaImg = res.data.data.captchaImg;
})
}
created() {
this.getCaptCha();
},
1.在loginForm里添加UUID,
2.在data里面添加captchaImg,
3.验证码展示
<el-image :src="captchaImg" class="captchaImg"></el-image>
6.4.5页面展示
6.5登录
6.5.1使用mockjs模拟登录
在mock.js添加
Mock.mock('/login','post',()=>{ return Result})
6.5.2将token存到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: {}
})
6.5.3发送login请求
this.$axios.post('/login', this.loginForm).then(res => {
const jwt = res.headers['authorization'];
this.$store.commit("SET_TOKEN", jwt);
this.$router.push("/index");
})
6.5.4测试
7.定义全局axios拦截器
7.1在src/axios.js
import axios from "axios";import router from "@/router";import Element from "element-ui"axios.defaults.daseURL = "http://localhost:8081"const request = axios.create({ timeout: 5000,
7.2在main.js全局引入
7.3测试
1.在mock.js中的login方法添加错误信息
2.页面测试
8.index页面编写
8.1页面
在src/views/创建index.vue
<template>
<el-container>
<!-- 侧边栏-->
<el-aside width="200px">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
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>
</el-aside>
<el-container>
<!-- 头部信息-->
<el-header>
<strong>VueAdmin后台管理系统学习</strong>
<div class="header-avatar">
<!-- 头像-->
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
<!-- 下拉框-->
<el-dropdown>
<span class="el-dropdown-link">
Admin<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 最后面的文字跳转-->
<el-link href="https://shimowendang.com/docs/pxwyJHgqcWjWkTKX/read" target="_blank">网站</el-link>
<el-link href="https://shimowendang.com/docs/pxwyJHgqcWjWkTKX/read" target="_blank">学习</el-link>
</div>
</el-header>
<!-- 主体信息-->
<el-main>主体信息</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
name: 'Index',
}
</script>
<style>
.el-container {
padding: 0;
margin: 0;
height: 100%;
}
.el-header {
background-color: cadetblue;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
line-height: 200px;
}
.el-main {
color: #333;
text-align: center;
line-height: 160px;
}
.header-avatar {
float: right;
width: 210px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-menu-vertical-demo {
height: 100%;
}
</style>
8.2效果图
9.抽取代码
将index中的左侧导航栏代码和头部代码抽取为公共的
9.1抽取后的导航栏
在views新建inc/SideMenu
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
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>
.el-menu-vertical-demo {
height: 100%;
}
</style>
9.2抽取后的头部
<template>
<el-header>
<strong>VueAdmin后台管理系统学习</strong>
<div class="header-avatar">
<!-- 头像-->
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
<!-- 下拉框-->
<el-dropdown>
<span class="el-dropdown-link">
Admin<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 最后面的文字跳转-->
<el-link href="https://shimowendang.com/docs/pxwyJHgqcWjWkTKX/read" target="_blank">网站</el-link>
<el-link href="https://shimowendang.com/docs/pxwyJHgqcWjWkTKX/read" target="_blank">学习</el-link>
</div>
</el-header>
</template>
<script>
export default {
name: 'SideHeader',
}
</script>
<style>
.header-avatar {
float: right;
width: 210px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-container {
padding: 0;
margin: 0;
height: 100%;
}
.el-header {
background-color: cadetblue;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
line-height: 200px;
}
</style>
9.3抽取后的Home
<!--<template>-->
<!-- <div>测试主页 </div>-->
<!--</template>-->
<!--<script> -->
<!--export default {-->
<!-- name: 'Home',-->
<!-- -->
<!--}-->
<!--</script>-->
<!--<style>-->
<!--</style>-->
<template>
<el-container>
<!-- 侧边栏-->
<el-aside width="200px">
<SideMenu></SideMenu>
</el-aside>
<el-container>
<!-- 头部信息-->
<el-header>
<SideHeader></SideHeader>
</el-header>
<!-- 主体信息-->
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import SideMenu from "@/views/inc/SideMenu";
import SideHeader from "@/views/inc/SideHeader";
export default {
name: 'Home',
components: {SideMenu, SideHeader},
}
</script>
<style>
.el-container {
padding: 0;
margin: 0;
height: 100%;
}
.el-header {
background-color: cadetblue;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
line-height: 200px;
}
.el-main {
color: #333;
text-align: center;
line-height: 160px;
}
.header-avatar {
float: right;
width: 210px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
</style>
9.4抽取后的index
<template>
<div>测试主页 </div>
</template>
<script>
export default {
name: 'Index',
}
</script>
<style>
</style>
9.5抽取的router.js
将index路由放到children中
9.5抽取后的页面
10.创建页面
10.1在views新建system/User.vue
<template>
<div>用户测试</div>
</template>
<script>
export default {
name: 'User',
}
</script>
<style>
</style>
10.2在views新建system/Role.vue
<template>
<div>角色测试</div>
</template>
<script>
export default {
name: 'Role',
}
</script>
<style>
</style>
10.3在views新建system/Menuvue
<template>
<div>菜单测试</div>
</template>
<script>
export default {
name: 'Menu',
}
</script>
<style>
</style>
10.4在router.js添加
以下是代码router.js代码
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import Index from '@/views/index'
import User from "@/views/system/User";
import Role from "@/views/system/Role";
import Menu from "@/views/system/Menu";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/index',
name: 'Index',
component: Index
},
{
path: '/users',
name: 'SysUser',
component: User
},
{
path: '/roles',
name: 'SysRole',
component: Role
},
{
path: '/menus',
name: 'SysMenu',
component: Menu
},
]
},
{
path: '/login',
name: 'Login',
component: Login
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
10.4测试
分别访问:看页面的变化
http://localhost:8080/menus
http://localhost:8080/roles
http://localhost:8080/users
11.导航栏绑定页面
完成后点击页面测试看是否发生变化
<router-link to="/users">
</router-link>
12修改头像和用户名
在inc/SideHeader中修改头像和用户名从后端获取
13.个人中心
13.1在views/system/新建UserCenter.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: '111111', checkPass: '111111', currentPass: '111111'},
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/updataPass', 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>
13.2在router.js添加
{
path: '/userCenter',
name: 'UserCenter',
component: ()=>import("@/views/system/UserCenter")
},
13.3修改SideHeader.vue
删除所有页面的 line-height: 160px;
13.4测试页面
14退出
<!-- 在el-dropdown-item标签里, @click不可以用,要使用@click.native-->
<el-dropdown-item @click.native="logout">退出</el-dropdown-item>
- 在SideHeader.vue的methods编写点击退出方法
logout(){
this.$axios.post("/logout").then(res => {
localStorage.clear();
sessionStorage.clear();
this.$store.commit("resetState");
this.$router.push("/login")
})
},
Mock.mock('/logout','post',()=>{ return Result})
点击页面退出看效果
15将导航改为动态获取(可忽略)
可以忽略,直接过16
先用mock.js模拟从服务器返回数据
15.1主要修改
15.2修改后SideMenu.vue的页面
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
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">
<template slot="title">
<i :class="menu.icon"></i>
<span>{{ menu.title }}</span>
</template>
<router-link :to="item.path" v-for="item in menu.children">
<el-menu-item :index="item.name">
<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: [],
}
},
created() {
this.getMenuList();
},
methods: {
getMenuList() {
this.$axios.get("/sys/menuList").then(res => {
this.menuList = res.data.data;
})
}
}
}
</script>
<style>
.el-menu-vertical-demo {
height: 100%;
}
.a {
text-decoration: none;
}
</style>
15.3mock.js
Mock.mock('/sys/menuList', 'get', () => {
Result.data = [
{
name: 'SysManga',
title: '系统管理',
icon: 'el-icon-s-operation',
path: '',
component: '',
children: [
{
name: 'SysUser',
title: '用户管理',
icon: 'el-icon-s-custom',
path: '/users',
children: []
}]
}, {
name: 'SysTools',
title: '系统工具',
icon: 'el-icon-s-tools',
path: '',
children: [
{
name: 'SysDict',
title: '数字字典',
icon: 'el-icon-s-order',
path: '/dicts',
children: []
},]
}
]
return Result
})
15.4修改后的导航栏
16.将导航栏改为动态获取
16.1mock.js模拟后端返回的数据
修改15的mock.js后的方法
Mock.mock('/sys/menuList', 'get', () => {
let nav = [
{
name: 'SysManga',
title: '系统管理',
component:'',
icon: 'el-icon-s-operation',
path: '',
component: '',
children: [
{
name: 'SysUser',
title: '用户管理',
component:'system/User',
icon: 'el-icon-s-custom',
path: '/users',
children: []
}]
}, {
name: 'SysTools',
title: '系统工具',
component:'',
icon: 'el-icon-s-tools',
path: '',
children: [
{
name: 'SysDict',
title: '数字字典',
component:'',
icon: 'el-icon-s-order',
path: '/dicts',
children: []
},]
}
]
let authoritys = []
Result.data = {
nav: nav,
authoritys: authoritys
}
return Result
})
16.2将菜单/权限存放到全局
在/store/创建modules/menus.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default {
state: {
menuList: [],
permList: [],
hasRoute: false
},
mutations: {
setMenuList(state, menuList) {
state.menuList = menuList
},
setPermList(state, permList) {
state.setPermList = permList;
},
changeRouteStatus(state, hasRoute) {
state.hasRoute = hasRoute;
sessionStorage.setItem("hasRoute", hasRoute)
},
},
actions: {},
modules: {}
}
16.3将menus.js注册到src/store/index.js
16.4修改route/index.js方法
添加这个方法,注释掉写死的children的菜单,动态获取,下面有修改后的整个页面
router.beforeEach(((to, from, next) => {
let hasRoute = store.state.menus.hasRoute
console.log("hasRoute", hasRoute)
if (!hasRoute) {
axios.get("/sys/menuList", {
headers: {
Authorization: localStorage.getItem("token")
}
}).then(res => {
store.commit("setMenuList", res.data.data.nav)
store.commit("setPermList", res.data.data.authoritys)
let newRoutes = router.options.routes
res.data.data.nav.forEach(menu => {
if (menu.children) {
menu.children.forEach(e => {
if (e.component) {
console.log("e.component", e.component)
let route = {
name: e.name,
path: e.path,
mate: {
icon: e.icon,
title: e.title
},
component: () => import("@/views/" + e.component + '.vue')
}
if (route) {
newRoutes[0].children.push(route)
}
}
})
}
})
console.log("newRoutes", newRoutes)
router.addRoutes(newRoutes)
hasRoute = true
store.commit("changeRouteStatus", hasRoute)
})
}
next()
}))
修改后的整个页面
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import Index from '@/views/index'
import User from "@/views/system/User";
import Role from "@/views/system/Role";
import Menu from "@/views/system/Menu";
import axios from "axios";
import store from "@/store"
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/index',
name: 'Index',
component: Index
},
{
path: '/userCenter',
name: 'UserCenter',
component: () => import("@/views/system/UserCenter")
},
]
},
{
path: '/login',
name: 'Login',
component: Login
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach(((to, from, next) => {
let hasRoute = store.state.menus.hasRoute
console.log("hasRoute", hasRoute)
if (!hasRoute) {
axios.get("/sys/menuList", {
headers: {
Authorization: localStorage.getItem("token")
}
}).then(res => {
store.commit("setMenuList", res.data.data.nav)
store.commit("setPermList", res.data.data.authoritys)
let newRoutes = router.options.routes
res.data.data.nav.forEach(menu => {
if (menu.children) {
menu.children.forEach(e => {
if (e.component) {
console.log("e.component", e.component)
let route = {
name: e.name,
path: e.path,
mate: {
icon: e.icon,
title: e.title
},
component: () => import("@/views/" + e.component + '.vue')
}
if (route) {
newRoutes[0].children.push(route)
}
}
})
}
})
console.log("newRoutes", newRoutes)
router.addRoutes(newRoutes)
hasRoute = true
console.log("hasRoute2", hasRoute)
store.commit("changeRouteStatus", hasRoute)
})
}
next()
}))
export default router
16.5修改/inc/SideMenu.vue
修改后的整个页面
<template>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
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">
<template slot="title">
<i :class="menu.icon"></i>
<span>{{ menu.title }}</span>
</template>
<router-link :to="item.path" v-for="item in menu.children">
<el-menu-item :index="item.name">
<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: [],
}
},
computed: {
// this.getMenuList();
menuList: {
get() {
//return this.$store.state+ menus.js的模块名+menuList
return this.$store.state.menus.menuList
}
}
},
methods: {
// getMenuList() {
// this.$axios.get("/sys/menuList").then(res => {
// this.menuList = res.data.data.nav;
// })
// }
}
}
</script>
<style>
.el-menu-vertical-demo {
height: 100%;
}
.a {
text-decoration: none;
}
</style>
17动态标签页
17.1在/store/modules/menus.js添加
17.2在inc/新建Tabs.vue
<template>
<el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTab">
<el-tab-pane
v-for="(item, index) 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,
// editableTabs: this.$store.state.menus.editableTabs,
}
},
// tab标签放到computed里面监听
computed: {
editableTabsValue: {
// getter方法
get() {
return this.$store.state.menus.editableTabsValue
},
// setter方法
set(val) {
this.$store.state.menus.editableTabsValue = val
}
},
editableTabs: {
// getter方法
get() {
return this.$store.state.menus.editableTabs
},
// setter方法
set(val) {
this.$store.state.menus.editableTabs = val
}
}
},
methods: {
// 点击tab标签切换页面数据
clickTab(target) {
this.$router.push({name: target.name})
},
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
// 首页不能删除
if (activeName==='Index'){
return
}
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let 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})
}
}
}
</script>
<style>
</style>
17.3页面使用
在视图上面添加Tabs组件
17.4修改inc/SideMenu.vue
添加selectMenu点击事件添加tab标签
:default-active是标签点击是相应的菜单高亮
<template>
<!--点击tab时菜单高亮 :default-active=this.$store.state.menus.editableTabsValue-->
<el-menu
:default-active=this.$store.state.menus.editableTabsValue
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">
<template slot="title">
<i :class="menu.icon"></i>
<span>{{ menu.title }}</span>
</template>
<router-link :to="item.path" v-for="item in menu.children">
<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: [],
}
},
computed: {
// this.getMenuList();
menuList: {
get() {
//return this.$store.state+ menus.js的模块名+menuList
return this.$store.state.menus.menuList
}
}
},
methods: {
// getMenuList() {
// this.$axios.get("/sys/menuList").then(res => {
// this.menuList = res.data.data.nav;
// })
// }
selectMenu(item){
this.$store.commit("addTab",item)
}
},
}
</script>
<style>
.el-menu-vertical-demo {
height: 100%;
}
.a {
text-decoration: none;
}
</style>
18通过url地址跳转
在src/App.vue添加方法
http://localhost:8080/users跳转不过去,添加以下代码
<script>
export default {
name: "App",
watch: {
$route(to, from) {
debugger;
console.log("to",to)
if (to.path != "/login") {
let obj = {
name: to.name,
title: to.name
}
this.$store.commit("addTab", obj)
}
}
}
}
</script>
19退出清空信息
在src/store/modules/menus.js的mutations方法里添加,因为退出已经调用过resetState这个,直接添加即可
resetState: (state) => {
state.menuList = [],
state.permList = [],
state.hasRoute = false,
state.editableTabsValue = 'Index',
state.editableTabs = [{
title: '首页',
name: 'Index',
}]
},
20.菜單頁面
20.1是文字不居中
將.el-main的text-align: center;刪除
.el-main {
color: #333;
padding: 0;
}
20.2mock.js模拟后端
Mock.mock('/sys/menu/list', 'get', () => {
let menus = [
{
"id": 1,
"created": "2021-01-15T18:58:18",
"updated": "2021-01-15T18:58:20",
"statu": 1,
"parentId": 0,
"name": "系统管理",
"path": "",
"perms": "sys:manage",
"component": "",
"type": 0,
"icon": "el-icon-eleme",
"ordernum": 1,
"children": [
{
"id": 2,
"created": "2021-01-15T19:03:45",
"updated": "2021-01-15T19:03:48",
"statu": 1,
"parentId": 1,
"name": "用户管理",
"path": "/sys/users",
"perms": "sys:user:list",
"component": "sys/User",
"type": 1,
"icon": "el-icon-s-custom",
"ordernum": 1,
"children": [
{
"id": 9,
"created": "2021-01-17T21:48:32",
"updated": null,
"statu": 1,
"parentId": 2,
"name": "添加用户",
"path": null,
"perms": "sys:user:save",
"component": null,
"type": 2,
"icon": null,
"ordernum": 1,
"children": []
},
{
"id": 10,
"created": "2021-01-17T21:49:03",
"updated": "2021-01-17T21:53:04",
"statu": 1,
"parentId": 2,
"name": "修改用户",
"path": null,
"perms": "sys:user:update",
"component": null,
"type": 2,
"icon": null,
"ordernum": 2,
"children": []
},
{
"id": 11,
"created": "2021-01-17T21:49:21",
"updated": null,
"statu": 1,
"parentId": 2,
"name": "删除用户",
"path": null,
"perms": "sys:user:delete",
"component": null,
"type": 2,
"icon": null,
"ordernum": 3,
"children": []
},
{
"id": 12,
"created": "2021-01-17T21:49:58",
"updated": null,
"statu": 1,
"parentId": 2,
"name": "分配角色",
"path": null,
"perms": "sys:user:role",
"component": null,
"type": 2,
"icon": null,
"ordernum": 4,
"children": []
},
{
"id": 13,
"created": "2021-01-17T21:50:36",
"updated": null,
"statu": 1,
"parentId": 2,
"name": "重置密码",
"path": null,
"perms": "sys:user:repass",
"component": null,
"type": 2,
"icon": null,
"ordernum": 5,
"children": []
}
]
},
{
"id": 3,
"created": "2021-01-15T19:03:45",
"updated": "2021-01-15T19:03:48",
"statu": 1,
"parentId": 1,
"name": "角色管理",
"path": "/sys/roles",
"perms": "sys:role:list",
"component": "sys/Role",
"type": 1,
"icon": "el-icon-rank",
"ordernum": 2,
"children": []
},
]
},
{
"id": 5,
"created": "2021-01-15T19:06:11",
"updated": null,
"statu": 1,
"parentId": 0,
"name": "系统工具",
"path": "",
"perms": "sys:tools",
"component": null,
"type": 0,
"icon": "el-icon-s-tools",
"ordernum": 2,
"children": [
{
"id": 6,
"created": "2021-01-15T19:07:18",
"updated": "2021-01-18T16:32:13",
"statu": 1,
"parentId": 5,
"name": "数字字典",
"path": "/sys/dicts",
"perms": "sys:dict:list",
"component": "sys/Dict",
"type": 1,
"icon": "el-icon-s-order",
"ordernum": 1,
"children": []
}
]
}
]
Result.data = menus
return Result
})
Mock.mock(RegExp('/sys/menu/info/*'), 'get', () => {
Result.data = {
"id": 3,
"statu": 1,
"parentId": 1,
"name": "角色管理",
"path": "/sys/roles",
"perms": "sys:role:list",
"component": "sys/Role",
"type": 1,
"icon": "el-icon-rank",
"orderNum": 2,
"children": []
}
return Result
})
Mock.mock(RegExp('/sys/menu/*'), 'post', () => {
return Result
})
Mock.mock(RegExp('/sys/menu/*'), 'delete', () => {
return Result
})
20.3菜单页面
<template>
<div>
<!-- 新增表单-->
<el-form :inline="true">
<el-form-item>
<el-button type="primary" @click="onSubmit">新增</el-button>
</el-form-item>
</el-form>
<!-- Table表格-->
<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="perms" label="权限编码" width="180"></el-table-column>
<el-table-column prop="icon" label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon"></i>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.type === 0" size="small">目录</el-tag>
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
<el-tag v-else-if="scope.row.type === 2" size="small" 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="状态" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.statu === 0" size="small" type="danger">禁用</el-tag>
<el-tag v-else-if="scope.row.statu === 1" size="small" type="success">正常</el-tag>
</template>
</el-table-column>
<!-- fixed="right"固定操作栏-->
<el-table-column prop="icon" label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="changeUpdate(scope.row)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<!-- <el-button type="text">删除</el-button>-->
<!-- 删除-->
<template>
<el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row)">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 新增或修改的弹窗-->
<el-dialog
title="新增"
:visible.sync="dialogVisible"
width="500px"
:before-close="handleClose">
<!-- 弹框里的表单-->
<el-form :model="editForm" :rules="rules" 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,index) in tableData">
<el-option :label=item.name :value=item.id></el-option>
<!-- 二级目录-->
<template v-for="child in item.children">
<el-option :label=child.name :value=child.id>
<span>{{ ' - ' + child.name }}</span>
</el-option>
</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>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm('editForm')">取 消</el-button>
<el-button type="primary" @click="submitEditForm('editForm')">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'Role',
data() {
return {
// 弹框
dialogVisible: false,
// 新增数据
editForm: {},
// form表单
tableData: [],
// 表单校验
rules: {
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'}]
},
}
},
created() {
this.getMenuTree()
},
methods: {
// 获取列表
getMenuTree() {
this.$axios.get("/sys/menu/list").then(res => {
this.tableData = res.data.data
})
},
// 点击新增
onSubmit() {
// 点击新增弹窗
this.dialogVisible = true
},
// X 关闭
handleClose(done) {
this.editForm = {}
this.dialogVisible = false
},
// 清空表单的方法
resetForm(formName) {
debugger;
this.$refs[formName].resetFields();
this.editForm = {}
this.dialogVisible = false
},
// 新增或修改方法
submitEditForm(editForm) {
this.$refs["editForm"].validate((valid) => {
if (valid) {
// 如果id不为null,修改
if (this.editForm.id != null) {
debugger
this.$axios.post("/sys/menu/update", editForm).then(res => {
// 保存成功弹框消息提醒
this.$message({
message: '修改成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getMenuTree()
}
});
this.editForm = {}
this.dialogVisible = false;
})
} else { // 否则新增
debugger
this.$axios.post("/sys/menu/save", editForm).then(res => {
// 保存成功弹框消息提醒
this.$message({
message: '保存成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getMenuTree()
}
});
this.editForm = {}
this.dialogVisible = false;
})
}
}
});
},
// 点击编辑查询回显
changeUpdate(row) {
let id = row.id;
this.$axios.get("/sys/menu/info/" + id).then(res => {
this.editForm = res.data.data;
// 打开窗口
this.dialogVisible = true;
})
},
// 删除方法
delHandle(row) {
let id = row.id;
this.$axios.delete("/sys/menu/delete/" + id).then(res => {
// 删除成功弹框消息提醒
this.$message({
message: '删除成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getMenuTree()
}
});
})
},
}
}
</script>
<style>
</style>
20.4页面展示
21角色页面
21.1mock.js模拟后端
Mock.mock(RegExp('/sys/role/list*'), 'get', () => {
Result.data = {
"records": [
{
"id": 3,
"created": "2021-01-04T10:09:14",
"updated": "2021-01-30T08:19:52",
"statu": 1,
"name": "普通用户",
"code": "normal",
"remark": "只有基本查看功能",
"menuIds": []
},
{
"id": 6,
"created": "2021-01-16T13:29:03",
"updated": "2021-01-17T15:50:45",
"statu": 1,
"name": "超级管理员",
"code": "admin",
"remark": "系统默认最高权限,不可以编辑和任意修改",
"menuIds": []
}
],
"total": 2,
"size": 10,
"current": 1,
"orders": [],
"optimizeCountSql": true,
"hitCount": false,
"countId": null,
"maxLimit": null,
"searchCount": true,
"pages": 1
}
return Result
})
Mock.mock(RegExp('/sys/role/info/*'), 'get', () => {
Result.data = {
"id": 6,
"created": "2021-01-16T13:29:03",
"updated": "2021-01-17T15:50:45",
"statu": 1,
"name": "超级管理员",
"code": "admin",
"remark": "系统默认最高权限,不可以编辑和任意修改",
"menuIds": [1,3]
}
return Result
})
Mock.mock(RegExp('/sys/role/*'), 'post', () => {
return Result
})
Mock.mock(RegExp('/sys/role/delete/*'), 'delete', () => {
return Result
})
21.2views/system/Role.vue页面
<template>
<div>
<!-- 顶部搜索新增删除按钮 start-->
<el-form :inline="true" :model="searchForm" class="demo-form-inline">
<el-form-item label="名称">
<el-input v-model="searchForm.name"
placeholder="角色名称"
clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getRoleList()">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">新增</el-button>
</el-form-item>
<el-form-item>
<el-popconfirm title="这是一段内容确定批量删除吗?" @confirm="delByIds">
<el-button type="danger" slot="reference" :disabled="delStatus">批量删除</el-button>
</el-popconfirm>
</el-form-item>
</el-form>
<!-- 顶部搜索新增删除按钮 end-->
<!-- 数据table start -->
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
border
stripe
style="width: 100%"
@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="唯一编码" width="120"></el-table-column>
<el-table-column prop="remark" label="描述"></el-table-column>
<el-table-column prop="statu" label="状态" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.statu === 0" size="small" type="danger">禁用</el-tag>
<el-tag v-else-if="scope.row.statu === 1" size="small" type="success">正常</el-tag>
</template>
</el-table-column>
<!-- fixed="right"固定操作栏-->
<el-table-column prop="icon" label="操作" width="200" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="perHandle(scope.row)">分配权限</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="changeUpdate(scope.row)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<!-- <el-button type="text">删除</el-button>-->
<!-- 删除-->
<template>
<el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row)">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 数据table end -->
<!-- 分页功能 start
:current-page="current" 当前页
:page-size="size" 每页有几条
:total="total" 总条数
-->
<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>
<!-- 分页功能 end-->
<!-- 新增或修改的弹窗 start-->
<el-dialog
title="新增"
:visible.sync="dialogVisible"
width="500px"
:before-close="handleClose">
<!-- 弹框里的表单-->
<el-form :model="editForm" :rules="rules" 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>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm('editForm')">取 消</el-button
<el-button type="primary" @click="submitEditForm('editForm')">确 定</el-button>
</div>
</el-dialog>
<!-- 新增或修改的弹窗 end-->
<!-- 分配权限 start-->
<el-dialog
title="分配权限"
:visible.sync="dialogPrem"
width="500px"
>
<!-- 分配权限树
defaultProps 分配权限指定label和children
:default-expand-all=true 属性全部展开
check-strictly="true" 子父节点不再强制关联
-->
<el-tree
:data="premData"
show-checkbox
node-key="id"
ref="premTree"
:check-strictly="true"
:default-expand-all=true
:props="defaultProps">
</el-tree>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="submitPremForm('permForm')">分配权限</el-button>
</div>
</el-dialog>
<!-- 分配权限 end-->
</div>
</template>
<script>
export default {
name: 'Role',
data() {
return {
// 分配权限弹框
dialogPrem: false,
// 分配权限数据来源
premData: [],
// 分配权限指定label和children
defaultProps: {
children: 'children',
label: 'name'
},
// 搜索参数
params: {},
// 新增或修改提交的数据
editForm: {},
// 新增或修改弹框
dialogVisible: false,
// 分页参数(默认为第一页,每页10条,总条数0)
current: 1,
size: 10,
total: 0,
// 搜索条件
searchForm: {},
// 顶部删除
delStatus: true,
// 多选框
multipleSelection: [],
// 删除时的ids
ids: [],
// 列表数据
tableData: [],
// 表单校验
rules: {
name: [{required: true, message: '请输入角色名称', trigger: 'blur'}],
code: [{required: true, message: '请输入唯一编码', trigger: 'blur'}],
statu: [{required: true, message: '请选择状态', trigger: 'blur'}]
},
}
},
created() {
this.getRoleList()
},
methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
// 分页方法
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.size = val;
this.getRoleList();
},
// 分页方法
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.current = val;
this.getRoleList();
},
// 点击新增
onSubmit() {
// 点击新增弹窗
this.dialogVisible = true
},
// X 关闭
handleClose(done) {
this.editForm = {}
this.dialogVisible = false
// 分配权限弹框
this.dialogPrem = false,
// 分配权限数据来源
this.premData = []
},
// 清空表单的方法
resetForm(formName) {
debugger;
this.$refs[formName].resetFields();
this.editForm = {}
this.dialogVisible = false
},
// 查询列表
getRoleList() {
// 搜索的参数
this.params = {
name: this.searchForm.name,
current: this.current,
size: this.size
},
// 发起请求
this.$axios.get("/sys/role/list", this.params).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;
})
},
// 新增或修改方法
submitEditForm(editForm) {
this.$refs["editForm"].validate((valid) => {
if (valid) {
// 如果id不为null,修改
debugger
this.$axios.post("/sys/role/" + (this.editForm.id ? 'update' : 'save'), editForm).then(res => {
// 保存成功弹框消息提醒
this.$message({
message: '操作成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getRoleList()
}
});
this.editForm = {}
this.dialogVisible = false;
})
}
});
},
// 编辑回显数据
changeUpdate(row) {
this.$axios.get("/sys/role/info/" + row.id).then(res => {
this.editForm = res.data.data
this.dialogVisible = true
})
},
// 单个删除
delHandle(row) {
let id = row.id;
this.$axios.delete("/sys/role/delete/" + id).then(res => {
// 删除成功弹框消息提醒
this.$message({
message: '删除成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getMenuTree()
}
});
})
},
// 批量删除(选择多选框激活批量删除)
handleSelectionChange(val) {
console.log("多选框", val)
this.multipleSelection = val;
// 来判断是否激活批量删除
this.delStatus = val.length == 0
},
// 批量删除
delByIds() {
this.multipleSelection.forEach(row => {
this.ids.push(row.id)
})
console.log(this.ids)
this.$axios.delete("/sys/role/delete/" + this.ids).then(res => {
// 删除成功弹框消息提醒
this.$message({
message: '批量删除成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getMenuTree()
}
});
})
},
// 点击分配权限
perHandle(row) {
// 查询所有的菜单数据
this.$axios.get("/sys/menu/list").then(res => {
this.premData = res.data.data;
console.log(this.premData)
})
// 查询已经存在的
this.$axios.get("/sys/role/info/" + row.id).then(res => {
// 通过这个赋值,menuIds里是已选择的id集合
this.$refs.premTree.setCheckedKeys(res.data.data.menuIds);
this.permForm = res.data.data;
})
// 弹窗打开
this.dialogPrem = true
},
// 分配权限提交
submitPremForm() {
// 通过key已选择的id
let menus = this.$refs.premTree.getCheckedKeys();
console.log(menus);
this.$axios.post("/sys/role/perm" + this.permForm.id, menus).then(res => {
// 删除成功弹框消息提醒
this.$message({
message: '分配权限成功',
type: 'success',
// onClose是保存成功后关闭后查询列表
onClose: () => {
this.getRoleList()
}
});
this.dialogPrem = false;
})
},
}
}
</script>
<style>
/*分页样式,向右便宜,上下有10个边框*/
.el-pagination {
float: right;
margin-top: 10px;
}
</style>
22用户页面
22.1mock.js模拟后端
Mock.mock(RegExp('/sys/user/list*'), 'get', () => {
Result.data = {
"records": [
{
"id": 1,
"created": "2021-01-12T22:13:53",
"updated": "2021-01-16T16:57:32",
"statu": 1,
"username": "admin",
"password": "$2a$10$R7zegeWzOXPw871CmNuJ6upC0v8D373GuLuTw8jn6NET4BkPRZfgK",
"avatar": "https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg",
"email": "123@qq.com",
"city": "广州",
"lastLogin": "2020-12-30T08:38:37",
"roles": [
{
"id": 6,
"created": "2021-01-16T13:29:03",
"updated": "2021-01-17T15:50:45",
"statu": 1,
"name": "超级管理员",
"code": "admin",
"remark": "系统默认最高权限,不可以编辑和任意修改",
"menuIds": []
},
{
"id": 3,
"created": "2021-01-04T10:09:14",
"updated": "2021-01-30T08:19:52",
"statu": 1,
"name": "普通用户",
"code": "normal",
"remark": "只有基本查看功能",
"menuIds": []
}
]
},
{
"id": 2,
"created": "2021-01-30T08:20:22",
"updated": "2021-01-30T08:55:57",
"statu": 1,
"username": "test",
"password": "$2a$10$0ilP4ZD1kLugYwLCs4pmb.ZT9cFqzOZTNaMiHxrBnVIQUGUwEvBIO",
"avatar": "https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg",
"email": "test@qq.com",
"city": null,
"lastLogin": null,
"roles": [
{
"id": 3,
"created": "2021-01-04T10:09:14",
"updated": "2021-01-30T08:19:52",
"statu": 1,
"name": "普通用户",
"code": "normal",
"remark": "只有基本查看功能",
"menuIds": []
}
]
}
],
"total": 2,
"size": 10,
"current": 1,
"orders": [],
"optimizeCountSql": true,
"hitCount": false,
"countId": null,
"maxLimit": null,
"searchCount": true,
"pages": 1
}
return Result
})
Mock.mock(RegExp('/sys/user/*'), 'post', () => {
return Result
})
Mock.mock(RegExp('/sys/user/info/*'), 'get', () => {
Result.data = {
"id": 2,
"created": "2021-01-30T08:20:22",
"updated": "2021-01-30T08:55:57",
"statu": 1,
"username": "test",
"password": "$2a$10$0ilP4ZD1kLugYwLCs4pmb.ZT9cFqzOZTNaMiHxrBnVIQUGUwEvBIO",
"avatar": "https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg",
"email": "test@qq.com",
"city": null,
"lastLogin": null,
"roles": []
}
return Result
})
22.2views/system/User.vue页面
<template>
<div> <!--搜索框-->
<el-form :inline="true" :model="searchForm">
<el-form-item>
<el-input v-model="searchForm.username" placeholder="名称" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button>搜索</el-button>
<!-- :v-if="hasAuth('sys:user:save')-->
<el-button type="primary"
@click="dialogFormVisible = true"
:v-if="hasAuth('sys:user:save')"
>新增
</el-button>
<el-popconfirm title="确定要删除这些记录吗?" @confirm="delHandle(null)" style="margin-left: 10px;"
>
<el-button type="danger" slot="reference" :disabled="delBtnStu">批量删除</el-button>
</el-popconfirm>
</el-form-item>
</el-form>
<!--列表-->
<el-table ref="multipleTable" border stripe :data="tableData" tooltip-effect="dark" style="width: 100%"
@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 label="角色名称" width="180">
<template slot-scope="scope">
<el-tag style="margin-right: 5px;" size="small" type="info" v-for="item in scope.row.roles">{{ 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 label="状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.statu === 0" size="small" type="danger">禁用</el-tag>
<el-tag v-else-if="scope.row.statu === 1" size="small" type="success">正常</el-tag>
</template>
</el-table-column>
<el-table-column prop="created" label="创建时间" width="200"></el-table-column>
<el-table-column 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>
<el-popconfirm title="确定要删除这条记录吗?" @confirm="delHandle(scope.row.id)">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!--页码-->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="current"
:page-sizes="[10, 20, 50, 100]" :page-size="size" layout="total, sizes, prev, pager, next, jumper"
:total="total"></el-pagination>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="600px">
<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="submitEditForm('editForm')">确 定</el-button>
</div>
</el-dialog>
<!-- 分配权限对话框 -->
<el-dialog title="分配角色" :visible.sync="roleDialogFormVisible" width="600px" @closed="resetForm('roleForm')">
<el-form :model="roleForm" ref="roleForm">
<el-tree :data="roleTreeData" show-checkbox ref="roleTree" node-key="id" :default-expand-all=true
:props="defaultProps"></el-tree>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm('roleForm')">取 消</el-button>
<el-button type="primary" @click="submitRoleForm('roleForm')">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'User',
data() {
return {
searchForm: {username: ''},
editForm: {},
editFormRules: {
username: [{required: true, message: '请输入用户名称', trigger: 'blur'}],
email: [{required: true, message: '请输入邮箱', trigger: 'blur'}],
statu: [{required: true, message: '请选择状态', trigger: 'blur'}]
},
current: 1,
total: 0,
size: 10,
dialogFormVisible: false,
tableData: [],
multipleSelection: [],
delBtnStu: true,
roleDialogFormVisible: false,
roleForm: {},
defaultProps: {children: 'children', label: 'name'},
roleTreeData: [],
treeCheckedKeys: [],
}
}, methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
}, handleSelectionChange(rows) {
this.multipleSelection = rows;
this.delBtnStu = rows.length == 0
}, getUserList() {
this.$axios.get('/sys/user/list', {
params: {
name: this.searchForm.name,
current: this.current,
size: this.size
}
}).then(res => {
this.tableData = res.data.data.records
this.current = res.data.data.current
this.size = res.data.data.size
this.total = res.data.data.total
})
}, handleSizeChange(val) {
this.size = val
this.getUserList()
}, handleCurrentChange(val) {
this.current = val
this.getUserList()
}, submitEditForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post('/sys/user/' + (this.editForm.id ? "update" : "save"), this.editForm).then(res => {
console.log(res.data)
this.resetForm(formName)
this.$message({
showClose: true, message: '恭喜你,操作成功', type: 'success', onClose: () => {
this.getUserList()
}
});
})
} else {
console.log('error submit!!');
return false;
}
});
}, editHandle(id) {
console.log(id)
this.$axios.get("/sys/user/info/" + id).then(res => {
this.editForm = res.data.data
this.dialogFormVisible = true
})
}, delHandle(id) {
var ids = []
id ? ids.push(id) : 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()
}
});
})
}, resetForm(formName) {
this.$refs[formName].resetFields();
this.editForm = {}
this.dialogFormVisible = false
this.roleDialogFormVisible = false
}, roleHandle(id) {
this.$axios.get("/sys/user/info/" + id).then(res => {
const sysuser = res.data.data
var roleIds = []
sysuser.roles.forEach(row => {
roleIds.push(row.id)
})
console.log("roleIds")
console.log(roleIds)
this.roleForm = res.data.data
console.log("this.treeCheckedKeys")
console.log(this.treeCheckedKeys)
this.$axios.get("/sys/role/list").then(res => {
this.roleTreeData = res.data.data.records
this.$refs.roleTree.setCheckedKeys(roleIds);
})
})
this.roleDialogFormVisible = true
}, submitRoleForm(formName) {
var roleIds = []
roleIds = this.$refs.roleTree.getCheckedKeys()
console.log(roleIds)
console.log(this.roleForm.id)
this.$axios.post("/sys/user/role/" + this.roleForm.id, roleIds).then(res => {
this.$message({
showClose: true, message: '恭喜你,操作成功', type: 'success', onClose: () => {
this.resetForm(formName)
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: () => {
}
});
})
})
}
},
created() {
this.getUserList()
},
}</script>
<style>
</style>
23前端权限控制
23.1在src/main.js添加
Vue.mixin({
methods: {
hasAuth(perm) {
var authority = this.$store.state.menus.permList
console.log(authority)
return authority.indexOf(perm) > -1
}
}
})
23.2使用权限
:v-if="hasAuth('sys:user:save')"
23.2mock.js模拟返回权限
let authoritys = ['sys:user:save']
23.3其他
之前写的
1.在src/store/modules/menus.js设置权限的方法
2.在src/router/index.js拿到权限并设置到localStorage中
store.commit("setPermList", res.data.data.authoritys)
24结束语
VueAdmin - 前后端分离后台管理系统
线上演示:https://www.markerhub.com/vueadmin
登录密码:1234567
前端笔记:https://shimo.im/docs/pxwyJHgqcWjWkTKX/
后端笔记:https://shimo.im/docs/OnZDwoxFFL8bnP1c/
源码分享:
https://github.com/markerhub/vueadmin
https://gitee.com/markerhub/VueAdmin
视频讲解:https://www.bilibili.com/video/BV1af4y1s7Wh/
|