ant-design-vue-pro icon indicating copy to clipboard operation
ant-design-vue-pro copied to clipboard

请问如何在展开一个菜单项时,不收缩其他菜单

Open aaronchen2k opened this issue 4 years ago • 6 comments

Question (问题描述) 现在展开一个菜单项时,其他同级的菜单就被收缩了。

Describe the solution you'd like (你期待的是什么?) 在展开一个菜单项时,不收缩其他菜单

aaronchen2k avatar Dec 27 '20 02:12 aaronchen2k

这个默认无法做到,之前 antd pro v3 的设计如此。 但你可以通过自定义渲染 menuRender 来自己渲染菜单。 一旦这么做,你需要自己控制 openKeys, selectedKeys

https://github.com/vueComponent/pro-layout/blob/master/examples/src/layouts/BasicLayout.vue#L13-L17

sendya avatar Dec 28 '20 02:12 sendya

刚改了下 components/Menu/menu.js,可以实现。 在菜单meta里配置 "resident":true 即可。


import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'

export default {
  name: 'SMenu',
  props: {
    menu: {
      type: Array,
      required: true
    },
    theme: {
      type: String,
      required: false,
      default: 'dark'
    },
    mode: {
      type: String,
      required: false,
      default: 'inline'
    },
    collapsed: {
      type: Boolean,
      required: false,
      default: false
    }
  },
  data () {
    return {
      openKeys: [],
      selectedKeys: [],
      cachedOpenKeys: [],

      residentKeys: []
    }
  },
  computed: {
    rootSubmenuKeys: vm => {
      const keys = []
      vm.menu.forEach(item => keys.push(item.path))
      return keys
    }
  },
  mounted () {
    this.updateMenu()
  },
  watch: {
    collapsed (val) {
      if (val) {
        this.cachedOpenKeys = this.openKeys.concat()
        this.openKeys = []
      } else {
        this.openKeys = this.cachedOpenKeys
      }
    },
    $route: function () {
      this.updateMenu()
    }
  },
  methods: {
    // select menu item
    onOpenChange (openKeys) {

      // 在水平模式下时执行,并且不再执行后续
      if (this.mode === 'horizontal') {
        this.openKeys = openKeys
        return
      }
      // 非水平模式时
      const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
      if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
        this.openKeys = openKeys
      } else {
        this.openKeys = latestOpenKey ? this.uniqueArr([latestOpenKey].concat(this.residentKeys)) : []
      }
    },
    uniqueArr(array) {
      var n = []; 
      for (var i = 0; i < array.length; i++) {
          if (n.indexOf(array[i]) == -1) n.push(array[i]);
      }
      return n;
    },
  
    onSelect ({ item, key, selectedKeys }) {
      this.selectedKeys = selectedKeys
      this.$emit('select', { item, key, selectedKeys })
    },
    updateMenu () {
      const that = this
      const routes = this.$route.matched.concat()
      const { hidden } = this.$route.meta
      if (routes.length >= 3 && hidden) {
        routes.pop()
        this.selectedKeys = [routes[routes.length - 1].path]
      } else {
        this.selectedKeys = [routes.pop().path]
      }
      const openKeys = []
      if (this.mode === 'inline') {
        routes.forEach(item => {
          openKeys.push(item.path)
          if(item.meta.resident){
            that.residentKeys.push(item.path)
            that.residentKeys = that.uniqueArr(that.residentKeys)
          }
        })
      }
      this.collapsed ? (this.cachedOpenKeys = that.uniqueArr(openKeys.concat(that.residentKeys))) : (this.openKeys = that.uniqueArr(openKeys.concat(that.residentKeys)))
    },

    // render
    renderItem (menu) {
      if (!menu.hidden) {
        return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
      }
      return null
    },
    renderMenuItem (menu) {
      const target = menu.meta.target || null
      const CustomTag = target && 'a' || 'router-link'
      const props = { to: { name: menu.name } }
      const attrs = { href: menu.path, target: menu.meta.target }

      if (menu.children && menu.hideChildrenInMenu) {
        // 把有子菜单的 并且 父菜单是要隐藏子菜单的
        // 都给子菜单增加一个 hidden 属性
        // 用来给刷新页面时, selectedKeys 做控制用
        menu.children.forEach(item => {
          item.meta = Object.assign(item.meta, { hidden: true })
        })
      }

      return (
        <Menu.Item {...{ key: menu.path }}>
          <CustomTag {...{ props, attrs }}>
            {this.renderIcon(menu.meta.icon)}
            <span>{menu.meta.title}</span>
          </CustomTag>
        </Menu.Item>
      )
    },
    renderSubMenu (menu) {
      const itemArr = []
      if (!menu.hideChildrenInMenu) {
        menu.children.forEach(item => itemArr.push(this.renderItem(item)))
      }
      return (
        <Menu.SubMenu {...{ key: menu.path }}>
          <span slot="title">
            {this.renderIcon(menu.meta.icon)}
            <span>{menu.meta.title}</span>
          </span>
          {itemArr}
        </Menu.SubMenu>
      )
    },
    renderIcon (icon) {
      if (icon === 'none' || icon === undefined) {
        return null
      }
      const props = {}
      typeof (icon) === 'object' ? props.component = icon : props.type = icon
      return (
        <Icon {... { props } }/>
      )
    }
  },

  render () {
    const dynamicProps = {
      props: {
        mode: this.mode,
        theme: this.theme,
        openKeys: this.openKeys,
        selectedKeys: this.selectedKeys
      },
      on: {
        openChange: this.onOpenChange,
        select: this.onSelect
      }
    }

    const that = this
    const menuTree = this.menu.map(item => {

      if(!that.residentLoop && item.meta.resident){
        that.residentKeys.push(item.path)
        that.residentKeys = that.uniqueArr(that.residentKeys)
      }

      if (item.hidden) {
        return null
      }
      return this.renderItem(item)
    })

    that.residentLoop = true

    return (<Menu {...dynamicProps}>{menuTree}</Menu>)
  }
}

