From d35d498f636fe77a35fdb74143e7bfea10ca41ab Mon Sep 17 00:00:00 2001 From: cipchk Date: Wed, 19 Jul 2023 16:56:09 +0800 Subject: [PATCH 1/2] feat(abc:st): add `onCell`, support colSpan and rowSpan merging - close https://github.com/ng-alain/ng-alain/issues/2404 --- packages/abc/st/demo/colspan-rowspan.md | 73 +++++++++++++++++++++ packages/abc/st/index.en-US.md | 3 +- packages/abc/st/index.zh-CN.md | 3 +- packages/abc/st/st-data-source.ts | 21 ++++-- packages/abc/st/st.component.html | 32 +++++---- packages/abc/st/st.interfaces.ts | 13 ++++ packages/abc/st/st.types.ts | 3 +- packages/abc/st/test/st-data-source.spec.ts | 48 +++++++++++++- 8 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 packages/abc/st/demo/colspan-rowspan.md 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..891503063 100644 --- a/packages/abc/st/index.en-US.md +++ b/packages/abc/st/index.en-US.md @@ -265,7 +265,8 @@ class TestComponent { | `[fixed]` | Set column to be fixed, must specify `width` | `left,right` | - | | `[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` | - | +| deprecated `[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..a538533f2 100644 --- a/packages/abc/st/index.zh-CN.md +++ b/packages/abc/st/index.zh-CN.md @@ -265,7 +265,8 @@ class TestComponent { | `[fixed]` | 固定前后列,当指定时务必指定 `width` 否则视为无效 | `left,right` | - | | `[format]` | 格式化列值 | `(item: STData, col: STColumn, index: number) => string` | - | | `[className]` | 列 `class` 属性值,例如:`text-center` 居中; `text-right` 居右; `text-error` 异常色,更多参考[样式工具类](/theme/tools) | `string` | - | -| `[colSpan]` | 合并列 | `number` | - | +| deprecated `[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..399cc6437 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, @@ -204,15 +205,26 @@ export class STDataSource { } private get(item: STData, col: _STColumn, idx: number): _STDataValue { + const safeHtml = col.safeType === 'safeHtml'; + let onCellResult = typeof col.onCell === 'function' ? col.onCell(item, idx) : null; + if (onCellResult == null && col.colSpan != null) { + onCellResult = { colSpan: col.colSpan }; + } + const mergedColSpan = onCellResult?.colSpan ?? 1; + const mergedRowSpan = onCellResult?.rowSpan ?? 1; + const props: STOnCellResult = { + colSpan: mergedColSpan <= 0 ? null : mergedColSpan, + rowSpan: mergedRowSpan <= 0 ? null : mergedRowSpan + }; try { - const safeHtml = col.safeType === 'safeHtml'; if (col.format) { const formatRes = col.format(item, col, idx) || ''; return { text: formatRes, _text: safeHtml ? this.dom.bypassSecurityTrustHtml(formatRes) : formatRes, org: formatRes, - safeType: col.safeType! + safeType: col.safeType!, + props }; } @@ -261,12 +273,13 @@ export class STDataSource { org: value, color, safeType: col.safeType!, - buttons: [] + buttons: [], + props }; } catch (ex) { const text = `INVALID DATA`; console.error(`Failed to get data`, item, col, ex); - return { text, _text: text, org: text, buttons: [], safeType: 'text' }; + return { text, _text: text, org: text, buttons: [], safeType: 'text', props }; } } 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" > - - - - - - + + + + + + + + { className?: NgClassType; /** * 合并列 + * + * @deprecated Will be removed by 17.0.0, Pls use `onCell` instead. */ colSpan?: number; + /** + * Table cell supports `colSpan` and `rowSpan`. When each of them is set to 0, the cell will not be rendered. + * + * 表格支持行/列合并,若返回的 `colSpan` 或者 `rowSpan` 设值为 0 时表示不会渲染 + */ + onCell?: (item: T, index: number) => STOnCellResult; /** * 数字格式,`type=number` 有效 */ @@ -1280,3 +1288,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..6bc1fdefc 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,52 @@ 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(); + }); + }); + it('@deprecated colSpan', done => { + options.data = genData(); + options.columns = [{ index: 'a', colSpan: 2 }] as _STColumn[]; + srv.process(options).subscribe(res => { + const values = res.list[0]._values as _STDataValue[]; + expect(values[0].props?.colSpan).toBe(2); + expect(values[0].props?.rowSpan).toBe(1); + done(); + }); + }); + }); }); describe('[buttons]', () => { From f53db2ea050086d26af3f53b65998e12ef4e8927 Mon Sep 17 00:00:00 2001 From: cipchk Date: Thu, 20 Jul 2023 13:37:35 +0800 Subject: [PATCH 2/2] chore: fix test --- packages/abc/st/index.en-US.md | 2 +- packages/abc/st/index.zh-CN.md | 2 +- packages/abc/st/st-data-source.ts | 36 ++++++++++----------- packages/abc/st/st.component.ts | 21 ++++++++---- packages/abc/st/st.interfaces.ts | 8 +---- packages/abc/st/test/st-data-source.spec.ts | 10 ------ 6 files changed, 35 insertions(+), 44 deletions(-) diff --git a/packages/abc/st/index.en-US.md b/packages/abc/st/index.en-US.md index 891503063..27ee448e1 100644 --- a/packages/abc/st/index.en-US.md +++ b/packages/abc/st/index.en-US.md @@ -265,7 +265,7 @@ class TestComponent { | `[fixed]` | Set column to be fixed, must specify `width` | `left,right` | - | | `[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` | - | -| deprecated `[colSpan]` | Span of this column's title | `number` | - | +| `[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` | - | diff --git a/packages/abc/st/index.zh-CN.md b/packages/abc/st/index.zh-CN.md index a538533f2..576478668 100644 --- a/packages/abc/st/index.zh-CN.md +++ b/packages/abc/st/index.zh-CN.md @@ -265,7 +265,7 @@ class TestComponent { | `[fixed]` | 固定前后列,当指定时务必指定 `width` 否则视为无效 | `left,right` | - | | `[format]` | 格式化列值 | `(item: STData, col: STColumn, index: number) => string` | - | | `[className]` | 列 `class` 属性值,例如:`text-center` 居中; `text-right` 居右; `text-error` 异常色,更多参考[样式工具类](/theme/tools) | `string` | - | -| deprecated `[colSpan]` | 合并列 | `number` | - | +| `[colSpan]` | 合并列 | `number` | - | | `[onCell]` | 设置单元格属性 | `(item: T, index: number) => STOnCellResult;` | - | | `[sort]` | 排序配置项,远程数据配置**优先**规则:
`true` 表示允许排序,且若数据源为本地数据时会自动生成 `compare: (a, b) => a[index] - b[index]` 方法
`string` 表示远程数据排序相对应 `key` 值 | `true,string,STColumnSort` | - | | `[filter]` | 过滤配置项 | `STColumnFilter` | - | diff --git a/packages/abc/st/st-data-source.ts b/packages/abc/st/st-data-source.ts index 399cc6437..15027887b 100644 --- a/packages/abc/st/st-data-source.ts +++ b/packages/abc/st/st-data-source.ts @@ -205,26 +205,15 @@ export class STDataSource { } private get(item: STData, col: _STColumn, idx: number): _STDataValue { - const safeHtml = col.safeType === 'safeHtml'; - let onCellResult = typeof col.onCell === 'function' ? col.onCell(item, idx) : null; - if (onCellResult == null && col.colSpan != null) { - onCellResult = { colSpan: col.colSpan }; - } - const mergedColSpan = onCellResult?.colSpan ?? 1; - const mergedRowSpan = onCellResult?.rowSpan ?? 1; - const props: STOnCellResult = { - colSpan: mergedColSpan <= 0 ? null : mergedColSpan, - rowSpan: mergedRowSpan <= 0 ? null : mergedRowSpan - }; try { + const safeHtml = col.safeType === 'safeHtml'; if (col.format) { const formatRes = col.format(item, col, idx) || ''; return { text: formatRes, _text: safeHtml ? this.dom.bypassSecurityTrustHtml(formatRes) : formatRes, org: formatRes, - safeType: col.safeType!, - props + safeType: col.safeType! }; } @@ -273,13 +262,12 @@ export class STDataSource { org: value, color, safeType: col.safeType!, - buttons: [], - props + buttons: [] }; } catch (ex) { const text = `INVALID DATA`; console.error(`Failed to get data`, item, col, ex); - return { text, _text: text, org: text, buttons: [], safeType: 'text', props }; + return { text, _text: text, org: text, buttons: [], safeType: 'text' }; } } @@ -337,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.ts b/packages/abc/st/st.component.ts index 8e0be868d..24b66318a 100644 --- a/packages/abc/st/st.component.ts +++ b/packages/abc/st/st.component.ts @@ -559,14 +559,21 @@ export class STComponent implements AfterViewInit, OnChanges, OnDestroy { } private _refColAndData(): this { - this._columns - .filter(w => 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 adc924304..b90fb2836 100644 --- a/packages/abc/st/st.interfaces.ts +++ b/packages/abc/st/st.interfaces.ts @@ -356,12 +356,6 @@ export interface STColumn { * - `text-error` 异常色 */ className?: NgClassType; - /** - * 合并列 - * - * @deprecated Will be removed by 17.0.0, Pls use `onCell` instead. - */ - colSpan?: number; /** * Table cell supports `colSpan` and `rowSpan`. When each of them is set to 0, the cell will not be rendered. * @@ -440,7 +434,7 @@ export interface STColumn { * 分组表头 */ children?: Array>; - + colSpan?: number; rowSpan?: number; /** diff --git a/packages/abc/st/test/st-data-source.spec.ts b/packages/abc/st/test/st-data-source.spec.ts index 6bc1fdefc..b0a60ff50 100644 --- a/packages/abc/st/test/st-data-source.spec.ts +++ b/packages/abc/st/test/st-data-source.spec.ts @@ -943,16 +943,6 @@ describe('abc: table: data-souce', () => { done(); }); }); - it('@deprecated colSpan', done => { - options.data = genData(); - options.columns = [{ index: 'a', colSpan: 2 }] as _STColumn[]; - srv.process(options).subscribe(res => { - const values = res.list[0]._values as _STDataValue[]; - expect(values[0].props?.colSpan).toBe(2); - expect(values[0].props?.rowSpan).toBe(1); - done(); - }); - }); }); });