【vue】a-tree中使用自定义指令监听DOM元素变化

本文档介绍了在Ant Design的Tree组件中,如何为每个节点右侧添加复选框并实现多选操作。由于官方逻辑不满足业务需求,作者手动实现了这一逻辑。遇到的问题包括:1) 复选框区域的sticky布局实现,通过CSS调整解决了定位问题;2) 动态变化的处理,使用自定义指令监听DOM变化,动态更新节点宽度。解决方案涉及Vue的指令注册与事件监听,确保了不同状态下的节点布局正确。
摘要由CSDN通过智能技术生成

【需求】

在antdesign中的a-tree组件时,为每个节点右侧添加了复选框以及多选操作,由于ant官方tree的逻辑并不支持项目的业务逻辑,因此手撸了这边的逻辑,过程中遇到了两个问题。如下图所示:

【解决办法】

问题1,为右侧复选框区域添加sticky布局即可,代码如下:

<div
    :class="['check-box', 
        viewParam.checkable&&viewParam.treeSelectable ? 'check-box-2' : !viewParam.checkable && !viewParam.treeSelectable ? 'check-box-0' : 'check-box-1']">
...
</div>
.check-box {
  position: sticky;
  background-color: #fafbfc;
  padding-right: 10px;
  margin-top: -24px;
  text-align: right;
}
.check-box-1 {
  width: 30px;
  left: 220px;
}
.check-box-2 {
  width: 50px;
  left: 204px;
}

问题2,涉及到动态变化,一开始准备通过变量统一改改变,但每个树的节点,位置状态都不同,无法进行统一设置,因此想到了自定义指令监听,代码如下:

    <a-tree
        id="wd-org-tree-inside"
        v-if="renderComponent"
        v-model="valueCode"
        v-titleBoxChange="titleBoxChange"
        v-selectOrgChange="selectOrgChange"
        showIcon
        showLine
        :tree-data="treeData"
        :replaceFields="{ key: 'id', title: 'title', children: 'children' }"
        :load-data="onLoadData"
        :expandedKeys="expandedKeys"
        :multiple="viewParam.multiple"
        :checkable="false"
        :selectable="viewParam.selectable"
        :defaultSelectedKeys="viewParam.defaultSelectedKeys"
        :selectedKeys="selectedKeys"
        :draggable="viewParam.draggable"
        @expand="onExpand"
        @select="onSelect"
        @load="onLoad"
      >
        <template slot-scope="item" slot="custom">
          <div :class="['title-box']" v-on:mouseover="handleListFocus(item)">
            <!--字体、logo颜色规则: 绿色代表虚拟机构  灰色代表失效状态  蓝色代表待生效状态; 虚拟和生效中则显示虚拟部门的颜色; 虚拟和失效则显示虚拟的颜色、失效标签用失效颜色; 待生效和虚拟则显示虚拟颜色、未来标签用未来颜色 -->
            <span
              :class="{
              'color-blue':
                item.status == '20' && (item.type == '10' || item.type == '20'),
              'color-aaa':
                item.status == '30' && (item.type == '10' || item.type == '20'),
              'green-color': item.type == '50',
              'color-freeze': item.b05214 == '20', // 冻结
              'color-purple' :item.b00208 == '1', // 境外
              'color-gray': item.status == '30' // 失效
            }"
            >
              <!-- <span v-if="pid == item.pId"> -->
              <span v-if="pid == item.id">
                <!--最上层节点前展示此logo-->
                <icon-font class="ico" type="icon-shu-jituan"></icon-font>
              </span>
              <span v-else>
                <!--其它节点前展示logo: 机构使用机构图标,部门和虚拟节点用部门图标-->
                <icon-font v-if="item.type === '10'" type="icon-shu-jigou"></icon-font>
                <icon-font v-if="item.type === '20' || item.type === '50'" type="icon-shu-bumen"></icon-font>
                <icon-font v-if="item.type === '30'" type="icon-shu-gangwei"></icon-font>
              </span>

              <!-- {{ item.title }} -->
              &nbsp;
              <span
                v-html="
                item.title.replace(
                  new RegExp(queryParam.searchStr, 'g'),
                  '<span style=color:#f50>' + queryParam.searchStr + '</span>'
                )
              "
              ></span>
            </span>

            <span v-if="item.b05214 == '20'">
              &nbsp;
              <i class="btn-freeze">冻结</i>
            </span>

            <span v-if="item.type == '50'">
              <!--节点类型为50,则显示‘虚拟’-->
              &nbsp;
              <i class="btn-br">虚拟</i>
            </span>
            <span v-if="item.status == '20'">
              <!--节点状态为20,则显示‘未来’-->
              &nbsp;
              <i class="btn-tree">未来</i>
            </span>
            <span v-else-if="item.status == '30'">
              <!--节点状态为30,则显示‘失效’-->
              &nbsp;
              <i class="btn-no">失效</i>
            </span>
            <span v-if="item.b00208 == '1'">
              &nbsp;
              <i class="btn-abroad">境外</i>
            </span>
            <div
              :class="['check-box', viewParam.checkable&&viewParam.treeSelectable ? 'check-box-2' : !viewParam.checkable && !viewParam.treeSelectable? 'check-box-0' : 'check-box-1']"
            >
              <a-popover
                v-if="viewParam.treeSelectable"
                :arrowPointAtCenter="true"
                :getPopupContainer="
                triggerNode => {
                  return triggerNode.parentNode.parentNode
                }
              "
              >
                <template slot="content">
                  <div class="trigger-select-box">
                    <div
                      v-for="(operator, index) in triggerSelectOptions"
                      :key="operator+index"
                      class="trigger-select-item"
                      @click.stop="handeleSelect(operator.value, item)"
                    >{{ operator.text }}</div>
                  </div>
                </template>
                <icon-font v-show="item.isShowIcon" class="trigger-icon" type="icon-piliangxuanze"></icon-font>
              </a-popover>
              <a-checkbox v-if="viewParam.checkable" v-model="item.isChecked"></a-checkbox>
            </div>
          </div>
        </template>
      </a-tree>

