fix: 修复 InputTable 编辑树形结构数据的问题 Close: #9379 (#9852)

This commit is contained in:
liaoxuezhi 2024-03-26 17:14:57 +08:00 committed by GitHub
parent 9e57111669
commit 9bf28b2f85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 412 additions and 112 deletions

View File

@ -687,6 +687,54 @@ order: 54
}
```
## 树形模式
配置 `childrenAddable` 为 true可以开启新增子节点功能。
```schema: scope="body"
{
"type": "form",
"data": {
"table": [
{
"a": "a1",
"b": "b1"
},
{
"a": "a2",
"b": "b2"
},
{
"a": "a3",
"b": "b3"
}
]
},
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-table",
"name": "table",
"label": "Table",
"addable": true,
"childrenAddable": true,
"editable": true,
"removable": true,
"columns": [
{
"label": "A",
"name": "a"
},
{
"label": "B",
"name": "b"
}
]
}
]
}
```
## 获取父级数据
默认情况下Table 内表达项无法获取父级数据域的数据,如下,我们添加 Table 表单项时,尽管 Table 内的文本框的`name`与父级数据域中的`super_text`变量同名,但是没有自动映射值。
@ -869,6 +917,7 @@ order: 54
| ---------------------------- | ----------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------- |
| type | `string` | `"input-table"` | 指定为 Table 渲染器 |
| addable | `boolean` | `false` | 是否可增加一行 |
| childrenAddable | `boolean` | `false` | 是否可增加子级节点 |
| editable | `boolean` | `false` | 是否可编辑 |
| removable | `boolean` | `false` | 是否可删除 |
| showTableAddBtn | `boolean` | `true` | 是否显示表格操作栏添加按钮,前提是要开启可新增功能 |
@ -879,6 +928,8 @@ order: 54
| deleteApi | [API](../../../docs/types/api) | - | 删除时提交的 API |
| addBtnLabel | `string` | | 增加按钮名称 |
| addBtnIcon | `string` | `"plus"` | 增加按钮图标 |
| subAddBtnLabel | `string` | | 子级增加按钮名称 |
| subAddBtnIcon | `string` | `"sub-plus"` | 子级增加按钮图标 |
| copyBtnLabel | `string` | | 复制按钮文字 |
| copyBtnIcon | `string` | `"copy"` | 复制按钮图标 |
| editBtnLabel | `string` | `""` | 编辑按钮名称 |

View File

@ -1817,7 +1817,7 @@ popOver 的其它配置请参考 [popover](./popover)
> 1.5.0 及以上版本
通过 `autoFillHeight` 可以让表格内容区自适应高度,具体效果请看这个[示例](../../../examples/crud/auto-fill)。
通过 `autoFillHeight` 可以让表格内容区自适应高度,具体效果请看这个[示例](../../examples/crud/auto-fill)。
它的展现效果是整个内容区域高度自适应,表格内容较多时在内容区域内出滚动条,这样顶部筛选和底部翻页的位置都是固定的。

View File

@ -44,7 +44,8 @@ function initChildren(
depth: number,
pindex: number,
parentId: string,
path: string = ''
path: string = '',
getEntryId?: (entry: any, index: number) => string
): any {
depth += 1;
return children.map((item, index) => {
@ -53,7 +54,9 @@ function initChildren(
: {
item
};
const id = item.__id ?? guid();
const id = String(
getEntryId ? getEntryId(item, index) : item.__id ?? guid()
);
return {
// id: String(item && (item as any)[self.primaryField] || `${pindex}-${depth}-${key}`),
@ -72,7 +75,14 @@ function initChildren(
rowSpans: {},
children:
item && Array.isArray(item.children)
? initChildren(item.children, depth, index, id, `${path}${index}.`)
? initChildren(
item.children,
depth,
index,
id,
`${path}${index}.`,
getEntryId
)
: []
};
});
@ -387,6 +397,13 @@ export const Row = types
);
},
setExpanded(expanded: boolean) {
(getParent(self, self.depth * 2) as ITableStore).setExpanded(
self as IRow,
expanded
);
},
change(values: object, savePristine?: boolean) {
self.data = immutableExtends(self.data, values);
savePristine && (self.pristine = self.data);
@ -1450,7 +1467,14 @@ export const TableStore = iRendererStore
loading: false,
children:
item && Array.isArray(item.children)
? initChildren(item.children, 1, index, id, `${index}.`)
? initChildren(
item.children,
1,
index,
id,
`${index}.`,
getEntryId
)
: []
};
});
@ -1767,6 +1791,19 @@ export const TableStore = iRendererStore
}
}
function setExpanded(row: IRow | string, expanded: boolean) {
const id = typeof row === 'string' ? row : row.id;
const idx = self.expandedRows.indexOf(id);
if (expanded) {
if (!~idx) {
self.expandedRows.push(id);
}
} else {
~idx && self.expandedRows.splice(idx, 1);
}
}
function collapseAllAtDepth(depth: number) {
let rows = self.getExpandedRows().filter(item => item.depth !== depth);
self.expandedRows.replace(rows.map(item => item.id));
@ -1928,6 +1965,7 @@ export const TableStore = iRendererStore
getToggleShiftRows,
toggleExpandAll,
toggleExpanded,
setExpanded,
collapseAllAtDepth,
clear,
setOrderByInfo,

View File

@ -643,6 +643,11 @@
> .#{$ns}Spinner {
vertical-align: middle;
}
> [data-role='form-item'] {
display: inline-block;
min-width: px2rem(160px);
}
}
}

View File

@ -22,6 +22,7 @@ import ArrowDoubleLeftIcon from '../icons/arrow-double-left.svg';
import ArrowDoubleRightIcon from '../icons/arrow-double-right.svg';
import CheckIcon from '../icons/check.svg';
import PlusIcon from '../icons/plus.svg';
import SubPlusIcon from '../icons/sub-plus.svg';
import MinusIcon from '../icons/minus.svg';
import PencilIcon from '../icons/pencil.svg';
import ViewIcon from '../icons/view.svg';
@ -161,6 +162,7 @@ registerIcon('prev', LeftArrowIcon);
registerIcon('next', RightArrowIcon);
registerIcon('check', CheckIcon);
registerIcon('plus', PlusIcon);
registerIcon('sub-plus', SubPlusIcon);
registerIcon('add', PlusIcon);
registerIcon('minus', MinusIcon);
registerIcon('pencil', PencilIcon);

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5078" xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M128.010044 134.579337h767.979912c47.102768 0 85.355482 38.106432 85.355481 85.28234v206.769449c0 47.175909-38.252714 85.355482-85.355481 85.355481H256.884388v298.561334c0 2.925638 1.023973 5.485571 3.14506 7.533517a10.239732 10.239732 0 0 0 7.533517 3.14506h220.885651a31.962592 31.962592 0 1 1 0 63.998326h-220.885651a71.970689 71.970689 0 0 1-52.807761-21.942283 71.970689 71.970689 0 0 1-21.869142-52.73462V511.986607H128.010044A85.355482 85.355482 0 0 1 42.654563 426.631126V219.861677c0-47.175909 38.252714-85.355482 85.355481-85.355481z m0 313.408945h767.979912a20.479464 20.479464 0 0 0 15.067034-6.216981 20.552605 20.552605 0 0 0 6.290121-15.140175V219.861677c0-5.851276-2.121087-10.971142-6.290121-15.067034a20.479464 20.479464 0 0 0-15.067034-6.290121H128.010044c-5.851276 0-10.971142 2.121087-15.067034 6.290121a20.552605 20.552605 0 0 0-6.290121 15.067034v206.769449c0 5.851276 2.121087 10.971142 6.290121 15.140175a20.552605 20.552605 0 0 0 15.067034 6.216981z m575.69237 532.173508a31.962592 31.962592 0 0 1-31.962592-31.962593v-62.974353h-62.75493a32.035733 32.035733 0 0 1 0-63.998326h62.75493v-62.754929a31.962592 31.962592 0 1 1 63.998325 0v62.754929h62.901212a31.962592 31.962592 0 1 1 0 63.998326h-62.901212v62.901212a31.962592 31.962592 0 0 1-32.035733 32.035734z"
p-id="5079"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -253,6 +253,7 @@ register('de-DE', {
'Table.valueField': 'valueField muss vorhanden sein',
'Table.index': 'Index',
'Table.add': 'Neu',
'Table.subAddRow': 'Unterzeile hinzufügen',
'Table.addButtonDisabledTip':
'Reichen Sie bei der Inhaltsbearbeitung zuerst ein und erstellen Sie dann eine neue Option',
'Table.toggleColumn': 'Spalten anzeigen',

View File

@ -227,6 +227,7 @@ register('en-US', {
'System.requestError': 'Request error: ',
'System.requestErrorStatus': 'Request error, status code: ',
'Table.addRow': 'Add row',
'Table.subAddRow': 'Add sub row',
'Table.copyRow': 'Copy row',
'Table.columnsVisibility': 'Click to control columns visibility',
'Table.deleteRow': 'Delete current row',

View File

@ -232,6 +232,7 @@ register('zh-CN', {
'System.requestError': '接口报错:',
'System.requestErrorStatus': '接口出错,状态码是:',
'Table.addRow': '新增一行',
'Table.subAddRow': '新增孩子',
'Table.copyRow': '复制一行',
'Table.columnsVisibility': '点击选择显示列',
'Table.deleteRow': '删除当前行',

View File

@ -26,7 +26,10 @@ import {
ListenerAction,
evalExpressionWithConditionBuilder,
mapTree,
isObject
isObject,
eachTree,
everyTree,
findTreeIndex
} from 'amis-core';
import {Button, Icon} from 'amis-ui';
import omit from 'lodash/omit';
@ -50,6 +53,11 @@ export interface TableControlSchema
*/
addable?: boolean;
/**
*
*/
childrenAddable?: boolean;
/**
*
*/
@ -90,6 +98,16 @@ export interface TableControlSchema
*/
addBtnIcon?: string;
/**
*
*/
subAddBtnLabel?: string;
/**
*
*/
subAddBtnIcon?: string;
/**
*
*/
@ -231,11 +249,11 @@ export interface TableProps
export interface TableState {
items: Array<any>;
columns: Array<any>;
editIndex: number;
editIndex: string;
isCreateMode?: boolean;
page?: number;
lastModifiedRow?: {
index: number;
index: string;
data: Record<string, any>;
};
}
@ -260,6 +278,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
placeholder: 'placeholder.empty',
scaffold: {},
addBtnIcon: 'plus',
subAddBtnIcon: 'sub-plus',
copyBtnIcon: 'copy',
editBtnIcon: 'pencil',
deleteBtnIcon: 'minus',
@ -306,7 +325,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
this.state = {
columns: this.buildColumns(props),
editIndex: -1,
editIndex: '',
items: Array.isArray(props.value) ? props.value.concat() : []
};
@ -340,7 +359,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
toUpdate = {
...toUpdate,
items,
editIndex: -1,
editIndex: '',
columns: this.buildColumns(props)
};
}
@ -356,7 +375,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
toUpdate = {
...toUpdate,
items: Array.isArray(props.value) ? props.value.concat() : [],
editIndex: -1
editIndex: ''
};
}
@ -409,7 +428,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
const maxLength = this.resolveVariableProps(this.props, 'maxLength');
// todo: 如果当前正在编辑中,表单提交了,应该先让正在编辑的东西提交然后再做验证。
if (~this.state.editIndex) {
if (this.state.editIndex) {
return __('Table.editing');
}
@ -546,7 +565,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
},
() => {
if (toAdd.length === 1 && needConfirm !== false) {
this.startEdit(items.length - 1, true);
this.startEdit(`${items.length - 1}`, true);
} else {
onChange?.(items);
}
@ -555,7 +574,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return;
} else {
return this.addItem(items.length - 1, false);
return this.addItem(`${items.length - 1}`, false);
}
} else if (actionType === 'remove' || actionType === 'delete') {
if (!valueField) {
@ -564,17 +583,18 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return env.alert(__('Table.playload'));
}
const items = this.state.items.concat();
let items = this.state.items.concat();
let toRemove: any = dataMapping(action.payload, ctx);
toRemove = Array.isArray(toRemove) ? toRemove : [toRemove];
toRemove.forEach((toRemove: any) => {
const idx = findIndex(
const idex = findTreeIndex(
items,
item => item[valueField as string] == toRemove[valueField as string]
);
if (~idx) {
items.splice(idx, 1);
if (idex?.length) {
items = spliceTree(items, idex, 1);
}
});
@ -596,43 +616,56 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return onAction && onAction(action, ctx, ...rest);
}
async copyItem(index: number) {
async copyItem(index: string) {
const {needConfirm} = this.props;
const items = this.state.items.concat();
let items = this.state.items.concat();
const indexes = index.split('.').map(item => parseInt(item, 10));
const next = indexes.concat();
next[next.length - 1] += 1;
const originItems = items;
if (needConfirm === false) {
items.splice(index + 1, 0, items[index]);
items = spliceTree(items, next, 0, getTree(items, indexes));
} else {
// 复制相当于新增一行
// 需要同addItem一致添加__placeholder属性
items.splice(index + 1, 0, {
...items[index],
items = spliceTree(items, next, 0, {
...getTree(items, indexes),
__isPlaceholder: true
});
}
index = Math.min(index + 1, items.length - 1);
this.reUseRowId(items, originItems, next);
this.setState(
{
items
},
async () => {
// 派发add事件
const isPrevented = await this.dispatchEvent('add', {index});
const isPrevented = await this.dispatchEvent('add', {
index: next[next.length - 1],
value: getTree(items, next)
});
if (isPrevented) {
return;
}
if (needConfirm === false) {
this.emitValue();
} else {
this.startEdit(index, true);
this.startEdit(next.join('.'), true);
}
}
);
}
async addItem(index: number, isDispatch: boolean = true) {
async addItem(
index?: string,
isDispatch: boolean = true,
callback?: () => void
) {
index = index || `${this.state.items.length - 1}`;
const {needConfirm, scaffold, columns, data} = this.props;
const items = this.state.items.concat();
let items = this.state.items.concat();
let value: any = {
__isPlaceholder: true
};
@ -680,8 +713,13 @@ export default class FormTable extends React.Component<TableProps, TableState> {
delete value.__isPlaceholder;
}
items.splice(index + 1, 0, value);
index = Math.min(index + 1, items.length - 1);
const indexes = index.split('.').map(item => parseInt(item, 10));
const next = indexes.concat();
next[next.length - 1] += 1;
let originHost = items;
items = spliceTree(items, next, 0, value);
this.reUseRowId(items, originHost, next);
this.setState(
{
@ -695,21 +733,24 @@ export default class FormTable extends React.Component<TableProps, TableState> {
...((needConfirm === false
? {}
: {
editIndex: index,
editIndex: next.join('.'),
isCreateMode: true,
columns: this.buildColumns(this.props, true, index)
columns: this.buildColumns(this.props, true, `${index}`)
}) as any)
},
async () => {
if (isDispatch) {
const isPrevented = await this.dispatchEvent('add', {index});
if (isPrevented) {
return;
}
// todo: add 无法阻止, state 状态也要还原
await this.dispatchEvent('add', {
index: next[next.length - 1],
value
});
}
if (needConfirm === false) {
this.emitValue();
}
callback?.();
}
);
@ -722,14 +763,24 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return false;
}
async subAddItem(index?: string, isDispatch: boolean = true, item?: any) {
return this.addItem(index + '.-1', isDispatch, () => {
item?.setExpanded(true);
});
}
/**
*
* @param index
*/
async editItem(index: number) {
async editItem(index: string) {
const {items} = this.state;
const item = items[index];
const isPrevented = await this.dispatchEvent('edit', {index, item});
const indexes = index.split('.').map(item => parseInt(item, 10));
const item = getTree(items, indexes);
const isPrevented = await this.dispatchEvent('edit', {
index: indexes[indexes.length - 1],
item
});
!isPrevented && this.startEdit(index, true);
}
@ -753,7 +804,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return !!rendererEvent?.prevented;
}
startEdit(index: number, isCreate: boolean = false) {
startEdit(index: string, isCreate: boolean = false) {
this.setState({
editIndex: index,
isCreateMode: isCreate,
@ -777,14 +828,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
);
subFormItems.forEach(item => item.props.onFlushChange?.());
const validateForms: Array<any> = [];
Object.keys(this.subForms).forEach(key => {
const arr = key.split('-');
const num = +arr[1];
if (num === this.state.editIndex && this.subForms[key]) {
validateForms.push(this.subForms[key]);
}
});
const validateForms: Array<any> = subForms;
const results = await Promise.all(
validateForms
@ -797,14 +841,18 @@ export default class FormTable extends React.Component<TableProps, TableState> {
return;
}
const items = this.state.items.concat();
let items = this.state.items.concat();
const indexes = this.state.editIndex
.split('.')
.map(item => parseInt(item, 10));
let item = {
...items[this.state.editIndex]
...getTree(items, indexes)
};
const isNew = !!item.__isPlaceholder;
const confirmEventName = isNew ? 'addConfirm' : 'editConfirm';
let isPrevented = await this.dispatchEvent(confirmEventName, {
index: this.state.editIndex,
index: indexes[indexes.length - 1],
item
});
if (isPrevented) {
@ -826,7 +874,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
env.notify('error', apiMsg ?? (remote.msg || __('saveFailed')));
const failEventName = isNew ? 'addFail' : 'editFail';
this.dispatchEvent(failEventName, {
index: this.state.editIndex,
index: indexes[indexes.length - 1],
item,
error: remote
});
@ -840,11 +888,13 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
delete item.__isPlaceholder;
items.splice(this.state.editIndex, 1, item);
const originItems = items;
items = spliceTree(items, indexes, 1, item);
this.reUseRowId(items, originItems, indexes);
this.setState(
{
editIndex: -1,
editIndex: '',
items: items,
columns: this.buildColumns(this.props)
},
@ -855,7 +905,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
const successEventName = isNew ? 'addSuccess' : 'editSuccess';
this.dispatchEvent(successEventName, {
index: this.state.editIndex,
index: indexes[indexes.length - 1],
item
});
}
@ -863,16 +913,20 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
cancelEdit() {
const items = this.state.items.concat();
let items = this.state.items.concat();
const lastModifiedRow = this.state.lastModifiedRow;
const indexes = this.state.editIndex
.split('.')
.map(item => parseInt(item, 10));
let item = {
...items[this.state.editIndex]
...getTree(items, indexes)
};
const isNew = !!item.__isPlaceholder;
const originItems = items;
if (isNew) {
items.splice(this.state.editIndex, 1);
items = spliceTree(items, indexes, 1);
} else {
/** 恢复编辑前的值 */
if (
@ -880,16 +934,17 @@ export default class FormTable extends React.Component<TableProps, TableState> {
~lastModifiedRow?.index &&
isObject(lastModifiedRow?.data)
) {
items.splice(this.state.editIndex, 1, {
items = spliceTree(items, indexes, 1, {
...item,
...lastModifiedRow.data
});
}
}
this.reUseRowId(items, originItems, indexes);
this.setState(
{
editIndex: -1,
editIndex: '',
items: items,
columns: this.buildColumns(this.props),
lastModifiedRow: undefined
@ -898,7 +953,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
);
}
async removeItem(index: number) {
async removeItem(index: string) {
const {
value,
onChange,
@ -910,13 +965,17 @@ export default class FormTable extends React.Component<TableProps, TableState> {
} = this.props;
let newValue = Array.isArray(value) ? value.concat() : [];
const item = newValue[index];
const indexes = index.split('.').map(item => parseInt(item, 10));
const item = getTree(newValue, indexes);
if (!item) {
return;
}
let isPrevented = await this.dispatchEvent('delete', {index, item});
let isPrevented = await this.dispatchEvent('delete', {
index: indexes[indexes.length - 1],
item
});
if (isPrevented) {
return;
}
@ -939,15 +998,55 @@ export default class FormTable extends React.Component<TableProps, TableState> {
'error',
(deleteApi as ApiObject)?.messages?.failed ?? __('deleteFailed')
);
this.dispatchEvent('deleteFail', {index, item, error: result});
this.dispatchEvent('deleteFail', {
index: indexes[indexes.length - 1],
item,
error: result
});
return;
}
}
this.removeEntry(item);
newValue.splice(index, 1);
const originItems = newValue;
newValue = spliceTree(newValue, indexes, 1);
this.reUseRowId(newValue, originItems, indexes);
onChange(newValue);
this.dispatchEvent('deleteSuccess', {value: newValue, index, item});
this.dispatchEvent('deleteSuccess', {
value: newValue,
index: indexes[indexes.length - 1],
item
});
}
rowPathPlusOffset(path: string, offset = 0) {
const list = path.split('.').map((item: any) => parseInt(item, 10));
list[0] += offset;
return list.join('.');
}
reUseRowId(
items: Array<any>,
originItems: Array<any>,
indexes: Array<number>
) {
// row 不能换 id否则会重新渲染导致编辑状态丢失
// 展开状态也会丢失
let originHost = originItems;
let host = items;
for (let i = 0, len = indexes.length; i < len; i++) {
const idx = indexes[i];
if (!originHost?.[idx] || !host?.[idx]) {
break;
}
this.entries.set(
host[idx],
this.entries.get(originHost[idx]) || this.entityId++
);
this.entries.delete(originHost[idx]);
host = host[idx].children;
originHost = originHost[idx].children;
}
}
buildItemProps(item: any, index: number) {
@ -971,14 +1070,15 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
return {
quickEditEnabled: this.state.editIndex === index + offset
quickEditEnabled:
this.state.editIndex === this.rowPathPlusOffset(item.path, offset)
};
}
buildColumns(
props: TableProps,
isCreateMode = false,
editRowIndex?: number
editRowIndex?: string
): Array<any> {
const {env, enableStaticTransform, testIdBuilder} = this.props;
let columns: Array<any> = Array.isArray(props.columns)
@ -999,13 +1099,15 @@ export default class FormTable extends React.Component<TableProps, TableState> {
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
(~this.state.editIndex && needConfirm !== false) ||
(this.state.editIndex && needConfirm !== false) ||
maxLength <= this.state.items.length ? null : (
<Button
classPrefix={ns}
@ -1015,7 +1117,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltip={__('Table.addRow')}
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.addItem.bind(this, rowIndex + offset, undefined)}
onClick={this.addItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
undefined,
undefined
)}
testIdBuilder={testIdBuilder?.getChild(
`addRow-${rowIndex + offset}`
)}
@ -1033,18 +1140,69 @@ export default class FormTable extends React.Component<TableProps, TableState> {
});
}
if (!isStatic && props.childrenAddable && props.showTableAddBtn !== false) {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset,
row
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
row: any;
}) =>
this.state.editIndex && needConfirm !== false ? null : (
<Button
classPrefix={ns}
size="sm"
key={key}
level="link"
tooltip={__('Table.subAddRow')}
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.subAddItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
undefined,
row
)}
testIdBuilder={testIdBuilder?.getChild(
`subAddRow-${rowIndex + offset}`
)}
>
{props.subAddBtnIcon ? (
<Icon
cx={props.classnames}
icon={props.subAddBtnIcon}
className="icon"
/>
) : null}
{props.subAddBtnLabel ? (
<span>{props.subAddBtnLabel}</span>
) : null}
</Button>
)
});
}
if (!isStatic && props.copyable && props.showCopyBtn !== false) {
btns.push({
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
~this.state.editIndex && needConfirm !== false ? null : (
this.state.editIndex && needConfirm !== false ? null : (
<Button
classPrefix={ns}
size="sm"
@ -1053,7 +1211,11 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltip={__('Table.copyRow')}
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.copyItem.bind(this, rowIndex + offset, undefined)}
onClick={this.copyItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset),
undefined
)}
testIdBuilder={testIdBuilder?.getChild(
`copyRow-${rowIndex + offset}`
)}
@ -1124,7 +1286,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
* 使
*/
...(enableStaticTransform && props.needConfirm !== false
? {staticOn: `${!isCreateMode} || data.index !== ${editRowIndex}`}
? {staticOn: `${!isCreateMode} || data.index !== '${editRowIndex}'`}
: {})
};
});
@ -1135,15 +1297,17 @@ export default class FormTable extends React.Component<TableProps, TableState> {
children: ({
key,
rowIndex,
rowIndexPath,
data,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
data: any;
offset: number;
}) =>
~this.state.editIndex || (data && data.__isPlaceholder) ? null : (
this.state.editIndex || (data && data.__isPlaceholder) ? null : (
<Button
classPrefix={ns}
size="sm"
@ -1154,7 +1318,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
props.popOverContainer || env.getModalContainer
}
disabled={disabled}
onClick={() => this.editItem(rowIndex + offset)}
onClick={() =>
this.editItem(this.rowPathPlusOffset(rowIndexPath, offset))
}
testIdBuilder={testIdBuilder?.getChild(
`editRow-${rowIndex + offset}`
)}
@ -1187,13 +1353,16 @@ export default class FormTable extends React.Component<TableProps, TableState> {
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
this.state.editIndex === rowIndex + offset ? (
this.state.editIndex ===
this.rowPathPlusOffset(rowIndexPath, offset) ? (
<Button
classPrefix={ns}
size="sm"
@ -1227,13 +1396,16 @@ export default class FormTable extends React.Component<TableProps, TableState> {
children: ({
key,
rowIndex,
rowIndexPath,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
offset: number;
}) =>
this.state.editIndex === rowIndex + offset ? (
this.state.editIndex ===
this.rowPathPlusOffset(rowIndexPath, offset) ? (
<Button
classPrefix={ns}
size="sm"
@ -1282,15 +1454,17 @@ export default class FormTable extends React.Component<TableProps, TableState> {
children: ({
key,
rowIndex,
rowIndexPath,
data,
offset
}: {
key: any;
rowIndex: number;
rowIndexPath: string;
data: any;
offset: number;
}) =>
((~this.state.editIndex || (data && data.__isPlaceholder)) &&
((this.state.editIndex || (data && data.__isPlaceholder)) &&
needConfirm !== false) ||
minLength >= this.state.items.length ? null : (
<Button
@ -1301,7 +1475,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltip={__('Table.deleteRow')}
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.removeItem.bind(this, rowIndex + offset)}
onClick={this.removeItem.bind(
this,
this.rowPathPlusOffset(rowIndexPath, offset)
)}
testIdBuilder={testIdBuilder?.getChild(
`delRow-${rowIndex + offset}`
)}
@ -1393,9 +1570,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
const editIndex = state.editIndex;
const lastModifiedRow = state.lastModifiedRow;
if (~editIndex) {
const items = state.items.concat();
const origin = items[editIndex];
if (editIndex) {
const indexes = editIndex.split('.').map(item => parseInt(item, 10));
let items = state.items.concat();
const origin = getTree(items, indexes);
if (!origin) {
return newState;
@ -1404,9 +1582,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
const value: any = {
...rows
};
this.entries.set(value, this.entries.get(origin) || this.entityId++);
this.entries.delete(origin);
items.splice(editIndex, 1, value);
const originItems = items;
items = spliceTree(items, indexes, 1, value);
this.reUseRowId(items, originItems, indexes);
Object.assign(newState, {
items,
@ -1476,9 +1654,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
);
const originItems = items;
items = spliceTree(items, indexes, 1, data);
this.entries.set(data, this.entries.get(origin) || this.entityId++);
// this.entries.delete(origin); // 反正最后都会清理的,先不删了吧。
this.reUseRowId(items, originItems, indexes);
}
Object.assign(newState, {
@ -1552,9 +1730,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
...origin,
...data
};
this.entries.set(value, this.entries.get(origin) || this.entityId++);
this.entries.delete(origin);
const originItems = items;
items = spliceTree(items, indexes, 1, value);
this.reUseRowId(items, originItems, indexes);
return {
items
@ -1591,7 +1770,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
computedAddBtnDisabled() {
const {disabled} = this.props;
return disabled || !!~this.state.editIndex;
return disabled || !!this.state.editIndex;
}
render() {
@ -1668,7 +1847,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
value: undefined,
saveImmediately: true,
disabled,
draggable: draggable && !~this.state.editIndex,
draggable: draggable && !this.state.editIndex,
items: items,
getEntryId: this.getEntryId,
onSave: this.handleTableSave,
@ -1709,7 +1888,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
},
{
disabled: this.computedAddBtnDisabled(),
onClick: () => this.addItem(this.state.items.length),
onClick: () => this.addItem(),
testIdBuilder: testIdBuilder?.getChild('add')
}
)
@ -1753,25 +1932,31 @@ export class TableControlRenderer extends FormTable {
let items = [...this.state.items];
const indexs = String(index).split(',');
indexs.forEach(i => {
const intIndex = Number(i);
items.splice(intIndex, 1, value);
const indexes = i.split('.').map(item => parseInt(item, 10));
items = spliceTree(items, indexes, 1, value);
});
this.setState({items}, () => {
this.emitValue();
});
} else if (condition !== undefined) {
let items = [...this.state.items];
for (let i = 0; i < len; i++) {
const item = items[i];
const isUpdate = await evalExpressionWithConditionBuilder(
condition,
item
);
if (isUpdate) {
items.splice(i, 1, value);
}
}
const promises: Array<() => Promise<any>> = [];
everyTree(items, (item, index, paths, indexes) => {
promises.unshift(async () => {
const isUpdate = await evalExpressionWithConditionBuilder(
condition,
item
);
if (isUpdate) {
items = spliceTree(items, indexes, 1, value);
}
});
return true;
});
await Promise.all(promises.map(fn => fn()));
this.setState({items}, () => {
this.emitValue();
@ -1811,7 +1996,7 @@ export class TableControlRenderer extends FormTable {
const ctx = this.props.store?.data || {}; // 获取当前上下文数据
if (actionType === 'addItem') {
const items = this.state.items.concat();
let items = this.state.items.concat();
if (addApi || args) {
let toAdd: any = null;
@ -1842,13 +2027,18 @@ export class TableControlRenderer extends FormTable {
)
);
let index = args.index;
if (typeof index === 'string' && /^\d+$/.test(index)) {
index = parseInt(index, 10);
let indexes: Array<number> = [];
if (
typeof args.index === 'string' &&
/^\d+(\.\d+)*$/.test(args.index)
) {
indexes = (args.index as string).split('.').map(i => parseInt(i, 10));
} else if (typeof args.index === 'number') {
indexes = [args.index];
}
if (typeof index === 'number') {
items.splice(index, 0, ...toAdd);
if (indexes.length) {
items = spliceTree(items, indexes, 0, ...toAdd);
} else {
// 没有指定默认插入在最后
items.push(...toAdd);
@ -1860,7 +2050,9 @@ export class TableControlRenderer extends FormTable {
},
() => {
if (toAdd.length === 1 && needConfirm !== false) {
this.startEdit(items.length - 1, true);
const next = indexes.concat();
next[next.length - 1] += 1;
this.startEdit(next.join('.'), true);
} else {
onChange?.(items);
}
@ -1868,7 +2060,7 @@ export class TableControlRenderer extends FormTable {
);
return;
} else {
return this.addItem(items.length - 1, false);
return this.addItem(`${items.length - 1}`, false);
}
} else if (actionType === 'deleteItem') {
const items = [...this.state.items];

View File

@ -264,6 +264,7 @@ export class TableRow extends React.PureComponent<
...rest,
width: null,
rowIndex: itemIndex,
rowIndexPath: item.path,
colIndex: column.index,
rowPath,
key: column.index,
@ -327,6 +328,7 @@ export class TableRow extends React.PureComponent<
...rest,
rowIndex: itemIndex,
colIndex: column.index,
rowIndexPath: item.path,
rowPath,
key: column.id,
onAction: this.handleAction,