feat: reload 支持动态目标, 解决目标在循环中场景 (#6372)

This commit is contained in:
liaoxuezhi 2023-03-15 16:18:05 +08:00 committed by GitHub
parent e15892f8dd
commit b415a1f0f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 197 additions and 32 deletions

View File

@ -485,3 +485,160 @@ amis 会将返回的 `data` 写入表单数据域,因此下面的 `static` 组
上例中点击按钮会刷新`target1`和`target2`组件。
事实上,**组件间联动也可以实现上述任意的 [基本联动效果](./linkage#%E5%9F%BA%E6%9C%AC%E8%81%94%E5%8A%A8)(显隐联动、接口联动等其他联动)。**
### 动态目标
> 2.9.0 及以上版本
刷新目标支持表达式,比如目标可以配置成 `form-${ xxx ? '1' : '2'}`
```schema: scope="body"
[
{
"title": "查询条件",
"type": "form",
"target": "my_crud_${searchTarget}",
"body": [
{
"type": "radios",
"name": "searchTarget",
"label": "选择目标",
value: 1,
options: [
{
value: 1,
label: "列表 1"
},
{
value: 2,
label: "列表 2"
}
],
},
{
"type": "input-text",
"name": "keywords",
"label": "关键字:"
}
],
"submitText": "搜索"
},
{
"type": "crud",
"api": "/api/mock2/sample",
"name": "my_crud_1",
"title": "列表 1",
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "engine",
"label": "Rendering engine"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "platform",
"label": "Platform(s)"
},
{
"name": "version",
"label": "Engine version"
}
]
},
{
"type": "crud",
"api": "/api/mock2/sample",
"name": "my_crud_2",
"title": "列表 2",
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "engine",
"label": "Rendering engine"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "platform",
"label": "Platform(s)"
},
{
"name": "version",
"label": "Engine version"
}
]
}
]
```
如果目标组件在列表中,则实际渲染的时候会存在多份,通过某个固定名字没办法找到对应的组件。比如某个 crud 里面,某列设置的是一个 service通过 service 拉取数据。如果想在操作栏里面某个操作完后刷新对应的 service通过固定的名字是没办法找到对应的 service 的。所以名字 `name` 也支持动态名字。如: `my-service-${id}` 把行数据中动态的 id 设置进去。
```schema: scope="body"
{
type: 'crud',
api: "/api/mock2/sample",
columns: [
{
name: 'id',
label: 'ID'
},
{
type: 'service',
api: "/api/mock2/sample/${id}",
label: 'Service',
name: "my-servce-${id}",
body: [
{
type: "tpl",
tpl: "${browser}"
}
]
},
{
type: 'operation',
label: '操作',
buttons: [
{
type: "button",
label: "编辑",
actionType: "dialog",
dialog: {
"title": "编辑",
body: [
{
type: 'form',
api: "/api/mock2/sample/${id}",
body: [
{
type: 'input-text',
name: 'browser',
label: 'Browser'
}
]
}
]
},
reload: "my-servce-${id}"
}
]
}
]
}
```
> 这个例子 api 是 mock 的,所以修改后刷新没效果。

View File

@ -201,7 +201,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
action.reload &&
this.reloadTarget(
delegate || this.context,
action.reload,
filter(action.reload, ctx),
store.data
);
})

View File

