
我这里采用的UI库是Element,库自带了一种实现方式:

但是这种自带的实现方式,可扩展性很差,就很low,而且这样偷懒会让自己的技术止步不前,心里不安呀。
首先明确实现逻辑,表格树状结构中所谓的树状,并不是数据结构上的树状,只是布局上的效果而已,这里要求的数据结构是平级的数据列表。
还是上篇文章中说的,后端直接返回树状结构数据。

所以这里需要对数据处理一下,将树状结构的数据转换成平级的。getTreeList 方法中调用接口先获取到后端数据。
getTreeList() { this.treeList = []; Catalog.getListByParent(this.catalogId).then((res) => { if (res.status === 0) { const treeList = res.data || []; const tableList: ITree[] = []; this.setCatalogList(tableList, treeList); this.treeList = treeList; this.tableList = tableList; } });},这里有个需要注意的点,接口返回的数据并没有直接赋给 this.treeList ,因为直接赋值之后 vue 不会一直监听数据的变化,而树状结构是需要在原有数据上修改的,例如 setCatalogList 方法中将树状结构数据转换成平级列表数据,所以定义个常量中间过渡一下,防止数据不实时更新。
setCatalogList(arr: ITree[], treeList: ITree[]) { treeList.forEach((item) => { arr.push(item); if (item.catalogTreeVoList && item.catalogTreeVoList.length > 0) { this.setCatalogList(arr, item.catalogTreeVoList); } });},因为树状列表中一些交互操作,如收起展开子节点,需要前端自定义一些字段属性用来控制,所以还需要将拿到的数据再构建一个map对象,更方面后面对自定义字段属性的操作。
将this.treeList 完善一下。
getTreeList() { this.treeList = []; Catalog.getListByParent(this.catalogId).then((res) => { if (res.status === 0) { const treeList = res.data || []; const tableList: ITree[] = []; const treeMap: ITreeMap = {}; this.setCatalogList(tableList, treeList); this.setCatalogMap(treeMap, treeList, 0); this.treeList = treeList; this.tableList = tableList; this.treeMap = treeMap; } });},setCatalogMap(catalogMap: ITreeMap , list: ITree[], level: number) { list.forEach((item, index) => { catalogMap[item.catalogId] = item; item.level = level; item.index = index; if (item.catalogTreeVoList && item.catalogTreeVoList.length > 0) { this.setCatalogMap(catalogMap, item.catalogTreeVoList, level + 1); } });},这时候打印一下treeList,tableList或者treeMap,会发现数据中已经添加了level和index字段。但这里暂时不用,后面其他操作的时候需要用的。
那对于树状列表最基础的收起展开操作,需要的字段是isOpen和isShow,我们这里规定isOpen控制展开收起图标的切换,isShow控制子节点的显示和隐藏。
这两个字段的添加还是在this.treeList 中,完整的this.treeList 代码如下:
getTreeList() { this.treeList = []; Catalog.getListByParent(this.catalogId).then((res) => { if (res.status === 0) { const treeList = res.data || []; const tableList: ITree[] = []; const treeMap: ITreeMap = {}; this.setCatalogList(tableList, treeList); this.setCatalogMap(treeMap, treeList, 0); Object.keys(treeMap).forEach((key) => { const item = treeMap[key]; if (this.treeMap[key]) { item.isOpen = this.treeMap[key].isOpen; item.isShow = this.treeMap[key].isShow; } else { item.isOpen = true; item.isShow = true; } }); this.treeList = treeList; this.tableList = tableList; this.treeMap = treeMap; } });},其实,应该先给出页面结构的代码:
ref="table" :data="tableList" :row-class-name="cellClass"> {{scope.row.catalogName}}上移 下移 编辑 删除展开收起操作的实现逻辑,就是修改isOpen的值,但是相应的判断当前节点的子节点的显示和隐藏。
当子节点存在的时候,如果是要收起当前节点,就很简单的不用管子节点的状态,全部隐藏就好。如果是要展开当前节点,isOpen为真所有子节点显示,否则隐藏。
tabNode(node: ITree) { this.treeMap[node.catalogId].isOpen = !this.treeMap[node.catalogId].isOpen; this.statusChange(node, node.isOpen);},statusChange(data: ITree, status: boolean) { if (data.catalogTreeVoList) { data.catalogTreeVoList.forEach((childEl: ITree) => { if (status) { if (data.isOpen) { childEl.isShow = true; } else { childEl.isShow = false; } } else { childEl.isShow = status; } if (childEl.catalogTreeVoList) { this.statusChange(childEl, status); } }); }},isShow控制子节点显示或隐藏,是基于Element表格自带的一个方法,这里的类名rowNone就是display: none。
cellClass({ row, rowIndex }: ITableRow) { if (!row.isShow) { return "rowNone"; }},上篇文章结尾处提到的上下移排序的问题,这里需要做相应的改变,就是用到了前文写的index字段。
上移排序的逻辑,修改当前节点和上一个节点的下标值,就是经典的基础题:a=1,b=2,交换a,b的值,这就需要定义个中间变量过渡一下。最后会发现点击上移之后,打印出来的数据修改了,但是页面并没有更新。
这是因为index字段是在构建map对象时定义的,修改index的值只会让treeList更新,而此时排序需要修改的数据是tableList,所以最后还需要重新调用 setCatalogList ,将最新的treeList转换成tableList。
doUp(node: ICatalogModel, index: number) { if (index === 0) { return; } const parentId: string = node.catalogParent as string; const parentItem: ICatalogModel = this.treeMap[parentId]; let dataList: ICatalogModel[] = []; // 如果为空则是顶级 if (parentItem) { if (parentItem.catalogTreeVoList) { dataList = parentItem.catalogTreeVoList; } } else { dataList = this.treeList; } const beforeItem = dataList[index - 1]; // a const item = dataList[index]; // b // 中间变量 let itemIndex: number = 0; // c itemIndex = item.index; // c = b item.index = beforeItem.index; // b = a beforeItem.index = itemIndex; // a = c dataList.splice(index, 1); dataList.splice(index - 1, 0, item); this.doSaveSort(dataList); const tableList: ITree[] = []; this.setCatalogList(tableList, this.treeList); this.tableList = tableList;},相应的下移操作,修改的是当前节点和下一个节点的下标值,就不赘述了。
最后效果如下(样式没咋优化):

最后的上移下移的操作只是附带的,这篇文章主要总结的还是表单树状结构的实现,磨了一下午,这算是最彻底完整的一次总结了,但其中还是存在了一些没理解透的点,如表单选中的问题,还需要多练习。 over~ 下班