最近在用antd的框架,框架的具体样式如下:

antd Dropdown item 点击 antd菜单_javascript

由于此菜单比较常规,现有设计图如下:

antd Dropdown item 点击 antd菜单_javascript_02


给出的菜单触发要求:

左菜单实现规则:

显示:
鼠标点击相应的一级菜单,右边浮动面板出现
注:鼠标hover其他一级菜单,右边浮动面板
不会切换

关闭:
1、鼠标点击其他一级菜单,右边浮动面板
切换到相应的内容
2、点击面板右上角关闭图标也可消失

跳转:
点击三级菜单跳转到相应页面

因此需要对antd框架的菜单部分进行相应的改动。

1.antd的菜单组件位置——src/components/menu/SideMenu.vue

antd Dropdown item 点击 antd菜单_ico_03

2.原有菜单使用的是menu.js——是dom拼接的结构

原有部分的内容:

2.1 html部分内容
<i-menu
   :theme="theme"
   :collapsed="collapsed"
   :options="menuData"
   @select="onSelect"
   class="menu"
 />
2.2 script部分内容
import IMenu from "./menu";
export default{
	name:'SideMenu',
	components: { IMenu },
}

3.分析菜单中的参数——theme collapsed menuData

1. theme:主题:跟随系统的主题改变而改变
2. collapsed:菜单是否折叠,改变的是内容区距离浏览器左边的位置
3. menuData:菜单的数据,是路由的数组形式

menuData菜单的过滤:将meta中的invisible为true的菜单要隐藏掉。由于可以有多级菜单,因此需要通过递归的方式来处理。

created(){
	this.menuData.map((menu) => {
	 if (menu.meta && menu.meta.invisible) {
	   return;
	 } else if (menu.children && menu.children.length) {
	   this.firstMenu.push({
	     ...menu,
	     children: this.filterMenu(menu.children),
	   });
	 } else {
	   this.firstMenu.push(menu);
	 }
	});
	console.log("firstMenu", this.firstMenu);
},
methods:{
 	filterMenu(arr) {
     var list =
       arr &&
       arr.filter((menu) => {
         if (menu.meta && menu.meta.invisible) {
           return;
         } else if (menu.children && menu.children.length) {
           return this.filterMenu(menu.children);
         } else {
           return menu;
         }
       });
     return list;
   },
}

4.自定义菜单

菜单的布局如下:

antd Dropdown item 点击 antd菜单_二级菜单_04

4.1 一级菜单可以通过a-menu组件来搭建

antd Dropdown item 点击 antd菜单_二级菜单_05


注意这个组件的用法:

如果没有二级目录,则直接使用a-menu-item,如果有二级目录,则需要用a-sub-menu标签包裹,里面再使用a-menu-item等标签

<a-menu mode="vertical" :theme="theme">
 <template v-for="(menu, mindex) in firstMenu">
   <a-menu-item
     @click="turnTo(menu)"
     :key="menu.path"
     v-if="!menu.children || menu.children.length == 0"
     :style="{
       color:
         currentMenu.indexOf(menu.fullPath || menu.path) > -1
           ? '#f90'
           : '#666',
     }"
   >
     <a-icon
       :type="menu.meta && menu.meta.icon"
       v-if="menu.meta && menu.meta.icon"
     />
     <span>{{ menu.name }}</span>
   </a-menu-item>
   <a-sub-menu
     :key="menu.path"
     :ref="'subRef' + mindex"
     :style="{
       color:
         currentMenu.indexOf(menu.fullPath || menu.path) > -1
           ? '#f90'
           : '#666',
     }"
     v-else
     @titleClick="mouseover($event, menu, mindex)"
     overflowedIndicator='<a-icon type="right" />'
   >
     <span slot="title"
       ><a-icon
         :type="menu.meta && menu.meta.icon"
         v-if="menu.meta && menu.meta.icon"
       /><span>{{ menu.name }}</span></span
     >
   </a-sub-menu>
 </template>
</a-menu>

