diff --git a/packages/abc/st/demo/colspan-rowspan.md b/packages/abc/st/demo/colspan-rowspan.md
new file mode 100644
index 000000000..3051d52ef
--- /dev/null
+++ b/packages/abc/st/demo/colspan-rowspan.md
@@ -0,0 +1,73 @@
+---
+order: 8
+title:
+ en-US: colSpan and rowSpan
+ zh-CN: 表格行/列合并
+---
+
+## zh-CN
+
+表格支持行/列合并,使用 `onCell` 进行设置,若返回 `colSpan` 或者 `rowSpan` 设值为 0 时,设置的表格不会渲染。
+
+## en-US
+
+The table supports row/column merging, use `onCell` to set, if return `colSpan` or `rowSpan` is set to 0, the set table will not be rendered.
+
+```ts
+import { Component } from '@angular/core';
+
+import { STColumn, STData, STOnCellResult } from '@delon/abc/st';
+
+// In the fifth row, other columns are merged into first column
+// by setting it's colSpan to be 0
+const sharedOnCell = (_: STData, index: number): STOnCellResult => {
+ if (index === 1) {
+ return { colSpan: 0 };
+ }
+
+ return {};
+};
+
+@Component({
+ selector: 'app-demo',
+ template: `
+ `
+})
+export class DemoComponent {
+ url = `/users?total=2&field=list`;
+ params = { a: 1, b: 2 };
+
+ columns: STColumn[] = [
+ { title: '编号', index: 'id', sort: true, width: 100 },
+ { title: '头像', type: 'img', index: 'picture.thumbnail', width: 60 },
+ {
+ title: '邮箱',
+ index: 'email',
+ onCell: (_, index) => ({
+ colSpan: index === 1 ? 5 : 1
+ })
+ },
+ {
+ title: 'first',
+ index: 'name.first',
+ sort: true,
+ className: 'text-center',
+ onCell: (_, index) => {
+ if (index === 3) {
+ return { rowSpan: 2 };
+ }
+ // These two are merged into above cell
+ if (index === 4) {
+ return { rowSpan: 0 };
+ }
+ if (index === 1) {
+ return { colSpan: 0 };
+ }
+
+ return {};
+ }
+ },
+ { title: 'last', index: 'name.last', onCell: sharedOnCell }
+ ];
+}
+```
diff --git a/packages/abc/st/index.en-US.md b/packages/abc/st/index.en-US.md
index 84cddca7c..27ee448e1 100644
--- a/packages/abc/st/index.en-US.md
+++ b/packages/abc/st/index.en-US.md
@@ -266,6 +266,7 @@ class TestComponent {
| `[format]` | Format value of this column | `(item: STData, col: STColumn, index: number) => string` | - |
| `[className]` | Class name of this column, e.g: `text-center`, `text-right`, `text-error`, pls refer to [Style Tools](/theme/tools) | `string` | - |
| `[colSpan]` | Span of this column's title | `number` | - |
+| `[onCell]` | Set props on per cell | `(item: T, index: number) => STOnCellResult;` | - |
| `[sort]` | Sort config of this column, Remote Data Configuration**Priority** Rule:
`true` allow sorting, should be auto generate compose `compare: (a, b) => a[index] - b[index]` method when data is local
`string` corresponding `key` value | `true,string,STColumnSort` | - |
| `[filter]` | Filter config of this column | `STColumnFilter` | - |
| `[selections]` | Config of type is checkbox | `STColumnSelection[]` | - |
diff --git a/packages/abc/st/index.zh-CN.md b/packages/abc/st/index.zh-CN.md
index 1dcd1b460..576478668 100644
--- a/packages/abc/st/index.zh-CN.md
+++ b/packages/abc/st/index.zh-CN.md
@@ -266,6 +266,7 @@ class TestComponent {
| `[format]` | 格式化列值 | `(item: STData, col: STColumn, index: number) => string` | - |
| `[className]` | 列 `class` 属性值,例如:`text-center` 居中; `text-right` 居右; `text-error` 异常色,更多参考[样式工具类](/theme/tools) | `string` | - |
| `[colSpan]` | 合并列 | `number` | - |
+| `[onCell]` | 设置单元格属性 | `(item: T, index: number) => STOnCellResult;` | - |
| `[sort]` | 排序配置项,远程数据配置**优先**规则:
`true` 表示允许排序,且若数据源为本地数据时会自动生成 `compare: (a, b) => a[index] - b[index]` 方法
`string` 表示远程数据排序相对应 `key` 值 | `true,string,STColumnSort` | - |
| `[filter]` | 过滤配置项 | `STColumnFilter` | - |
| `[selections]` | 选择功能配置 | `STColumnSelection[]` | - |
diff --git a/packages/abc/st/st-data-source.ts b/packages/abc/st/st-data-source.ts
index 181f6d87b..15027887b 100644
--- a/packages/abc/st/st-data-source.ts
+++ b/packages/abc/st/st-data-source.ts
@@ -19,6 +19,7 @@ import {
STData,
STMultiSort,
STMultiSortResultType,
+ STOnCellResult,
STPage,
STReq,
STReqReNameType,
@@ -324,15 +325,27 @@ export class STDataSource {
return this.http.request(method, url, reqOptions);
}
+ getCell(c: STColumn, item: STData, idx: number): STOnCellResult {
+ const onCellResult = typeof c.onCell === 'function' ? c.onCell(item, idx) : null;
+ const mergedColSpan = onCellResult?.colSpan ?? 1;
+ const mergedRowSpan = onCellResult?.rowSpan ?? 1;
+ return {
+ colSpan: mergedColSpan <= 0 ? null : mergedColSpan,
+ rowSpan: mergedRowSpan <= 0 ? null : mergedRowSpan
+ } as STOnCellResult;
+ }
+
optimizeData(options: { columns: _STColumn[]; result: STData[]; rowClassName?: STRowClassName | null }): STData[] {
const { result, columns, rowClassName } = options;
for (let i = 0, len = result.length; i < len; i++) {
result[i]._values = columns.map(c => {
+ const props = this.getCell(c, result[i], i);
+
if (Array.isArray(c.buttons) && c.buttons.length > 0) {
- return { buttons: this.genButtons(c.buttons, result[i], c), _text: '' };
+ return { buttons: this.genButtons(c.buttons, result[i], c), _text: '', props };
}
- return this.get(result[i], c, i);
+ return { ...this.get(result[i], c, i), props };
});
result[i]._rowClassName = [rowClassName ? rowClassName(result[i], i) : null, result[i].className]
.filter(w => !!w)
diff --git a/packages/abc/st/st.component.html b/packages/abc/st/st.component.html
index 68d931792..20993b762 100644
--- a/packages/abc/st/st.component.html
+++ b/packages/abc/st/st.component.html
@@ -167,19 +167,25 @@
(click)="_stopPropagation($event)"
nzWidth="50px"
>
-
-
-
-
-
- |
+
+ 0 && i._values[cIdx].props?.rowSpan > 0"
+ [nzLeft]="!!c._left"
+ [nzRight]="!!c._right"
+ [attr.data-col-index]="cIdx"
+ [ngClass]="c._className"
+ [attr.colspan]="i._values[cIdx].props?.colSpan === 1 ? null : i._values[cIdx].props?.colSpan"
+ [attr.rowspan]="i._values[cIdx].props?.rowSpan === 1 ? null : i._values[cIdx].props?.rowSpan"
+ >
+
+
+
+
+ |
+
w.type === 'no')
- .forEach(c =>
- this._data.forEach((i, idx) => {
+ this._columns.forEach(c => {
+ this._data.forEach((i, idx) => {
+ const values = i._values as _STDataValue[];
+ if (c.type === 'no') {
const text = `${this.dataSource.getNoIndex(i, c, idx)}`;
- i._values![c.__point!] = { text, _text: text, org: idx, safeType: 'text' } as _STDataValue;
- })
- );
+ values[c.__point!] = {
+ text,
+ _text: text,
+ org: idx,
+ safeType: 'text'
+ } as _STDataValue;
+ }
+ values[c.__point!].props = this.dataSource.getCell(c, i, idx);
+ });
+ });
return this.refreshData();
}
diff --git a/packages/abc/st/st.interfaces.ts b/packages/abc/st/st.interfaces.ts
index ed8e65c08..b90fb2836 100644
--- a/packages/abc/st/st.interfaces.ts
+++ b/packages/abc/st/st.interfaces.ts
@@ -357,9 +357,11 @@ export interface STColumn {
*/
className?: NgClassType;
/**
- * 合并列
+ * Table cell supports `colSpan` and `rowSpan`. When each of them is set to 0, the cell will not be rendered.
+ *
+ * 表格支持行/列合并,若返回的 `colSpan` 或者 `rowSpan` 设值为 0 时表示不会渲染
*/
- colSpan?: number;
+ onCell?: (item: T, index: number) => STOnCellResult;
/**
* 数字格式,`type=number` 有效
*/
@@ -432,7 +434,7 @@ export interface STColumn {
* 分组表头
*/
children?: Array>;
-
+ colSpan?: number;
rowSpan?: number;
/**
@@ -1280,3 +1282,8 @@ export interface STCustomRequestOptions {
url: string;
options: STRequestOptions;
}
+
+export interface STOnCellResult {
+ rowSpan?: number | null;
+ colSpan?: number | null;
+}
diff --git a/packages/abc/st/st.types.ts b/packages/abc/st/st.types.ts
index 6d8016e6a..8837bed09 100644
--- a/packages/abc/st/st.types.ts
+++ b/packages/abc/st/st.types.ts
@@ -2,7 +2,7 @@
import { TemplateRef } from '@angular/core';
import { SafeHtml } from '@angular/platform-browser';
-import { STColumn, STColumnButton, STColumnSafeType, STData, STSortMap } from './st.interfaces';
+import { STColumn, STColumnButton, STColumnSafeType, STData, STOnCellResult, STSortMap } from './st.interfaces';
/**
* @inner
@@ -74,4 +74,5 @@ export interface _STDataValue {
color?: string;
safeType: STColumnSafeType;
buttons?: _STColumnButton[];
+ props?: STOnCellResult | null;
}
diff --git a/packages/abc/st/test/st-data-source.spec.ts b/packages/abc/st/test/st-data-source.spec.ts
index 482b67f7e..b0a60ff50 100644
--- a/packages/abc/st/test/st-data-source.spec.ts
+++ b/packages/abc/st/test/st-data-source.spec.ts
@@ -10,7 +10,7 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { STDataSource, STDataSourceOptions } from '../st-data-source';
import { ST_DEFAULT_CONFIG } from '../st.config';
import { STColumnButton, STColumnFilterMenu, STData } from '../st.interfaces';
-import { _STColumn } from '../st.types';
+import { _STColumn, _STDataValue } from '../st.types';
const DEFAULT = {
pi: 1,
@@ -908,6 +908,42 @@ describe('abc: table: data-souce', () => {
done();
});
});
+ describe('#onCell', () => {
+ it('should be working', done => {
+ const index = 1;
+ options.data = genData();
+ options.columns = [
+ { index: 'a', onCell: (_, idx) => ({ colSpan: idx === index ? 2 : 1 }) },
+ { index: 'b', onCell: (_, idx) => ({ rowSpan: idx === index ? 2 : 1 }) }
+ ] as _STColumn[];
+ srv.process(options).subscribe(res => {
+ const values = res.list[index]._values as _STDataValue[];
+ expect(values[0].props?.colSpan).toBe(2);
+ expect(values[0].props?.rowSpan).toBe(1);
+
+ expect(values[1].props?.colSpan).toBe(1);
+ expect(values[1].props?.rowSpan).toBe(2);
+ done();
+ });
+ });
+ it('should be ignore when set 0', done => {
+ const index = 1;
+ options.data = genData();
+ options.columns = [
+ { index: 'a', onCell: (_, idx) => ({ colSpan: idx === index ? 0 : 1 }) },
+ { index: 'b', onCell: (_, idx) => ({ rowSpan: idx === index ? 0 : 1 }) }
+ ] as _STColumn[];
+ srv.process(options).subscribe(res => {
+ const values = res.list[index]._values as _STDataValue[];
+ expect(values[0].props?.colSpan).toBeNull();
+ expect(values[0].props?.rowSpan).toBe(1);
+
+ expect(values[1].props?.colSpan).toBe(1);
+ expect(values[1].props?.rowSpan).toBeNull();
+ done();
+ });
+ });
+ });
});
describe('[buttons]', () => {