• 【前端】Vue+Element UI案例:通用后台管理系统-面包屑、tag栏



    参考视频: VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目

    案例链接
    【前端】Vue+Element UI案例:通用后台管理系统-导航栏(视频p1-16)https://blog.csdn.net/karshey/article/details/127640658
    【前端】Vue+Element UI案例:通用后台管理系统-Header+导航栏折叠(p17-19)https://blog.csdn.net/karshey/article/details/127652862
    【前端】Vue+Element UI案例:通用后台管理系统-Home组件:卡片、表格(p20-22)https://blog.csdn.net/karshey/article/details/127674643
    【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表准备:axios封装、mock数据模拟实战(p23-25)https://blog.csdn.net/karshey/article/details/127735159
    【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表:折线图、柱状图、饼状图(p27-30)https://blog.csdn.net/karshey/article/details/127737979
    【前端】Vue+Element UI案例:通用后台管理系统-面包屑、tag栏(p31-35)https://blog.csdn.net/karshey/article/details/127756733
    【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出(p36-38)https://blog.csdn.net/karshey/article/details/127787418
    【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框(p39-42)https://blog.csdn.net/karshey/article/details/127777962
    【前端】Vue+Element UI案例:通用后台管理系统-登陆页面Login(p44)https://blog.csdn.net/karshey/article/details/127795302
    【前端】Vue+Element UI案例:通用后台管理系统-登陆页面功能:登录权限跳转、路由守卫、退出(p45-46)https://blog.csdn.net/karshey/article/details/127849502
    【前端】Vue+Element UI案例:通用后台管理系统-登陆不同用户显示不同菜单、动态添加路由(p47-48)https://blog.csdn.net/karshey/article/details/127865621
    【前端】Vue+Element UI案例:通用后台管理系统-项目总结https://blog.csdn.net/karshey/article/details/127867638

    目标

    在这里插入图片描述

    • 点击左边的tab栏,如果在面包屑上没有则添加
    • 点击面包屑或tag可以进行路由跳转
    • tag可以删除
    • 若删除的是当前页面,则路由跳转至下一个tag
    • 若删除的当前页面是最后一个,则跳转至前一个
    • 用vuex完成组件间的通信

    代码

    0.创建组件、完成路由

    首先,我们并没有Mall、User等组件,我们要先创建它们,然后写到路由上。至于有哪些路由,见数据MenuData

    const MenuData= [
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home',
          url: 'Home/Home'
        },
        {
          path: '/mall',
          name: 'mall',
          label: '商品管理',
          icon: 'video-play',
          url: 'MallManage/MallManage'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user',
          url: 'UserManage/UserManage'
        },
        {
          label: '其他',
          icon: 'location',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting',
              url: 'Other/PageOne'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting',
              url: 'Other/PageTwo'
            }
          ]
        }
    ]
    
    export default MenuData
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    则router下的index.js如下:

    import Vue from "vue";
    import VueRouter from "vue-router";
    import Main from '../Views/Main'
    import Home from '../Views/Home.vue'
    import Mall from '../Views/Mall.vue'
    import User from '../Views/User.vue'
    import PageOne from '../Views/PageOne.vue'
    import PageTwo from '../Views/PageTwo.vue'
    Vue.use(VueRouter)
    
    const routes=[
        // 主路由
        {
            path:'/',
            component:Main,
            redirect: '/home', // 重定向
            children:[
                // 子路由
                // 这是本次写的部分
                { path: '/home', name: 'home', component: Home }, // 首页
                { path: '/user', name: 'user', component: User }, // 用户管理
                { path: '/mall', name: 'mall', component: Mall }, // 商品管理
                { path: '/page1', name: 'page1', component: PageOne }, // 页面1
                { path: '/page2', name: 'page2', component: PageTwo }, // 页面2
            ]
        }
    ]
    
    const router = new VueRouter({
        routes
    })
    
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    1.面包屑

    面包屑是放在Header的。我们打开Element UI,找到对应的组件:
    在这里插入图片描述

    <el-breadcrumb separator="/">
      <el-breadcrumb-item :to="{ path: '/' }">首页el-breadcrumb-item>
      <el-breadcrumb-item><a href="/">活动管理a>el-breadcrumb-item>
      <el-breadcrumb-item>活动列表el-breadcrumb-item>
      <el-breadcrumb-item>活动详情el-breadcrumb-item>
    el-breadcrumb>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    写进CommonHeader中:

    <div class="l-content">
       <el-button @click="handleMenu" icon="el-icon-menu" size="mini">el-button>
       
       <el-breadcrumb separator="/">
           <el-breadcrumb-item :to="{ path: '/' }">首页el-breadcrumb-item>
       el-breadcrumb>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们这里要动态绑定路由的数据,点了某个路由才会显示它。

    注意:首页是不管怎样都会有的,因此首页的路由数据是写死在vuex的store的state中的,而其他的是动态添加的(Array.push)

    2.用Vuex完成数据的通信:从Aside和Header到面包屑和tag

    为什么会有组件间数据的通信呢?因为我们点击路由跳转在Aside,显示的面包屑在Header,而tag在每一个组件都要显示,所以它要单独写一个组件放进Main中。

    而组件间的通信我们用的是Vuex,这个在之前用过,具体不再赘述。

    在store文件夹下的tab.js添加:

    • tagList:在state中,用于表示面包屑的数据
    • selectMenu:在mutation中,用于更新面包屑的数据
    export default {
        state: {
            isCollapse: false,//导航栏是否折叠
            tabList: [
                {
                    path: '/',
                    name: 'home',
                    label: '首页',
                    icon: 's-home',
                    url: 'Home/Home'
                }
            ],//面包屑的数据:点了哪个路由,首页是一定有的
        },
        mutations: {
            // 修改导航栏展开和收起的方法
            CollapseMenu(state) {
                state.isCollapse = !state.isCollapse
            },
            // 更新面包屑的数据
            SelectMenu(state, item) {
                // 如果点击的不在面包屑数据中,则添加
                const index = state.tabList.findIndex(val => val.name === item.name)
                if (index === -1) {
                    state.tabList.push(item)
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    要在Aside的侧边栏点击事件处添加面包屑部分代码:

    clickItem(item) {
       // 防止自己跳自己的报错
        if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) {
            this.$router.push(item.path)
        }
        // 面包屑
        this.$store.commit('SelectMenu',item)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    到这里,只要在侧边栏点击了tab,就会产生路由跳转,且点击的数据若是新产生的,则会添加到tagList中。加粗部分是我们上面代码所完成的需求。

    接下来我们需要将tagList中的数据在面包屑中显示出来。在Header的面包屑部分绑定数据:v-for对每一个tagList:

    
    <el-breadcrumb separator="/">
        <el-breadcrumb-item v-for="item in tags" 
        :key="item.path" 
        :to="{ path: item.path }">
        {{item.label}}
        el-breadcrumb-item>
    el-breadcrumb>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    js:mapState 是辅助函数,不了解的话可以去看vuex官方文档。由于本篇目的只在于做项目,函数功能不赘述。

    import { mapState } from 'vuex'
    export default {
        computed: {
            ...mapState({
                tags: state => state.tab.tabList
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    效果:从上到下把所有tab都点一遍。显然完成了需求,但是样式不对。
    在这里插入图片描述

    3.面包屑样式

    面包屑样式需求:

    • 和button同一行
    • 上下居中
    • 和左边button有距离
    • 最后一个的字是白色(#fff)
    • 其他颜色的字是灰色(#666)

    css:

    .l-content {
        display: flex;
        // 上下居中
        align-items: center;
    
        .el-breadcrumb {
            margin-left: 15px;
    
            // deep 强制生效
            /deep/.el-breadcrumb__item {
                .el-breadcrumb__inner {
                    &.is-link {
                        color: #666;
                    }
                }
    
                &:last-child {
                    .el-breadcrumb__inner {
                        color: #fff;
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    效果:
    在这里插入图片描述

    4.tag栏结构

    tag栏在任何页面都要出现,说明它要单独写成一个组件CommonTags.vue,并放在Main中。

    在这里插入图片描述
    在Element UI中找到tag组件:
    在这里插入图片描述

    稍微看一下script代码,很明显我们用不到它。

    <el-tag
      v-for="tag in tags"
      :key="tag.name"
      closable
      :type="tag.type">
      {{tag.name}}
    el-tag>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码中不了解的属性(Attributes)可以在文档中查一下。红框中为本次会用到的属性:
    在这里插入图片描述

    <template>
        <div class="tabs">
            
            
            <el-tag v-for="item in tags" :key="item.path" :closable="item.name !== 'home'"
                :effect="item.name === $route.name ? 'dark' : 'plain'">
                {{ item.label }}
            el-tag>
        div>
    template>
    
    <script>
    import { mapState } from 'vuex'
    
    export default {
        computed: {
            ...mapState({
                tags: state => state.tab.tabList
            })
        }
    }
    script>
    
    <style>
    
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    5.tag事件

    • 删除:点击x删除对应的tag和面包屑
    • 若删除的是当前页面,则路由跳转至下一个tag
    • 若删除的当前页面是最后一个,则跳转至前一个
    • 点击:点击哪个tag就跳转到哪个tag

    这两个事件分别为:(第一万次感叹,组件真好用)
    在这里插入图片描述
    html:

    <el-tag v-for="(item, index) in tags" :key="item.path" :closable="item.name !== 'home'"
        :effect="item.name === $route.name ? 'dark' : 'plain'" @click="changeMenu(item)"
        @close="handleClose(item, index)">
        {{ item.label }}
    el-tag>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    点击事件:

    changeMenu(item) {
        this.$router.push({ name: item.name })
    }
    
    • 1
    • 2
    • 3

    删除事件:

    handleClose(item, index) {
        // 删除面包屑数据
        this.$store.commit('closeTag', item)
        // 如果删除的刚好是自己
        if (item.name === this.$route.name) {
            const length = this.tags.length
            // 如果删除的是最后一个:跳到前一个
            if (length === index) {
                this.$router.push({ name: this.tags[index - 1].name })
            }
            // 不是最后一个:往后一个
            else {
                this.$router.push({ name: this.tags[index].name })
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    store中的tab.js,在mutation里:

    // 删除tag:删除tabList中对应的item
    closeTag(state, item) {
        // 要删除的是state.tabList中的item
        const index = state.tabList.findIndex(val => val.name === item.name)
        state.tabList.splice(index, 1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.tag样式

    .tabs{
        padding: 20px;
    
        .el-tag{
            margin-right: 15px;
            // 鼠标悬停:小手
            cursor: pointer;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    效果

    在这里插入图片描述

    总代码

    本篇修改或新建的文件

    在这里插入图片描述

    CommonTags.vue

    <template>
        <div class="tabs">
            
            
            <el-tag v-for="(item, index) in tags" :key="item.path" :closable="item.name !== 'home'"
                :effect="item.name === $route.name ? 'dark' : 'plain'" @click="changeMenu(item)"
                @close="handleClose(item, index)">
                {{ item.label }}
            el-tag>
        div>
    template>
    
    <script>
    import { mapState } from 'vuex'
    
    export default {
        methods: {
            changeMenu(item) {
                this.$router.push({ name: item.name })
            },
            handleClose(item, index) {
                // 删除面包屑数据
                this.$store.commit('closeTag', item)
                // 如果删除的刚好是自己
                if (item.name === this.$route.name) {
                    const length = this.tags.length
                    // 如果删除的是最后一个:跳到前一个
                    if (length === index) {
                        this.$router.push({ name: this.tags[index - 1].name })
                    }
                    // 不是最后一个:往后一个
                    else {
                        this.$router.push({ name: this.tags[index].name })
                    }
                }
            }
        },
        computed: {
            ...mapState({
                tags: state => state.tab.tabList
            })
        }
    }
    script>
    
    <style lang="less" scoped>
    .tabs{
        padding: 20px;
    
        .el-tag{
            margin-right: 15px;
            // 鼠标悬停:小手
            cursor: pointer;
        }
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    tab.js

    export default {
        state: {
            isCollapse: false,//导航栏是否折叠
            tabList: [
                {
                    path: '/',
                    name: 'home',
                    label: '首页',
                    icon: 's-home',
                    url: 'Home/Home'
                }
            ],//面包屑的数据:点了哪个路由,首页是一定有的
        },
        mutations: {
            // 修改导航栏展开和收起的方法
            CollapseMenu(state) {
                state.isCollapse = !state.isCollapse
            },
            // 更新面包屑的数据
            SelectMenu(state, item) {
                // 如果点击的不在面包屑数据中,则添加
                const index = state.tabList.findIndex(val => val.name === item.name)
                if (index === -1) {
                    state.tabList.push(item)
                }
            },
            // 删除tag:删除tabList中对应的item
            closeTag(state, item) {
                // 要删除的是state.tabList中的item
                const index = state.tabList.findIndex(val => val.name === item.name)
                state.tabList.splice(index, 1)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    router的index.js

    import Vue from "vue";
    import VueRouter from "vue-router";
    import Main from '../Views/Main'
    import Home from '../Views/Home.vue'
    import Mall from '../Views/Mall.vue'
    import User from '../Views/User.vue'
    import PageOne from '../Views/PageOne.vue'
    import PageTwo from '../Views/PageTwo.vue'
    Vue.use(VueRouter)
    
    const routes=[
        // 主路由
        {
            path:'/',
            component:Main,
            redirect: '/home', // 重定向
            children:[
                // 子路由
                { path: '/home', name: 'home', component: Home }, // 首页
                { path: '/user', name: 'user', component: User }, // 用户管理
                { path: '/mall', name: 'mall', component: Mall }, // 商品管理
                { path: '/page1', name: 'page1', component: PageOne }, // 页面1
                { path: '/page2', name: 'page2', component: PageTwo }, // 页面2
            ]
        }
    ]
    
    const router = new VueRouter({
        routes
    })
    
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    隐藏底部任务栏图标的方法
    asp.net core之中间件
    vscode debug go
    postgresql14-表的管理(四)
    bus_type、device、device_driver
    Vue页面嵌入其他页面
    一种基于堆的链式优先队列实现(使用golang)
    当用Kiel打开代码时,电脑显示只读
    java计算机毕业设计小型酒店管理系统源代码+数据库+系统+lw文档
    JavaFX开发教程——快速入门FX
  • 原文地址:https://blog.csdn.net/karshey/article/details/127756733