feat: inputTable 支持前端过滤与排序并优化新增节点不可见的问题 (#11208)
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
gh-pages / build (20.x) (push) Waiting to run

* feat: inputTable 新增选项时自动跳转到对应的分页

* feat: inputTable 支持前端过滤与排序并优化新增节点不可见的问题
This commit is contained in:
liaoxuezhi 2024-11-18 10:37:39 +08:00 committed by GitHub
parent 539074e395
commit 4e4f06cdea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 401 additions and 228 deletions

View File

@ -478,18 +478,21 @@ order: 54
},
"body": [
{
"showIndex": true,
"type":"input-table",
"perPage": 5,
"name":"table",
"addable": true,
"showIndex": true,
"columns":[
{
"name": "a",
"label": "A"
"label": "A",
"searchable": true
},
{
"name": "b",
"label": "B"
"label": "B",
"sortable": true
}
]
}
@ -497,6 +500,96 @@ order: 54
}
```
## 前端过滤与排序
> 6.10.0 及以上版本
在列上配置 `searchable`、`sortable` 或者 `filterable` 来开启对应功能,用法与 [CRUD](../crud#快速搜索) 一致。
```schema: scope="body"
{
"type": "form",
"initApi": "/api/mock2/sample",
"body": [
{
"type":"input-table",
"perPage": 10,
"name":"rows",
"addable": true,
"copyable": true,
"editable": true,
"removable": true,
"showIndex": true,
"columns":[
{
"name": "grade",
"label": "CSS grade",
"filterable": {
"options": [
"A",
"B",
"C",
"D",
"X"
]
}
},
{
"name": "version",
"label": "Version",
"searchable": true
}
]
}
]
}
```
默认前端只是简单的过滤,如果要有复杂过滤,请通过 `matchFunc` 来实现,函数签名 `(items: Record<string, any>[], itemsRaw: Record<string, any>[], options: {query: string, columns: Column[], matchSorter: (a: any, b: any) => number}) => Record<string, any>[]`
- `items` 当前表格数据
- `itemsRaw` 与 items 一样,(历史用法,保持不变)
- `options` 配置
- `options.query` 查询条件
- `options.columns` 列配置
- `options.matchSorter` 系统默认的排序方法
```schema: scope="body"
{
"type": "form",
"initApi": "/api/mock2/sample",
"body": [
{
"type":"input-table",
"perPage": 10,
"name":"rows",
"addable": true,
"matchFunc": "const query = options.query;if (query.version === '>=20') {items = items.filter(item => parseFloat(item.version) >= 20);} else if (query.version ==='<20') {items = items.filter(item => parseFloat(item.version) < 20);}return items;",
"columns":[
{
"name": "id",
"label": "ID"
},
{
"name": "grade",
"label": "CSS grade"
},
{
"name": "version",
"label": "Version",
"filterable": {
"options": [
">=20",
"<20"
]
}
}
]
}
]
}
```
## 可拖拽
配置`"draggable": true`,实现可拖拽调整顺序

View File

@ -7,6 +7,7 @@ import {
createObject,
isObjectShallowModified,
sortArray,
applyFilters,
isEmpty,
qsstringify,
getVariable
@ -237,51 +238,11 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
)
: self.items.concat();
/** 字段的格式类型无法穷举,所以支持使用函数过滤 */
if (matchFunc && typeof matchFunc === 'function') {
items = matchFunc(items, self.data.itemsRaw, {
query: self.query,
columns: options.columns,
matchSorter: matchSorter
});
} else {
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(self.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
});
}
}
items = applyFilters(items, {
query: self.query,
columns: options.columns,
matchFunc: matchFunc
});
if (self.query.orderBy) {
const dir = /desc/i.test(self.query.orderDir) ? -1 : 1;

View File

@ -11,6 +11,7 @@ import isNumber from 'lodash/isNumber';
import isString from 'lodash/isString';
import qs from 'qs';
import {compile} from 'path-to-regexp';
import {matchSorter} from 'match-sorter';
import type {Schema, PlainObject, FunctionPropertyNames} from '../types';
@ -1497,6 +1498,57 @@ export function sortArray<T extends any>(
});
}
export function applyFilters<T extends any>(
items: Array<T>,
options: {
query: any;
columns?: Array<any>;
matchFunc?: Function;
}
) {
if (options.matchFunc && typeof options.matchFunc === 'function') {
items = options.matchFunc(items, items, options);
} else {
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(options.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
});
}
} /** 字段的格式类型无法穷举,所以支持使用函数过滤 */
return items;
}
// 只判断一层, 如果层级很深form-data 也不好表达。
export function hasFile(object: any): boolean {
return Object.keys(object).some(key => {

View File

@ -325,6 +325,15 @@ export interface CRUDCommonSchema extends BaseSchema, SpinnerExtraProps {
/**
* loadDataOnce时
*
*
*
* * `items`
* * `itemsRaw`
* * `options`
* * `options.query`
* * `options.columns`
* * `options.matchSorter`
* @since 3.5.0
*/
matchFunc?: string | any;

View File

@ -29,7 +29,8 @@ import {
isObject,
eachTree,
everyTree,
findTreeIndex
findTreeIndex,
applyFilters
} from 'amis-core';
import {Button, Icon} from 'amis-ui';
import omit from 'lodash/omit';
@ -39,6 +40,7 @@ import {SchemaApi, SchemaCollection, SchemaClassName} from '../../Schema';
import find from 'lodash/find';
import debounce from 'lodash/debounce';
import moment from 'moment';
import {sortArray, str2function} from 'amis-core';
import type {SchemaTokenizeableString} from '../../Schema';
@ -249,6 +251,21 @@ export interface TableControlSchema
* CSS样式类
*/
toolbarClassName?: SchemaClassName;
/**
* searchable true
*
*
*
* * `items`
* * `itemsRaw`
* * `options`
* * `options.query`
* * `options.columns`
* * `options.matchSorter`
* @since 6.10.0
*/
matchFunc?: string | any;
}
export interface TableProps
@ -260,10 +277,13 @@ export interface TableProps
export interface TableState {
items: Array<TableDataItem>;
filteredItems: Array<TableDataItem>;
columns: Array<any>;
editIndex: string;
isCreateMode?: boolean;
page?: number;
total?: number;
query?: any;
lastModifiedRow?: {
index: string;
data: Record<string, any>;
@ -341,11 +361,13 @@ export default class FormTable extends React.Component<TableProps, TableState> {
constructor(props: TableProps) {
super(props);
const {addHook} = props;
const items = Array.isArray(props.value) ? props.value.concat() : [];
this.state = {
columns: this.buildColumns(props),
editIndex: '',
items: Array.isArray(props.value) ? props.value.concat() : []
items: items,
...this.transformState(items)
};
this.entries = new SimpleMap();
@ -359,6 +381,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.subFormRef = this.subFormRef.bind(this);
this.subFormItemRef = this.subFormItemRef.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.handleTableQuery = this.handleTableQuery.bind(this);
this.emitValue = this.emitValue.bind(this);
this.tableRef = this.tableRef.bind(this);
this.flush = this.flush.bind(this);
@ -387,6 +410,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
toUpdate = {
...toUpdate,
items,
filteredItems: this.transformState(items),
editIndex: '',
columns: this.buildColumns(props)
};
@ -400,9 +424,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
if (props.value !== prevProps.value && props.value !== this.emittedValue) {
const items = Array.isArray(props.value) ? props.value.concat() : [];
toUpdate = {
...toUpdate,
items: Array.isArray(props.value) ? props.value.concat() : [],
items: items,
...this.transformState(items),
editIndex: ''
};
}
@ -417,6 +443,63 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.toDispose = [];
}
transformState(
items: Array<TableDataItem>,
state?: Partial<TableState>,
activeRow?: TableDataItem
): Pick<TableState, 'filteredItems' | 'total' | 'page'> {
const {perPage, matchFunc} = this.props;
let {query, page} = {...this.state, ...state};
const {orderBy, orderDir, ...rest} = query ?? {};
const fields = Object.keys(rest);
if (fields.length) {
// apply filters
items = applyFilters(items, {
query: rest,
columns: this.state.columns,
matchFunc:
typeof matchFunc === 'string' && matchFunc
? str2function(matchFunc, 'items', 'itemsRaw', 'options')
: typeof matchFunc === 'function'
? matchFunc
: undefined
});
}
if (orderBy) {
items = sortArray(
items.concat(),
orderBy,
typeof orderDir === 'string' && /desc/i.test(orderDir) ? -1 : 1
);
}
let total = items.length;
page = Math.min(
page ?? 1,
typeof perPage === 'number' ? Math.max(1, Math.ceil(total / perPage)) : 1
);
if (activeRow) {
const index = items.indexOf(activeRow);
if (~index) {
page = Math.ceil((index + 1) / perPage!);
}
}
if (typeof perPage === 'number' && perPage && items.length > perPage) {
items = items.slice((page - 1) * perPage, page * perPage);
}
return {
filteredItems: items,
page,
total
};
}
async flush() {
const subForms: Array<any> = [];
Object.keys(this.subForms).forEach(
@ -622,7 +705,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.setState(
{
items
items,
...this.transformState(items)
},
() => {
if (toAdd.length === 1 && needConfirm !== false) {
@ -661,7 +745,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.setState(
{
items
items,
...this.transformState(items)
},
() => {
onChange?.(items);
@ -698,28 +783,37 @@ export default class FormTable extends React.Component<TableProps, TableState> {
});
}
this.reUseRowId(items, originItems, next);
const newRow = items[next[0]];
const toUpdate = {
...this.transformState(items),
items
};
this.setState(
{
items
},
async () => {
// 派发add事件
const isPrevented = await this.dispatchEvent('add', {
index: next[next.length - 1],
indexPath: next.join('.'),
item: item
});
if (isPrevented) {
return;
}
if (needConfirm === false) {
this.emitValue();
} else {
this.startEdit(next.join('.'), true);
}
if (!toUpdate.filteredItems.includes(newRow)) {
// 如果新插入的待编辑的行不在过滤后的列表中,则需要更新过滤后的列表
const insertAfter = items[indexes[0]];
const idx = toUpdate.filteredItems.findIndex(
(a: any) => a === insertAfter
);
toUpdate.filteredItems.splice(idx + 1, 0, newRow);
}
this.setState(toUpdate, async () => {
// 派发add事件
const isPrevented = await this.dispatchEvent('add', {
index: next[next.length - 1],
indexPath: next.join('.'),
item: item
});
if (isPrevented) {
return;
}
);
if (needConfirm === false) {
this.emitValue();
} else {
this.startEdit(next.join('.'), true);
}
});
}
async addItem(
@ -728,7 +822,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
callback?: () => void
) {
index = index || `${this.state.items.length - 1}`;
const {needConfirm, scaffold, columns, data} = this.props;
const {needConfirm, scaffold, columns, data, perPage} = this.props;
let items = this.state.items.concat();
let value: TableDataItem = {
[PLACE_HOLDER]: true
@ -787,40 +881,50 @@ export default class FormTable extends React.Component<TableProps, TableState> {
let originHost = items;
items = spliceTree(items, next, 0, value);
this.reUseRowId(items, originHost, next);
const newRow = items[next[0]];
this.setState(
{
items,
// 需要一起修改state 不能分批次 setState
// 因为第一步添加成员,单元格的表单项如果有默认值就会触发 onChange
// 然后 handleTableSave 里面就会执行,因为没有 editIndex 会以为是批量更新 state 后 emitValue
// 而 emitValue 又会干掉 __isPlaceholder 后 onChange 出去一个新数组,空数组
// 然后 didUpdate 里面检测到上层 value 变化了,又重置 state导致新增无效
// 所以这里直接让 items 和 editIndex 一起调整,这样 handleTableSave 发现有 editIndex 会走不同逻辑,不会触发 emitValue
...((needConfirm === false
? {}
: {
editIndex: next.join('.'),
isCreateMode: true,
columns: this.buildColumns(this.props, true, `${index}`)
}) as any)
},
async () => {
if (isDispatch) {
// todo: add 无法阻止, state 状态也要还原
await this.dispatchEvent('add', {
index: next[next.length - 1],
indexPath: next.join('.'),
item: value
});
}
if (needConfirm === false) {
this.emitValue();
}
const toUpdate = {
items,
...this.transformState(items, undefined, newRow),
// 需要一起修改state 不能分批次 setState
// 因为第一步添加成员,单元格的表单项如果有默认值就会触发 onChange
// 然后 handleTableSave 里面就会执行,因为没有 editIndex 会以为是批量更新 state 后 emitValue
// 而 emitValue 又会干掉 __isPlaceholder 后 onChange 出去一个新数组,空数组
// 然后 didUpdate 里面检测到上层 value 变化了,又重置 state导致新增无效
// 所以这里直接让 items 和 editIndex 一起调整,这样 handleTableSave 发现有 editIndex 会走不同逻辑,不会触发 emitValue
...((needConfirm === false
? {}
: {
editIndex: next.join('.'),
isCreateMode: true,
columns: this.buildColumns(this.props, true, `${index}`)
}) as any)
};
callback?.();
if (!toUpdate.filteredItems.includes(newRow)) {
// 如果新插入的待编辑的行不在过滤后的列表中,则需要更新过滤后的列表
const insertAfter = items[indexes[0]];
const idx = toUpdate.filteredItems.findIndex(
(a: any) => a === insertAfter
);
toUpdate.filteredItems.splice(idx + 1, 0, newRow);
}
this.setState(toUpdate, async () => {
if (isDispatch) {
// todo: add 无法阻止, state 状态也要还原
await this.dispatchEvent('add', {
index: next[next.length - 1],
indexPath: next.join('.'),
item: value
});
}
);
if (needConfirm === false) {
this.emitValue();
}
callback?.();
});
// 阻止触发 onAction 动作
// 因为 footerAddButton 的 onClick 也绑定了这个
@ -969,6 +1073,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
{
editIndex: '',
items: items,
...this.transformState(items),
columns: this.buildColumns(this.props)
},
async () => {
@ -1020,6 +1125,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
{
editIndex: '',
items: items,
...this.transformState(items),
columns: this.buildColumns(this.props),
lastModifiedRow: undefined
},
@ -1090,7 +1196,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.setState(
{
items: newValue
items: newValue,
...this.transformState(newValue)
},
async () => {
// change value
@ -1109,9 +1216,14 @@ export default class FormTable extends React.Component<TableProps, TableState> {
);
}
rowPathPlusOffset(path: string, offset = 0) {
convertToRawPath(path: string, state?: Partial<TableState>) {
const {filteredItems, items} = {...this.state, ...state};
const list = path.split('.').map((item: any) => parseInt(item, 10));
list[0] += offset;
const firstRow = filteredItems[list[0]];
list[0] = items.findIndex(item => item === firstRow);
if (list[0] === -1) {
throw new Error('row not found');
}
return list.join('.');
}
@ -1163,15 +1275,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return rowProps;
}
const perPage = this.props.perPage;
const page = this.state.page || 1;
let offset = 0;
if (typeof perPage === 'number' && perPage) {
offset = (page - 1) * perPage;
}
rowProps.quickEditEnabled =
this.state.editIndex === this.rowPathPlusOffset(item.path, offset);
this.state.editIndex === this.convertToRawPath(item.path);
return rowProps;
}
@ -1196,15 +1301,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset,
inputTableCanAddItem
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
inputTableCanAddItem: boolean;
inputTableCanRemoveItem: boolean;
}) =>
@ -1220,12 +1321,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
disabled={disabled}
onClick={this.addItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
this.convertToRawPath(rowIndexPath),
undefined,
undefined
)}
testIdBuilder={testIdBuilder?.getChild(
`addRow-${rowIndex + offset}`
`addRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.addBtnIcon ? (
@ -1245,15 +1346,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset,
row
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
row: any;
}) =>
this.state.editIndex && needConfirm !== false ? null : (
@ -1267,12 +1364,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
disabled={disabled}
onClick={this.subAddItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
this.convertToRawPath(rowIndexPath),
undefined,
row
)}
testIdBuilder={testIdBuilder?.getChild(
`subAddRow-${rowIndex + offset}`
`subAddRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.subAddBtnIcon ? (
@ -1292,17 +1389,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
if (!isStatic && props.copyable && props.showCopyBtn !== false) {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
children: ({key, rowIndexPath}: {key: any; rowIndexPath: string}) =>
this.state.editIndex && needConfirm !== false ? null : (
<Button
classPrefix={ns}
@ -1314,11 +1401,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
disabled={disabled}
onClick={this.copyItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
this.convertToRawPath(rowIndexPath),
undefined
)}
testIdBuilder={testIdBuilder?.getChild(
`copyRow-${rowIndex + offset}`
`copyRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.copyBtnIcon ? (
@ -1407,16 +1494,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
data,
offset
data
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
data: any;
offset: number;
}) =>
this.state.editIndex ||
(data && data.hasOwnProperty(PLACE_HOLDER)) ? null : (
@ -1431,10 +1514,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
disabled={disabled}
onClick={() =>
this.editItem(this.rowPathPlusOffset(rowIndexPath, offset))
this.editItem(this.convertToRawPath(rowIndexPath))
}
testIdBuilder={testIdBuilder?.getChild(
`editRow-${rowIndex + offset}`
`editRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{/* 兼容之前的写法 */}
@ -1462,19 +1545,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
!isStatic &&
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
this.state.editIndex ===
this.rowPathPlusOffset(rowIndexPath, offset) ? (
children: ({key, rowIndexPath}: {key: any; rowIndexPath: string}) =>
this.state.editIndex === this.convertToRawPath(rowIndexPath) ? (
<Button
classPrefix={ns}
size="sm"
@ -1486,7 +1558,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
onClick={this.confirmEdit}
testIdBuilder={testIdBuilder?.getChild(
`confirmRow-${rowIndex + offset}`
`confirmRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.confirmBtnIcon ? (
@ -1505,19 +1577,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
!isStatic &&
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
this.state.editIndex ===
this.rowPathPlusOffset(rowIndexPath, offset) ? (
children: ({key, rowIndexPath}: {key: any; rowIndexPath: string}) =>
this.state.editIndex === this.convertToRawPath(rowIndexPath) ? (
<Button
classPrefix={ns}
size="sm"
@ -1529,7 +1590,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
onClick={this.cancelEdit}
testIdBuilder={testIdBuilder?.getChild(
`cancelRow-${rowIndex + offset}`
`cancelRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.cancelBtnIcon ? (
@ -1570,17 +1631,13 @@ export default class FormTable extends React.Component<TableProps, TableState> {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
data,
offset,
inputTableCanRemoveItem
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
data: any;
offset: number;
inputTableCanRemoveItem: boolean;
}) =>
((this.state.editIndex ||
@ -1597,10 +1654,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
disabled={disabled}
onClick={this.removeItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset)
this.convertToRawPath(rowIndexPath)
)}
testIdBuilder={testIdBuilder?.getChild(
`delRow-${rowIndex + offset}`
`delRow-${this.convertToRawPath(rowIndexPath)}`
)}
>
{props.deleteBtnIcon ? (
@ -1649,10 +1706,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
label: __('Table.index'),
width: 50,
children: (props: any) => {
const indexes = (props.rowIndexPath as string)
const indexes = this.convertToRawPath(props.rowIndexPath as string)
.split('.')
.map(item => parseInt(item, 10) + 1);
indexes[0] += props.offset;
return (
<td className={props.className}>
{props.cellPrefix}
@ -1696,7 +1752,6 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.setState(
(state, props) => {
const newState = {};
const {perPage} = props;
const editIndex = state.editIndex;
const lastModifiedRow = state.lastModifiedRow;
@ -1718,6 +1773,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
Object.assign(newState, {
items,
filteredItems: state.filteredItems.map(a =>
a === origin ? value : a
),
/** 记录最近一次编辑记录,用于取消编辑数据回溯, */
...(lastModifiedRow?.index === editIndex
? {}
@ -1730,16 +1788,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return newState;
}
const page = state.page;
let items = state.items.concat();
if (Array.isArray(rows)) {
(rowIndexes as Array<string>).forEach((rowIndex, index) => {
rowIndex = this.convertToRawPath(rowIndex, state);
const indexes = rowIndex.split('.').map(item => parseInt(item, 10));
if (page && page > 1 && typeof perPage === 'number') {
indexes[0] += (page - 1) * perPage;
}
// const origin = getTree(items, indexes);
const data = {
...getTree(rows, indexes)
@ -1748,14 +1802,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
items = spliceTree(items, indexes, 1, data);
});
} else {
rowIndexes = this.convertToRawPath(rowIndexes as string, state);
const indexes = (rowIndexes as string)
.split('.')
.map(item => parseInt(item, 10));
if (page && page > 1 && typeof perPage === 'number') {
indexes[0] += (page - 1) * perPage;
}
// const origin = getTree(items, indexes);
const data = {...rows};
@ -1766,7 +1817,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
Object.assign(newState, {
items
items,
...this.transformState(items, state)
});
callback = this.lazyEmitValue;
@ -1798,7 +1850,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
);
callback = state.editIndex == row.path ? undefined : this.lazyEmitValue;
return {
items
items,
...this.transformState(items)
};
},
() => {
@ -1816,7 +1869,17 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
handlePageChange(page: number) {
this.setState({page});
this.setState({
...this.transformState(this.state.items, {page})
});
}
handleTableQuery(query: any): any {
query = {...this.state.query, ...query};
this.setState({
query,
...this.transformState(this.state.items, {query})
});
}
/**
@ -1828,17 +1891,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
*/
@autobind
handlePristineChange(data: Record<string, any>, rowIndex: string) {
const {needConfirm, perPage} = this.props;
const {needConfirm} = this.props;
const indexes = rowIndex.split('.').map(item => parseInt(item, 10));
this.setState(
prevState => {
let items = prevState.items.concat();
const page = prevState.page;
if (page && page > 1 && typeof perPage === 'number') {
indexes[0] += (page - 1) * perPage;
}
const origin = getTree(items, indexes);
const value = {
...origin,
@ -1850,7 +1908,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.reUseRowId(items, originItems, indexes);
return {
items
items,
...this.transformState(items)
};
},
() => {
@ -1926,20 +1985,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return null;
}
let items = this.state.items;
let showPager = false;
const query = this.state.query;
const filteredItems = this.state.filteredItems;
let showPager = typeof perPage === 'number';
let page = this.state.page || 1;
let offset = 0;
let lastPage = 1;
if (typeof perPage === 'number' && perPage && items.length > perPage) {
lastPage = Math.ceil(items.length / perPage);
if (page > lastPage) {
page = lastPage;
}
items = items.slice((page - 1) * perPage, page * perPage);
showPager = true;
offset = (page - 1) * perPage;
}
// 底部新增按钮是否显示
const footerAddBtnVisible =
@ -1969,7 +2018,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
saveImmediately: true,
disabled,
draggable: draggable && !this.state.editIndex,
items: items,
items: filteredItems,
getEntryId: this.getEntryId,
reUseRow: 'match', // 寻找 id 相同的行,更新数据
onSave: this.handleTableSave,
@ -1983,11 +2032,14 @@ export default class FormTable extends React.Component<TableProps, TableState> {
combineFromIndex: combineFromIndex,
expandConfig,
canAccessSuperData,
offset,
rowClassName,
rowClassNameExpr,
onPristineChange: this.handlePristineChange,
testIdBuilder: testIdBuilder?.getChild('table')
testIdBuilder: testIdBuilder?.getChild('table'),
onQuery: this.handleTableQuery,
query: query,
orderBy: query?.orderBy,
orderDir: query?.orderDir
}
)}
{footerAddBtnVisible || showPager ? (
@ -2021,10 +2073,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
{
activePage: page,
perPage,
total: this.state.items.length,
total: this.state.total,
onPageChange: this.handlePageChange,
className: 'InputTable-pager',
testIdBuilder: testIdBuilder?.getChild('page')
testIdBuilder: testIdBuilder?.getChild('page'),
disabled: !!this.state.editIndex
}
)
: null}
@ -2053,7 +2106,7 @@ export class TableControlRenderer extends FormTable {
const indexes = i.split('.').map(item => parseInt(item, 10));
items = spliceTree(items, indexes, 1, value);
});
this.setState({items}, () => {
this.setState({items, ...this.transformState(items)}, () => {
this.emitValue();
});
} else if (condition !== undefined) {
@ -2076,14 +2129,16 @@ export class TableControlRenderer extends FormTable {
});
await Promise.all(promises.map(fn => fn()));
this.setState({items}, () => {
this.setState({items, ...this.transformState(items)}, () => {
this.emitValue();
});
} else {
// 如果setValue动作没有传入index则直接替换组件数据
const items = [...value];
this.setState(
{
items: [...value]
items: items,
...this.transformState(items)
},
() => {
this.emitValue();
@ -2167,7 +2222,8 @@ export class TableControlRenderer extends FormTable {
this.setState(
{
items
items,
...this.transformState(items)
},
() => {
if (toAdd.length === 1 && needConfirm !== false) {
@ -2234,7 +2290,8 @@ export class TableControlRenderer extends FormTable {
}
this.setState(
{
items: items
items: items,
...this.transformState(items)
},
() => {
onChange?.(items);
@ -2257,7 +2314,8 @@ export class TableControlRenderer extends FormTable {
const newItems = Array.isArray(pristineVal) ? pristineVal : [];
this.setState(
{
items: newItems
items: newItems,
...this.transformState(newItems)
},
() => {
onChange?.(newItems);