- 上两节代码中,左侧的菜单栏的数据是写死的,在实际场景中我们不可能这样做,因为菜单是需要根据登录用户的权限动态显示菜单的,也就是用户看到的菜单栏可能是不一样的,这些数据需要去后端访问获取。首先我们先把写死的数据简化成一个json数组数据,然后for循环展示出来,代码如下:
一步一步来,不要急躁哦!!
<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">
<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: [{
name: 'SysManga',
title: '系统管理',
icon: 'el-icon-s-operation',
path: '',
component: '',
children: [{
name: 'SysUser',
title: '用户管理',
icon: 'el-icon-s-custom',
path: '/sys/user',
children: []
},{
name: 'SysUser',
title: '角色管理',
icon: 'el-icon-s-custom',
path: '/sys/user',
children: []
}]
}, {
name: 'SysTools',
title: '系统工具',
icon: 'el-icon-s-tools',
path: '',
children: [{
name: 'SysDict',
title: '数字字典',
icon: 'el-icon-s-order',
path: '/sys/dict',
children: []
},]
}],
}
}
}
script>
<style scoped>
.el-menu-vertical-demo {
height: 100%;
}
style>
当时显示:
可以看到,我用for循环显示数据,那么这样变动菜单栏时候只需要修改data中的menuList即可。效果和之前的完全一样。 现在menuList的数据我们是直接写到页面data上的,一般我们是要请求后端的,所以这里我们定义一个mock接口,因为是动态菜单,一般我们也要考虑到权限问题,所以我们请求数据的时候一般除了动态菜单,还要权限的数据,比如菜单的添加、删除是否有权限,是否能显示该按钮等,有了权限数据我们就定动态决定是否展示这些按钮了。
Mock.mock('/sys/menu/nav', 'post', () => {
/*导航信息和权限信息,打开页面作者所专有的权限*/
//菜单json
let nav = [
{
name: 'SysManga',
title: '系统管理',
icon: 'el-icon-s-operation',
path: '',
component:'',
children: [{
name: 'SysUser',
title: '用户管理',
icon: 'el-icon-s-custom',
path: '/sys/user',
component:'sys/user',
children: []
},{
name: 'SysUser',
title: '角色管理',
icon: 'el-icon-s-custom',
path: 'sys/user',
component:'/sys/role',
children: []
}]
}, {
name: 'SysTools',
title: '系统工具',
icon: 'el-icon-s-tools',
path: '',
children: [{
name: 'SysDict',
title: '数字字典',
icon: 'el-icon-s-order',
path: '/sys/dict',
component:'sys/dict',
children: []
},]
}
];
//权限数据
let authoritys = ['SysUser',"SysUser:save"];
Result.data = {
};
Result.data.nav = nav
Result.data.authoritys = authoritys
return Result
});
这样我们就定义好了导航菜单的接口,什么时候调用呢?应该登录成功完成之后调用,但是并不是每一次打开我们都需要去登录,也就是浏览器已经存储到用户token的时候我们不需要再去登录的了,所以我们不能放在登录完成的方法里。那么是当前这个Home.vue页面吗?看起来没什么问题,反正每次都会进入这个页面,然后搞个开关控制是否重新加载就行?
我们这里还要考虑一个问题,就是导航菜单的路由问题,啥意思?就是点击菜单之后路由到哪个页面是需要在router中声明的。
这个路由问题我提供两个解决方案:
- 全部写死,也就是提前写好所有的路由,不管用户有没有权限,后面在通过权限数据来判断用户是否有权限访问路由。
- 动态渲染,就是把加载到的导航菜单数据动态绑定路由
这里我们使用第二种解决方案,这类简单点,后续我们再开发页面的时候就不需要去改动路由,可以动态绑定。
综上,我们把加载菜单数据这个动作放在router.js中。Router有个前缀拦截,就是在路由到页面之前我们可以做一些判断或者加载数据。
src/router/index.js
router.beforeEach((to, from, next) => {
let hasRoute = store.state.menus.hasRoute;
if (!hasRoute) {
axios.get("/sys/menu/nav", {
headers: {
Authorization: localStorage.getItem("token")
}
}).then(res => {
console.log(res.data.data);
//拿到menuList
store.commit("setMenuList", res.data.data.nav);
//拿到用户权限(权限是操作的权限,不是路由的权限)
store.commit("setPermList", res.data.data.authoritys);
console.log(store.state.menus.menuList);
//动态绑定路由
let newRoutes = router.options.routes;
resp.data.data.nav.forEach(menu =>{
if(menu.children){
menu.children.forEach(e =>{
//转成路由
let 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
}
let route = {
name:menu.name,
path:menu.path,
meta:{
icon:menu.icon,
title:menu.title
}
};
route.component = () => import( '@/views/'+menu.component+'.vue')
return route
};
export default router
可以看到,我们通过menuToRoute就是把menu数据转换成路由对象,然后router.addRoutes(newRoutes)动态添加路由对象。 同时上面的menu对象中,有个menu.component,这个就是连接对应的组件,我们需要添加上去,比如说/sys/user链接对应到component(sys/User)。
//动态菜单
Mock.mock('/sys/menu/nav', 'get', () => {
/*导航信息和权限信息,打开页面作者所专有的权限*/
//菜单json
let nav = [{
"id": 1,
"title": "系统管理",
"icon": "el-icon-s-operation",
"path": "",
"name": "sys:manage",
"component": "",
"children": [{
"id": 2,
"title": "用户管理",
"icon": "el-icon-s-custom",
"path": "/sys/user",
"name": "sys:user:list",
"component": "sys/User",
"children": []
}, {
"id": 3,
"title": "角色管理",
"icon": "el-icon-rank",
"path": "/sys/role",
"name": "sys:role:list",
"component": "sys/Role",
"children": []
}, {
"id": 4,
"title": "菜单管理",
"icon": "el-icon-menu",
"path": "/sys/menu",
"name": "sys:menu:list",
"component": "sys/Menu",
"children": []
}]
}, {
"id": 5,
"title": "系统工具",
"icon": "el-icon-s-tools",
"path": "",
"name": "sys:tools",
"component": null,
"children": [{
"id": 6,
"title": "数字字典",
"icon": "el-icon-s-order",
"path": "/sys/dict",
"name": "sys:dict:list",
"component": "sys/Dict",
"children": []
}]
}];
let authoritys = [];
Result.data = {
nav:nav,
authoritys:authoritys
};
return Result
});
同时上面router中我们还通过判断是否登录页面,是否有token等判断提前判断是否能加载菜单,同时还做了个开关hasRoute来动态判断是否已经加载过菜单。
还需要在store中定义几个方法用于存储数据,我们定义一个menu模块,所以在store中新建文件夹modules,然后新建menus.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default({
state: {
menuList: [],
setPermList:[],
hasRoute:false
},
mutations: {
setMenuList(state,menu){
state.menuList = menu
},
setPermList(state,perms){
state.permList = perms
},
changeRouteStatus(state,hasRoute){
state.hasRoute = hasRoute;
sessionStorage.setItem("hasRoute",hasRoute)
}
},
actions: {
},
})
这样我们菜单的数据就可以加载了,然后再SideMenu.vue中直接获取store中的menuList数据即可显示菜单出来了。
data() {
return {
/* menuList: this.$store.state.menus.menuList,*/
}
},
computed:{
menuList:{
get(){
return this.$store.state.menus.menuList
}
}
},
http://localhost:8080/index
上面做完之后,总还觉得少点什么,对了标签页,我看别的后台管理系统都有这个,效果是这样的:
搞起搞起,别人有我不能没有,于是我去element-ui中寻了一圈,发现Tab标签页组件挺符合我们要求的,可以动态增减标签页。
理想的动作是这样的:
- 当我们点击导航菜单,上方会添加一个对应的标签,注意不能重复添加,发现已存在标签直接切换到这标签即可
- 删除当前标签的时候会自动切换到前一个标签页
- 点击标签页的时候会调整到对应的内容页中
综合Vue的思想,我们可以这样设计:在Store中统一存储:
1、当前标签Tab,
2、已存在的标签Tab列表,然后页面从Store中获取列表显示,并切换到当前Tab即可。
删除时候我们循环当前Tab列表,剔除Tab,并切换到指定Tab。
我们先和左侧菜单一样单独定义一个组件Tabs.vue放在views/inc文件夹内:
src/views/inc/Tabs.vue
Tabs.vue
<template>
<el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
<el-tab-pane
:key="item.name"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
el-tab-pane>
el-tabs>
template>
<script>
export default {
name: "Tabs.vue",
data() {
return {
editableTabsValue: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
}
},
methods: {
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
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);
}
}
}
}
script>
<style scoped>
style>
ok,然后再Home.vue中引入我们Tabs.vue这个组件
Home.vue
<template>
<el-container>
<el-aside width="200px">
<SideMenu>SideMenu>
el-aside>
<el-container>
<el-header>
<strong>欢迎来到Daniel的vue-admin管理系统strong>
<div class="header-avatar block">
<el-avatar size="medium" :src="userInfo.avatar">el-avatar>
<el-dropdown>
<span class="el-dropdown-link">{{userInfo.username}}<i class="el-icon-arrow-down el-icon--right">i>span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<router-link :to="{name:'UserCenter'}">个人中心router-link>
el-dropdown-item>
<el-dropdown-item @click.native="logout">退出el-dropdown-item>
el-dropdown-menu>
el-dropdown>
<el-link href="https://blog.csdn.net/weixin_42171159?spm=1010.2135.3001.5343" target="_blank">CSDNel-link>
<el-link href="https://mp.weixin.qq.com/s/pmeZuoOR1hj-KS2NSwPPHg" target="_blank">个人公众号el-link>
div>
el-header>
<el-main>
<Tabs>Tabs>
<router-view>router-view>
el-main>
el-container>
el-container>
template>
<script>
import SideMenu from "./inc/SideMenu";
import Tabs from "./inc/Tabs"
export default {
name: "Home.vue",
components: {
SideMenu,Tabs
},
data(){
return{
userInfo:{ /*定义用户信息*/
id:'',
avatar:'',
username:''
}
}
},
created(){ /*调用获取用户的方法*/
this.getUserInfo() /*当页面渲染出来的时候,调用这个方法*/
},
methods:{
getUserInfo(){
this.$axios.get('/sys/userInfo').then(resp=>{
this.userInfo = resp.data.data;
});
},
logout(){
this.$axios.post('/logout').then(resp=>{
//console.log(resp.data.data)
localStorage.clear();
sessionStorage.clear();
this.$store.commit('resetState');
this.$router.push('/Login')
})
}
}
}
script>
<style scoped>
.el-container{
padding: 0;
margin: 0;
height: 700px; /*这里写个100不起作用?*/
}
.header-avatar{
float: right;
width: 250px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
}
.el-menu-vertical-demo {
height: 100%;
}
.el-header{
background-color: #17b3a3;
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;
text-align: center;
padding: 0;
}
a {
text-decoration: none;
}
style>
最终显示如下:
上面代码中,computed 表示当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值。这样我们就可以实时监测Tabs标签的动态变化实时显示(相当于实时get、set)。其他clickTab、removeTab的逻辑其实也还算简单,特别是removeTab注意考虑多种情况就可以。 然后我们来到store中的menu.js,我们添加 editableTabsValue和editableTabs,然后把首页作为默认显示的页面。
src/store/modules/menus.js
menus.js
state: {
// 菜单栏数据
menuList: [],
setPermList:[],
hasRoute:false,
editableTabsValue: 'index',
editableTabs: [{
title: '首页',
name: 'index',
content: 'Tab 1 content'
}],
},
Tabs.vue
data() {
return {
editableTabsValue: this.$store.state.menus.editableTabsValue,
editableTabs:this.$store.state.menus.editableTabs
}
},
因为tabs标签列表我们是存储在store中的,因此我们需要commit提交事件,因此我们在menu.js中添加addTabs方法
menu.js
addTab(state,tab) {
state.editableTabs.push({
title: tab.title,
name: tab.name,
});
state.editableTabsValue = tab.name;
}
效果显示:
添加tab标签的时候注意需要激活指定当前标签,也就是设置editableTabsValue。然后我们也添加了setActiveTab方法,方便其他地方指定激活某个标签。
优化1:添加的能添加了,但是添加的标签没法删除(因为没写set方法)
解决:
优化 2 :当连续点击左边菜单时候会出现如下,应该将重复菜单仅显示一个,就是当点击出现的菜单的时候,直接切换的已有的菜单上面
解决:
addTab(state,tab) {
let index = state.editableTabs.findIndex(e =>e.name === tab.name)
if(index === -1){
state.editableTabs.push({
title: tab.title,
name: tab.name,
});
}
state.editableTabsValue = tab.name;
}
好了完成了第一步了,现在我们需要点击菜单导航,然后再tabs列表中添加tab标签页,那么我们来到SideMenu.vue,我们给el-menu-item每个菜单都添加一个点击事件:
优化3: 首页的显示切换与高亮显示
SideMenu.vue
SideMenu.vue
<template>
<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" @click="selectMenu({name:'Index',title:'首页'})">
<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>
优化4: 当点击左边导航菜单管理时,页面和标签会同步显示相应菜单及菜单页
Tabs.vue
<template>
<el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit" @tab-click="clickTab">
<el-tab-pane
:key="item.name"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
el-tab-pane>
el-tabs>
template>
<script>
export default {
name: "Tabs.vue",
data() {
return {
//editableTabsValue: this.$store.state.menus.editableTabsValue,
//editableTabs:this.$store.state.menus.editableTabs
}
},
computed:{
editableTabs:{
get(){
return this.$store.state.menus.editableTabs
},
set(val){
this.$store.state.menus.editableTabs = val
}
},
editableTabsValue:{
get(){
return this.$store.state.menus.editableTabsValue
},
set(val){
this.$store.state.menus.editableTabsValue = val
}
}
},
methods: {
handleTabsEdit(targetName, action) {
/*if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}*/
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
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);
}
clickTab(target);
{
this.$router.push({name:target.name})
}
}
}
}
script>
<style scoped>
style>
优化5: 当删除 标签导航 的时候页面还显示之前菜单页面,并非显示标签前面菜单页面(如:菜单管理)
解决:
优化 6:当刷新浏览器的时候,发现标签菜单以及不存在了,但是浏览器路径依然存在,这就有问题
- 从上面图中我们可以看出刷新浏览器之后链接/sys/users不变,内容不变,但是Tab却不见了,所以我们需要修补一下,当用户是直接通过输入链接形式打开页面的时候我们也能根据链接自动添加激活指定的tab。那么在哪里添加这个回显的方法呢?router中?其实可以,只不过我们需要做判断,因为每次点击导航都会触发router。有没有更简便的方法?有的!因为刷新或者打开页面都是一次性的行为,所以我们可以在更高层的App.vue中做这个回显动作,具体如下:
解决:
App.vue
<script>
export default {
name: "App",
watch: { // 解决刷新浏览器没有tab的问题
$route(to, from) {
if (to.path != '/login') {
let obj = {
name: to.name,
title: to.meta.title
};
this.$store.commit("addTabs", obj)
}
}
}
}
</script>
上面代码可以看到,除了login页面,其他页面都会触发addTabs方法,这样我们就可以添加tab和激活tab了。
优化 7 : 当点击退出登录时,再次登录则标签导航继续以上次的方式存在,这个该怎么解决:
解决:
menus.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default({
state: {
// 菜单栏数据
menuList: [],
setPermList:[],
hasRoute:false,
editableTabsValue: 'index',
editableTabs: [{
title: '首页',
name: 'index',
content: 'Tab 1 content'
}],
},
mutations: {
setMenuList(state,menu){
state.menuList = menu
},
setPermList(state,perms){
state.permList = perms
},
changeRouteStatus(state,hasRoute){
state.hasRoute = hasRoute;
},
addTab(state,tab) {
let index = state.editableTabs.findIndex(e =>e.name === tab.name)
if(index === -1){
state.editableTabs.push({
title: tab.title,
name: tab.name,
});
}
state.editableTabsValue = tab.name;
},
resetState :(state) => {
state.menuList = [];
state.setPermList = [];
state.hasRoute = false;
state.editableTabsValue = "index";
state.editableTabs = [{
title: '首页',
name: 'index',
content: 'Tab 1 content'
}]
}
},
actions: {
},
})
Menu.vue
<template>
<div>
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item>
<el-button type="primary" @click="onSubmit">新增el-button>
el-form-item>
el-form>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
row-key="id"
border
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column
prop="date"
label="日期"
sortable
width="180">
el-table-column>
<el-table-column
prop="name"
label="姓名"
sortable
width="180">
el-table-column>
<el-table-column
prop="address"
label="地址">
el-table-column>
el-table>
div>
template>
<script>
export default {
name: "Menu.vue",
data() {
return {
tableData: [{
id: 1,
date: '2022-11-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: 2,
date: '2022-11-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: 3,
date: '2022-11-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [{
id: 31,
date: '2022-11-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: 32,
date: '2022-11-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}]
}, {
id: 4,
date: '2022-11-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}],
tableData1: [{
id: 1,
date: '2022-11-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: 2,
date: '2022-11-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: 3,
date: '2022-11-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
hasChildren: true
}, {
id: 4,
date: '2022-11-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
},
}
script>
<style scoped>
style>
引入结果显示:
上述 文章源码
菜单界面源码
Menu.vue
<template>
<div>
<el-form :inline="true">
<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="perms"
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="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="排序号">1el-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: "Menu",
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: {
}
}
script>
<style scoped>
style>
角色需要和菜单权限做关联,菜单是个树形结构的
Role.vue
<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>
因为我们父节点是列表,所以注意不要选中父节点就自动选子节点,注意分开哈哈。贴代码啦:
<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>
用户界面、按钮权限的控制——>后续再更新
注意:
导入前端建议按下面步骤运行
npm install cnpm -g 安装vue的脚手架工具
cnpm install vue-cli -g #安装依赖包
npm i element-ui -S #安装element-ui
cnpm install axios --save #安装 axios
cnpm install qs --save #安装 qs
cnpm install mockjs --save-dev #mockjs 插件
npm run serve #启动运行