mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: inputTable 支持前端过滤与排序并优化新增节点不可见的问题 (#11208)
* feat: inputTable 新增选项时自动跳转到对应的分页 * feat: inputTable 支持前端过滤与排序并优化新增节点不可见的问题
This commit is contained in:
parent
539074e395
commit
4e4f06cdea
@ -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`,实现可拖拽调整顺序
|
||||
|
@ -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;
|
||||
|
@ -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 => {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user