Skip to content

Commit

Permalink
feat(Table): support rows highlight (#3442)
Browse files Browse the repository at this point in the history
* feat(Table): support row highlight

* chore: update snapshot

* chore: update snapshot

* chore: update snapshot
  • Loading branch information
uyarn authored Dec 27, 2024
1 parent 6b99c82 commit 0bfd0e7
Show file tree
Hide file tree
Showing 13 changed files with 853 additions and 40 deletions.
3 changes: 2 additions & 1 deletion src/table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

因此,表格组件有三个:`BaseTable`(基础表格)、`PrimaryTable`(主表格)、`EnhancedTable`(增强型表格),三种表格都会导出。默认导出 `PrimaryTable`

- `BaseTable`(基础表格)包含一些基础功能:固定表头、固定列、冻结行、加载态、分页、多级表头、合并单元格、自定义单元格、自定义表头、自定义表尾、文本省略、对齐方式、表格事件、尺寸、行类名、边框、斑马线、悬浮态、空数据等
- `BaseTable`(基础表格)包含一些基础功能:行高亮、固定表头、固定列、冻结行、加载态、分页、多级表头、合并单元格、自定义单元格、自定义表头、自定义表尾、文本省略、对齐方式、表格事件、尺寸、行类名、边框、斑马线、悬浮态、空数据等
- `PrimaryTable``Table`(主表格)包含一些更高级的功能:行展开/收起、过滤、排序、异步加载、拖拽排序等。`PrimaryTable``Table` 包含 `BaseTable` 的所有功能。`Table``PrimaryTable` 完全等价。
- `EnhancedTable`(增强型表格)包含一些更复杂的功能:树形结构等。`EnhancedTable` 包含 `BaseTable``PrimaryTable` 的所有功能

Expand All @@ -22,6 +22,7 @@
- hooks/useMultiHeader 多级表头,BaseTable
- hooks/usePagination 分页,BaseTable
- hooks/useLazyLoad 懒加载,BaseTable
- hooks/useRowHighlight 行高亮,BaseTable

- hooks/useAsyncLoading 异步加载功能,PrimaryTable
- hooks/useColumnController 自定义列配置,PrimaryTable
Expand Down
12 changes: 6 additions & 6 deletions src/table/__tests__/__snapshots__/vitest-table.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`BaseTable Component > props.showHeader: BaseTable contains element \`thead\` 1`] = `
<div
class="t-table"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down Expand Up @@ -205,7 +205,7 @@ exports[`BaseTable Component > props.showHeader: BaseTable contains element \`th
exports[`BaseTable Component > props.size is equal to large 1`] = `
<div
class="t-table t-size-l"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down Expand Up @@ -407,7 +407,7 @@ exports[`BaseTable Component > props.size is equal to large 1`] = `
exports[`BaseTable Component > props.size is equal to medium 1`] = `
<div
class="t-table"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down Expand Up @@ -609,7 +609,7 @@ exports[`BaseTable Component > props.size is equal to medium 1`] = `
exports[`BaseTable Component > props.size is equal to small 1`] = `
<div
class="t-table t-size-s"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down Expand Up @@ -811,7 +811,7 @@ exports[`BaseTable Component > props.size is equal to small 1`] = `
exports[`BaseTable Component > props.tableLayout is equal to auto 1`] = `
<div
class="t-table"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down Expand Up @@ -1007,7 +1007,7 @@ exports[`BaseTable Component > props.tableLayout is equal to auto 1`] = `
exports[`BaseTable Component > props.tableLayout is equal to fixed 1`] = `
<div
class="t-table"
style="position: relative;"
tabindex="0"
>
<div
class="t-table__content"
Expand Down
69 changes: 69 additions & 0 deletions src/table/_example-composition/highlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<t-space direction="vertical">
<t-space align="center">
<t-radio-group v-model="activeRowType" variant="default-filled">
<t-radio-button value="">不高亮</t-radio-button>
<t-radio-button value="single">单行高亮</t-radio-button>
<t-radio-button value="multiple">多行高亮</t-radio-button>
</t-radio-group>
<t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox>
</t-space>

<!-- v-model:activeRowKeys 父组件控制高亮行 -->
<!-- defaultActiveRowKeys 组件内部控制高亮行,父组件无法使用这个属性控制 -->
<t-table
row-key="key"
:data="tableData"
:columns="columns"
:active-row-type="activeRowType"
:hover="hover"
@active-change="onActiveChange"
></t-table>
</t-space>
</template>

<script></script>

