二十二、SpringBoot + Jwt + Vue 权限管理系统 (3)

一、动态菜单栏开发

  1. 上两节代码中,左侧的菜单栏的数据是写死的,在实际场景中我们不可能这样做,因为菜单是需要根据登录用户的权限动态显示菜单的,也就是用户看到的菜单栏可能是不一样的,这些数据需要去后端访问获取。首先我们先把写死的数据简化成一个json数组数据,然后for循环展示出来,代码如下:

一步一步来,不要急躁哦!!

  1. /src/views/inc/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">
            <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接口,因为是动态菜单,一般我们也要考虑到权限问题,所以我们请求数据的时候一般除了动态菜单,还要权限的数据,比如菜单的添加、删除是否有权限,是否能显示该按钮等,有了权限数据我们就定动态决定是否展示这些按钮了。

  1. mock.js

在这里插入图片描述

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中声明的。

这个路由问题我提供两个解决方案:

  1. 全部写死,也就是提前写好所有的路由,不管用户有没有权限,后面在通过权限数据来判断用户是否有权限访问路由。
  2. 动态渲染,就是把加载到的导航菜单数据动态绑定路由

这里我们使用第二种解决方案,这类简单点,后续我们再开发页面的时候就不需要去改动路由,可以动态绑定。

综上,我们把加载菜单数据这个动作放在router.js中。Router有个前缀拦截,就是在路由到页面之前我们可以做一些判断或者加载数据。

  1. 在router下的 index.js 中添加一下代码:

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)。

  1. 这样我们才能绑定添加到路由。所以我会修改mock中的nav的数据成这样:

在这里插入图片描述

//动态菜单
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: {
    },
})
  1. 注意这两个的引入哦!

在这里插入图片描述

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

  1. src/views/inc/SideMenu.vue

在这里插入图片描述

data() {
    return {
       /* menuList: this.$store.state.menus.menuList,*/
    }
},
computed:{
    menuList:{
        get(){
           return this.$store.state.menus.menuList
        }
    }
},
  1. 最后效果如下: http://localhost:8080/index

在这里插入图片描述

二、动态标签页开发

2.1 动态标签引入

上面做完之后,总还觉得少点什么,对了标签页,我看别的后台管理系统都有这个,效果是这样的:

在这里插入图片描述

搞起搞起,别人有我不能没有,于是我去element-ui中寻了一圈,发现Tab标签页组件挺符合我们要求的,可以动态增减标签页。

理想的动作是这样的:

  1. 当我们点击导航菜单,上方会添加一个对应的标签,注意不能重复添加,发现已存在标签直接切换到这标签即可
  2. 删除当前标签的时候会自动切换到前一个标签页
  3. 点击标签页的时候会调整到对应的内容页中

综合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">CSDN</el-link>
                    <el-link href="https://mp.weixin.qq.com/s/pmeZuoOR1hj-KS2NSwPPHg" target="_blank">个人公众号</el-link>
                </div>
            </el-header>
            <el-main>
                <Tabs></Tabs>
                <!--<el-image :src="require('@/assets/家乡.jpg')" style="width: 100%; height: 100%"></el-image>-->
                <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>

最终显示如下:

在这里插入图片描述

2.2 解释动态标

上面代码中,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;
}

2.3 添加导航标签

在这里插入图片描述

效果显示:

在这里插入图片描述

添加tab标签的时候注意需要激活指定当前标签,也就是设置editableTabsValue。然后我们也添加了setActiveTab方法,方便其他地方指定激活某个标签。

2.4 优化标签导航及页面同步显示

优化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: {
    },
})

三、菜单界面开发

3.1 先调整下页面样式

  1. 先调整下页面样式

在这里插入图片描述

3.2 Element-ui页面引入

Element-ui页面

在这里插入图片描述

在这里插入图片描述

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="排序号">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: "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    #启动运行
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Daniel521-Spark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值