Skip to content

feat(table): Table组件添加懒加载功能 #1058 #1087

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

Merged
merged 1 commit into from
Jul 20, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-vue": "^7.11.1",
"husky": "^7.0.4",
"intersection-observer": "^0.12.2",
"lint-staged": "^11.0.0",
"npm-run-all": "^4.1.5",
"stylelint": "^13.13.1",
Expand Down
37 changes: 37 additions & 0 deletions packages/devui-vue/devui/table/__tests__/table.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button } from '../../button';
import { useNamespace } from '../../shared/hooks/use-namespace';
import { nextTick, ref } from 'vue';
import { Input } from '../../input';
import 'intersection-observer';

let data: Array<Record<string, unknown>> = [];
const ns = useNamespace('table', true);
Expand Down Expand Up @@ -637,4 +638,40 @@ describe('d-table', () => {
expect(inputNew.exists()).toBeFalsy();
expect(cellItem.text()).toBe('Mark1');
});

it('table lazy mode work', async () => {
const wrapper = mount({
setup() {
const handleLoadMore = () => {

// TODO: add exception to test emit event(Jest don't have IntersectionObserver)
// The 'intersection-observer' polyfill don't work in Jest env? It's just prevent error report in DTable.
data.push({
firstName: 'loadMore',
lastName: 'loadMore',
gender: 'Female',
date: '1990/01/12',
});
};

return () => (
<DTable data={data} table-height="100px" lazy={true} onLoadMore={handleLoadMore}>
<DColumn field="firstName" header="First Name"></DColumn>
<DColumn field="lastName" header="Last Name"></DColumn>
<DColumn field="gender" header="Gender"></DColumn>
<DColumn field="date" header="Date of birth"></DColumn>
</DTable>
);
},
});

await nextTick();
await nextTick();
const table = wrapper.find(ns.b());
const lazyEle = table.find(ns.e('lazy__flag'));

// test lazyFlagElement exist
expect(lazyEle.exists()).toBeTruthy();
wrapper.unmount();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComputedRef } from 'vue';
import type { ComputedRef, Ref } from 'vue';
import { DefaultRow } from '../../table-types';
import { Column } from '../column/column-types';

Expand All @@ -21,3 +21,8 @@ export interface UseMergeCell {
export interface UseBodyRender {
getTableRowClass: (row: DefaultRow) => Record<string, unknown>;
}

export interface UseLazyLoad {
lazy: boolean;
lazyFlagRef: Ref;
}
6 changes: 6 additions & 0 deletions packages/devui-vue/devui/table/src/components/body/body.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ $input-height-md: 28px;
.is-hidden {
display: none;
}

}

&__lazy__flag {
width: 0;
height: 0;
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/devui-vue/devui/table/src/components/body/body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Column } from '../column/column-types';
import { CellClickArg, RowClickArg } from './body-types';
import TD from '../body-td/body-td';
import { useNamespace } from '../../../../shared/hooks/use-namespace';
import { useMergeCell, useBodyRender } from './use-body';
import { useMergeCell, useBodyRender, useLazyLoad } from './use-body';
import './body.scss';

export default defineComponent({
Expand All @@ -15,6 +15,7 @@ export default defineComponent({
const ns = useNamespace('table');
const { tableSpans, removeCells } = useMergeCell();
const { getTableRowClass } = useBodyRender();
const { lazy, lazyFlagRef } = useLazyLoad();
const onCellClick = (cellClickArg: CellClickArg) => {
table.emit('cell-click', cellClickArg);
};
Expand Down Expand Up @@ -59,6 +60,7 @@ export default defineComponent({
)}
</>
))}
{lazy && <span ref={lazyFlagRef} class={ns.e('lazy__flag')}></span>}
</tbody>
);
},
Expand Down
57 changes: 55 additions & 2 deletions packages/devui-vue/devui/table/src/components/body/use-body.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject, computed, Ref } from 'vue';
import { inject, computed, Ref, ref, onMounted, onBeforeUnmount } from 'vue';
import { TABLE_TOKEN, ITableInstanceAndDefaultRow } from '../../table-types';
import { useNamespace } from '../../../../shared/hooks/use-namespace';
import { UseBodyRender, UseMergeCell, CellClickArg } from './body-types';
import { UseBodyRender, UseMergeCell, CellClickArg, UseLazyLoad } from './body-types';
import { getRowIdentity } from '../../utils';

const ns = useNamespace('table');
Expand Down Expand Up @@ -87,3 +87,56 @@ export function useBodyRender(): UseBodyRender {

return { getTableRowClass };
}

export function useLazyLoad(): UseLazyLoad {
const table = inject(TABLE_TOKEN) as ITableInstanceAndDefaultRow;
const { lazy } = table.props;

// set an empty tag in the bottom of tbody. when the tbody scroll to bottom. The empty tag's intersectionObserver will be triggered.
const lazyFlagRef = ref();
let lazyObserver: IntersectionObserver;

onMounted(() => {

// if lazy mode is turn on. It'll observe an empty tag to determine whether the bottom has been reached.
if (lazy) {

// when the tbody reached bottom(because the lazyFlagElement is at the bottom of tbody), the observe's callback will be triggered
lazyObserver = new IntersectionObserver(
(entries) => {

// Not support IE
const lazyFlagOb = entries[0];

// isIntersecting is true ==> the lazyFlagElement is in viewport
if (lazyFlagOb.isIntersecting) {

// exec user props.loadMore to load more data
// loadMore();
table.emit('load-more');
}
},
{

// only fired in the table scroll event
root: table.tableRef.value,
}
);
lazyObserver.observe(lazyFlagRef.value);
}
});

onBeforeUnmount(() => {

// unload intersectionObserver
if (lazy) {
lazyObserver.unobserve(lazyFlagRef.value);
lazyObserver.disconnect();
}
});

return {
lazy,
lazyFlagRef,
};
}
4 changes: 4 additions & 0 deletions packages/devui-vue/devui/table/src/table-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export const tableProps = {
type: Number,
default: 16,
},
lazy: {
type: Boolean,
default: false,
},
};

