Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

树形结构支持懒加载 #1128

Merged
merged 4 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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