ant.design-vue 级联多选组件

取自doit-ui-web  ,基于ant.design封装的功能组件,

实现多选级联样式表功能,根据自身情况 引入到自己的项目中

 

<template>
  <div
    class="gb-ant-select-multiple-cascader"
    :class="{ 'multiple-cascader-outside': !isClickOutSide }"
    v-clickoutside="handleDropDownOutSide"
  >
    <a-select
      v-bind="$attrs"
      v-on="$listeners"
      dropdownClassName="ht-cascader-style"
      mode="multiple"
      :maxTagTextLength="maxTagTextLength"
      :maxTagCount="maxTagCount"
      style="width:100%"
      :placeholder="placeholder"
      :value="selectedValueArr"
      :notFoundContent="null"
      :open="isOpen"
      showArrow
      :allowClear="allowClear"
      optionLabelProp="text"
      :dropdownMatchSelectWidth="false"
      :getPopupContainer="tirggerNode => tirggerNode.parentNode"
      @focus="handleFocus"
      @deselect="handleDeselect"
    >
      <a-icon type="close-circle" slot="clearIcon" theme="filled" @click="handleClearAll" />
      <template slot="dropdownRender" slot-scope="menu">
        <v-nodes :vnodes="menu" />
        <div :class="dropdownClassName">
          <div class="cascader-not-content" v-if="treeDataList.length <= 0">
            <a-empty :image="simpleImage" :description="noDataText"></a-empty>
          </div>
          <div class="cascader-content-wrap" v-else>
            <div class="cascader-content-container">
              <ul
                class="cascader-content-list"
                v-for="(itemList, levelIndex) in treeDataList"
                :key="levelIndex"
              >
                <!-- <li
                  class="cascader-content-item cascader-content-item-check-all"
                  @click="handleCascaderAllItemClick(isCheckAll)"
                  v-if="levelIndex === 0"
                >
                  <a-checkbox
                    :indeterminate="isIndeterminateAll"
                    v-model="isCheckAll"
                  />
                  <span class="checkbox-text">{{ allText }}</span>
                </li>-->
                <li
                  class="cascader-content-item"
                  v-for="(subItem, subIndex) in itemList"
                  :key="subItem[selectOptionsConfig.key]"
                  :title="subItem[selectOptionsConfig.text]"
                  :class="{ 'cascader-content-item-active': subItem.$active }"
                  @click="
                    handleCascaderItemClick(subItem, subIndex, levelIndex)
                  "
                >
                  <a-checkbox
                    :indeterminate="!!subItem.$indeterminate"
                    :checked="!!subItem.$checked"
                    @click.self="
                      e => handleCheckClick(e, subItem, subIndex, levelIndex)
                    "
                  />
                  <span class="checkbox-text">{{ subItem[selectOptionsConfig.text] }}</span>
                  <a-icon
                    type="right"
                    style="font-size:10px;"
                    v-if=" subItem[selectOptionsConfig.children] && subItem[selectOptionsConfig.children].length > 0 "
                  />
                </li>
              </ul>
            </div>
          </div>
        </div>
      </template>
      <a-select-option
        v-for="item in treeToList"
        :key="item[selectOptionsConfig.key]"
        :value="item[selectOptionsConfig.value]"
        :text="item[selectOptionsConfig.text]"
      >{{ item[selectOptionsConfig.text] }}</a-select-option>
    </a-select>
  </div>
</template>