alpheus55 avatar Dec 31 '20 08:12 alpheus55

我用的3.0版本,请问怎么升级vue-antd? "name": "vue-antd-pro", "version": "3.0.0",

aaronchen2k avatar Dec 31 '20 09:12 aaronchen2k

哈哈,不知道,我还在用2.1.0

alpheus55 avatar Jan 01 '21 17:01 alpheus55

刚改了下 components/Menu/menu.js,可以实现。 在菜单meta里配置 "resident":true 即可。


import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'

export default {
  name: 'SMenu',
  props: {
    menu: {
      type: Array,
      required: true
    },
    theme: {
      type: String,
      required: false,
      default: 'dark'
    },
    mode: {
      type: String,
      required: false,
      default: 'inline'
    },
    collapsed: {
      type: Boolean,
      required: false,
      default: false
    }
  },
  data () {
    return {
      openKeys: [],
      selectedKeys: [],
      cachedOpenKeys: [],

      residentKeys: []
    }
  },
  computed: {
    rootSubmenuKeys: vm => {
      const keys = []
      vm.menu.forEach(item => keys.push(item.path))
      return keys
    }
  },
  mounted () {
    this.updateMenu()
  },
  watch: {
    collapsed (val) {
      if (val) {
        this.cachedOpenKeys = this.openKeys.concat()
        this.openKeys = []
      } else {
        this.openKeys = this.cachedOpenKeys
      }
    },
    $route: function () {
      this.updateMenu()
    }
  },
  methods: {
    // select menu item
    onOpenChange (openKeys) {

      // 在水平模式下时执行,并且不再执行后续
      if (this.mode === 'horizontal') {
        this.openKeys = openKeys
        return
      }
      // 非水平模式时
      const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
      if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
        this.openKeys = openKeys
      } else {
        this.openKeys = latestOpenKey ? this.uniqueArr([latestOpenKey].concat(this.residentKeys)) : []
      }
    },
    uniqueArr(array) {
      var n = []; 
      for (var i = 0; i < array.length; i++) {
          if (n.indexOf(array[i]) == -1) n.push(array[i]);
      }
      return n;
    },
  
    onSelect ({ item, key, selectedKeys }) {
      this.selectedKeys = selectedKeys
      this.$emit('select', { item, key, selectedKeys })
    },
    updateMenu () {
      const that = this
      const routes = this.$route.matched.concat()
      const { hidden } = this.$route.meta
      if (routes.length >= 3 && hidden) {
        routes.pop()
        this.selectedKeys = [routes[routes.length - 1].path]
      } else {
        this.selectedKeys = [routes.pop().path]
      }
      const openKeys = []
      if (this.mode === 'inline') {
        routes.forEach(item => {
          openKeys.push(item.path)
          if(item.meta.resident){
            that.residentKeys.push(item.path)
            that.residentKeys = that.uniqueArr(that.residentKeys)
          }
        })
      }
      this.collapsed ? (this.cachedOpenKeys = that.uniqueArr(openKeys.concat(that.residentKeys))) : (this.openKeys = that.uniqueArr(openKeys.concat(that.residentKeys)))
    },

    // render
    renderItem (menu) {
      if (!menu.hidden) {
        return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
      }
      return null
    },
    renderMenuItem (menu) {
      const target = menu.meta.target || null
      const CustomTag = target && 'a' || 'router-link'
      const props = { to: { name: menu.name } }
      const attrs = { href: menu.path, target: menu.meta.target }

      if (menu.children && menu.hideChildrenInMenu) {
        // 把有子菜单的 并且 父菜单是要隐藏子菜单的
        // 都给子菜单增加一个 hidden 属性
        // 用来给刷新页面时, selectedKeys 做控制用
        menu.children.forEach(item => {
          item.meta = Object.assign(item.meta, { hidden: true })
        })
      }

      return (
        <Menu.Item {...{ key: menu.path }}>
          <CustomTag {...{ props, attrs }}>
            {this.renderIcon(menu.meta.icon)}
            <span>{menu.meta.title}</span>
          </CustomTag>
        </Menu.Item>
      )
    },
    renderSubMenu (menu) {
      const itemArr = []
      if (!menu.hideChildrenInMenu) {
        menu.children.forEach(item => itemArr.push(this.renderItem(item)))
      }
      return (
        <Menu.SubMenu {...{ key: menu.path }}>
          <span slot="title">
            {this.renderIcon(menu.meta.icon)}
            <span>{menu.meta.title}</span>
          </span>
          {itemArr}
        </Menu.SubMenu>
      )
    },
    renderIcon (icon) {
      if (icon === 'none' || icon === undefined) {
        return null
      }
      const props = {}
      typeof (icon) === 'object' ? props.component = icon : props.type = icon
      return (
        <Icon {... { props } }/>
      )
    }
  },

  render () {
    const dynamicProps = {
      props: {
        mode: this.mode,
        theme: this.theme,
        openKeys: this.openKeys,
        selectedKeys: this.selectedKeys
      },
      on: {
        openChange: this.onOpenChange,
        select: this.onSelect
      }
    }

    const that = this
    const menuTree = this.menu.map(item => {

      if(!that.residentLoop && item.meta.resident){
        that.residentKeys.push(item.path)
        that.residentKeys = that.uniqueArr(that.residentKeys)
      }

      if (item.hidden) {
        return null
      }
      return this.renderItem(item)
    })

    that.residentLoop = true

    return (<Menu {...dynamicProps}>{menuTree}</Menu>)
  }
}

老哥,我试了你写的代码,真的可以用,但是有个问题,当用户收缩一个默认展开的菜单后,点击任何一个菜单,收缩的菜单都会被打开,不能保持住用户点击的状态,当设置为顶部导航栏时问题更明显,请问这个问题怎么解决呢,感谢感谢

bailihuiyue avatar Jan 06 '21 05:01 bailihuiyue

为什么当我有2层菜单的时候 即2个children的时候 发现收缩不了菜单了

yehoan avatar Dec 18 '21 03:12 yehoan