分析一下上面的代码:

  1. theme:主题色,组件的主题色跟框架本身的主题色保持一致
  2. antd Dropdown item 点击 antd菜单_二级菜单_06

  3. mode:模式,菜单类型,现在支持垂直、水平、和内嵌模式三种,string: vertical vertical-right horizontal inline,影响到菜单的整体布局。
  4. antd Dropdown item 点击 antd菜单_javascript_07

  5. 根据设计图,我们可以看到菜单要求是向右的箭头,则可以使用mode="vertical"的垂直菜单方式。
  6. 图标与菜单标题展示:key要取唯一值,唯一值可以是menu.path路由路径
  7. 如果没有二级菜单和有二级菜单使用的组件不同,则需要根据是否有children参数来判断层级。
    一级菜单如果也要有图标,则需要添加a-icon组件,单独的一级菜单,则需要监听click点击事件。
<a-menu-item
  @click="turnTo(menu)"
  :key="menu.path"
  v-if="!menu.children || menu.children.length == 0"
  :style="{
    color:
      currentMenu.indexOf(menu.fullPath || menu.path) > -1
        ? '#f90'
        : '#666',
  }"
>
  <a-icon
    :type="menu.meta && menu.meta.icon"
    v-if="menu.meta && menu.meta.icon"
  />
  <span>{{ menu.name }}</span>
</a-menu-item>

单独的一级菜单的点击事件——也就是上面代码的turnTo方法如下

turnTo(menu){
	this.visibleFlag = false;
	this.$router.push({ path: menu.fullPath });
}

antd Dropdown item 点击 antd菜单_antd_08

更改后的函数如下:

turnTo(menu){
	this.visibleFlag = false;
    if (this.currentMenu != menu.fullPath) {
      this.$router.push({ path: menu.fullPath });
    }
}

4.2 二级菜单需要自定义dom

html部分的代码:

<a-sub-menu
  :key="menu.path"
  :ref="'subRef' + mindex"
  :style="{
    color:
      currentMenu.indexOf(menu.fullPath || menu.path) > -1
        ? '#f90'
        : '#666',
  }"
  v-else
  @titleClick="mouseover($event, menu, mindex)"
>
  <span slot="title"
    ><a-icon
      :type="menu.meta && menu.meta.icon"
      v-if="menu.meta && menu.meta.icon"
    /><span>{{ menu.name }}</span></span
  >
</a-sub-menu>

二级菜单需要实现以下几个功能:

4.2.1 根据点击内容,右侧相应位置出现弹层

这个功能最主要就是获取点击元素对应的一级菜单的位置,可以通过给一级菜单绑定ref属性来进行处理。

antd Dropdown item 点击 antd菜单_二级菜单_09


subMenu组件有个titleClick的点击事件,可以获取到当前元素的位置。

右侧弹层是否显示是通过visibleFlag参数来控制的。
secondMenu右侧菜单数据根据menu.children来展示。
右侧弹层的位置,特别是top值,需要根据this.$refs[ "subRef" + index][0].$el.getBoundingClientRect().top来实现,这行代码花了我好久的时间。。。

根据点击区域获取菜单的top—this.$refs["subRef" + index][0].$el.getBoundingClientRect().top

通过ref属性获取的dom元素,打印出来是vueComponent的类数组形式,则需要通过[0]选取数组的第一项,然后通过其中的$el.getBoundingClientRect().top的方式来获取top值

vue根据ref获取dom的top值的方法!!!

还有就是右侧弹层的宽度,我这边的基数是170px,根据二级菜单的数量来换列展示,因此宽度跟菜单的数量相关。

mouseover(e,menu,index){
	this.visibleFlag = true;
    this.secondMenu = menu.children;
    this.top = this.$refs[ "subRef" + index][0].$el.getBoundingClientRect().top;
    // console.log("top", this.top);
    var n = this.secondMenu.length / 5;
    this.width = Math.ceil(n) * 170;
}
4.2.2 右侧弹层的内容根据点击的内容展示
<div class="menuBgCls" :style="{ left: left + 'px' }" v-if="visibleFlag" @click="turnToPage">
  <div
    class="secondMenuCls"
    @click.stop
    :style="{ top: top + 'px', width: width + 'px' }"
  >
    <div class="item" v-for="(item, index) in secondMenu" :key="index">
      <div class="routerCls" @click="turnToPage">
        <router-link :to="item.fullPath">{{ item.name }}</router-link>
        <a-icon
          @click.stop
          type="star"
          class="starCls"
          :theme="index % 2 == 0 ? 'filled' : 'outlined'"
        />
      </div>
    </div>
    <div class="closeCls" @click="closeSecondMenu">
      <a-icon type="close" />
    </div>
  </div>