@ -21,6 +21,7 @@ import {
} from './utils/helper';
import {RendererData, ActionObject} from './types';
import {isPureVariable} from './utils/isPureVariable';
import {filter} from './utils';
export interface ScopedComponentType extends React.Component<RendererProps> {
focus?: () => void;
@ -36,11 +37,7 @@ export interface ScopedComponentType extends React.Component<RendererProps> {
ctx?: RendererData
) => void;
context: any;
setData?: (
value?: object,
replace?: boolean,
index?: number
) => void;
setData?: (value?: object, replace?: boolean, index?: number) => void;
}
export interface IScopedContext {
@ -109,7 +106,8 @@ function createScopedTools(
const resolved = find(
components,
component =>
component.props.name === name || component.props.id === name
filter(component.props.name, component.props.data) === name ||
component.props.id === name
);
return resolved || (parent && parent.getComponentByName(name));
},

View File

@ -1091,10 +1091,11 @@ export default class Form extends React.Component<FormProps, object> {
dispatchEvent('validateSucc', this.props.data);
if (target) {
this.submitToTarget(target, values);
this.submitToTarget(filter(target, values), values);
dispatchEvent('submitSucc', createObject(this.props.data, values));
} else if (action.actionType === 'reload') {
action.target && this.reloadTarget(action.target, values);
action.target &&
this.reloadTarget(filter(action.target, values), values);
} else if (action.actionType === 'dialog') {
store.openDialog(data);
} else if (action.actionType === 'drawer') {
@ -1193,7 +1194,10 @@ export default class Form extends React.Component<FormProps, object> {
);
finalRedirect && env.jumpTo(finalRedirect, action);
} else if (action.reload || reload) {
this.reloadTarget(action.reload || reload!, store.data);
this.reloadTarget(
filter(action.reload || reload!, store.data),
store.data
);
}
action.close && this.closeTarget(action.close);
@ -1256,7 +1260,8 @@ export default class Form extends React.Component<FormProps, object> {
action.redirect && filter(action.redirect, store.data);
redirect && env.jumpTo(redirect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
action.close && this.closeTarget(action.close);
})
.catch(e => {
@ -1268,7 +1273,7 @@ export default class Form extends React.Component<FormProps, object> {
} else if (action.actionType === 'reload') {
store.setCurrentAction(action);
if (action.target) {
this.reloadTarget(action.target, data);
this.reloadTarget(filter(action.target, data), data);
} else {
this.receive(data);
}

View File

@ -680,7 +680,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
const redirect = action.redirect && filter(action.redirect, data);
redirect && !action.blank && env.jumpTo(redirect, action);
action.reload
? this.reloadTarget(action.reload, data)
? this.reloadTarget(filter(action.reload, data), data)
: redirect
? null
: this.search(undefined, undefined, true, true);
@ -779,7 +779,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
}
action.reload
? this.reloadTarget(action.reload, data)
? this.reloadTarget(filter(action.reload, data), data)
: this.search(
{[pageField || 'page']: 1},
undefined,
@ -1038,7 +1038,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
const reload = action.reload ?? dialogAction.reload;
if (reload) {
this.reloadTarget(reload, ctx);
this.reloadTarget(filter(reload, ctx), ctx);
}
let redirect = action.redirect ?? dialogAction.redirect;
@ -1279,7 +1279,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
.then(() => {
const finalReload = options?.reload ?? reload;
finalReload
? this.reloadTarget(finalReload, data)
? this.reloadTarget(filter(finalReload, data), data)
: this.search(undefined, undefined, true, true);
})
.catch(() => {});
@ -1301,7 +1301,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
.then(() => {
const finalReload = options?.reload ?? reload;
finalReload
? this.reloadTarget(finalReload, data)
? this.reloadTarget(filter(finalReload, data), data)
: this.search(undefined, undefined, true, true);
})
.catch(() => {
@ -1406,7 +1406,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
store
.saveRemote(saveOrderApi, model)
.then(() => {
reload && this.reloadTarget(reload, model);
reload && this.reloadTarget(filter(reload, model), model);
this.search(undefined, undefined, true, true);
})
.catch(() => {});

View File

@ -658,7 +658,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
errorMessage: messages && messages.saveSuccess
})
.then(() => {
reload && this.reloadTarget(reload, data);
reload && this.reloadTarget(filter(reload, data), data);
this.getData(undefined, undefined, true, true);
})
.catch(() => {});
@ -678,7 +678,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
store
.saveRemote(quickSaveItemApi, sendData)
.then(() => {
reload && this.reloadTarget(reload, data);
reload && this.reloadTarget(filter(reload, data), data);
this.getData(undefined, undefined, true, true);
})
.catch(() => {
@ -784,7 +784,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
store
.saveRemote(saveOrderApi, model)
.then(() => {
reload && this.reloadTarget(reload, model);
reload && this.reloadTarget(filter(reload, model), model);
this.getData(undefined, undefined, true, true);
})
.catch(() => {});

View File

@ -891,7 +891,8 @@ export class DialogRenderer extends Dialog {
const reidrect =
action.redirect && filter(action.redirect, store.data);
reidrect && env.jumpTo(reidrect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
if (action.close) {
this.handleSelfClose();
this.closeTarget(action.close);

View File

@ -852,7 +852,8 @@ export class DrawerRenderer extends Drawer {
const redirect =
action.redirect && filter(action.redirect, store.data);
redirect && env.jumpTo(redirect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
if (action.close) {
this.handleSelfClose();
this.closeTarget(action.close);

View File

@ -508,7 +508,8 @@ export default class Page extends React.Component<PageProps> {
const redirect =
action.redirect && filter(action.redirect, store.data);
redirect && env.jumpTo(redirect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
})
.catch(e => {
if (throwErrors || action.countDown) {

View File

@ -47,7 +47,7 @@ export const eventTypes = [
'onWsFetched'
] as const;
export type ProviderEventType = typeof eventTypes[number];
export type ProviderEventType = (typeof eventTypes)[number];
export type DataProviderCollection = Partial<
Record<ProviderEventType, DataProvider>
@ -684,7 +684,8 @@ export default class Service extends React.Component<ServiceProps> {
const redirect =
action.redirect && filter(action.redirect, store.data);
redirect && env.jumpTo(redirect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
})
.catch(e => {
if (throwErrors || action.countDown) {

View File

@ -910,7 +910,7 @@ export default class Table2 extends React.Component<Table2Props, object> {
errorMessage: messages && messages.saveSuccess
})
.then(() => {
reload && this.reloadTarget(reload, data);
reload && this.reloadTarget(filter(reload, data), data);
})
.catch(() => {});
} else {
@ -929,7 +929,7 @@ export default class Table2 extends React.Component<Table2Props, object> {
store
.saveRemote(quickSaveItemApi, sendData)
.then(() => {
reload && this.reloadTarget(reload, data);
reload && this.reloadTarget(filter(reload, data), data);
})
.catch(() => {
options?.resetOnFailed && this.reset();

View File

@ -600,11 +600,12 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
action.redirect && filter(action.redirect, store.data);
reidrect && env.jumpTo(reidrect, action);
action.reload && this.reloadTarget(action.reload, store.data);
action.reload &&
this.reloadTarget(filter(action.reload, store.data), store.data);
})
.catch(reason => {});
} else if (action.actionType === 'reload') {
action.target && this.reloadTarget(action.target, data);
action.target && this.reloadTarget(filter(action.target, data), data);
} else if (action.actionType === 'goto-step') {
const targetStep = (data as any).step;
@ -708,7 +709,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
// 最后一步
if (target) {
this.submitToTarget(target, store.data);
this.submitToTarget(filter(target, store.data), store.data);
this.setState({completeStep: steps.length});
} else if (action.api || step.api || api) {
let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi;
@ -797,7 +798,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
env.jumpTo(finalRedirect, action);
} else if (action.reload || step.reload || reload) {
this.reloadTarget(
action.reload || step.reload || reload!,
filter(action.reload || step.reload || reload!, store.data),
store.data
);
}