element提供了tree和table组件,并支持懒加载方法,但是默认是无法做到能(远程)搜索的,
自己实现的话主要难点在于 远程搜索到结果后,把结果展示在当前表格或者树种,并自动选中当前项,(如果折叠着的需要自动展开,)
树表格实现后大致效果如下
- 下图为懒加载的树表格的全部数据,
- 首次加载的时候只会加载首层根节点,如002的下层节点此时还没有,需要点击002才能加载过来
- 此时我们想搜索 2.1.1的内容,则在输入框输入2.1, 搜索框会出现2.1相关的全部列表,此时我们点击2.1.1
- 表格会自动展开2.1.1 并选中当前项
主要实现思路是通过修改elp-table源码 形式来支持
主要点在于 区分是节点是懒加载的还是 通过搜索手动添加的,
eltable的懒加载主要通过 hasChildren 来区分是否需要懒加载当前节点, 并且对于懒加载出来的节点,并不会放在默认的tabledata中, 而是放在lazyTreeNodeMap 里,
所以我们需要做的主要是如果搜索出来数据后,把它添加到tabledata,并同时把这些数据的 hasChildren 改为 false,其他一些细节处理可以参考以下代码
以下是全部代码
<template>
<div class="lazy-table">
<page-toolbar>
<page-toolbar-search :span="4">
<el-autocomplete
class="lazy-table-filter"
:placeholder="placeholder"
v-model="keywords"
:fetch-suggestions="handleSubmitSearch"
:trigger-on-focus="false"
@select="handleSelect"
>
<i class="el-input__icon el-icon-search" slot="suffix" />
</el-autocomplete>
</page-toolbar-search>
<page-toolbar-actions :span="20">
<el-dropdown class="margin" @command="handleCommand">
<el-button type="primary"> <i class="el-icon-expand"></i><i class="el-icon-arrow-down el-icon--right"></i> </el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="!tableData.length" command="allFold">{{ $t('allFold') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<slot name="rightTools"></slot>
</page-toolbar-actions>
</page-toolbar>
<TargetTable
:key="unequalTableKey"
ref="targetTable"
v-on="$listeners"
v-bind="attrs"
highlight-current-row
:data="tableData"
:expand-row-keys="expandRowKeys"
lazy
:load="load"
:needPage="false"
@select="rowSelect"
@select-all="selectAll"
>
<!-- 通过便利实现插槽透传 -->
<template v-for="(item, key) in slots" v-slot:[key]="slotScope">
<slot :name="key" v-bind="slotScope"></slot>
</template>
</TargetTable>
</div>
</template>
<i18n src="./locales/index.json"></i18n>
<script setup>
import Vue, { ref, onMounted, useAttrs, nextTick, defineExpose, useSlots } from 'vue'
import TargetTable from '../target-table/TargetTable.vue'
const props = defineProps({
fetchSuggestions: {
type: Function,
default: () => {}
},
handleSuggestionsSelected: {
type: Function,
default: () => {}
},
load: {
type: Function,
default: () => {}
},
placeholder: {
type: String,
default: '请输入关键字'
}
})
onMounted(() => {
load()
})
// 表格数据
const tableData = ref([])
const load = (tree, treeNode, resolve) => {
props.load(tree, treeNode, (res = []) => {
res.forEach((item) => {
item.hasChildren = !item.isLeaf
})
if (!tree) {
// 初次查询的时候存储
tableData.value = res
}
resolve && resolve(res)
})
}
const attrs = useAttrs()
// console.log('attrs', attrs)
const slots = useSlots()
// console.log('slots', slots)
// const { fetchSuggestions, handleSuggestionsSelected } = toRefs(props)
// 关键字
const keywords = ref('')
// 关键字列表
const handleSubmitSearch = (queryString, cb) => props.fetchSuggestions(queryString, (data) => cb(data))
// 表格展开行的key
const expandRowKeys = ref([])
// 关键字搜索
const handleSelect = (item) => {
console.log('handleSelect item', item)
props.handleSuggestionsSelected(item, (data) => {
const _walk = (data) => {
const child = data.children
if (!child) {
// 没有子节点的是当前末级节点,需要判断是否还有子集需要懒加载
data.hasChildren = !data.isLeaf
} else {
delete data.hasChildren
// 已经有子节点的就不需要设置hasChildren为true,不需要再点击该条目的时候在发出懒加载请求
child.forEach((childItem) => {
_walk(childItem)
})
}
}
_walk(data)
console.log('data', data)
const idx = tableData.value.findIndex((item) => item.id === data.id)
if (idx === -1) {
// TODO: 新建的为根节点时,暂时先push,需要等新建接口返回对象时,判断新建的为根节点则用load加载
tableData.value.push(data)
} else {
tableData.value.splice(idx, 1, data)
}
expandSelectKey(item)
updateLazyNodeMap(data)
})
}
const targetTable = ref(null)
const updateLazyNodeMap = (data) => {
const lazyTreeNodeMap = targetTable.value.$refs.singleTable.$refs.table.store.states.lazyTreeNodeMap
const id = data.id
Vue.delete(lazyTreeNodeMap, id)
const treeData = targetTable.value.$refs.singleTable.$refs.table.store.states.treeData
const oldLazedTreeData = treeData[id]
if (oldLazedTreeData) {
oldLazedTreeData.loaded = false
oldLazedTreeData.expanded = false
}
if (data.children) {
data.children.forEach((item) => {
updateLazyNodeMap(item)
})
}
}
const expandSelectKey = (item) => {
const fullId = item.fullId
if (!fullId) {
console.warn('[lazy-table] fullId is not fount', fullId)
return
}
const expandRowIds = fullId.split('/')
expandRowIds.pop() // 当前项不展开
// console.log('expandSelectKey', expandRowIds)
targetTable.value.$refs.singleTable.$refs.table.store.states.expandRowKeys = expandRowIds
expandRowKeys.value = expandRowIds
setCurrent(fullId)
}
const setCurrent = async (fullId) => {
const newExpandedKeys = fullId.split('/')
let currentData = tableData.value
newExpandedKeys.forEach((item) => {
const cuData = currentData.find((currentDataItem) => currentDataItem.id === item)
if (cuData.children) {
currentData = cuData.children
} else {
currentData = cuData
}
})
// console.log('current data', currentData)
await nextTick()
targetTable.value.$refs.singleTable.setCurrentRow(currentData)
await nextTick()
const currentRowElm = targetTable.value.$el.querySelector('.current-row')
currentRowElm && currentRowElm.scrollIntoView()
}
const unequalTableKey = ref(false)
const refresh = async () => {
load()
await nextTick()
unequalTableKey.value = !unequalTableKey.value
}
const handleCommand = (command) => {
if (command === 'allFold') {
handleAllFoldTableNodes()
} else {
console.error('不存在的command', command)
}
}
const handleAllFoldTableNodes = () => {
toggleRowExpansionAll(tableData.value, false)
}
const toggleRowExpansionAll = (data, isExpansion) => {
data.forEach((item) => {
targetTable.value.toggleRowExpansion(item, isExpansion)
if (item.children !== undefined && item.children !== null) {
toggleRowExpansionAll(item.children, isExpansion)
}
})
}
const checkedAllFlag = ref(false)
const rowSelect = (selection, row) => {
// 当选中的是父元素的时候
if (selection.indexOf(row) > -1 && row.children) {
toggleSelection(row.value.children, true)
}
// 取消选中父元素的时候
if (selection.indexOf(row) === -1 && row.children) {
toggleSelection(row.children, false)
}
}
// 勾选或者去掉勾选
const toggleSelection = (rows, flag) => {
if (rows) {
rows.forEach((row) => {
targetTable.value.$refs.singleTable.toggleRowSelection(row, flag)
if (row.children) {
// 如果子级还有数据 递归勾选或去掉勾选
toggleSelection(row.children, flag)
}
})
} else {
targetTable.value.$refs.singleTable.clearSelection()
}
}
const selectAll = () => {
checkedAllFlag.value = !(checkedAllFlag.value)
toggleSelection(tableData.value, checkedAllFlag.value)
}
defineExpose({
tableData,
handleSelect,
refresh
})
</script>
<style lang="scss">
.lazy-table {
.lazy-table-filter {
width: 100%;
}
.margin {
margin-right: 10px;
}
}
</style>