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