关于前端:单选-联动

47次阅读

共计 9226 个字符,预计需要花费 24 分钟才能阅读完成。

<template>
  <div class="cascader-wrapper">
    <crec-popover
      placement="bottomLeft"
      trigger="click"
      :popper-class="popOverClass"
      v-model="showPopover"
    >
      <!-- <div slot="reference"> -->
      <!-- multiple
        mode="tags" -->
      <crec-select
        v-model="selectedLabels"
        :default-value="defaultLabels"
        :placeholder="placeholder"
        :disabled="disabled"
        :size="size"
        :showArrow="true"
        style="width: 100%;"
        popper-class="hide-popper"
        :open="false"
        @focus="handleFocus"
        @dropdown-visible-change="visibleChange"
      ></crec-select>
      <!-- </div> -->
      <template slot="content">
        <div
          class="cascader-menu-wrapper"
          v-clickoutside="hidePopover"
        >
          <template v-if="options.length > 0">
            <ul
              class="crec-cascader-menu cascader-menu"
              :style="{'width': panelWidth ==='auto'?'auto': panelWidth +'px'}"
              v-for="(cas, index) in casTree"
              :key="index"
            >
              <!--  -->
              <!-- 'can-load-children': !item.isLeaf && !item[childrenKey] && allowLoadChildren && showLoadingIndicator, -->
              <!-- 'loading-children': !item.isLeaf && item.loading && allowLoadChildren && showLoadingIndicator, -->
              <!-- 'crec-cascader-menu-item-expand': item[childrenKey] && item[childrenKey].length > 0, -->
              <li
                :class="{'crec-cascader-menu-item': true,'crec-cascader-menu-item-expand': true,'has-checked-child': item.indeterminate || item.hasCheckedChild,'crec-cascader-menu-item-active': item.checked,}"@click="spreadNext(item[childrenKey], index, item)"v-for="(item, itemIdx) in cas":key="itemIdx":title="item[labelKey]"
              >
                <crec-checkbox
                  class="cascader-checkbox"
                  @click.native.stop
                  :disabled="item.disabled"
                  v-model="item.checked"
                  :indeterminate="item.indeterminate"
                  @change="({target}) => {checkedChange(item, target.checked) }"
                ></crec-checkbox>
                <span>{{item[labelKey] }}</span>
                <span
                  v-if="item[childrenKey] && item[childrenKey].length > 0"
                  class="crec-cascader-menu-item-expand-icon"
                >
                  <crec-icon type="right"></crec-icon>
                </span>
              </li>
            </ul>
          </template>
          <template v-else>
            <ul class="crec-cascader-menu cascader-menu">
              <li class="crec-cascader-menu-item dropdown__empty">
                {{noDataText}}
              </li>
            </ul>
          </template>
        </div>
      </template>

    </crec-popover>
  </div>
</template>

