el-menu el-collapse 左右滚动联动,左右展开项联动

本文档展示了如何在前端实现一个具有左侧菜单和右侧折叠面板的应用,要求左侧菜单每次只保持一个子菜单展开,右侧折叠面板也仅能展开一个。同时,滚动一侧时,另一侧应滚动到对应内容。文章详细说明了在数据异步加载和滚动联动过程中遇到的问题,如滚动位置计算错误和滚动卡顿,并提供了相应的解决方案。代码包括Vue组件的实现,如menu组件、menu item组件和右侧的collapseLabelList组件,以及它们之间的交互逻辑。最后,介绍了父组件如何整合这些组件并监听其交互事件。

需求:

  1. 左侧menu每次只保持一个子菜单的展开,右侧每次只能展开一个面板(手风琴效果)
  2. 滚动左侧的 menu 菜单,右侧的折叠面板(collapse)会跟随滚动到对应区域
  3. 滚动右侧的折叠面板,左侧的menu 菜单会跟随滚动到对应区域
  4. 展开左侧的menu 菜单,右侧的折叠面板对应的面板展开,滚动到顶部
  5. 点击左侧的 menu 子项item,右侧折叠面板中的二级菜单滚动到顶部
  6. 点击展开右侧的折叠面板,左侧对应的菜单项展开

遇到的问题:

  1. 左侧menu 的数据获取是一个接口,右侧菜单的子项是根据左侧的一级菜单id获取,每次展开左侧右侧需要去异步获取数据填充到面板中,展开的时候数据没有填充完滚动条还未撑起来滚动位置会出错
  2. 左右侧滚动联动的时候,左侧滚动去滚动右侧,右侧滚动去设置左侧滚动,当左右两侧都设置了对方跟随自己滚动的时候,滚动时会出现卡顿,他们会互相循环调用

解决问题:

  1. 动态计算前面有多少个兄弟节点,每个节点是固定高度,设置计算后的值
  2. 设置一个拦截标识,左侧滚动的时候禁止右侧去触发左侧的滚动;右侧滚动的时候禁止左侧去触发右侧的滚动

效果:

代码实现:

menu 组件:

<template>
  <div class="menuList">
    <div class="search-box">
      <el-autocomplete
        class="search-input"
        v-model="keyword"
        size="small"
        :clearable="true"
        placeholder="请输入关键字"
        :trigger-on-focus="false"
        :fetch-suggestions="querySeachSync"
        @select="handleSelect" />
      <div @click="seachData" class="bth-search el-icon-search"></div>
    </div>
    <el-menu
      id="label-mid-el-menu"
      :default-active="curMenuId[1]"
      @select="handleSelectMenu"
      @open="handleOpenMenu"
      :unique-opened="true">
      <nav-item v-for="nav in navList"
        :key="nav.classificationId"
        :ref="'menu_' + nav.classificationId"
        :nav="nav"
        @clickNav="hanleClickNavItem" />
    </el-menu>
  </div>
</template>

<script>
import NavItem from './NavItem.vue'
import { keywordLabels } from '@/api/modules/labelMiddleware.js'
export default {
  props: {
    navList: {
      type: Array,
      default: () => []
    },
    curMenuId: {
      type: Array
    }
  },
  watch: {
    curMenuId(arr) {
      // 折叠面板展开时展开对应的menu
      this.scrollToTarget(arr[0])
    }
  },
  components: {
    NavItem
  },
  data() {
    return {
      keyword: '',
      isScroll: false,
      // 定时器
      scrollTimer: null
    }
  },
  methods: {
    // 展开右侧折叠面板的时候当前menu滚动到顶部
    scrollToTarget(classificationId) {
      // 设置当前为左侧滚动标识,不触发右侧滚动
      window.rightScroll = true
      window.clearTimeout(this.scrollTimer)
      this.scrollTimer = setTimeout(_ => {
        // 放开标识
        window.rightScroll = false
      }, 800)

      // 找到目标元素
      const id = classificationId
      let el = this.$refs['menu_' + id][0]
      el = el.$el ? el.$el : el

      // 通过计算前面有多少元素来手动计算scrollTop高度
      const scrollBox = document.querySelector('#label-mid-el-menu')
      const children = Array.from(el.parentNode.children)
      const prevSiblings = []
      for (const child of children) {
        if (child === el) {
          break
        }
        prevSiblings.push(child)
      }
      const top = prevSiblings.length * 44
      this.$nextTick(_ => {
        scrollBox.scrollTo({
          top: top,
          behavior: 'smooth'
        })
      })
    },
    // 模糊搜索
    async seachData() {
      const { data } = await keywordLabels({
        keyword: this.keyword
      })
      return data
    },
    // 搜索出来选择
    handleSelect(label) {
      this.$emit('setCurIdArea', label.firstClassificationId)
    },
    // 键入关键字搜索
    async querySeachSync(keyworld, callback) {
      const data = await this.seachData()
      const res = data.map(item => {
        return {
          ...item,
          value: item.labelName
        }
      })
      callback(res)
    },
    // 设置菜单默认选中项为第一个
    setDefaultActive(list) {
      while (true) {
        if (Array.isArray(list)) {
          list = list[0]
          continue
        }
        if (list.childList && list.childList.length > 0) {
          list = list.childList
          continue
        }
        break
      }
      this.defaultActive = list.classificationId
    },
    // 点击item
    hanleClickNavItem(key) {
    },
    // 展开某个菜单列表
    handleOpenMenu(key, keyPath) {
      // console.log(key, keyPath, 'openMenu');
      this.$emit('setCurIdArea', keyPath, 'menu')
    },
    // 选择菜单列表菜单
    handleSelectMenu(key, keyPath) {
      // console.log(typeof key, keyPath, 'itemMenu')
      if (keyPath.length === 1) {
        // 菜单没有子项,触发menu
        this.$emit('setCurIdArea', keyPath, 'menu')
      } else {
        this.$emit('setCurIdArea', keyPath, 'item')
      }
    },
    // 右侧滚动的时候左侧滚动到目标位置
    menuScroll() {
      // 右侧如果正在滚动不触发左侧滚动
      if (window.rightScroll === true) {
        return
      }
      // 设置左侧正在滚动的标识,此时不触发右侧滚动
      window.leftScroll = true
      window.clearTimeout(this.scrollTimer)
      this.scrollTim
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值