v-titleBoxChange="titleBoxChange"为例,其他可以不用过多关注

包含三部分

1、DOM绑定,即 v-titleBoxChange="titleBoxChange"

2、注册指令

在directives中注册,directives和methods、mounted等同级。如下代码所示,bind中进行绑定,设置定时器,一旦有变化就通过binding.value({})回调函数进行更新。

3、函数中输出DOM变化

directives: {
    titleBoxChange: {
      bind(el, binding) {
        console.log(el, '绑定', binding)
        let titleBoxDomLength = 0
        function titleBoxDomLengthChange() {
          const titleBoxDom = el.getElementsByClassName('title-box')
          if (titleBoxDomLength != titleBoxDom?.length) {
            binding.value({
              titleBoxDomLength: titleBoxDom?.length ? titleBoxDom.length : 0, // 关键(这传入的是函数,所以执行此函数)
              titleBoxDom: titleBoxDom,
            })
          }
          titleBoxDomLength = titleBoxDom?.length ? titleBoxDom.length : 0
        }
        el.__vueSetInterval__ = setInterval(titleBoxDomLengthChange, 300)
      },
      unbind(el) {
        console.log(el, '解绑')
        clearInterval(el.__vueSetInterval__)
      },
    },
  },
methods: {
    titleBoxChange({ titleBoxDomLength, titleBoxDom }) {
      let maxWidth = 0
      // 获取最大宽度
      for (let i = 0; i < titleBoxDomLength; i++) {
        const spanDom = titleBoxDom[i].firstChild
        maxWidth =
          maxWidth > spanDom.offsetWidth ? maxWidth : spanDom.offsetWidth
      }
      maxWidth += 100
      // 更新宽度
      for (let i = 0; i < titleBoxDomLength; i++) {
        let moveLeft = 0
        let tempDom = titleBoxDom[i].parentNode.parentNode.parentNode.parentNode // 最近的url
        while (tempDom.tagName == 'UL') {
          moveLeft += parseFloat(
            window
              .getComputedStyle(tempDom, null)
              .getPropertyValue('padding-left')
          )
          tempDom = tempDom.parentNode.parentNode
        }
        titleBoxDom[i].setAttribute('style', `width:${maxWidth - moveLeft}px`)
      }

      this.setSelectDomWidth() // 设置选中状态
    },
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值