export type TableProps = ExtractPropTypes<typeof tableProps>;
Expand Down
2 changes: 1 addition & 1 deletion packages/devui-vue/devui/table/src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineComponent({
dLoading: Loading,
},
props: tableProps,
emits: ['sort-change', 'cell-click', 'row-click', 'check-change', 'check-all-change', 'expand-change'],
emits: ['sort-change', 'cell-click', 'row-click', 'check-change', 'check-all-change', 'expand-change', 'load-more'],
setup(props: TableProps, ctx) {
const table = getCurrentInstance() as ITableInstanceAndDefaultRow;
const store = createStore(toRef(props, 'data'), table);
Expand Down
120 changes: 119 additions & 1 deletion packages/devui-vue/docs/components/table/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,123 @@ export default defineComponent({

:::

### 懒加载

:::demo 使用lazy启用懒加载,当滚动表格底部时到触发loadMore事件实现懒加载。

```vue
<template>
<d-table :data="dataSource" table-height="200px" :show-loading="showLoading" :lazy="true" @load-more="loadMore">
<d-column field="firstName" header="First Name"></d-column>
<d-column field="lastName" header="Last Name"></d-column>
<d-column field="gender" header="Gender"></d-column>
<d-column field="date" header="Date of birth"></d-column>
</d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
setup() {
const showLoading = ref(false)
const dataSource = ref([
{
firstName: 'diy0',
lastName: 'Otto',
date: '1990/01/11',
gender: 'Male',
},
{
firstName: 'diy1',
lastName: 'Otto',
date: '1990/01/11',
gender: 'Male',
},
{
firstName: 'diy2',
lastName: 'Thornton',
gender: 'Female',
date: '1990/01/12',
},
{
firstName: 'diy3',
lastName: 'Chen',
gender: 'Male',
date: '1990/01/13',
},
{
firstName: 'diy4',
lastName: 'gerong',
gender: 'Male',
date: '1990/01/14',
},
{
firstName: 'diy5',
lastName: 'lang',
gender: 'Male',
date: '1990/01/14',
},
{
firstName: 'diy6',
lastName: 'li',
gender: 'Male',
date: '1990/01/14',
},
{
firstName: 'diy7',
lastName: 'li',
gender: 'Female',
date: '1990/01/14',
},
{
firstName: 'diy8',
lastName: 'Yu',
gender: 'Female',
date: '1990/01/14',
},
{
firstName: 'diy9',
lastName: 'Yu',
gender: 'Female',
date: '1990/01/14',
},
]);

let total = 100

const loadMore = () => {
if (dataSource.value.length >= total || showLoading.value) {
return
}

showLoading.value = true
const moreData = []
const size = dataSource.value.length
for (let i = 0; i < 10; i++) {
moreData.push({
firstName: 'diy' + (i + size),
lastName: 'more data',
gender: 'Female',
date: '2022/07/20'
})
}

// mock ajax
setTimeout(() => {
showLoading.value = false
dataSource.value = dataSource.value.concat(moreData)
}, 200)
}

return { dataSource, loadMore, showLoading };
},
});
</script>
```

:::

### Table 参数

| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo |
Expand All @@ -1389,7 +1506,7 @@ export default defineComponent({
| show-header | `boolean` | true | 可选,配置是否显示表头 | [表格样式](#表格样式) |
| row-key | `string \| Function(item, index: number): string` | -- | 可选,行数据的 Key,用来优化 Table 渲染,类型为 string 时,支持多层访问:`item.user.id`,但不支持 `item.user[0].id`,此种情况请使用 Function。 | [表格交互(Function)](#表格交互) <br> [展开行(string)](#展开行) |
| indent | `number` | 16 | 可选,展示树形数据时,树节点的缩进 | [树形表格](#树形表格) |

| lazy | `boolean` | false | 可选,是否懒加载数据(搭配loadMore使用) | [懒加载](#懒加载) |
### Table 事件

| 事件名 | 回调参数 | 说明 | 跳转 Demo |
Expand All @@ -1399,6 +1516,7 @@ export default defineComponent({
| check-change | `Function(checked: boolean, row, selection)` | 勾选表格行回调事件,返回该行信息和表格所有选中行数据 | [表格交互](#表格交互) |
| check-all-change | `Function(checked: boolean, selection)` | 全选表格行回调事件,返回勾选状态和表格所有选中行数据 | [表格交互](#表格交互) |
| row-click | `Function(obj: RowClickArg)` | 某一行被点击时触发该事件,返回该行信息 | [表格交互](#表格交互) |
| load-more | `Function()` | 滚动到表格底部触发懒加载事件(需配合props.lazy开启) | [懒加载](#懒加载) |

### Table 方法

Expand Down
Loading