<tag-select                allowLoadChildren                :options="organOptions"                :defaultOptions="organDefaultOptions"                v-model="form.organId"                placeholder="抉择组织机构"                @change="ispChange"                @spread="spread"                @focus="focus"                ref="tagSelect"                size="default"                :loadChildrenMethod="loadChildren"                labelKey="organName"                valueKey="organId"                childrenKey="sub"                :panelWidth="'auto'"              ></tag-select>
<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        mode="multiple"        v-model="selectedLabels"        :default-value="defaultLabels"        :placeholder="placeholder"        :disabled="disabled"        :size="size"        :collapse-tags="collapseTags"        :showArrow="true"        style="width: 100%;"        popper-class="hide-popper"        :open="false"        @focus="handleFocus"        @deselect="removeTag"        @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 () {        if (this.selectedLabels && this.selectedLabels.length === 0) {          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.organName)      }    },    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.organName)      console.log(this.defaultLabels)      this.clonedOpts = deepClone(this.options)      this.recursiveOpt(this.clonedOpts, null)      this.casTree = [this.clonedOpts]      console.log(this.casTree)    },    /**     * 初始化数据     * 空值初始化,两个绑定不统一的状况     */    initDatas () {      this.pickCheckedItem(this.clonedOpts)    },    /**     * 递归option数据     * 标记数据树形层级 parent     * 打上初始状态 checked indeterminate     */    recursiveOpt (nodeArr, parent) {      const vm = this      nodeArr.forEach(node => {        if (parent) {          node.parent = parent        }        node.indeterminate = false        node.checked = false        if (this.value.some(val => val === this.getLevel(node, vm.valueKey, this.outputLevelValue))) {          node.checked = true        }        this.markChildrenChecked(node)        this.markParentChecked(node)        this.markParentHasCheckChild(node)        if (hasArrayChild(node, vm.childrenKey)) {          vm.recursiveOpt(node[vm.childrenKey], node)        }      })    },    /**     * 依据以后节点 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.push(newItem)              vm.selectedLabels.push(vm.getLevel(item, vm.labelKey, vm.showAllLevels))              vm.selectedValues.push(vm.getLevel(item, vm.valueKey, vm.outputLevelValue))            }            if (hasArrayChild(item, vm.childrenKey)) {              loop(item[vm.childrenKey])            }          })        }      }      loop(tree)    },    removeTag (label) {      /**     * 遍历 tree     * 依据传入label 寻找 item     */      const vm = this      function findNodeByLabel (label) {        let result = null        function loop (tree) {          if (tree) {            tree.find(node => {              if (vm.getLevel(node, vm.labelKey, vm.showAllLevels) === label) {                result = node                return true              }              if (hasArrayChild(node, vm.childrenKey)) {                loop(node[vm.childrenKey])              }            })          }        }        if (label) {          loop(vm.clonedOpts)          return result        }      }      const deletedItem = findNodeByLabel(label)      if (deletedItem) {        vm.checkedChange(deletedItem, false)      }      this.$emit('remove-tag', label, deletedItem)    },    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.recursiveOpt(result, item)            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>
export function deepClone (source) {  if (!source && typeof source !== 'object') {    throw new Error('error arguments', 'shallowClone')  }  const targetObj = source.constructor === Array ? [] : {}  Object.keys(source).forEach(keys => {    if (source[keys] && typeof source[keys] === 'object') {      targetObj[keys] = source[keys].constructor === Array ? [] : {}      targetObj[keys] = deepClone(source[keys])    } else {      targetObj[keys] = source[keys]    }  })  return targetObj}export function hasArrayChild (obj, childrenKey) {  return obj[childrenKey] && Array.isArray(obj[childrenKey])}let id = 0export function getId () {  return ++id}export function fireEvent (element, event) {  if (document.createEventObject) {    // IE浏览器反对fireEvent办法    const evt = document.createEventObject()    return element.fireEvent('on' + event, evt)  } else {    // 其余规范浏览器应用dispatchEvent办法    const evt = document.createEvent('HTMLEvents')    evt.initEvent(event, true, true)    return !element.dispatchEvent(evt)  }}export function isPromise (obj) {  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'}// 所有选项export const props = {  value: {    type: [Array, String],    default () {      return ''    }  },  placeholder: {    type: String,    default: '请抉择'  },  disabled: {    type: Boolean,    default: false  },  options: {    type: Array,    default () {      return []    }  },  defaultOptions: {    type: Array,    default () {      return []    }  },  size: {    type: String,    default: ''  },  selectChildren: {    type: Boolean,    default: false  },  noDataText: {    type: String,    default: '无数据'  },  collapseTags: {    type: Boolean,    default: false  },  separator: {    type: String,    default: '/'  },  showAllLevels: {    type: Boolean,    default: false  },  outputLevelValue: {    type: Boolean,    default: false  },  // 显示加载指示器  showLoadingIndicator: {    type: Boolean,    default: true  },  // 容许加载子项  allowLoadChildren: {    type: Boolean,    default: false  },  // 加载办法  loadChildrenMethod: {    type: Function,    default: null,    return: Promise  },  // key  labelKey: {    type: String,    default: 'label'  },  valueKey: {    type: String,    default: 'value'  },  childrenKey: {    type: String,    default: 'children'  },  popperClass: {    type: String,    default: ''  },  clearable: {    type: Boolean,    default: false  },  panelWidth: {    type: [Number, String],    default: 160  }}
import Vue from 'vue'const isServer = Vue.prototype.$isServer/* istanbul ignore next */export const on = (function () {  if (!isServer && document.addEventListener) {    return function (element, event, handler) {      if (element && event && handler) {        element.addEventListener(event, handler, false)      }    }  } else {    return function (element, event, handler) {      if (element && event && handler) {        element.attachEvent('on' + event, handler)      }    }  }})()const nodeList = []const ctx = '@@clickoutsideContext'let startClicklet seed = 0!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e))!Vue.prototype.$isServer && on(document, 'mouseup', e => {  nodeList.forEach(node => node[ctx].documentHandler(e, startClick))})function createDocumentHandler (el, binding, vnode) {  return function (mouseup = {}, mousedown = {}) {    if (!vnode ||      !vnode.context ||      !mouseup.target ||      !mousedown.target ||      el.contains(mouseup.target) ||      el.contains(mousedown.target) ||      el === mouseup.target ||      (vnode.context.popperElm &&        (vnode.context.popperElm.contains(mouseup.target) ||          vnode.context.popperElm.contains(mousedown.target)))) return    if (binding.expression &&      el[ctx].methodName &&      vnode.context[el[ctx].methodName]) {      vnode.context[el[ctx].methodName]()    } else {      el[ctx].bindingFn && el[ctx].bindingFn()    }  }}/** * v-clickoutside * @desc 点击元素里面才会触发的事件 * @example * ```vue * <div v-element-clickoutside="handleClose"> * ``` */export default {  bind (el, binding, vnode) {    nodeList.push(el)    const id = seed++    el[ctx] = {      id,      documentHandler: createDocumentHandler(el, binding, vnode),      methodName: binding.expression,      bindingFn: binding.value    }  },  update (el, binding, vnode) {    el[ctx].documentHandler = createDocumentHandler(el, binding, vnode)    el[ctx].methodName = binding.expression    el[ctx].bindingFn = binding.value  },  unbind (el) {    const len = nodeList.length    for (let i = 0; i < len; i++) {      if (nodeList[i][ctx].id === el[ctx].id) {        nodeList.splice(i, 1)        break      }    }    delete el[ctx]  }}