</div>

分析一下上面的代码:
首先要知道,右侧部分有两部分,底层是一个半透明的蒙层,上面是菜单展示区域。

  1. visibleFlag:控制弹层的显示与隐藏
  2. click:点击蒙层时,弹层部分要隐藏
  3. 蒙层的left:根据折叠与非折叠状态,left值不同。
  4. 菜单展示区域的left和top值:根据点击区域获取left和top值,主要是top值,left值还是跟折叠与非折叠有关系。
  5. 点击右上角的X,也可以关闭弹层部分
4.2.3 右侧弹层的样式
.menuBgCls {
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  right: 0;
  top: 0;
}
.secondMenuCls {
  position: absolute;
  left: 5px;
  padding: 20px 40px 10px 5px;
  background: #fff;
  z-index: 11111;
  min-width: 170px;
  width: auto;
  max-height: 240px;
  border-radius: 10px;
  box-shadow: 0 0 80px 5px rgba(0, 0, 0, 0.08);
  display: flex;
  // flex-wrap: wrap;
  flex-direction: column;
  flex-flow: column wrap;
  .closeCls {
    position: absolute;
    right: 2px;
    top: 8px;
    padding: 0 5px;
    cursor: pointer;
    i {
      font-size: 12px;
      &:hover {
        color: #f90;
      }
    }
  }
  .item {
    padding: 5px 0px;
    // border-right: 1px solid #f90;
    &:last-child {
      border: none;
    }
    .routerCls {
      display: flex;
      align-items: center;
      padding: 2px 15px;
      box-sizing: border-box;
      &:hover {
        background: #fff4e7;
        // a {
        //   color: #f90;
        // }
      }
      a {
        color: #666;
        cursor: pointer;
        padding: 2px 10px 2px 0;
        width: 100px;
        display: inline-block;
        overflow: hidden;
        text-overflow: -o-ellipsis-lastline;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 1;
        line-clamp: 1;
        -webkit-box-orient: vertical;
      }
      .starCls {
        flex-shrink: 0;
        color: #f90;
      }
    }
  }
}

5.监听路由改变,右侧内容区域与左侧菜单相关联

5.1 路由参数如下:
{
  path: 'messageManage',
  name: '消息管理',
  component: PageView,
  meta: {
      icon: 'message'
  },
  children: [{
      path: 'message',
      name: '消息列表',
      meta: {
          icon: 'message',
          authority: 'Message.Look'
      },
      component: () =>
          import ('@/pages/message/message')
  }]
}

消息列表对应的路由是:/messageManage/message,则右侧如果是消息列表的页面,则左侧需要指定的是消息管理的菜单,比如颜色是主题色,表示当前是此菜单。也就是需要监听当前页面的路由,然后判断是否含有当前页面的路由参数。

监听当前页面的路由——立即监听且深度监听

watch: {
  $route: {
    handler: function(val, oldVal) {
      this.currentMenu = val.fullPath || val.path;
    },
    deep: true,
    immediate: true,
  },
},

currentMenu就是当前内容区域的路由路径。
currentMenu.indexOf(menu.fullPath || menu.path) > -1:如果满足条件,则表示是当前一级菜单里面的菜单,否则颜色不改变。

:style="{color:currentMenu.indexOf(menu.fullPath || menu.path) > -1? '#f90': '#666'}"

完成!!!

上效果图:

菜单展开

antd Dropdown item 点击 antd菜单_vue.js_10

菜单展开时弹层显示

antd Dropdown item 点击 antd菜单_antd_11

菜单折叠

antd Dropdown item 点击 antd菜单_ico_12

菜单折叠时弹层展示

antd Dropdown item 点击 antd菜单_javascript_13

评论区有小朋友问横向菜单布局+竖向菜单布局怎么排布

antd Dropdown item 点击 antd菜单_二级菜单_14


如果是我理解的:一级菜单横向展示,二级及以下的菜单竖向排布的话,可以在src/config/default/setting.config.js中的layout设置为mix即可。