<script>
import Clickoutside from './clickoutside'
import {props, hasArrayChild, deepClone, getId, fireEvent, isPromise} from './utils'
export default {
  name: 'TagSelect',
  props,
  watch: {
    selectedLabels: {
      deep: true,
      handler () {console.log(this.selectedLabels)
        if (this.selectedLabels === '') {this.selectedLabels = this.defaultLabels}
      }
    },
    options: {
      deep: true,
      handler () {this.initOpts()
        this.initDatas()}
    },
    defaultOptions: {
      deep: true,
      handler () {console.log(this.defaultOptions)
        this.defaultLabels = this.defaultOptions.map(e => e.organId)[0]
      }
    },
    value: {
      deep: true,
      handler () {console.log(this.selectedValues, this.value)
        if (this.selectedValues !== this.value) {this.initOpts()
          this.initDatas()}
      }
    },
    disabled (disabled) {if (disabled) {this.hidePopover()
      }
    },
    showPopover (flag) {if (flag) {console.log(this.value)
        this.$emit('focus')
      }
    }
  },
  directives: {Clickoutside},
  created () {this.classRef = `popper-class-${getId()}`
    this.popOverClass = `cascader-popper ${this.classRef} ${this.popperClass}`
    this.initOpts()
    this.initDatas()},
  mounted () {
    // 设置弹出层宽度
    this.elWidth = this.$el.offsetWidth
  },
  destroyed () {
    this.clonedOpts = null
    this.casTree = null
    this.selectedItems = null
    this.selectedLabels = null
    this.selectedvalues = null
  },
  data () {
    return {
      elWidth: '',
      popperWidth: '',
      popOverClass: '',
      classRef: '',
      showPopover: false,
      clonedOpts: [],
      casTree: [],
      selectedItems: {},
      selectedLabels: '',
      selectedValues: '',
      loadChildrenPromise: null,
      defaultLabels: ''
    }
  },
  methods: {initOpts () {console.log(this.defaultOptions)
      this.defaultLabels = this.defaultOptions.map(e => e.organId)[0]
      console.log(this.defaultLabels)
      this.clonedOpts = deepClone(this.options)
      this.casTree = [this.clonedOpts]
      console.log(this.casTree)
    },
    /**
     * 初始化数据
     * 空值初始化,两个绑定不统一的状况
     */
    initDatas () {this.pickCheckedItem(this.clonedOpts)
    },

    /**
     * 依据以后节点 checked
     * 更改所有子孙节点 checked
     * 依赖 this.selectChildren
     */
    markChildrenChecked (node) {
      const vm = this
      function loop (children, status) {if (children) {
          children.map(child => {if (!child.disabled) {
              child.checked = status
              if (child.checked) {child.indeterminate = false}
            }
            if (hasArrayChild(child, vm.childrenKey)) {loop(child[vm.childrenKey], status)
            }
          })
        }
      }
      if (node && hasArrayChild(node, vm.childrenKey) && this.selectChildren) {loop(node[vm.childrenKey], node.checked)
      }
    },
    /**
     * 标记父节点 checked、indeterminate 状态
     * 依赖 this.selectChildren
     */
    markParentChecked (node) {
      const vm = this
      node.indeterminate = false
      function loop (node) {
        let checkCount = 0
        if (hasArrayChild(node, vm.childrenKey)) {const childIndeterminate = node[vm.childrenKey].some(child => child.indeterminate)
          node[vm.childrenKey].map(child => {if (child.checked) {checkCount++}
          })
          // 子节点全副被选中
          if (checkCount === node[vm.childrenKey].length) {
            node.checked = true
            node.indeterminate = false
          } else {
            node.checked = false
            if (checkCount > 0 || childIndeterminate) {node.indeterminate = true} else {node.indeterminate = false}
          }
        }
        if (node.parent) {loop(node.parent)
        }
      }
      if (node && node.parent && this.selectChildren) {loop(node.parent)
      }
    },
    /**
     * 标记是否有被选子项
     * 依赖 this.selectChildren
     */
    markParentHasCheckChild (node) {
      const vm = this
      node.hasCheckedChild = false
      function loop (node) {
        let checkCount = 0
        if (hasArrayChild(node, vm.childrenKey)) {const childHasCheckedChild = node[vm.childrenKey].some(child => child.hasCheckedChild)
          node[vm.childrenKey].map(child => {if (child.checked) {checkCount++}
          })
          // 子节点有被选中
          node.hasCheckedChild = (checkCount > 0) || childHasCheckedChild
        }
        if (node.parent) {loop(node.parent)
        }
      }
      if (node && node.parent && !this.selectChildren) {loop(node.parent)
      }
    },
    // 展现标签所有层级
    getLevel (node, key, leveled) {const levels = []
      function loop (data) {levels.push(data[key])
        if (data.parent) {loop(data.parent)
        }
      }
      if (leveled) {loop(node)
        return levels.reverse().join(this.separator)
      } else {return node[key]
      }
    },
    /**
     * 解决已选中
     * 从新遍历 tree,pick 除已选中我的项目
     */
    pickCheckedItem (tree) {
      const vm = this
      /**
       * 移除 parent 援用
       */
      function removeParent (node) {const obj = {}
        Object.keys(node).forEach(key => {if (key !== 'parent') {obj[key] = node[key]
          }
        })
        if (hasArrayChild(obj, vm.childrenKey)) {obj[vm.childrenKey] = obj[vm.childrenKey].map(child => {return removeParent(child)
          })
        }
        return obj
      }
      vm.selectedItems = {}
      vm.selectedLabels = ''vm.selectedValues =''
      function loop (data) {if (Array.isArray(data)) {
          data.map(item => {if (item.checked) {const newItem = removeParent(item)
              vm.selectedItems = (newItem)
              vm.selectedLabels = vm.getLevel(item, vm.labelKey, vm.showAllLevels)
              vm.selectedValues = vm.getLevel(item, vm.valueKey, vm.outputLevelValue)
            }
            if (hasArrayChild(item, vm.childrenKey)) {loop(item[vm.childrenKey])
            }
          })
        }
      }
      loop(tree)
    },
    clearTag () {
      const vm = this
      function loop (nodeArr) {
        nodeArr.forEach(node => {
          node.checked = false
          node.indeterminate = false
          if (hasArrayChild(node, vm.childrenKey)) {loop(node[vm.childrenKey])
          }
        })
      }
      // 敞开全副状态
      loop(this.clonedOpts)
      this.selectedLabels = ''this.selectedValues =''
      this.selectedItems = {}
      this.$emit('clear')
      this.syncData()},
    // 菜单选中变动
    checkedChange (item, checked) {
      this.defaultLabels = ''
      this.clearTag()
      console.log(item, checked)
      item.checked = checked
      this.$emit('clickItem', item)
      this.markChildrenChecked(item)
      this.markParentChecked(item)
      this.markParentHasCheckChild(item)
      this.pickCheckedItem(this.clonedOpts)
      this.refresPopover()
      this.syncData()},
    // 同步数据到下层
    syncData () {console.log(this.selectedValues)
      this.$emit('input', this.selectedValues)
      this.$emit('change', this.selectedValues, this.selectedItems)
    },
    // 开展下一级
    async spreadNext (children, index, item) {
      const vm = this
      if (
        vm.allowLoadChildren &&
        !children && !item[vm.childrenKey] &&
        vm.loadChildrenMethod &&
        vm.loadChildrenMethod.constructor === Function &&
        !vm.loadChildrenPromise && // promise 不存在
        !item.isLeaf
      ) {const isPromiseMethod = this.loadChildrenMethod(item)
        if (isPromise(isPromiseMethod)) {
          vm.loadChildrenPromise = isPromiseMethod
          this.$set(item, 'loading', true)
          const result = await vm.loadChildrenPromise.catch(e => {this.$set(item, 'loading', false)
          })
          this.$set(item, 'loading', false)
          vm.loadChildrenPromise = null
          if (result && result.constructor === Array) {this.$set(item, vm.childrenKey, result)
            children = result
            this.initDatas()} else {console.warn('The resolved value by loadChildrenMethod must be an Option Array !')
          }
        } else {console.warn('You must return a Promise instance in loadChildrenMethod !')
        }
      }
      if (index || index === 0) {if (vm.casTree.indexOf(children) === -1) {if (children && children.length > 0) {vm.casTree.splice(index + 1, vm.casTree.length - 1, children)
          } else {vm.casTree.splice(index + 1, vm.casTree.length - 1)
          }
          vm.$emit('spread', item)
        }
      }
    },
    visibleChange (visible) {if (visible) {this.showPopover = true}
    },
    handleFocus (evt) {if (this.disabled) return
      this.$emit('focus', evt)
    },
    hidePopover (evt) {
      this.showPopover = false
      this.$emit('blur', evt)
    },
    refresPopover () {setTimeout(() => {fireEvent(window, 'resize')
      }, 66)
    }
  }
}
</script>