<script>
import { Empty } from 'ant-design-vue'
import { cloneDeep } from 'lodash'
const clickoutside = {
  // 初始化指令
  bind (el, binding) {
    function documentHandler (e) {
      if (el.contains(e.target)) {
        return false
      }
      if (binding.expression) {
        binding.value(e)
      }
    }
    el.__vueClickOutside__ = documentHandler
    document.addEventListener('click', documentHandler)
  },
  update () { },
  unbind (el) {
    document.removeEventListener('click', el.__vueClickOutside__)
    delete el.__vueClickOutside__
  }
}
export default {
  name: 'GbAntSelectMultipleCascader',
  model: { prop: 'value', event: 'change' },
  components: {
    VNodes: {
      functional: true,
      render: (h, ctx) => {
        return ctx.props.vnodes
      }
    }
  },
  directives: { clickoutside },
  props: {
    allowClear: {
      type: Boolean,
      default: true
    },
    allText: {
      type: String,
      default: '全部'
    },
    noDataText: {
      type: String,
      default: '暂无数据'
    },
    treeData: {
      type: Array,
      default: () => {
        return []
      }
    },
    dropdownClassName: {
      type: String,
      default: ''
    },
    selectOptionsConfig: {
      type: Object,
      default: () => {
        return {
          key: 'id',
          value: 'value',
          text: 'title',
          children: 'children'
        }
      }
    },
    maxTagCount: {
      type: Number,
      default: 1000
    },
    maxTagTextLength: {
      type: Number,
      default: 1000
    },
    placeholder: {
      type: String,
      default: '请搜索'
    },
    checkAll: {
      type: Boolean,
      default: false
    },
    value: {
      type: Array,
      default: () => []
    }
  },
  beforeCreate () {
    this.simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
  },
  destroyed () {
    this.timer && clearInterval(this.timer)
  },
  data () {
    return {
      isOpen: false,
      isCheckAll: this.checkAll,
      isIndeterminateAll: false,
      isClickOutSide: true,
      selectedValueArr: this.value || undefined,
      treeDataList: cloneDeep(this.treeData),
      treeToList: []
    }
  },
  watch: {
    checkAll: {
      handler (newVal) {
        if (newVal) {
          this.timer = setInterval(() => {
            if (this.treeDataList.length > 0) {
              clearInterval(this.timer)
              this.handleCascaderAllItemClick(!newVal)
            }
          }, 30)
        }
      },
      immediate: true
    },
    value: {
      handler (newVal) {
        this.selectedValueArr = newVal
        this.initDefaultKeys(newVal, this.treeDataList)
        return newVal
      },
      immediate: true
    },
    treeData (newVal) {
      this.treeListToArrList(newVal[0])
      this.initDefaultKeys(this.selectedValueArr, newVal)
      this.treeDataList = newVal
      return newVal
    }
  },
  methods: {
    handleClearAll () {
      this.treeDataList = this.treeData
      this.isOpen = false
      this.isCheckAll = false
      this.isClickOutSide = true
      this.isIndeterminateAll = false
      this.updatePostData()
    },
    initDefaultKeys (selectedArr, treeDataList) {
      if (
        selectedArr &&
        selectedArr.length > 0 &&
        treeDataList &&
        treeDataList.length > 0
      ) {
        const treeDataListClone = cloneDeep(treeDataList)
        const treeToListClone = cloneDeep(this.treeToList)
        selectedArr.forEach(item => {
          const curretnItemList = []
          this.findKeysItem(treeDataListClone[0], item, curretnItemList)
          let currentItem = {}
          if (curretnItemList.length > 0) {
            currentItem = curretnItemList[0]
          }
          if (
            !treeToListClone.find(
              listItem =>
                listItem[this.selectOptionsConfig.value] ===
                currentItem[this.selectOptionsConfig.value]
            )
          ) {
            treeToListClone.push(currentItem)
          }
          currentItem.$checked = true
          if (
            currentItem[this.selectOptionsConfig.children] &&
            currentItem[this.selectOptionsConfig.children].length > 0
          ) {
            this.setAllDataTreeChecked(
              currentItem[this.selectOptionsConfig.children],
              true
            )
          }
        })
        this.setCheckBoxIndeteriminate(treeDataListClone[0])
        this.$nextTick(() => {
          this.treeToList = treeToListClone
          this.treeDataList = treeDataListClone
        })
      }
    },
    handleCascaderAllItemClick (checkAll) {
      const treeDataListClone = cloneDeep(this.treeDataList)
      // 触发事件的时候v-model中的数据还没有改变,所以要取反
      this.setAllDataTreeChecked(treeDataListClone[0], !checkAll)
      this.clearAllDataActive(treeDataListClone[0])
      this.setCheckBoxIndeteriminate(treeDataListClone[0])
      this.treeDataList = treeDataListClone
      this.updatePostData()
    },
    handleDeselect (value) {
      const treeDataListClone = cloneDeep(this.treeDataList)
      const currentItemList = []
      this.findKeysItem(treeDataListClone[0], value, currentItemList)
      let currentItem = {}
      if (currentItemList.length > 0) {
        currentItem = currentItemList[0]
      }
      currentItem.$checked = false
      currentItem.$active = false
      if (
        currentItem[this.selectOptionsConfig.children] &&
        currentItem[this.selectOptionsConfig.children].length > 0
      ) {
        this.setAllDataTreeChecked(
          currentItem[this.selectOptionsConfig.children],
          false
        )
      }
      this.setCheckBoxIndeteriminate(treeDataListClone[0])
      this.treeDataList = treeDataListClone
      this.updatePostData()
    },
    handleCheckClick (e, item, index, levelIndex) {
      const treeDataListClone = cloneDeep(this.treeDataList)
      const childrenTag = this.selectOptionsConfig.children
      const valueTag = this.selectOptionsConfig.value
      const currentItemList = []
      this.findKeysItem(treeDataListClone[0], item[valueTag], currentItemList)
      let currentItem = {}
      if (currentItemList.length > 0) {
        currentItem = currentItemList[0]
      }
      e.stopPropagation()
      e.cancelBubble = true
      const checked = e.target.checked
      if (!currentItem.$active && levelIndex !== treeDataListClone.length - 1) {
        // 清空所有选中状态
        this.clearAllDataActive(treeDataListClone[0])
      }
      if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
        // 可展开
        if (!currentItem.$active) {
          const nextIndex = levelIndex + 1
          if (treeDataListClone[nextIndex]) {
            treeDataListClone.splice(nextIndex)
          }
        }
      } else {
        // 不可展开
        const nextIndex = levelIndex + 1
        if (treeDataListClone[nextIndex]) {
          treeDataListClone.splice(nextIndex)
        }
      }
      if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
        this.setAllDataTreeChecked(currentItem[childrenTag], checked)
      } else {
        currentItem.$checked = checked
      }
      this.setCheckBoxIndeteriminate(treeDataListClone[0])
      this.treeDataList = treeDataListClone
      this.$emit('handleCheckboxChange', {
        event: e,
        item,
        index,
        levelIndex
      })
      this.updatePostData()
    },
    // 单个item点击事件
    handleCascaderItemClick (item, index, levelIndex) {
      const treeDataListClone = cloneDeep(this.treeDataList)
      const childrenTag = this.selectOptionsConfig.children
      const valueTag = this.selectOptionsConfig.value
      const currentItemList = []
      this.findKeysItem(treeDataListClone[0], item[valueTag], currentItemList)
      let currentItem = {}
      if (currentItemList.length > 0) {
        currentItem = currentItemList[0]
      }
      const parentItemsList = []
      this.findAllParentItems(
        currentItem[valueTag],
        treeDataListClone[0],
        treeDataListClone[0],
        parentItemsList
      )
      parentItemsList.push(currentItem)
      this.clearAllDataActive(treeDataListClone[0])
      parentItemsList.forEach(activeItem => {
        if (activeItem[childrenTag] && activeItem[childrenTag].length > 0) {
          activeItem.$active = true
        }
      })
      if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
        const nextIndex = levelIndex + 1
        if (treeDataListClone[nextIndex]) {
          treeDataListClone.splice(nextIndex)
          treeDataListClone[nextIndex] = currentItem[childrenTag]
        } else {
          treeDataListClone.push(currentItem[childrenTag])
        }
        this.addHideSelectedList(currentItem[childrenTag])
      } else {
        const nextIndex = levelIndex + 1
        if (treeDataListClone[nextIndex]) {
          treeDataListClone.splice(nextIndex)
        }
        currentItem.$checked = !currentItem.$checked
      }
      this.setCheckBoxIndeteriminate(treeDataListClone[0])
      this.treeDataList = treeDataListClone
      this.updatePostData()
    },
    handleFocus () {
      this.isOpen = true
      this.isClickOutSide = false
    },
    updatePostData () {
      const childrenTag = this.selectOptionsConfig.children
      const valueTag = this.selectOptionsConfig.value
      const selectedValueArr = []
      _getAllItemChecked(this.treeDataList[0] || [])
      this.$emit('change', selectedValueArr)
      // this.getAllCheckedPoint()

      function _getAllItemChecked (treeDataList) {
        let parentItem
        treeDataList.forEach(item => {
          if (item[childrenTag] && item[childrenTag].length > 0) {
            parentItem = item
            const currentCheckedAll = []
            const currentCheckedAllItems = []
            _getSubChecked(parentItem[childrenTag], currentCheckedAll)
            _getSubCheckedItems(
              parentItem[childrenTag],
              currentCheckedAllItems
            )
            if (currentCheckedAll.every(checkItem => checkItem === true)) {
              if (parentItem[valueTag]) {
                selectedValueArr.push(parentItem[valueTag])
              }
            } else if (
              currentCheckedAll.some(checkItem => checkItem === true)
            ) {
              currentCheckedAllItems.forEach(selectedItem => {
                if (selectedItem[valueTag]) {
                  selectedValueArr.push(selectedItem[valueTag])
                }
              })
            }
          } else {
            if (item.$checked) {
              selectedValueArr.push(item[valueTag])
            }
          }
        })
      }
      function _getSubChecked (itemList, arr) {
        itemList.forEach(item => {
          arr.push(!!item.$checked)
          if (item[childrenTag] && item[childrenTag].length > 0) {
            _getSubChecked(item[childrenTag], arr)
          }
        })
      }
      function _getSubCheckedItems (itemList, arr) {
        itemList.forEach(item => {
          if (item.$checked) {
            arr.push(item)
          } else {
            if (item[childrenTag] && item[childrenTag].length > 0) {
              _getSubCheckedItems(item[childrenTag], arr)
            }
          }
        })
      }
    },
    // 点击组件外部区域
    handleDropDownOutSide () {
      this.isOpen = false
      this.isClickOutSide = true
    },
    // 设置当前的选中框的全选等状态
    setCheckBoxIndeteriminate (treeDataList) {
      const childrenTag = this.selectOptionsConfig.children
      _getAllItemChecked(treeDataList)
      const checkedAllArr = []
      _getAllItemCheckedAll(treeDataList, checkedAllArr)
      if (checkedAllArr.every(item => item === true)) {
        this.isCheckAll = true
        this.isIndeterminateAll = false
      } else if (checkedAllArr.some(item => item === true)) {
        this.isCheckAll = false
        this.isIndeterminateAll = true
      } else if (checkedAllArr.every(item => item === false)) {
        this.isCheckAll = false
        this.isIndeterminateAll = false
      }
      function _getAllItemCheckedAll (itemList, arr) {
        itemList.forEach(item => {
          arr.push(!!item.$checked)
          if (item[childrenTag] && item[childrenTag].length > 0) {
            _getAllItemCheckedAll(item[childrenTag], arr)
          }
        })
      }
      function _getAllItemChecked (treeDataList) {
        let parentItem
        treeDataList.forEach(item => {
          if (item[childrenTag] && item[childrenTag].length > 0) {
            parentItem = item
            _getAllItemChecked(item[childrenTag])
            const currentCheckedAll = []
            _getSubChecked(parentItem[childrenTag], currentCheckedAll)
            if (currentCheckedAll.every(checkItem => checkItem === true)) {
              parentItem.$checked = true
              parentItem.$indeterminate = false
            } else if (
              currentCheckedAll.some(checkItem => checkItem === true)
            ) {
              parentItem.$checked = false
              parentItem.$indeterminate = true
            } else if (
              currentCheckedAll.every(checkItem => checkItem === false)
            ) {
              parentItem.$checked = false
              parentItem.$indeterminate = false
            }
          }
        })
      }
      function _getSubChecked (itemList, arr) {
        itemList.forEach(item => {
          arr.push(!!item.$checked)
          if (item[childrenTag] && item[childrenTag].length > 0) {
            _getSubChecked(item[childrenTag], arr)
          }
        })
      }
    },
    // 设置所有选中和非选中
    setAllDataTreeChecked (treeList, checked) {
      const childrenTag = this.selectOptionsConfig.children
      treeList.forEach(item => {
        item.$checked = checked
        if (item[childrenTag] && item[childrenTag].length > 0) {
          this.setAllDataTreeChecked(item[childrenTag], checked)
        }
      })
    },
    // 清空所有的active状态
    clearAllDataActive (treeList) {
      const childrenTag = this.selectOptionsConfig.children
      treeList.forEach(item => {
        item.$active = false
        if (item[childrenTag] && item[childrenTag].length > 0) {
          this.clearAllDataActive(item[childrenTag])
        }
      })
    },
    findKeysItem (treeDataList, currentKey, currentItem) {
      const childrenTag = this.selectOptionsConfig.children
      treeDataList.forEach(item => {
        if (item[this.selectOptionsConfig.value] === currentKey) {
          currentItem.push(item)
        } else {
          if (item[childrenTag] && item[childrenTag].length > 0) {
            this.findKeysItem(item[childrenTag], currentKey, currentItem)
          }
        }
      })
    },
    findAllParentItems (
      currentKey,
      treeDataList,
      originTreeDataList,
      parentItemsList,
      parentItem,
      parentItemsHasKey
    ) {
      if (parentItem && parentItemsHasKey) {
        parentItemsList.unshift(parentItem)
        this.findAllParentItems(
          parentItem[this.selectOptionsConfig.value],
          treeDataList,
          originTreeDataList,
          parentItemsList
        )
      } else {
        this.findKeysAndParentItem(
          currentKey,
          treeDataList,
          originTreeDataList,
          parentItemsList,
          parentItem,
          parentItemsHasKey
        )
      }
    },
    findKeysAndParentItem (
      currentKey,
      treeDataList,
      originTreeDataList,
      parentItemsList,
      parentItem
    ) {
      const childrenTag = this.selectOptionsConfig.children
      treeDataList.forEach(item => {
        if (item[this.selectOptionsConfig.value] === currentKey && parentItem) {
          this.findAllParentItems(
            parentItem[this.selectOptionsConfig.value],
            originTreeDataList,
            originTreeDataList,
            parentItemsList,
            parentItem,
            true
          )
        } else {
          if (item[childrenTag] && item[childrenTag].length > 0) {
            this.findKeysAndParentItem(
              currentKey,
              item[childrenTag],
              originTreeDataList,
              parentItemsList,
              item
            )
          }
        }
      })
    },
    // 将树形结构数据转换成平面结构
    treeListToArrList (treeList) {
      const arrList = []
      if (treeList && treeList[0]) {
        treeList.forEach(item => {
          arrList.push(item)
        })
        this.treeToList = arrList
      }
    },
    addHideSelectedList (treeList) {
      const treeToListClone = Array.from(this.treeToList)
      treeList.forEach(item => {
        if (
          !treeToListClone.find(
            listItem =>
              listItem[this.selectOptionsConfig.value] ===
              item[this.selectOptionsConfig.value]
          )
        ) {
          treeToListClone.push(item)
        }
      })
      this.treeToList = treeToListClone
    },
    /**
     * 获取节点对应所有的子选项
     */
    getAllCheckedPoint () {
      const treeDataListClone = cloneDeep(this.treeData)
      const selectedValueArr = cloneDeep(this.selectedValueArr)
      const { children, value } = this.selectOptionsConfig
      const itemList = []
      const findTree = (list, key) => {
        list.forEach(i => {
          if (i[value] === key) {
            itemList.push(i)
          } else if (i[children] && i[children].length) {
            findTree(i[children], key)
          }
        })
      }
      selectedValueArr.forEach(i => {
        findTree(treeDataListClone[0], i)
      })
      const checkAll = []
      const getCheckList = (l) => {
        l.forEach(i => {
          if (!i.children || !i.children.length) {
            checkAll.push(i[value])
          } else {
            getCheckList(i[children])
          }
        })
      }
      getCheckList(itemList)
      this.$emit('allPoionts', checkAll)
      return checkAll
    }
  }
}
</script>
<style lang="less">
.gb-ant-select-multiple-cascader {
  .ht-cascader-style {
    top: 35px !important;
  }
  position: relative;
  .ant-select-dropdown-menu-item {
    display: none;
  }
  .cascader-content-item {
    .checkbox-text {
      padding-left: 8px;
      font-size: 12px;
      color: #4a4a4a;
      cursor: default;
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      text-align: left;
    }
    &.cascader-content-item-active {
      background-color: rgba(66, 70, 86, 0.05);
      .checkbox-text {
        color: #1d47aa;
      }
    }
    .ant-checkbox-wrapper {
      .ant-checkbox-indeterminate {
        .ant-checkbox-inner {
          &:after {
            background-color: #1d47aa;
            left: 48%;
          }
        }
      }
      .ant-checkbox-inner {
        width: 14px;
        height: 14px;
        background-color: #fff;
        border-color: #7d8292;
        &:after {
          border-color: #1d47aa;
          left: 20%;
        }
      }
    }
  }
  .ant-select-dropdown--multiple {
    min-width: auto !important;
  }
  &.multiple-cascader-outside {
    .ant-select-arrow {
      transform: rotate(180deg);
    }
  }
  .ant-select-arrow {
    margin-top: -8px;
    transition: transform 0.3s;
  }
  .ant-select-dropdown-menu-root {
    display: none;
  }
  .cascader-content-right {
    .right-wrap {
      .ant-checkbox {
        top: 0;
      }
    }
  }
}
</style>
<style lang="less" scoped>
.gb-ant-select-multiple-cascader {
  position: relative;
  .cascader-content-wrap {
    height: 100%;
    .cascader-content-container {
      display: flex;
      flex-direction: row;
      .cascader-content-list {
        list-style: none;
        padding: 0;
        margin: 0;
        width: auto;
        box-sizing: border-box;
        padding: 11px 0;
        max-height: 160px;
        overflow-y: auto;
        border-right: 1px solid rgba(222, 223, 226, 0.3);
        .cascader-content-item {
          padding: 5px 16px;
          margin-top: 5px;
          display: flex;
          flex-direction: row;
          align-items: center;
          line-height: 1;
          &:hover {
            background-color: rgba(66, 70, 86, 0.05);
          }
        }
      }
    }
  }
  .cascader-not-content {
    min-width: 200px;
    padding: 10px;
  }
}
</style>

 使用:亲测可用

<CascaderSelect
                        allowClear
                        ref="participants"
                        :maxTagCount="100"
                        :maxTagTextLength="8"
                        placeholder="请选择"
                        v-model="info.select"
                        :selectOptionsConfig="{
                          key: 'id',
                          value: 'id',
                          text: 'name',
                          children: 'children'
                        }"
                        allText="全选"
                        noDataText="暂无数据"
                        dropdownClassName="customer-multiple-cascader"
                        :treeData="treeData"
               @handleCheckboxChange="handleCheckboxChange"
                      />    

 

posted @ 2022-10-21 14:05  bomdeyada  阅读(3226)  评论(0编辑  收藏  举报