fix: table2行数据同步上层数据域、前端排序等问题修复 (#8905)

Co-authored-by: wanglinfang <wanglinfang@baidu.com>
This commit is contained in:
wanglinfang2014 2023-11-30 14:59:24 +08:00 committed by GitHub
parent 2c05f9c7fb
commit 0c20a947c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 272 additions and 152 deletions

View File

@ -16,68 +16,114 @@ order: 67
```schema: scope="body"
{
"type": "service",
"api": "/api/sample?perPage=5",
"type": "page",
"id": "page_001",
"data": {
"flag": true
},
"body": [
{
"type": "table2",
"title": "表格标题 - ${rows.length}",
"source": "$rows",
"columns": [
{
"title": "Engine",
"name": "engine",
"width": 120
},
{
"title": "Version",
"name": "version",
"type": "property",
"items": [
"type": "button",
"label": "启用行删除",
"className": "m-r",
"onEvent": {
"click": {
"actions": [
{
"label": "cpu",
"content": "1 core"
},
{
"label": "memory",
"content": "4G"
},
{
"label": "disk",
"content": "80G"
},
{
"label": "network",
"content": "4M",
"span": 2
},
{
"label": "IDC",
"content": "beijing"
},
{
"label": "Note",
"content": "其它说明",
"span": 3
"actionType": "setValue",
"componentId": "page_001",
"args": {
"value": {"flag": false}
}
}
]
},
{
"title": "Browser",
"name": "browser"
},
{
"title": "Operation",
"name": "operation",
"type": "button",
"label": "删除",
"size": "sm"
}
],
"footer": {
"type": "tpl",
"tpl": "表格Footer"
}
},
{
"type": "button",
"label": "禁用行删除",
"onEvent": {
"click": {
"actions": [
{
"actionType": "setValue",
"componentId": "page_001",
"args": {
"value": {"flag": true}
}
}
]
}
}
},
{
"type": "service",
"api": "/api/sample?perPage=5",
"body": [
{
"type": "table2",
"title": "表格标题 - ${rows.length}",
"source": "$rows",
"canAccessSuperData": true,
"columns": [
{
"title": "Engine",
"name": "engine",
"width": 120
},
{
"title": "Version",
"name": "version",
"type": "property",
"items": [
{
"label": "cpu",
"content": "1 core"
},
{
"label": "memory",
"content": "4G"
},
{
"label": "disk",
"content": "80G"
},
{
"label": "network",
"content": "4M",
"span": 2
},
{
"label": "IDC",
"content": "beijing"
},
{
"label": "Note",
"content": "其它说明",
"span": 3
}
]
},
{
"title": "Browser",
"name": "browser"
},
{
"title": "Operation",
"name": "operation",
"type": "button",
"label": "删除",
"size": "sm",
"disabledOn": "${flag}"
}
],
"footer": {
"type": "tpl",
"tpl": "表格Footer"
}
}
]
}
]
}

View File

@ -214,7 +214,8 @@ export const TableStore2 = ServiceStore.named('TableStore2')
pageSize: 10,
dragging: false,
rowSelectionKeyField: 'id',
formsRef: types.optional(types.array(types.frozen()), [])
formsRef: types.optional(types.array(types.frozen()), []),
canAccessSuperData: false
})
.views(self => {
function getToggable() {
@ -346,7 +347,9 @@ export const TableStore2 = ServiceStore.named('TableStore2')
},
get dataSource() {
return self.rows.map(item => item.data);
return self.rows.map(item =>
self.canAccessSuperData ? item.locals : item.data
);
},
get currentSelectedRowKeys() {
@ -405,28 +408,52 @@ export const TableStore2 = ServiceStore.named('TableStore2')
};
})
.actions(self => {
function updateColumns(columns: Array<SColumn2>) {
function updateColumns(
columns: Array<SColumn2>,
options?: {
resolveDefinitions?: (ref: string) => any;
}
) {
if (columns && Array.isArray(columns)) {
let cols: Array<SColumn2> = columns.filter(column => column).concat();
cols = cols.map((item, index) => ({
...item,
index,
type: item.type || 'plain',
pristine: item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint,
children: item.children ? updateColumns(item.children) : []
}));
cols = cols.map((item, index) => {
if (
options?.resolveDefinitions &&
typeof (item as any)?.$ref == 'string' &&
(item as any).$ref
) {
item = {
...options.resolveDefinitions((item as any).$ref),
...item
};
}
return {
...item,
index,
type: item.type || 'plain',
pristine: item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint,
children: item.children ? updateColumns(item.children, options) : []
};
});
return cols;
}
return;
}
function update(config: Partial<STableStore2>) {
function update(
config: Partial<STableStore2>,
options?: {
resolveDefinitions?: (ref: string) => any;
}
) {
config.columnsTogglable !== void 0 &&
(self.columnsTogglable = config.columnsTogglable);
config.canAccessSuperData !== undefined &&
(self.canAccessSuperData = !!config.canAccessSuperData);
if (typeof config.orderBy === 'string') {
setOrderByInfo(
@ -440,7 +467,7 @@ export const TableStore2 = ServiceStore.named('TableStore2')
}
if (config.columns && Array.isArray(config.columns)) {
self.columns.replace(updateColumns(config.columns) as any);
self.columns.replace(updateColumns(config.columns, options) as any);
}
}

View File

@ -25,6 +25,8 @@ import {
} from 'amis-core';
import {resizeSensor} from 'amis-core';
import {getStyleNumber} from 'amis-core';
import {filterTree} from 'amis-core';
import Spinner, {SpinnerExtraProps} from '../Spinner';
import ItemActionsWrapper from './ItemActionsWrapper';
import Cell from './Cell';
@ -36,8 +38,9 @@ import {
checkChildrenRow,
getDataChildrenKeys,
getBuildColumns,
getSelectedRows,
levelsSplit
getRowsByKeys,
levelsSplit,
getSortData
} from './util';
export interface ColumnProps {
@ -48,7 +51,7 @@ export interface ColumnProps {
render?: Function;
fixed?: boolean | string;
width?: number | string;
sorter?: (a: any, b: any) => number | boolean; // 设置为true时执行onSort否则执行前端排序
sorter?: ((a: any, b: any, order: string) => number) | boolean; // 设置为true时执行onSort否则执行前端排序
sortOrder?: string; // 升序asc、降序desc
filters?: Array<any>; // 筛选数据源,配置了数据源才展示
filterMode?: string; // menu/tree 默认menu 先只支持menu
@ -179,7 +182,6 @@ export interface ScrollProps {
export interface TableState {
selectedRowKeys: Array<string | number>;
dataSource: Array<any>;
expandedRowKeys: Array<string | number>;
colWidths: {
[name: string]: {
@ -218,7 +220,6 @@ export class Table extends React.PureComponent<TableProps, TableState> {
selectedRowKeys: props.rowSelection
? props.rowSelection.selectedRowKeys.map(key => key) || []
: [],
dataSource: props.dataSource || [], // 为了支持前端搜索
expandedRowKeys: [
...(props.expandable ? props.expandable.expandedRowKeys || [] : []),
...(props.expandable
@ -318,17 +319,9 @@ export class Table extends React.PureComponent<TableProps, TableState> {
}
componentDidUpdate(prevProps: TableProps, prevState: TableState) {
if (!isEqual(prevProps.dataSource, this.props.dataSource)) {
this.setState({
dataSource: [...this.props.dataSource]
});
}
if (
prevProps.autoFillHeight !== this.props.autoFillHeight ||
((!isEqual(prevState.dataSource, this.state.dataSource) ||
prevProps.loading !== this.props.loading) &&
this.props.autoFillHeight)
(prevProps.loading !== this.props.loading && this.props.autoFillHeight)
) {
this.updateAutoFillHeight();
}
@ -338,8 +331,8 @@ export class Table extends React.PureComponent<TableProps, TableState> {
const rowSelectionKeyField = this.getRowSelectionKeyField();
const childrenColumnName = this.getChildrenColumnName();
// 更新保存的已选择行数据
const selectedResult = getSelectedRows(
this.state.dataSource,
const selectedResult = getRowsByKeys(
this.props.dataSource,
this.state.selectedRowKeys,
rowSelectionKeyField,
childrenColumnName
@ -384,19 +377,19 @@ export class Table extends React.PureComponent<TableProps, TableState> {
// 展开行变化时触发
if (!isEqual(prevState.expandedRowKeys, this.state.expandedRowKeys)) {
if (this.props.expandable) {
const childrenColumnName = this.getChildrenColumnName();
const expandableKeyField = this.getExpandableKeyField();
const {onExpandedRowsChange} = this.props.expandable;
const expandedRows: Array<any> = [];
this.state.dataSource.forEach(item => {
if (
find(
this.state.expandedRowKeys,
key => key == item[this.getExpandableKeyField()]
)
) {
expandedRows.push(item);
}
});
onExpandedRowsChange && onExpandedRowsChange(expandedRows);
const expandedResult = getRowsByKeys(
this.props.dataSource,
this.state.selectedRowKeys,
expandableKeyField,
childrenColumnName
);
onExpandedRowsChange &&
onExpandedRowsChange(expandedResult.selectedRows);
}
}
@ -692,11 +685,11 @@ export class Table extends React.PureComponent<TableProps, TableState> {
const rowSelectionKeyField = this.getRowSelectionKeyField();
const dataList =
rowSelection && rowSelection.getCheckboxProps
? this.state.dataSource.filter((data, index) => {
? filterTree(dataSource, (data: any, index: number, level: number) => {
const props = rowSelection.getCheckboxProps(data, index);
return !props.disabled;
})
: this.state.dataSource;
: dataSource;
return (
<Head
@ -727,22 +720,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
sort: payload
});
if (onSort) {
onSort(payload);
} else {
if (typeof column.sorter === 'function') {
if (payload.orderBy) {
const sortList = [...this.state.dataSource];
this.setState({
dataSource: sortList.sort(
column.sorter as (a: any, b: any) => number
)
});
} else {
this.setState({dataSource: [...dataSource]});
}
}
}
onSort && onSort(payload);
}}
onSelectAll={async (
value: boolean,
@ -954,7 +932,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
}
async selectedSingleRow(value: boolean, data: any) {
const {onSelect} = this.props;
const {onSelect, dataSource} = this.props;
const selectedRowKeys = this.getSelectedRowKeys(value, data);
@ -962,8 +940,8 @@ export class Table extends React.PureComponent<TableProps, TableState> {
const rowSelectionKeyField = this.getRowSelectionKeyField();
const childrenColumnName = this.getChildrenColumnName();
const selectedResult = getSelectedRows(
this.state.dataSource,
const selectedResult = getRowsByKeys(
dataSource,
selectedRowKeys,
rowSelectionKeyField,
childrenColumnName
@ -1116,6 +1094,14 @@ export class Table extends React.PureComponent<TableProps, TableState> {
const tdColumns = this.tdColumns;
const hasScrollY = scroll && scroll.y;
const colCount = this.getExtraColumnCount();
const childrenColumnName = this.getChildrenColumnName();
const dataSource = getSortData(
this.props.dataSource,
tdColumns,
childrenColumnName,
this.state.sort
);
return (
<tbody ref={this.tbodyDom} className={cx('Table-tbody')}>
{!hasScrollY && !sticky && headSummary
@ -1141,7 +1127,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
</div>
</Cell>
</tr>
) : !this.state.dataSource.length ? (
) : !dataSource.length ? (
<tr className={cx('Table-row', 'Table-empty-row')}>
<Cell
classnames={cx}
@ -1156,9 +1142,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
</Cell>
</tr>
) : (
this.state.dataSource.map((data, index) =>
this.renderRow(data, index, [])
)
dataSource.map((data, index) => this.renderRow(data, index, []))
)}
</tbody>
);
@ -1207,9 +1191,9 @@ export class Table extends React.PureComponent<TableProps, TableState> {
}
renderSummaryRow(summary: any) {
const {classnames: cx, classPrefix} = this.props;
const {classnames: cx, classPrefix, dataSource} = this.props;
if (typeof summary === 'function') {
return summary(this.state.dataSource);
return summary(dataSource);
}
if (React.isValidElement(summary)) {
return summary;
@ -1222,7 +1206,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
isRightExpandable={this.isRightExpandable()}
classnames={cx}
classPrefix={classPrefix}
dataSource={this.state.dataSource}
dataSource={dataSource}
onMouseEnter={this.onRowMouseEnter}
onMouseLeave={this.onRowMouseLeave}
/>

View File

@ -2,7 +2,7 @@ import findLastIndex from 'lodash/findLastIndex';
import find from 'lodash/find';
import {isBreakpoint} from 'amis-core';
import {ColumnProps, SummaryProps, ThProps, TdProps} from './index';
import {ColumnProps, SummaryProps, ThProps, TdProps, SortProps} from './index';
// 当前行包含子数据
export function checkChildrenRow(data: any, childrenColumnName: string) {
@ -74,29 +74,29 @@ export function getAllSelectableRows(
return {rows: allRows, rowKeys: allRowKeys, restSelectedKeys};
}
export function getSelectedRows(
export function getRowsByKeys(
dataSource: Array<any>,
selectedRowKeys: Array<string | number>,
rowSelectionKeyField: string,
keys: Array<string | number>,
keyField: string,
childrenColumnName: string
) {
let selectedRows: Array<any> = [];
let unSelectedRows: Array<any> = [];
dataSource.forEach(data => {
if (selectedRowKeys.find(key => key === data[rowSelectionKeyField])) {
if (keys.find(key => key === data[keyField])) {
selectedRows.push(data);
} else {
unSelectedRows.push(data);
}
if (checkChildrenRow(data, childrenColumnName)) {
const childrenSelected = getSelectedRows(
const childrenResult = getRowsByKeys(
data[childrenColumnName],
selectedRowKeys,
rowSelectionKeyField,
keys,
keyField,
childrenColumnName
);
selectedRows = [...selectedRows, ...childrenSelected.selectedRows];
unSelectedRows = [...unSelectedRows, ...childrenSelected.unSelectedRows];
selectedRows = [...selectedRows, ...childrenResult.selectedRows];
unSelectedRows = [...unSelectedRows, ...childrenResult.unSelectedRows];
}
});
@ -299,3 +299,49 @@ export function levelsSplit(level: string) {
}
return level.split(',').map((l: string) => +l);
}
export function getSortData(
data: Array<any>,
columns: Array<ColumnProps>,
childrenColumnName: string,
sort?: SortProps
): Array<any> {
const cloneData = data.slice();
if (!sort?.orderBy) {
return cloneData;
}
const column = columns.find(column => column.name === sort.orderBy);
if (!column) {
return cloneData;
}
if (typeof column.sorter !== 'function') {
return cloneData;
}
const sortOrder = sort.orderDir;
return cloneData
.sort((record1, record2) => {
const compareResult =
typeof column.sorter === 'function'
? column.sorter(record1, record2, sortOrder)
: 0;
if (compareResult !== 0) {
return sortOrder === 'asc' ? compareResult : -compareResult;
}
return 0;
})
.map(record => {
const subRecords = (record as any)[childrenColumnName];
if (subRecords) {
return {
...record,
[childrenColumnName]: getSortData(
data,
columns,
childrenColumnName,
sort
)
};
}
return record;
});
}

View File

@ -28,7 +28,8 @@ import {
IRow2,
ClassNamesFn,
isArrayChildrenModified,
filterTarget
filterTarget,
changedEffect
} from 'amis-core';
import {Icon, Table, BadgeObject, SpinnerExtraProps} from 'amis-ui';
import type {
@ -158,6 +159,11 @@ export interface ColumnSchema {
quickEdit?: SchemaQuickEdit;
width?: string | number;
/**
* true
*/
canAccessSuperData?: boolean;
}
export interface RowSelectionOptionsSchema {
@ -398,6 +404,11 @@ export interface TableSchema2 extends BaseSchema {
*
*/
autoFillHeight?: boolean | AutoFillHeightObject;
/**
* false
*/
canAccessSuperData?: boolean;
}
// 事件调整 对应CRUD2里的事件配置也需要同步修改
@ -443,6 +454,7 @@ export interface Table2Props extends RendererProps, SpinnerExtraProps {
headingClassName?: string;
keepItemSelectionOnPageChange?: boolean;
maxKeepItemSelectionLength?: number;
canAccessSuperData?: boolean;
}
export default class Table2 extends React.Component<Table2Props, object> {
@ -489,7 +501,8 @@ export default class Table2 extends React.Component<Table2Props, object> {
reactions: Array<any> = [];
static defaultProps: Partial<Table2Props> = {
keyField: 'id'
keyField: 'id',
canAccessSuperData: false
};
constructor(props: Table2Props, context: IScopedContext) {
@ -504,12 +517,14 @@ export default class Table2 extends React.Component<Table2Props, object> {
columns,
rowSelection,
keyField,
primaryField
primaryField,
canAccessSuperData
} = props;
store.update({
columnsTogglable,
columns,
canAccessSuperData,
rowSelectionKeyField: primaryField || rowSelection?.keyField || keyField
});
Table2.syncRows(store, props, undefined) && this.syncSelected();
@ -657,11 +672,19 @@ export default class Table2 extends React.Component<Table2Props, object> {
const props = this.props;
const store = props.store;
if (anyChanged(['columnsTogglable'], prevProps, props)) {
store.update({
columnsTogglable: props.columnsTogglable
});
}
changedEffect(
['orderBy', 'columnsTogglable', 'canAccessSuperData'],
prevProps,
props,
changes => {
if (changes.orderBy && !props.onQuery) {
delete changes.orderBy;
}
store.update(changes as any, {
resolveDefinitions: props.resolveDefinitions
});
}
);
if (
anyChanged(['source', 'value', 'items'], prevProps, props) ||
@ -816,7 +839,6 @@ export default class Table2 extends React.Component<Table2Props, object> {
render,
store,
popOverContainer,
canAccessSuperData,
showBadge,
itemBadge,
classnames: cx
@ -887,17 +909,12 @@ export default class Table2 extends React.Component<Table2Props, object> {
levels?: Array<number>
) => {
const props: RenderProps = {};
const item =
store.getRowByIndex(rowIndex, [...(levels || [])]) || {};
const obj = {
children: this.renderCellSchema(column, {
data: record,
value: column.name
? resolveVariable(
column.name,
canAccessSuperData ? item.locals : item.data
)
? resolveVariable(column.name, record)
: column.name,
popOverContainer:
popOverContainer || this.getPopOverContainer,
@ -912,14 +929,14 @@ export default class Table2 extends React.Component<Table2Props, object> {
}
) => {
this.handleQuickChange(
item,
record,
values,
saveImmediately,
savePristine,
options
);
},
row: item,
row: record,
showBadge,
itemBadge
}),