<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>