I. Scenario Overview
This article explains how to implement table cell merging and header grouping through customization. 
II. Code Example
Click to download the corresponding code
III. Operation Steps
(Ⅰ) Customize widget
Create a custom MergeTableWidget to support cell merging and header grouping.
typescript
// MergeTableWidget.ts
import { BaseElementWidget, SPI, ViewType, TableWidget, Widget, DslRender } from '@oinone/kunlun-dependencies';
import MergeTable from './MergeTable.vue';
@SPI.ClassFactory(
BaseElementWidget.Token({
viewType: ViewType.Table,
widget: 'MergeTableWidget'
})
)
export class MergeTableWidget extends TableWidget {
public initialize(props) {
super.initialize(props);
this.setComponent(MergeTable);
return this;
}
/**
* Table display fields
*/
@Widget.Reactive()
public get currentModelFields() {
return this.metadataRuntimeContext.model.modelFields.filter((f) => !f.invisible);
}
/**
* Render in-row action VNodes
*/
@Widget.Method()
protected renderRowActionVNodes() {
const table = this.metadataRuntimeContext.viewDsl!;
const rowAction = table?.widgets.find((w) => w.slot === 'rowActions');
if (rowAction) {
return rowAction.widgets.map((w) => DslRender.render(w));
}
return null;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(Ⅱ) Create Corresponding Vue Component
Define a Vue component that supports cell merging and header grouping.
vue
<!-- MergeTable.vue -->
<template>
<vxe-table
border
height="500"
:column-config="{ resizable: true }"
:merge-cells="mergeCells"
:data="showDataSource"
@checkbox-change="checkboxChange"
@checkbox-all="checkedAllChange"
>
<vxe-column type="checkbox" width="50"></vxe-column>
<!-- Render fields configured in the interface designer -->
<vxe-column
v-for="field in currentModelFields"
:key="field.name"
:field="field.name"
:title="field.label"
></vxe-column>
<!-- Header grouping https://vxetable.cn/v4.6/#/table/base/group -->
<vxe-colgroup title="更多信息">
<vxe-column field="role" title="Role"></vxe-column>
<vxe-colgroup title="详细信息">
<vxe-column field="sex" title="Sex"></vxe-column>
<vxe-column field="age" title="Age"></vxe-column>
</vxe-colgroup>
</vxe-colgroup>
<vxe-column title="操作" width="120">
<template #default="{ row, $rowIndex }">
<!-- Render in-row actions configured in the interface designer -->
<row-action-render
:renderRowActionVNodes="renderRowActionVNodes"
:row="row"
:rowIndex="$rowIndex"
:parentHandle="currentHandle"
></row-action-render>
</template>
</vxe-column>
</vxe-table>
<!-- Pagination -->
<oio-pagination
:pageSizeOptions="pageSizeOptions"
:currentPage="pagination.current"
:pageSize="pagination.pageSize"
:total="pagination.total"
show-total
:showJumper="paginationStyle != ListPaginationStyle.SIMPLE"
:showLastPage="paginationStyle != ListPaginationStyle.SIMPLE"
:onChange="onPaginationChange"
></oio-pagination>
</template>
<script lang="ts">
import { defineComponent, PropType, ref } from 'vue';
import { CheckedChangeEvent } from '@oinone/kunlun-vue-ui';
import { ActiveRecord, ActiveRecords, ManualWidget, Pagination, RuntimeModelField } from '@oinone/kunlun-dependencies';
import { ListPaginationStyle, OioPagination, OioSpin, ReturnPromise } from '@oinone/kunlun-vue-ui-antd';
import RowActionRender from './RowActionRender.vue';
export default defineComponent({
mixins: [ManualWidget],
components: {
OioSpin,
OioPagination,
RowActionRender
},
inheritAttrs: false,
props: {
currentHandle: {
type: String,
required: true
},
// loading
loading: {
type: Boolean,
default: undefined
},
// Table display data
showDataSource: {
type: Array as PropType<ActiveRecord[]>
},
// Pagination
pagination: {
type: Object as PropType<Pagination>,
required: true
},
pageSizeOptions: {
type: Array as PropType<(number | string)[]>,
required: true
},
paginationStyle: {
type: String as PropType<ListPaginationStyle>
},
// Modify pagination
onPaginationChange: {
type: Function as PropType<(currentPage: number, pageSize: number) => ReturnPromise<void>>
},
// Table selection
onCheckedChange: {
type: Function as PropType<(data: ActiveRecords, event?: CheckedChangeEvent) => void>
},
// Table full selection
onCheckedAllChange: {
type: Function as PropType<(selected: boolean, data: ActiveRecord[], event?: CheckedChangeEvent) => void>
},
// Display fields
currentModelFields: {
type: Array as PropType<RuntimeModelField[]>
},
// Render in-row actions
renderRowActionVNodes: {
type: Function as PropType<(row: any) => any>,
required: true
}
},
setup(props, ctx) {
/**
* Cell merging
* https://vxetable.cn/v4.6/#/table/advanced/span
*/
const mergeCells = ref([
{ row: 1, col: 1, rowspan: 3, colspan: 3 },
{ row: 5, col: 0, rowspan: 2, colspan: 2 }
]);
// Single selection
const checkboxChange = (e) => {
const { checked, record, records } = e;
const event: CheckedChangeEvent = {
checked,
record,
records,
origin: e
};
props.onCheckedChange?.(records, event);
};
// Full selection
const checkedAllChange = (e) => {
const { checked, record, records } = e;
const event: CheckedChangeEvent = {
checked,
record,
records,
origin: e
};
props.onCheckedAllChange?.(checked, records, event);
};
return {
mergeCells,
ListPaginationStyle,
checkboxChange,
checkedAllChange
};
}
});
</script>
<style lang="scss"></style>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
(Ⅲ) Create In-Row Actions
vue
<script lang="ts">
import { ActionBar, RowActionBarWidget } from '@oinone/kunlun-dependencies';
import { debounce } from 'lodash-es';
import { createVNode, defineComponent } from 'vue';
export default defineComponent({
inheritAttrs: false,
props: {
row: {
type: Object,
required: true
},
rowIndex: {
type: Number,
required: true
},
renderRowActionVNodes: {
type: Function,
required: true
},
parentHandle: {
type: String,
required: true
}
},
render() {
const vnode = this.renderRowActionVNodes();
return createVNode(
ActionBar,
{
widget: 'rowAction',
parentHandle: this.parentHandle,
inline: true,
activeRecords: this.row,
rowIndex: this.rowIndex,
key: this.rowIndex,
refreshWidgetRecord: debounce((widget?: RowActionBarWidget) => {
if (widget) {
widget.setCurrentActiveRecords(this.row);
}
})
},
{
default: () => vnode
}
);
}
});
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
(Ⅳ) Register Layout
javascript
// registry.ts
import { registerLayout, ViewType } from '@oinone/kunlun-dependencies';
registerLayout(
`<view type="TABLE">
<pack widget="group">
<view type="SEARCH">
<element widget="search" slot="search" slotSupport="field">
<xslot name="searchFields" slotSupport="field" />
</element>
</view>
</pack>
<pack widget="group" slot="tableGroup">
<element widget="actionBar" slot="actionBar" slotSupport="action">
<xslot name="actions" slotSupport="action" />
</element>
<element widget="MergeTableWidget" slot="table" slotSupport="field">
<element widget="expandColumn" slot="expandRow" />
<xslot name="fields" slotSupport="field" />
<element widget="rowActions" slot="rowActions" slotSupport="action" />
</element>
</pack>
</view>`,
{
model: '模型',
viewType: ViewType.Table,
actionName: '动作名称'
}
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Through the above steps, the custom table can achieve cell merging and header grouping functions, while supporting dynamic rendering of fields and actions configured in the interface designer.