1、版本
虚拟表格组件要求组件库最低版本为2.2.0 虚拟表格
2、了解主要属性、API
| Table属性 | 说明 | 默认值 |
|---|---|---|
| header-height | Header 的高度由height设置。 如果传入数组,它会使 header row 等于数组长度 | 50 |
| row-height | 每行的高度, 用于计算表的总高度 | 50 |
| columns | 列 column 的配置数组 | - |
| data | 要在表中渲染的数据数组 | - |
| fixed | 单元格宽度是自适应还是固定 | false |
| width | 表格宽度 | - |
| height | 表格高度 | - |
| column属性 | 说明 | 默认值 |
|---|---|---|
| align | 表格单元格内容对齐方式 | left |
| hidden | 此列是否不可见 | - |
| style | 自定义列单元格的类名,将会与 gird 单元格合并 | - |
| title | Header 头部单元格中的默认文本 | - |
| width | 列宽度 | - |
| cellRenderer | 自定义单元格渲染器 | - |
| headerCellRenderer | 自定义头部渲染器 | - |
| 插槽 | 参数 |
|---|---|
| row | RowSlotProps |
| cell | CellSlotProps |
3、使用
eg1:
<te-table-v2
v-if="!loading"
fixed
stripe
:row-height="40"
:header-height="40"
:columns="columns"
:data="dataSource"
:width="width"
:height="height"
>
<template #row="props">
<Row v-bind="props" />
</template>
</te-table-v2>
列渲染
/**
* 列
*/
const columns = ref<Column<any>[]>([
{
key: 'text',
dataKey: 'text',
title: '测试文本',
width: 90,
cellRenderer: ({ cellData: text }: any) => `测试-${text}`,
rowSpan: ({ rowIndex }: any) => {
const row = dataSource.value[rowIndex];
const firstIndex = dataSource.value?.findIndex((item) => item.month === row.month);
return firstIndex === rowIndex ? monthCountMap.value.get(row.month + '') ?? 1 : 0;
},
},
{
key: 'name',
dataKey: 'name',
title: '测试',
hidden: currentType.value,
width: 400,
cellRenderer: ({ cellData: name, rowData: row }: any) =>
editing.value
? h(
ElSelect,
{
modelValue: row.id,
clearable: true,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.id = value;
},
onChange: () => handleInputChange(),
},
() =>
list.value?.map((item) =>
h(ElOption, {
value: item.id,
label: item.name,
}),
) ?? [],
)
: h(
'span',
{},
{
default: () => (row?.name ? row?.name : '-'),
},
),
},
{
key: 'count',
dataKey: 'count',
title: '数值',
align: 'right',
width: 160,
cellRenderer: ({ cellData: count, rowData: row }: any) =>
editing.value
? withDirectives(
h(TeInput, {
modelValue: count,
clearable: true,
placeholder: '请输入',
['onUpdate:modelValue']: (value: string) => {
row.count = value;
},
onInput: () => handleInputChange(),
}),
[[inputFilterDirective, { decimal: 2 }, 'number']],
)
: h(
'span',
{},
{
default: () => (row?.count ? row?.count : '-'),
},
),
},
{
key: 'operate',
dataKey: 'operate',
title: '操作',
width: 64,
cellRenderer: ({ rowData: row, rowIndex: rowIndex }: any) =>
h(
ElButton,
{
type: 'primary',
link: true,
disabled: mapDeleteDisabled(row),
onClick: () => handleSingleDelete(row, rowIndex),
},
{
default: () => '删除',
},
),
},
{
key: 'operate-2',
dataKey: 'operate-2',
title: '操作',
width: 64,
cellRenderer: ({ rowData: row }: any) =>
h(
ElButton,
{
type: 'primary',
link: true,
onClick: () => handleSingleAdd(row),
},
{
default: () => '新增',
},
),
rowSpan: ({ rowIndex }: any) => {
const row = dataSource.value[rowIndex];
const firstIndex = dataSource.value?.findIndex((item) => item.month === row.month);
return firstIndex === rowIndex ? monthCountMap.value.get(row.month + '') ?? 1 : 0;
},
headerCellRenderer: () => h('span', { default: () => '' }),
},
]);
合并单元格
const Row = ({ rowData, rowIndex, cells, columns }: any) => {
const rowSpan = columns[0].rowSpan({ rowData, rowIndex });
if (rowSpan > 1) {
const cell = cells[0];
const style = {
...cell.props.style,
height: `${rowSpan * 40 - 1}px`,
alignSelf: 'flex-start',
zIndex: 1,
backgroundColor: '#ffffff',
};
cells[0] = cloneVNode(cell, { style });
}
return cells;
};
eg2:
// 虚拟表格列
const virtualColumnList = ref<Column<any>[]>([
{
key: 'sort',
dataKey: 'sort',
fixed: 'left' as any,
title: '序号',
width: 64,
cellRenderer: ({ rowIndex }: any): any => `${rowIndex + 1}`,
},
{
key: 'pointId',
dataKey: 'pointId',
fixed: 'left' as any,
title: '点位名称',
headerCellRenderer: () =>
h(
'div',
{
className: 'arcd-custom-header',
},
['点位名称'],
),
width: 180,
cellRenderer: ({ rowData: row }: any): any =>
h(
TeSelect,
{
modelValue: row.pointId,
clearable: false,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.pointId = value;
},
onChange: (value: number) => handlePointChange(value, row),
},
() =>
pointList.value?.map((item) =>
h(TeOption, { value: item.pointId, label: item.name }),
) ?? [],
),
},
{
key: 'code',
dataKey: 'code',
title: '编码',
width: 180,
cellRenderer: ({ rowData: row }: any) =>
h(TeInput, {
modelValue: row.code,
disabled: true,
}),
},
{
key: 'triggerLogic',
dataKey: 'triggerLogic',
title: '触发逻辑',
hidden: mapIsDataAlarmRule(currentSelectModule.value),
headerCellRenderer: () =>
h(
'div',
{
className: 'arcd-custom-header',
},
['触发逻辑'],
),
width: 128,
cellRenderer: ({ rowData: row }: any): any =>
h(
TeSelect,
{
modelValue: row.triggerLogic,
clearable: false,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.triggerLogic = value;
},
onChange: () => handleTriggerLogicChange(row),
},
() =>
triggerLogicTypeOptions?.map((item) =>
h(TeOption, { value: item.value, label: item.label }),
) ?? [],
),
},
{
key: 'limitType',
dataKey: 'limitType',
title: '越限类别',
hidden: mapIsStatusAlarmRule(currentSelectModule.value),
headerCellRenderer: () =>
h(
'div',
{
className: 'arcd-custom-header',
},
['越限类别'],
),
width: 160,
cellRenderer: ({ rowData: row }: any): any =>
h(
TeSelect,
{
modelValue: row.limitType,
clearable: false,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.limitType = value;
},
onChange: () => handleLimitTypeChange(row),
},
() =>
overrunTypeOptions?.map((item) =>
h(TeOption, { value: item.value, label: item.label }),
) ?? [],
),
},
{
key: 'faultTypeName',
dataKey: 'faultTypeName',
title: '故障类别',
width: 160,
cellRenderer: ({ rowData: row }: any): any =>
h(TeSelect, {
modelValue: row.faultTypeName,
clearable: false,
placeholder: '请选择',
disabled: true,
}),
},
{
key: 'overrunValue',
dataKey: 'overrunValue',
title: '越限告警值',
hidden: mapIsStatusAlarmRule(currentSelectModule.value),
width: 102,
cellRenderer: ({ rowData: row }: any): any =>
withDirectives(
h(TeInput, {
modelValue: row.overrunValue,
clearable: false,
placeholder: '请输入',
['onUpdate:modelValue']: (value: number) => {
row.overrunValue = value;
},
}),
[[inputFilterDirectiveInstance, { negative: true }, 'numberV2']],
),
},
{
key: 'deadValue',
dataKey: 'deadValue',
title: '死区值',
hidden: mapIsStatusAlarmRule(currentSelectModule.value),
width: 102,
cellRenderer: ({ rowData: row }: any): any =>
withDirectives(
h(TeInput, {
modelValue: row.deadValue,
clearable: false,
placeholder: '请输入',
['onUpdate:modelValue']: (value: number) => {
row.deadValue = value;
},
}),
[[inputFilterDirectiveInstance, { negative: true }, 'numberV2']],
),
},
{
key: 'duration',
dataKey: 'duration',
title: '触发延时',
headerCellRenderer: () =>
h(
'div',
{
className: 'arcd-custom-header',
},
['触发延时'],
),
width: 112,
cellRenderer: ({ rowData: row }: any): any =>
withDirectives(
h(
TeInput,
{
modelValue: row.duration,
clearable: false,
placeholder: '请输入',
['onUpdate:modelValue']: (value: number) => {
row.duration = value;
},
},
{
suffix: () => h('span', null, 's'),
},
),
[
[
inputFilterDirectiveInstance,
{ negative: false, integral: 4, decimal: 0, maxValue: 3600 },
'numberV2',
],
],
),
},
{
key: 'levelCode',
dataKey: 'levelCode',
title: '告警等级',
headerCellRenderer: () =>
h(
'div',
{
className: 'arcd-custom-header',
},
['告警等级'],
),
width: 160,
cellRenderer: ({ rowData: row }: any): any =>
h(
TeSelect,
{
modelValue: row.levelCode,
clearable: false,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.levelCode = value;
},
},
() =>
alarmLevelList.value?.map((item) =>
h(TeOption, { value: item.code, label: item.name }),
) ?? [],
),
},
{
key: 'content',
dataKey: 'content',
title: '告警文本',
width: 228,
showOverflowEllipsis: true,
cellRenderer: ({ rowData: row, rowIndex }: any) =>
h(
'div',
{
className: 'arcd-edit-cell',
},
[
h(
'span',
{
title: row.content,
},
row.content,
),
h(
TeButton,
{
link: true,
type: 'primary',
onClick: () =>
handleEdit(rowIndex, EEditLabelKey.告警文本, row.content),
},
() => '编辑',
),
],
),
},
{
key: 'suggest',
dataKey: 'suggest',
title: '告警处理建议',
width: 228,
cellRenderer: ({ rowData: row, rowIndex }: any) =>
h(
'div',
{
className: 'arcd-edit-cell',
},
[
h(
'span',
{
title: row.suggest,
},
() => row.suggest,
),
h(
TeButton,
{
link: true,
type: 'primary',
onClick: () =>
handleEdit(rowIndex, EEditLabelKey.告警建议, row.suggest),
},
() => '编辑',
),
],
),
},
{
key: 'effectModeId',
dataKey: 'effectModeId',
title: '生效时间',
width: 144,
cellRenderer: ({ rowData: row }: any): any =>
h(
TeSelect,
{
disabled: true,
modelValue: row.effectModeId,
clearable: false,
placeholder: '请选择',
['onUpdate:modelValue']: (value: number) => {
row.effectModeId = value;
},
onChange: (value: number) => handlePointChange(value, row),
},
() =>
workModeList.value?.map((item) =>
h(TeOption, { value: item.id, label: item.name }),
) ?? [],
),
},
{
key: 'enabledFlag',
dataKey: 'enabledFlag',
title: '启用状态',
width: 88,
cellRenderer: ({ rowData: row }: any) =>
h(TeSwitch, {
modelValue: row.enabledFlag,
activeValue: ECommonStartStopStatusType.启用,
inactiveValue: ECommonStartStopStatusType.停用,
onChange: (value: any) => {
row.enabledFlag = value;
},
}),
},
{
key: 'publishStatus',
dataKey: 'publishStatus',
title: '发布状态',
width: 88,
cellRenderer: ({ rowData: row }: any) =>
h(
TeBadge,
{
isStatus: true,
type: mapReleaseStatusType(row.publishStatus),
},
() => mapReleaseStatusLabel(row.publishStatus),
),
},
{
key: 'operate',
dataKey: 'operate',
title: '操作',
width: 136,
cellRenderer: ({ rowIndex }: any) => {
return h('div', { class: 'arcd-operation-buttons' }, [
// 渲染 ElDropdown 下拉菜单
h(
TeDropdown,
{
onCommand: (value: string) => handleCommand(value, rowIndex),
},
{
default: () =>
h(
TeButton,
{
type: 'primary',
link: true,
},
{
default: () =>
h('div', null, [
h('span', null, '操作'),
h(IconDown, null),
]),
},
),
dropdown: () =>
h(TeDropdownMenu, null, {
default: () => [
h(
TeDropdownItem,
{
command: 'up',
disabled: rowIndex === 0,
},
{
default: () => '上移',
},
),
h(
TeDropdownItem,
{
command: 'down',
disabled: mapDownBtnDisabled(rowIndex),
},
{
default: () => '下移',
},
),
h(
TeDropdownItem,
{
command: 'move',
},
{
default: () => '移动',
},
),
],
}),
},
),
// 渲染 TePopconfirm 确认框
h(
TePopconfirm,
{
title: '确认删除这条数据吗?',
confirmButtonText: '确定',
cancelButtonText: '取消',
onConfirm: () => handleDeleteConfirm(rowIndex),
},
{
reference: () =>
h(
TeButton,
{
type: 'primary',
link: true,
},
{
default: () => '删除',
},
),
},
),
]);
},
},
]);
4、难点
1、hidden控制列隐藏 如果在初始化赋值columns设置了hidden等于一个表达式,重新渲染时是不会生效的,我是在重新渲染时给hidden重新赋值了,可以试试hidden赋值一个箭头函数
2、rowSpan字段是控制表格单元格合并的,支持传入一个函数返回rowSpan
3、单元格动态渲染cellRenderer需要返回VNode 这里可以去学习下Vue中h函数的使用,我这里用到了普通文本、输入框、下拉框的渲染
// 完整参数签名
function h(
type: string | Component,
props?: object | null,
children?: Children | Slot | Slots
): VNode
第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop,第三个参数是子节点。 当创建一个组件的 vnode 时,子节点必须以插槽函数进行传递。如果组件只有默认槽,可以使用单个插槽函数进行传递。否则,必须以插槽函数的对象形式来传递。
其中比较难的是h函数中指令的使用,Vue提供了一个withDirectives的API
function withDirectives(
vnode: VNode,
directives: DirectiveArguments
): VNode
// [Directive, value, argument, modifiers]
type DirectiveArguments = Array<
| [Directive]
| [Directive, any]
| [Directive, any, string]
| [Directive, any, string, DirectiveModifiers]
>
用自定义指令包装一个现有的 vnode。第二个参数是自定义指令数组。每个自定义指令也可以表示为 [Directive, value, argument, modifiers] 形式的数组。如果不需要,可以省略数组的尾元素。
比如这里我用了我自定义的指令inputFilterDirective,后面两个参数是我给指令的传参
[[inputFilterDirective, { decimal: 2 }, 'number']],
转换到template模板语法就是v-inputFilterDirective:number="{ decimal: 2 }"
4、使用row插槽实现单元格合并以及自定义样式
const Row = ({ rowData, rowIndex, cells, columns }: any) => {
return cells
}
搭配row插槽使用
<template #row="props">
<Row v-bind="props" />
</template>
5、问题
使用的过程中也发现了几个问题 1、貌似不支持斑马纹,如果设置了stripe="true",合并的单元格依然是白色背景,而没有合并的单元全都是灰色
2、已合并行的单元格在滚动到顶部是只留下几行在可视区域,会出现不合并的情况。
3、控制台警告提示 Non-function value encountered for default slot 解决办法:h函数的第三个参数使用箭头函数
h(
TeBadge,
{
isStatus: true,
type: mapReleaseStatusType(row.publishStatus),
},
() => mapReleaseStatusLabel(row.publishStatus),
),