<script setup>
import { ref, watch } from 'vue';
export default {
name: 'HighlightTable',
};
const activeRowType = ref('single');
const hover = ref(false);
const tableData = getTableData();
const columns = [
{ colKey: 'applicant', title: '申请人', width: '100' },
{ colKey: 'channel', title: '签署方式' },
{ colKey: 'detail.email', title: '邮箱地址', ellipsis: true },
{ colKey: 'createTime', title: '申请时间' },
];
const onActiveChange = (highlightRowKeys, ctx) => {
console.log(highlightRowKeys, ctx);
};
watch([activeRowType], ([activeRowType]) => {
if (!activeRowType) {
hover.value = true;
}
});
function getTableData(total = 5) {
const data = [];
for (let i = 0; i < total; i++) {
data.push({
key: i + 1,
applicant: ['贾明', '张三', '王芳'][i % 3],
status: i % 3,
channel: ['电子签署', '纸质签署', '纸质签署'][i % 3],
detail: {
email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3],
},
matters: ['宣传物料制作费用', 'algolia 服务报销', '相关周边制作费', '激励奖品快递费'][i % 4],
time: [2, 3, 1, 4][i % 4],
createTime: ['2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01'][i % 4],
});
}
return data;
}
</script>
74 changes: 74 additions & 0 deletions src/table/_example/highlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<template>
<t-space direction="vertical">
<t-space align="center">
<t-radio-group v-model="activeRowType" variant="default-filled">
<t-radio-button value="">不高亮</t-radio-button>
<t-radio-button value="single">单行高亮</t-radio-button>
<t-radio-button value="multiple">多行高亮</t-radio-button>
</t-radio-group>
<t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox>
</t-space>

<!-- v-model:activeRowKeys 父组件控制高亮行 -->
<!-- defaultActiveRowKeys 组件内部控制高亮行,父组件无法使用这个属性控制 -->
<t-table
row-key="key"
:data="tableData"
:columns="columns"
:active-row-type="activeRowType"
:hover="hover"
@active-change="onActiveChange"
></t-table>
</t-space>
</template>

<script>
export default {
data() {
return {
hover: false,
tableData: [],
activeRowType: 'single',
columns: [
{ colKey: 'applicant', title: '申请人', width: '100' },
{ colKey: 'channel', title: '签署方式' },
{ colKey: 'detail.email', title: '邮箱地址', ellipsis: true },
{ colKey: 'createTime', title: '申请时间' },
],
};
},
watch: {
activeRowType(v) {
if (!v) {
this.hover = true;
}
},
},
mounted() {
this.tableData = this.getTableData();
},
methods: {
getTableData(total = 5) {
const data = [];
for (let i = 0; i < total; i++) {
data.push({
key: i + 1,
applicant: ['贾明', '张三', '王芳'][i % 3],
status: i % 3,
channel: ['电子签署', '纸质签署', '纸质签署'][i % 3],
detail: {
email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3],
},
matters: ['宣传物料制作费用', 'algolia 服务报销', '相关周边制作费', '激励奖品快递费'][i % 4],
time: [2, 3, 1, 4][i % 4],
createTime: ['2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01'][i % 4],
});
}
return data;
},
onActiveChange(highlightRowKeys, ctx) {
console.log(highlightRowKeys, ctx);
},
},
};
</script>
49 changes: 48 additions & 1 deletion src/table/base-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { renderTNodeJSX, useElementLazyRender } from '../hooks';
import useStyle, { formatCSSUnit } from './hooks/useStyle';
import useClassName from './hooks/useClassName';
import { useConfig } from '../config-provider/useConfig';
import { useRowHighlight } from './hooks/useRowHighlight';
import useHoverKeyboardEvent from './hooks/useHoverKeyboardEvent';

import { Affix } from '../affix';
import { ROW_LISTENERS } from './tr';
import THead from './thead';
Expand Down Expand Up @@ -157,6 +160,7 @@ export default defineComponent({
[tableColFixedClasses.leftShadow]: showColumnShadow.left,
[tableColFixedClasses.rightShadow]: showColumnShadow.right,
[tableBaseClass.columnResizableTable]: props.resizable,
[`${classPrefix}-table__row--active-${props.activeRowType}`]: props.activeRowType,
},
]);

