• antd框架——实现自定义菜单功能——技能提升


    antd框架——实现自定义菜单功能

    最近在写后台管理系统,看到同事写的antd的框架,是在antd原有框架的基础上进行的修改。由于原有的框架中的菜单是menu.js的写法,类似于react的形式,后面要进行改动样式,因此自定义了一个menuNew的菜单组件,方便用于样式的修改。
    在这里插入图片描述

    1.左侧菜单可以通过a-layout-sider组件来展示

    在这里插入图片描述

    代码如下:

    1.1 html部分的代码
    <a-layout-sider
        :theme="sideTheme"
        :class="['side-menu', isMobile ? null : 'shadow']"
        width="220px"
        :collapsible="collapsible"
        v-model="collapsed"
        :trigger="null"
      >
        <div :class="['logo', theme]">
          <router-link to="/dashboard/workplace">
            <img src="@/assets/img/logo.png" />
            <h1>{{ systemName }}</h1>
          </router-link>
        </div>
        <div :class="['side-menu-content', 'beauty-scroll']">
          <MenuItem
            :theme="theme"
            :collapsed="collapsed"
            :options="menuData"
            @select="onSelect"
            class="menu"
          />
        </div>
      </a-layout-sider>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1.2 js部分的代码
    <script>
    import MenuItem from './MenuItem';
    import { mapState } from 'vuex';
    export default {
      name: 'SideMenu',
      components: { MenuItem },
      props: {
        collapsible: {
          type: Boolean,
          required: false,
          default: false,
        },
        collapsed: {
          type: Boolean,
          required: false,
          default: false,
        },
        menuData: {
          type: Array,
          required: true,
        },
        theme: {
          type: String,
          required: false,
          default: 'dark',
        },
      },
      computed: {
        sideTheme() {
          return this.theme == 'light' ? this.theme : 'dark';
        },
        ...mapState('setting', ['isMobile', 'systemName']),
      },
      methods: {
        onSelect(obj) {
          this.$emit('menuSelect', obj);
        },
      },
    };
    </script>
    
    • 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
    1.3 css部分的代码
    // .shadow{
    //   box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
    // }
    .side-menu{
      min-height: 100vh;
      // overflow-y: auto;
      z-index: 9;
      .logo{
        height: 64px;
        position: relative;
        line-height: 64px;
        padding-left: 24px;
        -webkit-transition: all .3s;
        transition: all .3s;
        overflow: hidden;
        background-color: #0B2540;
        &.light{
          background-color: #0B2540;
          h1{
            color: @primary-color;
          }
        }
        h1{
          color: @menu-dark-highlight-color;
          font-size: 20px;
          margin: 0 0 0 12px;
          display: inline-block;
          vertical-align: middle;
        }
        img{
          width:159px;
          vertical-align: middle;
        }
      }
      .side-menu-content{
        overflow-y: auto;
        height: calc(100vh - 64px);
      }
    }
    .ant-layout-sider{background: #213346;}
    // .menu{
    //   padding: 16px 0;
    //   background-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
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2.menuItem组件部分

    2.1 html部分代码
    <template>
      <div class="menu-wrap">
        <div
          class="menu-item-wrap"
          v-for="(item, index) in menuData"
          :key="`menu-${index}`"
        >
          <div
            class="menu-item"
            :class="{ active: item.active }"
            @click="handleRouter($event, item)"
            @mouseover="mouseover($event, item)"
          >
            <SvgIcon
              class="menu-icon"
              v-if="item.meta && item.meta.icon"
              :iconClass="item.meta.icon"
            />
            <div class="menu-text">{{ item.name }}</div>
            <SubItem
              class="menu-sub-item"
              :style="{ left: '80px', top: pageY + 'px' }"
              v-if="
                item.children &&
                  item.children.length > 0 &&
                  item.meta.type != 'link'
              "
              :list="item.children"
              :index="index"
              :collapsed="collapsed"
            />
          </div>
        </div>
      </div>
    </template>
    
    • 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
    2.2 js部分代码
    <script>
    import SubItem from './SubItem'
    export default {
      components: {
        SubItem
      },
      props: {
        options: {
          type: Array,
          required: true
        },
        theme: {
          type: String,
          required: false,
          default: 'dark'
        },
        collapsed: {
          type: Boolean,
          required: false,
          default: false
        }
      },
      watch: {
        $route() {
          this.menuData.forEach(item => {
            let active = false
            if (this.$route.fullPath.startsWith(item.fullPath)) {
              active = true
            }
            item.active = active
          })
        }
      },
      data() {
        return {
          menuData: [],
          openSelect: false,
          pageY: undefined,
          currentItem: null
        }
      },
      created() {
        this.menuData = JSON.parse(JSON.stringify(this.options))
          .map(item => {
            let active = false
            if (this.$route.fullPath.startsWith(item.fullPath)) {
              active = true
            }
            return {
              ...item,
              active: active
            }
          })
          .filter(item => {
            if (item.meta.type == 'link') {
              return true
            }
            if (item.meta.invisible) {
              return false
            }
            if (item.children?.length > 0) {
              return this.isShow(item.children)
            } else if (item.children) {
              return false
            } else {
              return true
            }
          })
      },
      methods: {
        isShow(list) {
          return list.some(item => {
            if (item.children?.length > 0) {
              return this.isShow(item.children)
            } else {
              return !item.meta.invisible
            }
          })
        },
        mouseover(e, item) {
          if (this.currentItem == item.name) {
            return
          }
          let height = window.innerHeight || document.body.clientHeight
          let pageY
          let doms
          if (e.currentTarget.getAttribute('class').includes('menu-item-wrap')) {
            pageY = e.clientY - e.offsetY
          } else if (
            e.currentTarget.parentElement
              .getAttribute('class')
              .includes('menu-item-wrap')
          ) {
            doms = e.currentTarget.parentElement
            pageY = doms.getBoundingClientRect().top
          }
    
          let domHeight
          if (doms && doms.getElementsByClassName('sub-item-content')[0]) {
            domHeight = doms
              .getElementsByClassName('sub-item-content')[0]
              .getBoundingClientRect().height
          } else {
            domHeight = 0
          }
          if (domHeight + pageY > height) {
            this.pageY = height - domHeight
          } else if (pageY < 64) {
            this.pageY = 64
          } else {
            this.pageY = pageY
          }
    
          this.currentItem = item.name
        },
        handleRouter(e, item) {
          if (item.redirect) {
            this.$router.push(item.fullPath)
          } else {
            if (item.meta.type == 'link') {
              this.$router.push(item.fullPath)
            }
          }
        }
      }
    }
    </script>
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    这个代码中有以下相关内容:

    2.2.1 vue项目——鼠标移入时判断是否含有某个类名
    e.currentTarget.getAttribute('class').includes('menu-item-wrap')
    
    • 1
    2.2.2 vue项目——鼠标移入时判断当前元素的父级元素是否含有某个类名

    当鼠标移入时,需要判断当前元素是否含有指定类名,如果不含有,则需要判断该元素的父级元素是否含有指定类名

    e.currentTarget.parentElement.getAttribute('class').includes('menu-item-wrap')
    判断当前元素的父级元素是否含有某个类名
    
    • 1
    • 2
    2.2.3 vue项目——判断父级元素距离页面顶部的距离
    let doms = e.currentTarget.parentElement;//获取当前元素的父级元素
    let pageY = doms.getBoundingClientRect().top
    //通过doms.getBoundingClientRect()可以获取该元素的很多参数,包含宽度高度 距离顶部 距离左边的大小等。
    判断父级元素距离页面顶部的距离
    
    • 1
    • 2
    • 3
    • 4
    2.2.4 vue项目——鼠标移入时判断当前元素距离页面顶部的距离
    let pageY = e.clientY - e.offsetY
    判断当前元素距离页面订单的距离
    
    • 1
    • 2
    2.2.5 vue项目——判断当前元素含有sub-item-content的元素的高度
    let domHeight = doms.getElementsByClassName('sub-item-content')[0].getBoundingClientRect().height
    判断当前元素的父级元素且类名为sub-item-content的元素的高度,如果该元素距离页面顶部的距离+元素高度超过了屏幕的高度,则该元素距离页面顶部的距离要改成 屏幕的可视高度-元素的高度,也就是元素与底部贴合,否则元素就是根据距离顶部的距离来渲染。
    
    • 1
    • 2

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

    2.2.6 vue项目——获取浏览器的可视区域的高度
    let height = window.innerHeight || document.body.clientHeight
    获取浏览器的可视区域的高度
    
    • 1
    • 2
    2.3 css部分代码
    
    
    
    • 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

    3.SubItem组件代码

    3.1 html部分代码
    <template>
      <div
        class="sub-item-wrap"
        :style="{ left: collapsed ? '80px' : '220px' }"
        v-if="visible"
      >
        <!-- :style="{top: top + 'px',left:collapsed ? '80px' : '220px'}" -->
        <div class="sub-item-content">
          <div class="item-box">
            <div
              class="item-list"
              v-for="(item, index) in subMenuData"
              :key="index"
            >
              <div
                class="sub-item"
                :class="{
                  'parent-node': subItem.hasChild,
                  'child-node': !subItem.hasChild,
                  'hide-node': subItem.childFlag,
                  active: $route.path == subItem.fullPath
                }"
                v-for="(subItem, i) in item"
                :key="i"
                @click.stop="handleRouter(subItem)"
              >
                <span>{{ subItem.name }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    • 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
    3.2 js部分代码
    <script>
    export default {
      props: {
        collapsed: {
          type: Boolean,
          required: false,
          default: false
        },
        list: Array,
        index: Number
      },
      data() {
        return {
          visible: false,
          subMenuData: [],
          top: 77
        }
      },
      mounted() {
        this.showSubItem(this.list, this.index)
      },
      methods: {
        showSubItem(data, index) {
          let top = 64 + 13 + index * 74 // 头部高度+搜索高度+当前点击下标*菜单高度
          const arr = this.getMenuData(JSON.parse(JSON.stringify(data)))
          const num = arr.length
          const contentHeight = 40 * num // 子菜单内容高度
          const clientHeight =
            document.documentElement.clientHeight || document.body.clientHeight // 可视区高度
          if (top + contentHeight + 16 + 5 <= clientHeight) {
            this.top = top
          } else {
            if (contentHeight + 16 + 64 <= clientHeight) {
              this.top = clientHeight - contentHeight - 16 - 5
            } else {
              this.top = 69
            }
          }
          this.visible = true
          // 处理多列子菜单
          let subMenuData = []
          let i = 0
          subMenuData[i] = []
          arr.forEach((item, index) => {
            subMenuData[i].push(item)
            if ((index + 1) % 6 == 0) {
              i++
              if (arr[index + 1]) {
                subMenuData[i] = []
              }
            }
          })
          this.subMenuData = subMenuData
        },
        getMenuData(data) {
          const arr = []
          data.forEach((item, i) => {
            if (!item.meta?.invisible) {
              arr.push({
                ...item,
                hasChild: item.children?.length > 0
              })
              if (item.children && item.children.length > 0) {
                arr.push(...this.getMenuData(item.children))
              }
            }
          })
          return arr
        },
        handleRouter(v) {
          if (v.hasChild) return
          if (this.$route.fullPath == v.fullPath) {
            return false
          }
          this.$router.push(v.fullPath)
        }
      }
    }
    </script>
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    3.3 css部分代码
    
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    效果图如下:

    完成!!!多多积累,多多收获!!!

  • 相关阅读:
    关于 分布式事务 你知道多少
    Vue轮播图
    linux内核管理
    java学习笔记---7
    求合伙人 求一个会做大模型开发的老板。。
    闲话Python编程-字典dict
    C++笔记
    【数据结构--排序】堆排序
    阿里云-源码构建容器镜像
    React中如何在事件处理的时候传参(详解)
  • 原文地址:https://blog.csdn.net/yehaocheng520/article/details/126584987