fix: 修复部分表单嵌套报错, 将错误的嵌套用法提示改成不那么激进的 warning 提示 (#2636)

* fix: 修复部分表单嵌套报错, 将错误的嵌套用法提示改成不那么激进的 warning 提示

* 更新用例 & 修复 dropdown-button 报错
This commit is contained in:
liaoxuezhi 2021-09-30 11:58:30 +08:00 committed by GitHub
parent c7f460a373
commit 137cbcb461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 399 additions and 109 deletions

View File

@ -195,65 +195,123 @@ exports[`factory:definitions 1`] = `
class="cxd-Combo-itemInner"
>
<div
class="cxd-Alert cxd-Alert--danger"
class="cxd-Form cxd-Form--normal cxd-Combo-form"
novalidate=""
>
<p>
Error: 不允许在表单及表单按钮组中直接嵌套表单
</p>
<p>
Path:
page/body/0/form/1/combo/multiple/0/form
</p>
<pre>
<code>
{
"type": "form",
"body": [
{
"label": "combo 1",
"type": "input-text",
"name": "key"
},
{
"type": "input-text",
"name": "value",
"value": "ref value",
"remark": "通过&lt;code&gt;\\\\$ref&lt;/code&gt;引入的组件",
"label": "combo 2"
},
{
"type": "combo",
"multiple": true,
"multiLine": true,
"remark": "&lt;code&gt;combo&lt;/code&gt;中的子项引入自身,实现嵌套的效果",
"controls": [
{
"label": "combo 1",
"type": "text",
"name": "key"
},
{
"label": "combo 2",
"name": "value",
"$ref": "aa"
},
{
"name": "children",
"label": "children",
"$ref": "bb"
}
],
"name": "children",
"label": "children"
}
],
"wrapperComponent": "div",
"wrapWithPanel": false,
"mode": "normal",
"className": "cxd-Combo-form"
}
</code>
</pre>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
combo 1
</span>
</span>
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="key"
placeholder=""
size="10"
type="text"
value=""
/>
</div>
</div>
</div>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
combo 2
</span>
</span>
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="value"
placeholder=""
size="10"
type="text"
value="ref value"
/>
</div>
</div>
<div
class="cxd-Remark cxd-Form-remark"
>
<span
class="cxd-Remark-icon"
>
<icon-mock
classname=" icon-warning-mark"
icon="warning-mark"
/>
</span>
</div>
</div>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
children
</span>
</span>
</span>
</label>
<div
class="cxd-Remark cxd-Form-remark"
>
<span
class="cxd-Remark-icon"
>
<icon-mock
classname=" icon-warning-mark"
icon="warning-mark"
/>
</span>
</div>
</div>
</div>
</div>
<a

View File

@ -796,6 +796,59 @@ exports[`Renderer:Page handleAction actionType=drawer 1`] = `
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Drawer cxd-Drawer--right cxd-Drawer--md cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Drawer-overlay in"
/>
<div
class="cxd-Drawer-content in"
>
<a
class="cxd-Drawer-close"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Drawer-header"
/>
<div
class="cxd-Drawer-body"
>
<div
class="cxd-Spinner-overlay in"
/>
<div
class="cxd-Spinner in cxd-Spinner--overlay cxd-Spinner--lg"
/>
</div>
<div
class="cxd-Drawer-footer"
>
<button
class="cxd-Button cxd-Button--default"
type="button"
>
<span>
取消
</span>
</button>
<button
class="cxd-Button cxd-Button--primary"
type="button"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
@ -868,6 +921,92 @@ exports[`Renderer:Page handleAction actionType=drawer mergeData 1`] = `
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Drawer cxd-Drawer--right cxd-Drawer--md cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Drawer-overlay in"
/>
<div
class="cxd-Drawer-content in"
>
<a
class="cxd-Drawer-close"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Drawer-header"
/>
<div
class="cxd-Drawer-body"
>
<form
class="cxd-Form cxd-Form--horizontal"
novalidate=""
>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--horizontal"
data-role="form-item"
>
<label
class="cxd-Form-label cxd-Form-itemColumn--normal"
>
<span>
<span
class="cxd-TplField"
>
<span>
A
</span>
</span>
</span>
</label>
<div
class="cxd-Form-value"
>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="a"
placeholder=""
size="10"
type="text"
value="3"
/>
</div>
</div>
</div>
</div>
</form>
</div>
<div
class="cxd-Drawer-footer"
>
<button
class="cxd-Button cxd-Button--default"
type="submit"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
@ -1442,6 +1581,92 @@ exports[`Renderer:Page initApi reload by Drawer action 2`] = `
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Drawer cxd-Drawer--right cxd-Drawer--md cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Drawer-overlay in"
/>
<div
class="cxd-Drawer-content in"
>
<a
class="cxd-Drawer-close"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Drawer-header"
/>
<div
class="cxd-Drawer-body"
>
<form
class="cxd-Form cxd-Form--horizontal"
novalidate=""
>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--horizontal"
data-role="form-item"
>
<label
class="cxd-Form-label cxd-Form-itemColumn--normal"
>
<span />
</label>
<div
class="cxd-Form-value"
>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="a"
placeholder=""
size="10"
type="text"
value="1"
/>
</div>
</div>
</div>
</div>
</form>
</div>
<div
class="cxd-Drawer-footer"
>
<button
class="cxd-Button cxd-Button--default"
type="button"
>
<span>
取消
</span>
</button>
<button
class="cxd-Button cxd-Button--primary"
type="button"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;

View File

@ -67,6 +67,20 @@ export class Drawer extends React.Component<DrawerProps, DrawerState> {
document.body.addEventListener('click', this.handleRootClick);
}
componentDidUpdate(prevProps: DrawerProps) {
// jest 里面没有触发 entered 导致后续的逻辑错误,
// 所以直接 300 ms 后触发
if (
typeof jest !== 'undefined' &&
prevProps.show !== this.props.show &&
this.props.show
) {
setTimeout(() => {
this.handleEntered();
}, 300);
}
}
componentWillUnmount() {
if (this.props.show) {
this.handleExited();

View File

@ -46,6 +46,13 @@ export default class LazyComponent extends React.Component<
};
}
componentDidMount() {
// jest 里面有点异常,先手动让它总是可见
if (typeof jest !== 'undefined') {
this.handleVisibleChange(true);
}
}
componentWillUnmount() {
this.mounted = false;
}

View File

@ -102,6 +102,7 @@ register('de-DE', {
'Form.title': 'Formular',
'Form.unique': 'Aktueller Wert ist nicht eindeutig',
'Form.validateFailed': 'Fehler bei der Überprüfung der Formulareingabe',
'Form.nestedError': 'Form kann nicht als Nachkomme von Form erscheinen',
'Image.configError': 'Es können nur eine Beschneidung oder mehrere festgelegt werden',
'Image.crop': 'Bild beschneiden',
'Image.dragDrop': 'Bilder per Drag & Drop hier ablegen',

View File

@ -103,6 +103,7 @@ register('en-US', {
'Form.title': 'Form',
'Form.unique': 'Current value is not unique',
'Form.validateFailed': 'Form input validation failed',
'Form.nestedError': 'Form cannot appear as a descendant of form',
'Image.configError': 'Can only set one of crop or multiple',
'Image.crop': 'Crop image',
'Image.dragDrop': `Drag 'n' drop some photos here`,

View File

@ -105,6 +105,7 @@ register('zh-CN', {
'Form.title': '表单',
'Form.unique': '当前值不唯一',
'Form.validateFailed': '依赖的部分字段没有通过验证',
'Form.nestedError': '表单不要直接嵌套在表单下面',
'Image.configError': '图片多选配置和裁剪配置只能设置一个',
'Image.crop': '裁剪图片',
'Image.dragDrop': '将图片拖拽到此处',

View File

@ -375,7 +375,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'onChange',
'onInit',
'onSaved',
'onQuery'
'onQuery',
'formStore'
];
static defaultProps = {
toolbarInline: true,

View File

@ -334,7 +334,8 @@ export default class ComboControl extends React.Component<ComboProps> {
'strictMode',
'items',
'conditions',
'messages'
'messages',
'formStore'
];
subForms: Array<any> = [];
@ -367,15 +368,8 @@ export default class ComboControl extends React.Component<ComboProps> {
...props.scaffold
};
const {
store,
value,
multiple,
minLength,
maxLength,
formItem,
addHook
} = props;
const {store, value, multiple, minLength, maxLength, formItem, addHook} =
props;
store.config({
multiple,
@ -451,14 +445,8 @@ export default class ComboControl extends React.Component<ComboProps> {
}
addItemWith(condition: ComboCondition) {
const {
flat,
joinValues,
delimiter,
scaffold,
disabled,
submitOnChange
} = this.props;
const {flat, joinValues, delimiter, scaffold, disabled, submitOnChange} =
this.props;
if (disabled) {
return;
@ -483,14 +471,8 @@ export default class ComboControl extends React.Component<ComboProps> {
}
addItem() {
const {
flat,
joinValues,
delimiter,
scaffold,
disabled,
submitOnChange
} = this.props;
const {flat, joinValues, delimiter, scaffold, disabled, submitOnChange} =
this.props;
if (disabled) {
return;
@ -562,14 +544,8 @@ export default class ComboControl extends React.Component<ComboProps> {
}
handleChange(values: any, diff: any, {index}: any) {
const {
flat,
store,
joinValues,
delimiter,
disabled,
submitOnChange
} = this.props;
const {flat, store, joinValues, delimiter, disabled, submitOnChange} =
this.props;
if (disabled) {
return;
@ -781,8 +757,8 @@ export default class ComboControl extends React.Component<ComboProps> {
[propName: number]: any;
} = {};
makeFormRef = memoize((index: number) => (ref: any) =>
this.formRef(ref, index)
makeFormRef = memoize(
(index: number) => (ref: any) => this.formRef(ref, index)
);
formRef(ref: any, index: number = 0) {

View File

@ -142,6 +142,8 @@ export default class SubFormControl extends React.PureComponent<
placeholder: 'placeholder.empty'
};
static propsList: Array<string> = ['form', 'formStore'];
state: SubFormState = {};
dragTip?: HTMLElement;
sortable?: Sortable;

View File

@ -187,7 +187,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
'updateApi',
'deleteApi',
'needConfirm',
'canAccessSuperData'
'canAccessSuperData',
'formStore'
];
entries: SimpleMap<any, number>;

View File

@ -1019,7 +1019,8 @@ export class FormItemWrap extends React.Component<FormItemProps> {
show: model.dialogOpen,
onClose: this.handleDialogClose,
onConfirm: this.handleDialogConfirm,
data: model.dialogData
data: model.dialogData,
formStore: undefined
}
)
: null}

View File

@ -1519,20 +1519,19 @@ export default class Form extends React.Component<FormProps, object> {
let body: JSX.Element = this.renderBody();
// 表单组件限制
const isFormInFormActions: boolean = /panel\/action\/form$/.test($path);
// props有formStore 说明是嵌套表单 || 不允许在表单的按钮组中再直接套表单
if (formStore || isFormInFormActions) {
if (formStore) {
body = (
<Alert level="danger">
<p>Error: </p>
<p>Path: {$path}</p>
<pre>
<code>{JSON.stringify($schema, null, 2)}</code>
</pre>
</Alert>
<>
<Alert level="warning" showCloseButton>
<p>{__('Form.nestedError')}</p>
</Alert>
{body}
</>
);
} else if (wrapWithPanel) {
}
if (wrapWithPanel) {
body = render(
'body',
{
@ -1541,6 +1540,7 @@ export default class Form extends React.Component<FormProps, object> {
},
{
className: cx(panelClassName, 'Panel--form'),
formStore: this.props.store,
children: body,
actions: this.buildActions(),
onAction: this.handleAction,

View File

@ -98,6 +98,8 @@ export interface PanelProps
export default class Panel extends React.Component<PanelProps> {
static propsList: Array<string> = [
'header',
'actions',
'children',
'headerClassName',
'footerClassName',
'footerWrapClassName',

View File

@ -8,5 +8,5 @@ import useRootClose from 'react-overlays/useRootClose';
export const RootClose = ({children, onRootClose, ...props}: any) => {
const [rootElement, attachRef] = useState(null);
useRootClose(rootElement, onRootClose, props);
return children(attachRef);
return typeof children === 'function' ? children(attachRef) : children;
};