Skip to content

Commit

Permalink
树形结构支持懒加载 (#1128)
Browse files Browse the repository at this point in the history
* feat(table): 树形结构,支持懒加载

* fix(table): add missing code

* feat(table): update snapshots

* fix(table): use lastFullRow insteadOf $lastFullRow
  • Loading branch information
chaishi authored Jul 6, 2022
1 parent 87deafd commit 874ff13
Show file tree
Hide file tree
Showing 7 changed files with 484 additions and 56 deletions.
122 changes: 103 additions & 19 deletions examples/table/demos/tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
:data="data"
:columns="columns"
:tree="{ childrenKey: 'list', treeNodeColumnIndex: 2 }"
:tree-expand-and-fold-icon="customTreeExpandAndFoldIcon ? treeExpandAndFoldIconRender : undefined"
:treeExpandAndFoldIcon="treeExpandIcon"
:pagination="pagination"
:beforeDragSort="beforeDragSort"
@page-change="onPageChange"
@abnormal-drag-sort="onAbnormalDragSort"
@drag-sort="onDragSort"
@tree-expand-change="onTreeExpandChange"
></t-enhanced-table>

<!-- 第二列展开树结点,缩进为 12px,示例代码有效,勿删 -->
Expand All @@ -51,27 +52,33 @@
</div>
</template>
<script lang="jsx">
import { EnhancedTable } from 'tdesign-vue';
import { ChevronRightIcon, ChevronDownIcon, MoveIcon } from 'tdesign-icons-vue';
import { MessagePlugin, EnhancedTable, Loading } from 'tdesign-vue';
import {
ChevronRightIcon, ChevronDownIcon, MoveIcon, AddRectangleIcon, MinusRectangleIcon,
} from 'tdesign-icons-vue';
const TOTAL = 5;
function getObject(i, currentPage) {
return {
id: i,
key: `我是 ${i}_${currentPage} 号`,
platform: i % 2 === 0 ? '共有' : '私有',
type: ['String', 'Number', 'Array', 'Object'][i % 4],
default: ['-', '0', '[]', '{}'][i % 4],
detail: {
position: `读取 ${i} 个数据的嵌套信息值`,
},
needed: i % 4 === 0 ? '是' : '否',
description: '数据源',
};
}
function getData(currentPage = 1) {
const data = [];
// const pageInfo = `第 ${currentPage} 页`;
for (let i = 0; i < TOTAL; i++) {
const obj = {
id: i,
key: `我是 ${i}_${currentPage} 号`,
platform: i % 2 === 0 ? '共有' : '私有',
type: ['String', 'Number', 'Array', 'Object'][i % 4],
default: ['-', '0', '[]', '{}'][i % 4],
detail: {
position: `读取 ${i} 个数据的嵌套信息值`,
},
needed: i % 4 === 0 ? '是' : '否',
description: '数据源',
};
const obj = getObject(i, currentPage);
// 第一行不设置子节点
obj.list = i === 0
? []
Expand All @@ -94,6 +101,20 @@ function getData(currentPage = 1) {
});
data.push(obj);
}
// 懒加载1
data.push({
...getObject(66666, currentPage),
/** 如果子节点为懒加载,则初始值设置为 true */
list: true,
key: '我是懒加载节点 66666,点我体验',
});
// 懒加载2
data.push({
...getObject(88888, currentPage),
/** 如果子节点为懒加载,则初始值设置为 true */
list: true,
key: '我是懒加载节点 88888,点我体验 ',
});
return data;
}
Expand All @@ -105,6 +126,7 @@ export default {
return {
customTreeExpandAndFoldIcon: false,
data,
lazyLoadingData: null,
expandAll: false,
pagination: {
current: 1,
Expand Down Expand Up @@ -183,6 +205,17 @@ export default {
};
},
computed: {
// 可以使用同名插槽代替渲染函数:<template #treeExpandAndFoldIcon><icon /></template>
treeExpandIcon() {
// 懒加载图标渲染
if (this.lazyLoadingData) return this.lazyLoadingTreeIconRender;
// 自定义展开图标
if (this.customTreeExpandAndFoldIcon) return this.treeExpandAndFoldIconRender;
return undefined;
},
},

// 默认展开全部。示例代码有效,勿删
// mounted() {
// this.$refs.table.expandAll();
Expand Down Expand Up @@ -222,14 +255,38 @@ export default {

// 新增子节点
appendTo(row) {
const randomKey = Math.round(Math.random() * Math.random() * 1000) + 10000;
const randomKey1 = Math.round(Math.random() * Math.random() * 1000) + 10000;
this.$refs.table.appendTo(row.key, {
id: randomKey,
key: `我是 ${randomKey} 号`,
id: randomKey1,
key: `我是 ${randomKey1} 号`,
platform: '私有',
type: 'Number',
});
this.$message.success(`已插入子节点我是 ${randomKey} 号,请展开查看`);
this.$message.success(`已插入子节点我是 ${randomKey1} 号,请展开查看`);

// 一次性添加多个子节点。示例代码有效,勿删!!!
// this.appendMultipleDataTo(row);
},

appendMultipleDataTo(row) {
const randomKey1 = Math.round(Math.random() * Math.random() * 1000) + 10000;
const randomKey2 = Math.round(Math.random() * Math.random() * 1000) + 10000;
const newData = [
{
id: randomKey1,
key: `我是 ${randomKey1} 号`,
platform: '私有',
type: 'Number',
},
{
id: randomKey2,
key: `我是 ${randomKey2} 号`,
platform: '私有',
type: 'Number',
},
];
this.$refs.table.appendTo(row.key, newData);
MessagePlugin.success(`已插入子节点我是 ${randomKey1} 和 ${randomKey2} 号,请展开查看`);
},

// 当前节点之前,新增兄弟节前
Expand Down Expand Up @@ -278,6 +335,33 @@ export default {
return type === 'expand' ? <ChevronRightIcon /> : <ChevronDownIcon />;
},
// 懒加载图标渲染
lazyLoadingTreeIconRender(h, params) {
const { type, row } = params;
if (this.lazyLoadingData?.id === row?.id) {
return <Loading size="14px" />;
}
return type === 'expand' ? <AddRectangleIcon /> : <MinusRectangleIcon />;
},
onTreeExpandChange(context) {
console.log(context.rowState.expanded ? '展开' : '收起', context);
/**
* 如果是懒加载,请确认自己完成了以下几个步骤
* 1. 提前设置 children 值为 true;
* 2. 在 onTreeExpandChange 事件中处理异步数据;
* 3. 自定义展开图标渲染 lazyLoadingTreeIconRender
*/
if (context.row.list === true) {
this.lazyLoadingData = context.row;
const timer = setTimeout(() => {
this.appendMultipleDataTo(context.row);
this.lazyLoadingData = null;
clearTimeout(timer);
}, 200);
}
},
getTreeNode() {
const treeData = this.$refs.table.getTreeNode();
console.log(treeData);
Expand Down
2 changes: 1 addition & 1 deletion src/table/editable-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export default defineComponent({
row,
(row) => {
let val = get(row, col.value.colKey);
if (typeof val === 'object') {
if (typeof val === 'object' && val !== null) {
val = val instanceof Array ? [...val] : { ...val };
}
editValue.value = val;
Expand Down
75 changes: 50 additions & 25 deletions src/table/hooks/tree-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class TableTreeStore<T extends TableRowData = TableRowData> {
log.error('EnhancedTable', '`rowKey` could be wrong, can not get rowValue from `data` by `rowKey`.');
return [];
}
const childrenNodes = get(p.row, keys.childrenKey);
// childrenNodes = true,表示懒加载,直接返回,暂时不做展开处理
if (childrenNodes === true) return dataSource;
const r = this.treeDataMap.get(rowValue);
r.rowIndex = p.rowIndex;
r.expanded = !r.expanded;
Expand Down Expand Up @@ -195,52 +198,73 @@ class TableTreeStore<T extends TableRowData = TableRowData> {
}

/**
* 为当前节点添加子节点,默认添加到最后一个节点
* 为当前节点添加子节点,默认添加到最后一个节点。允许添加单个或多个
* @param rowValue 当前节点唯一标识
* @param newData 待添加的新节点
*/
appendTo(rowValue: string | number, newData: T, dataSource: T[], keys: KeysType): T[] {
appendTo(rowValue: string | number, newData: T | T[], dataSource: T[], keys: KeysType): T[] {
const state = this.treeDataMap.get(rowValue);
if (!this.validateDataExist(state, rowValue)) return dataSource;
const newRowValue = get(newData, keys.rowKey);
const mapState = this.treeDataMap.get(newRowValue);
if (!this.validateDataDoubleExist(mapState, newRowValue)) return dataSource;
const children: T[] = get(state.row, keys.childrenKey);
// 子节点不存在,则表示为叶子节点
const isShowNewNode = state.expanded || !children?.length;
const rowIndex = isShowNewNode ? state.rowIndex + (state.expandChildrenLength || 0) + 1 : -1;
const newState = {
id: newRowValue,
row: newData,
rowIndex,
level: state.level + 1,
expanded: false,
expandChildrenLength: 0,
disabled: false,
path: [...state.path],
parent: state,
};
newState.path = newState.path.concat(newState);
// 添加多个子节点时,需去除重复子节点
const tmpData = newData instanceof Array ? newData : [newData];
const newChildrenData: T[] = [];
const newChildrenStates: TableRowState[] = [];
let firstNewChildrenIndex = -1;
for (let i = 0, len = tmpData.length; i < len; i++) {
const oneData = tmpData[i];
const newRowValue = get(oneData, keys.rowKey);
const mapState = this.treeDataMap.get(newRowValue);
if (!this.validateDataDoubleExist(mapState, newRowValue)) {
log.warn('Table', `Duplicated Data \`${newRowValue}\` has been removed.`);
} else {
const rowIndex = isShowNewNode ? state.rowIndex + (state.expandChildrenLength || 0) + (i + 1) : -1;
if (i === 0) {
firstNewChildrenIndex = rowIndex;
}
const newState = {
id: newRowValue,
row: oneData,
rowIndex,
level: state.level + 1,
expanded: false,
expandChildrenLength: 0,
disabled: false,
path: [...state.path],
parent: state,
};
newState.path = newState.path.concat(newState);
newChildrenData.push(oneData);
newChildrenStates.push(newState);
this.treeDataMap.set(newRowValue, newState);
}
}
if (!newChildrenData.length) return dataSource;

if (children?.length) {
state.row[keys.childrenKey].push(newData);
state.row[keys.childrenKey] = state.row[keys.childrenKey].concat(newChildrenData);
} else {
state.row[keys.childrenKey] = [newData];
state.row[keys.childrenKey] = newChildrenData;
state.expanded = true;
}
this.treeDataMap.set(newRowValue, newState);

// 如果当前节点为展开状态,则需要继续处理
if (isShowNewNode) {
dataSource.splice(newState.rowIndex, 0, newData);
dataSource.splice(firstNewChildrenIndex, 0, ...newChildrenData);
// 更新父元素及祖先元素展开子节点的数量
updateRowExpandLength(this.treeDataMap, state.row, 1, 'insert', {
const newChildrenCount = newChildrenData.length || 1;
updateRowExpandLength(this.treeDataMap, state.row, newChildrenCount, 'insert', {
rowKey: keys.rowKey,
childrenKey: keys.childrenKey,
});
// 更新 rowIndex 之后的下标
updateRowIndex(this.treeDataMap, dataSource, {
minRowIndex: newState.rowIndex,
minRowIndex: firstNewChildrenIndex,
rowKey: keys.rowKey,
type: 'add',
count: newChildrenData.length,
});
}

Expand Down Expand Up @@ -682,6 +706,7 @@ export function updateRowIndex<T>(
minRowIndex?: number;
maxRowIndex?: number;
type?: 'add' | 'remove';
count?: number;
},
) {
const start = extra.minRowIndex || 0;
Expand All @@ -692,6 +717,6 @@ export function updateRowIndex<T>(
if (!state) {
log.warn('Table', 'tree map went wrong');
}
state.rowIndex = rowIndex;
state.rowIndex = rowIndex + (extra?.count || 1) - 1;
}
}
25 changes: 15 additions & 10 deletions src/table/hooks/useTreeData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import get from 'lodash/get';
import { CreateElement } from 'vue';
import TableTreeStore, { SwapParams } from './tree-store';
import {
TdEnhancedTableProps, PrimaryTableCol, TableRowData, TableRowValue, TableRowState,
TdEnhancedTableProps,
PrimaryTableCol,
TableRowData,
TableRowValue,
TableRowState,
PrimaryTableCellParams,
} from '../type';
import useClassName from './useClassName';
import { renderCell } from '../tr';
Expand Down Expand Up @@ -35,17 +40,17 @@ export default function useTreeData(props: TdEnhancedTableProps, context: SetupC
store.value.updateDisabledState(dataSource.value, column, rowDataKeys.value);
});

function getFoldIcon(h: CreateElement) {
const params = { type: 'fold' };
function getFoldIcon(h: CreateElement, context: PrimaryTableCellParams<TableRowData>) {
const params = { ...context, type: 'fold' };
const defaultFoldIcon = t(global.value.treeExpandAndFoldIcon, h, params) || <MinusRectangleIcon />;
return renderTNode('treeExpandAndFoldIcon', {
defaultNode: defaultFoldIcon,
params,
});
}

function getExpandIcon(h: CreateElement) {
const params = { type: 'expand' };
function getExpandIcon(h: CreateElement, context: PrimaryTableCellParams<TableRowData>) {
const params = { ...context, type: 'expand' };
const defaultExpandIcon = t(global.value.treeExpandAndFoldIcon, h, params) || <AddRectangleIcon />;
return renderTNode('treeExpandAndFoldIcon', {
defaultNode: defaultExpandIcon,
Expand Down Expand Up @@ -140,13 +145,13 @@ export default function useTreeData(props: TdEnhancedTableProps, context: SetupC
const colStyle = getTreeNodeStyle(currentState?.level);
const classes = { [tableTreeClasses.inlineCol]: !!col.ellipsis };
const childrenNodes = get(p.row, rowDataKeys.value.childrenKey);
if (childrenNodes && childrenNodes instanceof Array) {
if ((childrenNodes && childrenNodes instanceof Array) || childrenNodes === true) {
const iconNode = store.value.treeDataMap.get(get(p.row, rowDataKeys.value.rowKey))?.expanded
? getFoldIcon(h)
: getExpandIcon(h);
? getFoldIcon(h, p)
: getExpandIcon(h, p);
return (
<div class={[tableTreeClasses.col, classes]} style={colStyle}>
{!!childrenNodes.length && (
{!!(childrenNodes.length || childrenNodes === true) && (
<span class={tableTreeClasses.icon} onClick={() => toggleExpandData(p, 'expand-fold-icon')}>
{iconNode}
</span>
Expand Down Expand Up @@ -203,7 +208,7 @@ export default function useTreeData(props: TdEnhancedTableProps, context: SetupC
* @param key 当前节点唯一标识
* @param newData 待添加的新节点
*/
function appendTo<T>(key: TableRowValue = '', newData: T) {
function appendTo<T>(key: TableRowValue = '', newData: T | T[]) {
if (!key) {
dataSource.value = store.value.appendToRoot(newData, dataSource.value, rowDataKeys.value);
return;
Expand Down
2 changes: 1 addition & 1 deletion src/table/tbody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export default defineComponent({
trNodeList,
getFullRow(h, columnLength, 'last-full-row'),
];
const isEmpty = !this.data?.length && !this.loading && !this.firstFullRow && !this.$lastFullRow;
const isEmpty = !this.data?.length && !this.loading && !this.firstFullRow && !this.lastFullRow;

const translate = `translate(0, ${this.translateY}px)`;
const posStyle = {
Expand Down
Loading

0 comments on commit 874ff13

Please sign in to comment.