使用element ui 二次封裝實現(xiàn)下拉樹組件,可以搜索,可以動態(tài)綁定
一 實現(xiàn)效果圖
1.1 單選模式

單選模式.gif
1.2 多選模式

多選模式.gif
二 相關(guān)說明
2.1 框架以及版本
1、element-ui 2.15.1 2
2、vue 2.6.11
2.2 組件概述
封裝的下拉樹,結(jié)合了element ui中的下拉組件 el-select 與 樹結(jié)構(gòu)組件 el-tree,取兩個組件中的一些屬性,在此基礎(chǔ)上進行的封裝。其中可通過v-model對數(shù)據(jù)進行動態(tài)綁定。自行封裝擴展性不高,可能會有一些問題,需求不同,可自行進行封裝。
2.3 組件api
- 相關(guān)屬性
| 屬性 | 說明 | 類型 | 默認(rèn)值 |
|---|---|---|---|
| defaultValue/v-model | 值 | String, Number, Array | - |
| treeData | 樹結(jié)構(gòu)數(shù)據(jù)源 | Array | [] |
| treeProps | 替換treeNode 中 label, children 字段為 treeData 中對應(yīng)的字段 | Object | {label: ‘name’, children: ‘children’} |
| nodeKey | 樹結(jié)構(gòu)數(shù)據(jù)key取值,數(shù)據(jù)唯一,默認(rèn)id | String | id |
| clearable | 是否顯示清空功能 | Boolean | false |
| multiple | 是否多選 | Boolean | false |
| placeholder | 選擇框默認(rèn)文字 | String | 請選擇 |
| showSearch | 是否顯示搜索框 | Boolean | false |
| searchPlaceholder | 搜索框默認(rèn)文字 | String | 請輸入關(guān)鍵字 |
| collapseTags | 多選時是否將選中值按文字的形式展示 | Boolean | true |
2 相關(guān)方法
| 名稱 | 說明 | 參數(shù) |
|---|---|---|
| change | 數(shù)據(jù)發(fā)生變化 | 數(shù)據(jù),選中的節(jié)點 |
| removeTag | 多選模式下,點擊刪除單個tag觸發(fā) | 當(dāng)前選中的值 |
| clear | 清空選項觸發(fā) | - |
三 代碼實現(xiàn)
3.1 組件頁面代碼
<template>
<el-select
ref="treeSelectRef"
style="width: 100%;"
clearable
:value="selectLabel"
:multiple="multiple"
:placeholder="placeholder"
:popper-class="popperClass"
:collapse-tags="collapseTags"
@remove-tag="handleSelectRemoveTag"
@clear="handleSelectClear"
>
<div class="tree-select-search" v-if="showSearch">
<el-input
placeholder="請輸入關(guān)鍵字"
v-model="treeFilterText"
clearable
size="mini"
>
</el-input>
</div>
<el-option :value="selectOptionValue"
class="tree-select-option-item"
>
<div class="tree-select-tree-body">
<el-tree
id="tree-option"
ref="optionTreeRef"
highlight-current
empty-text="加載中,請稍后......"
:show-checkbox="showCheckbox"
:data="treeData"
:props="treeProps"
:node-key="nodeKey"
:filter-node-method="filterTreeNode"
:default-expanded-keys="defaultExpandedKeys"
@node-click="handleNodeClick"
@check="handleTreeCheck">
</el-tree>
</div>
</el-option>
</el-select>
</template>
3.2 組件JS部分代碼
<script>
const defaultProps = {
children: 'children', // 子級字段
label: 'name' // 展示字段
}
export default {
name: 'CustomTreeSelect',
model: {
prop: "defaultValue",
event: "change"
},
props: {
// v-model值
defaultValue: {
type: [String, Number, Array]
},
// 數(shù)據(jù)源
treeData: {
type: Array,
default () {
return []
}
},
// 替換 treeNode 中 label, children 字段為 treeData 中對應(yīng)的字段
treeProps: {
type: Object,
default () {
return defaultProps
}
},
// 樹結(jié)構(gòu)key的取值,數(shù)據(jù)唯一, 默認(rèn)為id
nodeKey: {
type: String, default: 'id'
},
// 是否顯示清空功能
clearable: {
type: Boolean, default: false
},
// 是否多選
multiple: {
type: Boolean, default: false
},
// 選擇框默認(rèn)文字
placeholder: {
type: String, default: '請選擇'
},
// 搜索框默認(rèn)文字
searchPlaceholder: {
type: String, default: '請輸入關(guān)鍵字'
},
// 是否顯示搜索框
showSearch: {
type: Boolean, default: false
},
// 多選時是否將選中值按文字的形式展示
collapseTags: {
type: Boolean, default: true
}
},
data() {
return {
// 下拉框選中值
selectValue: '',
// 下拉框選中回顯值
selectLabel: '',
// 下拉框Option的值,設(shè)置為undefined,防止選中
selectOptionValue: undefined,
// 樹數(shù)據(jù)搜索內(nèi)容
treeFilterText: '',
// 默認(rèn)展開
defaultExpandedKeys: []
}
},
computed: {
// 樹結(jié)構(gòu)是否顯示復(fù)選框(多選情況下出現(xiàn))
showCheckbox () {
return this.multiple
},
// select 下拉框自定義的類名-可自行修改
popperClass () {
let classNames = ['custom-tree-select-popper']
if (this.showSearch) {
classNames.push('custom-tree-select-search')
}
return classNames.join(' ')
},
// 下拉框?qū)嵗? treeSelectRef () {
return this.$refs.treeSelectRef
},
// 樹結(jié)構(gòu)實例
optionTreeRef () {
return this.$refs.optionTreeRef
}
},
watch: {
// 監(jiān)聽默認(rèn)值,對數(shù)據(jù)賦值
defaultValue: {
deep: true,
immediate: true,
handler (newValue, oldValue) {
const { multiple } = this
if (newValue) {
if (!this.reloadTreeCheck)
this.handleSetTreeCheck(newValue);
} else {
this.selectValue = multiple ? [] : ''
this.selectLabel = multiple ? [] : ''
}
}
},
treeFilterText (value) {
setTimeout(() => {
this.handleInputChange(value)
}, 300)
}
},
mounted () {
// 重新加載樹結(jié)構(gòu)選中 false-加載 true-不加載
this.reloadTreeCheck = false
},
methods: {
/** 多選模式下,點擊移除單個tag */
handleSelectRemoveTag (tag) {
this.reloadTreeCheck = false
const { selectValue } = this
if (isArray(selectValue)) {
this.selectValue.shift()
}
this.$emit('removeTag', tag)
},
/** select框的清除按鈕 */
handleSelectClear () {
this.reloadTreeCheck = false
const { multiple } = this
const value = multiple ? [] : ''
this.treeFilterText = ''
this.selectValue = [ ...value ]
this.selectLabel = [ ...value ]
this.$emit('change', value, null)
this.$emit('clear')
},
/** 樹節(jié)點 點擊時 */
handleNodeClick (node) {
const { treeProps, multiple, nodeKey } = this
if (multiple) return
if (node.children && node.children.length > 0) return
this.selectValue = node[nodeKey]
this.selectLabel = node[treeProps.label]
this.$emit('change', this.selectValue, node)
// 下拉框失去焦點,隱藏下拉面板
this.treeSelectRef.blur()
// this.treeFilterText = ''
},
/** 樹節(jié)點 復(fù)選框選中時 */
handleTreeCheck (node, values) {
const { treeProps } = this
const { checkedKeys, checkedNodes } = values
this.selectValue = checkedKeys
const lableValues = checkedNodes.map(nodeItem => {
return nodeItem[treeProps.label]
})
this.selectLabel = lableValues
this.$emit('change', checkedKeys, checkedNodes)
},
/** 設(shè)置回顯數(shù)據(jù) */
handleSetTreeNode (value) {
const { optionTreeRef, treeProps } = this
this.selectValue = value
if (isArray(value)) {
// 多選
this.selectLabel = value.map(item => {
const treeNode = optionTreeRef.getNode(item)
return treeNode.data[treeProps.label]
})
} else {
// 單選
const treeNode = optionTreeRef.getNode(value)
this.selectLabel = treeNode.data[treeProps.label]
}
},
/** 處理數(shù)據(jù)樹結(jié)構(gòu)展開,并處理選中效果 */
handleTreeExpandKeys (value) {
const { optionTreeRef, multiple } = this
if (isArray(value) && multiple) {
optionTreeRef.setCheckedKeys(value)
value.forEach(item => {
const treeNode = optionTreeRef.getNode(item)
if (treeNode && treeNode.parent) {
this.setTreeExpandKeys(treeNode.parent)
}
})
} else {
optionTreeRef.setCurrentKey(value)
const treeNode = optionTreeRef.getNode(value)
if (treeNode && treeNode.parent) {
this.setTreeExpandKeys(treeNode.parent)
}
}
},
/** 處理樹結(jié)構(gòu)父級展開 */
setTreeExpandKeys (node) {
node.expanded = true
if (node.parent) {
this.setTreeExpandKeys(node.parent)
}
},
/** 搜索樹節(jié)點 */
filterTreeNode (value, data, node) {
if (!value) {
// if (node.expanded) node.expanded = false
return true
}
const { treeProps: { label } } = this
return data[label].indexOf(value) !== -1;
},
/** 設(shè)置樹結(jié)構(gòu)回顯選中 */
handleSetTreeCheck (value) {
if (!this.reloadTreeCheck && value) {
this.$nextTick(() => {
this.handleSetTreeNode(value)
this.handleTreeExpandKeys(value)
this.reloadTreeCheck = true
})
}
},
/** 搜索框中按下回車失去焦點觸發(fā) */
handleInputChange (value) {
this.optionTreeRef.filter(value)
}
}
}
/**
* 判斷數(shù)據(jù)類型是否為數(shù)組
*/
function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
</script>
3.3 組件樣式代碼
這里有覆蓋修改到element-ui的原始樣式,
注:如果需要修改下拉框的高度,修改樣式代碼中.el-select-dropdown__wrap 中的高度屬性
<style lang="scss">
.custom-tree-select-search {
.el-select-dropdown__list {
padding: 0;
}
}
.custom-tree-select-popper {
.el-scrollbar {
.el-select-dropdown__wrap {
max-height: 365px !important;
}
.el-scrollbar__bar.is-vertical {
z-index: 3;
}
}
}
</style>
<style lang="scss" scoped>
.tree-select-search {
position: sticky;
top: 0;
z-index: 2;
display: block;
padding: 6px;
background: #fff;
}
.tree-select-option-item {
background: #fff;
overflow: scroll;
height: 200px;
overflow-x: hidden;
}
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item{
height: auto;
min-height: 200px;
padding: 0;
overflow: hidden;
}
</style>
四 如何使用
4.1 組件引入注冊后頁面
<CustomTreeSelect
clearable
multiple
show-search
nodeKey="code"
v-model="treeValue"
:treeData="treeData"
:treeProps="treeProps"
/>
4.2 參數(shù)設(shè)置
export default {
data () {
return {
treeData: [
{
id: "321C8FF6CC6046D79CD8877526054BCF",
text: "1100 北京市本級",
name: "1100 北京市本級",
code: "1100"
},
{
id: "157EAB9EA1A34D0F824E97C2C7D9F0CA",
text: "1101 市轄區(qū)",
name: "1101 市轄區(qū)",
code: "1101",
children: [
{
id: "B08F7DB39F124ACFB2171508D8C5C0FD",
text: "110101 東城區(qū)",
name: "110101 東城區(qū)",
code: "110101"
},
{
id: "2AF3A9766AAD433A95D4EADE5FB97839",
text: "110102 西城區(qū)",
name: "110102 西城區(qū)",
code: "110102"
},
{
id: "A2058EAE12674B3D9BA3710C87DDB111",
text: "110105 朝陽區(qū)",
name: "110105 朝陽區(qū)",
code: "110105"
},
{
id: "C6BC2FE9FE86493291EE59171133ABB5",
text: "110106 豐臺區(qū)",
name: "110106 豐臺區(qū)",
code: "110106"
},
{
id: "06E478CB634F4FC19EC0381D55751218",
text: "110107 石景山區(qū)",
name: "110107 石景山區(qū)",
code: "110107"
},
{
id: "DD0B1A29473D4053989938700B73AE26",
text: "110108 海淀區(qū)",
name: "110108 海淀區(qū)",
code: "110108"
},
{
id: "5D3FF6E7729E43F9B813EAAB80796E41",
text: "110109 門頭溝區(qū)",
name: "110109 門頭溝區(qū)",
code: "110109"
},
{
id: "349609893FAC47BC97509B8FC411059A",
text: "110111 房山區(qū)",
name: "110111 房山區(qū)",
code: "110111"
},
{
id: "1E5B2DC4A1E84C959BB78D6E1B49A1DF",
text: "110112 通州區(qū)",
name: "110112 通州區(qū)",
code: "110112"
},
{
id: "2C9DC40AEFF8454ABF8A2EE6AD0A2DB5",
text: "110113 順義區(qū)",
name: "110113 順義區(qū)",
code: "110113"
},
{
id: "5C0342E4E41841AD837E365DB79E81B7",
text: "110114 昌平區(qū)",
name: "110114 昌平區(qū)",
code: "110114"
},
{
id: "DF25016580634FD39307FC0E18E7CF4F",
text: "110115 大興區(qū)",
name: "110115 大興區(qū)",
code: "110115"
},
{
id: "8DC99ABF109D40ED899552A08FE07C63",
text: "110116 懷柔區(qū)",
name: "110116 懷柔區(qū)",
code: "110116"
},
{
id: "9D2DA21BD26C40CC90B8BB26E1B437AC",
text: "110117 平谷區(qū)",
name: "110117 平谷區(qū)",
code: "110117"
},
{
id: "AD5172DDD7D547F5A06F022D64EB4DF5",
text: "110118 密云區(qū)",
name: "110118 密云區(qū)",
code: "110118"
},
{
id: "78B4DA4936A44314AD801B15988707D4",
text: "110119 延慶區(qū)",
name: "110119 延慶區(qū)",
code: "110119"
}
]
},
{
id: "E8ADAB8AFDB24F77B3FA89ED7E9A9F15",
text: "1102 縣",
name: "1102 縣",
code: "1102"
}
],
treeProps: {
label: 'name',
children: 'children'
},
treeValue: [],
}
}
}
參考文章
https://element.eleme.cn/#/zh-CN
https://blog.csdn.net/R_xxxxx/article/details/106112583
https://blog.csdn.net/sleepwalker_1992/article/details/87894588
http://www.itdecent.cn/p/47e13795cfe7
https://blog.csdn.net/Mrchai521/article/details/114993368
https://blog.csdn.net/qq_41791303/article/details/103710456