<style lang="less">
.hide-popper {display: none;}
.cascader-popper {padding: 0px;}
.crec-popover-inner-content {padding: 0;}
.cascader-menu-wrapper {
  white-space: nowrap;
  .crec-cascader-menu {
    padding: 8px 0;
    height: 240px;
  }
}
.cascader-menu-wrapper .cascader-checkbox {
  margin-right: 10px;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
  user-select: none;
}
.crec-cascader-menu-item.has-checked-child {background-color: #f5f7fa !important;}

.dropdown__empty {
  height: 100%;
  padding-top: 50%;
  margin: 0;
  text-align: center;
  color: #999;
  font-size: 14px;
}
.can-load-children {position: relative;}
.can-load-children::after {
  content: '';
  display: inline-block;
  position: absolute;
  width: 5px;
  height: 5px;
  background: #a5d279;
  right: 20px;
  top: 50%;
  border-radius: 50%;
  transform: translateY(-50%);
  -webkit-transform: translateY(-50%);
}
.can-load-children.loading-children::after {
  animation: loading 0.22s infinite alternate;
  -moz-animation: loading 0.22s infinite alternate; /* Firefox */
  -webkit-animation: loading 0.22s infinite alternate; /* Safari 和 Chrome */
  -o-animation: loading 0.22s infinite alternate; /* Opera */
}
@keyframes loading {
  from {background: #a5d279;}
  to {background: #334d19;}
}
</style>

正文完
 0