支持搜索的懒加载树表格table和树tree

352 阅读1分钟

element提供了tree和table组件,并支持懒加载方法,但是默认是无法做到能(远程)搜索的,

自己实现的话主要难点在于 远程搜索到结果后,把结果展示在当前表格或者树种,并自动选中当前项,(如果折叠着的需要自动展开,)

树表格实现后大致效果如下

  • 下图为懒加载的树表格的全部数据,

企业微信截图_16679918081569.png

  • 首次加载的时候只会加载首层根节点,如002的下层节点此时还没有,需要点击002才能加载过来

企业微信截图_16679917845127.png

  • 此时我们想搜索 2.1.1的内容,则在输入框输入2.1, 搜索框会出现2.1相关的全部列表,此时我们点击2.1.1

企业微信截图_16679920479361.png

  • 表格会自动展开2.1.1 并选中当前项

企业微信截图_16679921025821.png

主要实现思路是通过修改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>