parent
e7aebfc71a
commit
aa66f586cf
@ -0,0 +1,214 @@ |
||||
<template> |
||||
<el-select |
||||
ref="select" |
||||
:value="value" |
||||
:multiple="multiple" |
||||
:disabled="disabled" |
||||
:size="size" |
||||
:clearable="clearable" |
||||
:placeholder="placeholder" |
||||
:popper-append-to-body="popperAppendToBody" |
||||
:automatic-dropdown="automaticDropdown" |
||||
@input="$emit('input',$event)" |
||||
@change="$emit('change',$event)" |
||||
@remove-tag="removeTag" |
||||
> |
||||
<div class="tree-select-container" slot="empty"> |
||||
<el-input |
||||
ref="search" |
||||
v-if="filterable && filterMethod" |
||||
v-model="search" |
||||
size="mini" |
||||
clearable |
||||
@input="onSearch" |
||||
/> |
||||
<el-tree |
||||
ref="tree" |
||||
:data="data" |
||||
:node-key="nodeKey" |
||||
:props="props" |
||||
:show-checkbox="multiple" |
||||
:default-checked-keys="defaultCheckedKeys" |
||||
:current-node-key="multiple ? undefined : value" |
||||
:highlight-current="!multiple" |
||||
:expand-on-click-node="false" |
||||
:check-on-click-node="multiple" |
||||
:filter-node-method="filterMethod" |
||||
:accordion="accordion" |
||||
@node-click="nodeClick" |
||||
@check="check" |
||||
> |
||||
<slot slot-scope="{node,data}"> |
||||
<span :class="{'el-tree-node__label':true,'is-disabled':node.disabled}">{{node.label}}</span> |
||||
</slot> |
||||
</el-tree> |
||||
</div> |
||||
</el-select> |
||||
</template> |
||||
|
||||
<script> |
||||
import {debounce} from "@/utils" |
||||
import {flatTree} from "@/utils/tree" |
||||
|
||||
export default { |
||||
name: "TreeSelect", |
||||
|
||||
props: { |
||||
value: {required: true}, |
||||
data: {type: Array, default: () => []}, |
||||
multiple: Boolean, |
||||
disabled: Boolean, |
||||
size: String, |
||||
clearable: {type: Boolean, default: true}, |
||||
placeholder: String, |
||||
filterable: Boolean, |
||||
filterMethod: Function, |
||||
nodeKey: {type: String, default: 'id'}, |
||||
props: { |
||||
default() { |
||||
return { |
||||
children: 'children', |
||||
label: 'label', |
||||
disabled: 'disabled' |
||||
} |
||||
} |
||||
}, |
||||
accordion: {type: Boolean, default: true}, |
||||
popperAppendToBody: Boolean, |
||||
automaticDropdown: Boolean |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
currentNode: null, |
||||
search: '' |
||||
} |
||||
}, |
||||
|
||||
computed: { |
||||
defaultCheckedKeys() { |
||||
return this.multiple ? this.value : [] |
||||
} |
||||
}, |
||||
|
||||
watch: { |
||||
value(v) { |
||||
const method = this.multiple ? 'setCheckedKeys' : 'setCurrentKey' |
||||
this.$refs.tree[method](v || null) |
||||
}, |
||||
|
||||
data() { |
||||
this.initSelectOptions() |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
removeTag(v) { |
||||
const tree = this.$refs.tree |
||||
const value = [...this.value] |
||||
|
||||
//被移除的选项的所有父级的ID |
||||
const parents = [] |
||||
let removeNode = tree.getNode(v) |
||||
while (removeNode && removeNode.parent) { |
||||
parents.push(removeNode.parent.key) |
||||
removeNode = removeNode.parent |
||||
} |
||||
|
||||
//移除所有子级、所有父级 |
||||
for (let i = value.length - 1; i >= 0; i--) { |
||||
const isChild = this.getNodeParent(tree.getNode(value[i]), ({key}) => key === v) |
||||
if (isChild || parents.includes(value[i])) { |
||||
value.splice(i, 1) |
||||
} |
||||
} |
||||
|
||||
value.length < this.value.length && this.$emit('input', value) |
||||
}, |
||||
|
||||
onSearch(v) { |
||||
this.$refs.tree.filter(v) |
||||
}, |
||||
|
||||
nodeClick(data, node) { |
||||
if (this.multiple) return |
||||
|
||||
if (node.disabled) { |
||||
return this.$refs.tree.setCurrentKey() |
||||
} |
||||
|
||||
if (this.currentNode === data) { |
||||
this.currentNode = null |
||||
this.$refs.tree.setCurrentKey() |
||||
} |
||||
else this.currentNode = data |
||||
|
||||
this.$emit('input', this.currentNode && this.currentNode[this.nodeKey]) |
||||
this.$refs.select.blur() |
||||
}, |
||||
|
||||
check(data, {checkedKeys}) { |
||||
this.multiple && this.$emit('input', checkedKeys) |
||||
}, |
||||
|
||||
getNodeParent(node, predicate) { |
||||
if (!node || predicate(node)) return node |
||||
return this.getNodeParent(node.parent, predicate) |
||||
}, |
||||
|
||||
initSelectOptions() { |
||||
this.$refs.select.cachedOptions = |
||||
flatTree(this.data, this.props.children) |
||||
.map(i => ({ |
||||
value: i[this.nodeKey], |
||||
currentLabel: i[this.props.label] |
||||
})) |
||||
this.selectOptionsTrigger() |
||||
}, |
||||
|
||||
//模拟el-select对options的watch |
||||
selectOptionsTrigger() { |
||||
const select = this.$refs.select |
||||
if (select.$isServer) return |
||||
this.$nextTick(() => { |
||||
select.broadcast('ElSelectDropdown', 'updatePopper') |
||||
}) |
||||
if (select.multiple) { |
||||
select.resetInputHeight() |
||||
} |
||||
let inputs = select.$el.querySelectorAll('input') |
||||
if ([].indexOf.call(inputs, document.activeElement) === -1) { |
||||
select.setSelected() |
||||
} |
||||
if (select.defaultFirstOption && (select.filterable || select.remote) && select.filteredOptionsCount) { |
||||
select.checkDefaultFirstOption() |
||||
} |
||||
} |
||||
}, |
||||
|
||||
created() { |
||||
this.onSearch = debounce(this.onSearch) |
||||
}, |
||||
|
||||
mounted() { |
||||
this.currentNode = this.$refs.tree.getCurrentNode() |
||||
this.initSelectOptions() |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.tree-select-container { |
||||
padding: 8px; |
||||
|
||||
> .el-input { |
||||
margin-bottom: 8px; |
||||
} |
||||
|
||||
.el-tree-node__label.is-disabled { |
||||
background-color: #edf2fc; |
||||
border-color: #dcdfe6; |
||||
cursor: not-allowed; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,31 @@ |
||||
<template> |
||||
<div> |
||||
<tree-select v-model="value" :data="data" multiple filterable :filter-method="filter"/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import TreeSelect from '@/components/TreeSelect' |
||||
|
||||
export default { |
||||
name: "treeSelectExample", |
||||
|
||||
components: {TreeSelect}, |
||||
|
||||
data() { |
||||
return { |
||||
value: [], |
||||
data: [ |
||||
{id: 1, label: '一级 1', children: [{id: 2, label: '二级 1-1'}, {id: 3, label: '二级 1-2'}]}, |
||||
{id: 4, label: '一级 2', children: [{id: 5, label: '二级 2-1'}]}, |
||||
], |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
filter(v, data) { |
||||
return data.label.includes(v) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
Loading…
Reference in new issue