Expand All @@ -177,6 +181,19 @@ export default defineComponent({

const columnResizable = computed(() => props.allowResizeColumnWidth ?? props.resizable);

// 行高亮
const {
tActiveRow, onHighlightRow, addHighlightKeyboardListener, removeHighlightKeyboardListener,
} = useRowHighlight(props, tableRef);

const {
hoverRow,
needKeyboardRowHover,
clearHoverRow,
addRowHoverKeyboardListener,
removeRowHoverKeyboardListener,
} = useHoverKeyboardEvent(props, tableRef);

watch(tableElmRef, () => {
setUseFixedTableElmRef(tableElmRef.value);
});
Expand Down Expand Up @@ -276,6 +293,22 @@ export default defineComponent({
addTableResizeObserver(tableRef.value);
});

const onTableFocus = () => {
props.activeRowType && addHighlightKeyboardListener();
needKeyboardRowHover.value && addRowHoverKeyboardListener();
};

const onTableBlur = () => {
props.activeRowType && removeHighlightKeyboardListener();
needKeyboardRowHover.value && removeRowHoverKeyboardListener();
};

const onInnerRowClick: BaseTableProps['onRowClick'] = (ctx) => {
props.onRowClick?.(ctx);
props.activeRowType && onHighlightRow(ctx);
needKeyboardRowHover.value && clearHoverRow();
};

const tableData = computed(() => (isPaginateData.value ? dataSource.value : props.data));

const scrollToElement = (params: ComponentScrollToElementParams) => {
Expand Down Expand Up @@ -349,6 +382,8 @@ export default defineComponent({
horizontalScrollbarRef,
tableBodyRef,
showAffixPagination,
tActiveRow,
hoverRow,
showElement,
getListener,
renderPagination,
Expand All @@ -358,6 +393,9 @@ export default defineComponent({
refreshTable,
onInnerVirtualScroll,
scrollColumnIntoView,
onTableFocus,
onTableBlur,
onInnerRowClick,
paginationAffixRef,
horizontalScrollAffixRef,
headerTopAffixRef,
Expand Down Expand Up @@ -576,6 +614,9 @@ export default defineComponent({
// 内部使用分页信息必须取 innerPagination
pagination: this.innerPagination,
attach: this.attach,
hoverRow: this.hoverRow,
activeRow: this.tActiveRow,
onRowClick: this.onInnerRowClick,
};
// Vue3 do not need getListener
const tBodyListener = this.getListener();
Expand Down Expand Up @@ -650,7 +691,13 @@ export default defineComponent({
);

return (
<div ref="tableRef" class={this.dynamicBaseTableClasses} style="position: relative">
<div
ref="tableRef"
tabindex="0"
class={this.dynamicBaseTableClasses}
onFocus={this.onTableFocus}
onBlur={this.onTableBlur}
>
{!!topContent && <div class={this.tableBaseClass.topContent}>{topContent}</div>}

{this.renderAffixedHeader(columns)}
Expand Down
77 changes: 77 additions & 0 deletions src/table/hooks/useHoverKeyboardEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
toRefs, Ref, ref, computed,
} from '@vue/composition-api';
import get from 'lodash/get';
import { BaseTableProps } from '../interface';
import { on, off } from '../../utils/dom';
import { ARROW_DOWN_REG, ARROW_UP_REG, SPACE_REG } from '../../_common/js/common';
import { RowEventContext, TableRowData } from '../type';

/**
* 需要进行表格行操作时,则需要键盘操作的悬浮效果来表达当前的哪一行
* 如:高亮多行、行选中、行展开等功能
*/
export function useHoverKeyboardEvent(props: BaseTableProps, tableRef: Ref<HTMLDivElement>) {
const {
hover, data, activeRowType, keyboardRowHover,
} = toRefs(props);
const hoverRow = ref<string | number>();
const currentHoverRowIndex = ref(-1);

// 单行高亮场景,不需要键盘悬浮效果
const needKeyboardRowHover = computed(() => {
if (activeRowType.value === 'single') return false;
if (activeRowType.value === 'multiple') return true;
return hover.value || keyboardRowHover.value;
});

const onHoverRow = (ctx: RowEventContext<TableRowData>, extra?: { action?: 'hover' }) => {
const rowValue = get(ctx.row, props.rowKey);
if (hoverRow.value === rowValue && extra?.action !== 'hover') {
hoverRow.value = undefined;
} else {
hoverRow.value = rowValue;
}
currentHoverRowIndex.value = ctx.index;
};

const clearHoverRow = () => {
hoverRow.value = undefined;
currentHoverRowIndex.value = -1;
};

const keyboardDownListener = (e: KeyboardEvent) => {
if (!needKeyboardRowHover.value) return;
const code = e.key?.trim() || e.code;
if (ARROW_DOWN_REG.test(code)) {
e.preventDefault();
const index = Math.min(data.value.length - 1, currentHoverRowIndex.value + 1);
onHoverRow({ row: data.value[index], index, e }, { action: 'hover' });
} else if (ARROW_UP_REG.test(code)) {
e.preventDefault();
const index = Math.max(0, currentHoverRowIndex.value - 1);
onHoverRow({ row: data.value[index], index, e }, { action: 'hover' });
} else if (SPACE_REG.test(code) && props.activeRowType !== 'multiple') {
const index = currentHoverRowIndex.value;
onHoverRow({ row: data.value[index], index, e });
}
};

const addRowHoverKeyboardListener = () => {
on(tableRef.value, 'keydown', keyboardDownListener);
};

const removeRowHoverKeyboardListener = () => {
off(tableRef.value, 'keydown', keyboardDownListener);
};

return {
hoverRow,
needKeyboardRowHover,
clearHoverRow,
addRowHoverKeyboardListener,
removeRowHoverKeyboardListener,
};
}

export default useHoverKeyboardEvent;
Loading

0 comments on commit 0bfd0e7

Please sign in to comment.