el-tree 二次封装 含搜索
templatediv classsmart-tree-radioel-inputv-modelkeywordclearableplaceholder请输入关键字搜索classsmart-tree-radio__search/el-treereftreeRef:datafilterTreeData:propstreePropsnode-keyvaluedefault-expand-all:expand-on-click-nodefalsetemplate #default{ data }el-radioclasssmart-tree-radio__radio:model-valueinnerValue:valuedata.value:disableddata.disabledchangehandleRadioChange(data){{ data.label }}/el-radio/template/el-treediv classsmart-tree-radio__footerel-button sizesmall clickhandleClear清空/el-button/div/div/templatescript setup langtsimport { computed, ref, watch } from vueimport type { ElTree } from element-plusinterface TreeItem {label: stringvalue: string | numberdisabled?: booleanchildren?: TreeItem[]}const props withDefaults(defineProps{modelValue?: string | number | nulldata: TreeItem[]}(),{modelValue: null,data: () []})const emit defineEmits{update:modelValue: [value: string | number | null]change: [node: TreeItem | null]}()const treeRef refInstanceTypetypeof ElTree()const keyword ref()const innerValue refstring | number | null(props.modelValue)const treeProps {label: label,children: children}/*** 搜索核心* 命中当前节点保留当前节点* 命中子节点也保留父级路径。*/const filterTreeData computed(() {const key keyword.value.trim()if (!key) return props.dataconst loop (list: TreeItem[]): TreeItem[] {return list.reduceTreeItem[]((result, item) {const children item.children ? loop(item.children) : []const selfMatched item.label.includes(key)if (selfMatched || children.length) {result.push({...item,children})}return result}, [])}return loop(props.data)})/*** 外部传 value 后支持反显。*/watch(() props.modelValue,value {innerValue.value value ?? null},{immediate: true})/*** el-radio 单选* 只改变当前节点 value* 不影响父级不影响子级。*/const handleRadioChange (data: TreeItem) {if (data.disabled) returninnerValue.value data.valueemit(update:modelValue, data.value)emit(change, data)}/*** 清空* 清空当前选中 value。*/const handleClear () {innerValue.value nullemit(update:modelValue, null)emit(change, null)}/scriptstyle scoped langscss.smart-tree-radio {width: 100%;__search {margin-bottom: 8px;}__radio {width: 100%;height: 26px;display: flex;align-items: center;}__footer {margin-top: 8px;text-align: right;}}/style