amis-saas-5329 [Feature] 「feat」服务调用配置升级

Change-Id: I729bfc812ece83eac5de3b47c2ba340141b45a2d
This commit is contained in:
RickCole21 2022-08-04 11:16:57 +08:00
parent a3de043b1f
commit 1011cee9cc
6 changed files with 211 additions and 138 deletions

View File

@ -188,7 +188,7 @@ export class CRUDPlugin extends BasePlugin {
scaffoldForm: ScaffoldForm = { scaffoldForm: ScaffoldForm = {
title: '增删改查快速开始-CRUD', title: '增删改查快速开始-CRUD',
body: [ body: [
getSchemaTpl('api', { getSchemaTpl('apiControl', {
label: '接口地址', label: '接口地址',
sampleBuilder: (schema: any) => sampleBuilder: (schema: any) =>
JSON.stringify( JSON.stringify(

View File

@ -138,7 +138,7 @@ export class FormPlugin extends BasePlugin {
scaffoldForm: ScaffoldForm = { scaffoldForm: ScaffoldForm = {
title: '快速创建表单', title: '快速创建表单',
body: [ body: [
getSchemaTpl('api', { getSchemaTpl('apiControl', {
label: '提交地址' label: '提交地址'
}), }),
{ {
@ -534,7 +534,7 @@ export class FormPlugin extends BasePlugin {
: { : {
title: '接口', title: '接口',
body: [ body: [
getSchemaTpl('api', { getSchemaTpl('apiControl', {
label: '保存接口', label: '保存接口',
description: '用来保存表单数据', description: '用来保存表单数据',
sampleBuilder: () => `{ sampleBuilder: () => `{
@ -563,7 +563,7 @@ export class FormPlugin extends BasePlugin {
pipeOut: (value: any) => (value ? '' : undefined) pipeOut: (value: any) => (value ? '' : undefined)
}), }),
getSchemaTpl('api', { getSchemaTpl('apiControl', {
name: 'asyncApi', name: 'asyncApi',
label: '异步检测接口', label: '异步检测接口',
visibleOn: 'data.asyncApi != null', visibleOn: 'data.asyncApi != null',
@ -575,7 +575,7 @@ export class FormPlugin extends BasePlugin {
type: 'divider' type: 'divider'
}, },
getSchemaTpl('api', { getSchemaTpl('apiControl', {
name: 'initApi', name: 'initApi',
label: '初始化接口', label: '初始化接口',
description: '用来初始化表单数据', description: '用来初始化表单数据',
@ -657,7 +657,7 @@ export class FormPlugin extends BasePlugin {
pipeOut: (value: any) => (value ? '' : undefined) pipeOut: (value: any) => (value ? '' : undefined)
}), }),
getSchemaTpl('api', { getSchemaTpl('apiControl', {
name: 'initAsyncApi', name: 'initAsyncApi',
label: '异步检测接口', label: '异步检测接口',
visibleOn: 'data.initAsyncApi != null', visibleOn: 'data.initAsyncApi != null',

View File

@ -170,7 +170,7 @@ export class PagePlugin extends BasePlugin {
{ {
title: '接口', title: '接口',
body: [ body: [
getSchemaTpl('api', { getSchemaTpl('apiControl', {
label: '数据初始化接口', label: '数据初始化接口',
name: 'initApi', name: 'initApi',
sampleBuilder: () => `{ sampleBuilder: () => `{
@ -182,9 +182,24 @@ export class PagePlugin extends BasePlugin {
"id": 1, "id": 1,
"a": "sample" "a": "sample"
} }
}` }`
}), }),
// getSchemaTpl('api', {
// label: '数据初始化接口',
// name: 'initApi',
// sampleBuilder: () => `{
// "status": 0,
// "msg": "",
// data: {
// // 示例数据
// "id": 1,
// "a": "sample"
// }
// }`
// }),
getSchemaTpl('initFetch'), getSchemaTpl('initFetch'),
getSchemaTpl('switch', { getSchemaTpl('switch', {

View File

@ -237,24 +237,43 @@ export default class APIControl extends React.Component<
onPickerClose?.(); onPickerClose?.();
} }
@autobind
renderHeader() { renderHeader() {
const {render, actions, enablePickerMode} = this.props; const {render, label, labelRemark, useMobileUI, popOverContainer, env} =
this.props;
const classPrefix = env?.theme?.classPrefix;
const actionsDom = // const actionsDom =
Array.isArray(actions) && actions.length > 0 // Array.isArray(actions) && actions.length > 0
? actions.map((action, index) => { // ? actions.map((action, index) => {
return render(`action/${index}`, action, { // return render(`action/${index}`, action, {
key: index, // key: index,
onAction: this.handleAction.bind(this, action) // onAction: this.handleAction.bind(this, action)
}); // });
// })
// : null;
return (
<header className="ApiControl-header" key="header">
<label className={cx(`${classPrefix}Form-label`)}>
{label || ''}
{labelRemark
? render('label-remark', {
type: 'remark',
icon: labelRemark.icon || 'warning-mark',
tooltip: labelRemark,
className: cx(`Form-lableRemark`, labelRemark?.className),
useMobileUI,
container: popOverContainer
? popOverContainer
: env && env.getModalContainer
? env.getModalContainer
: undefined
}) })
: null; : null}
</label>
return actionsDom || enablePickerMode ? (
<header className="ae-ApiControl-header" key="header">
{enablePickerMode ? this.renderPickerSchema() : actionsDom}
</header> </header>
) : null; );
} }
renderPickerSchema() { renderPickerSchema() {
@ -953,6 +972,7 @@ export default class APIControl extends React.Component<
} }
@FormItem({ @FormItem({
type: 'ae-apiControl' type: 'ae-apiControl',
renderLabel: false
}) })
export class APIControlRenderer extends APIControl {} export class APIControlRenderer extends APIControl {}

View File

@ -47,7 +47,7 @@ export interface OptionControlState {
api: SchemaApi; api: SchemaApi;
labelField: string; labelField: string;
valueField: string; valueField: string;
source: 'custom' | 'api' | 'form'; source: 'custom' | 'api' | 'apicenter';
} }
export default class OptionControl extends React.Component< export default class OptionControl extends React.Component<
@ -209,7 +209,7 @@ export default class OptionControl extends React.Component<
data.value = defaultValue || undefined; data.value = defaultValue || undefined;
} }
if (source === 'api') { if (source === 'api' || source === 'apicenter') {
const {api, labelField, valueField} = this.state; const {api, labelField, valueField} = this.state;
data.source = api; data.source = api;
data.labelField = labelField; data.labelField = labelField;
@ -296,7 +296,7 @@ export default class OptionControl extends React.Component<
* *
*/ */
@autobind @autobind
handleSourceChange(source: 'custom' | 'api' | 'form') { handleSourceChange(source: 'custom' | 'api' | 'apicenter') {
this.setState({source: source}, this.onChange); this.setState({source: source}, this.onChange);
} }
@ -396,8 +396,15 @@ export default class OptionControl extends React.Component<
} }
renderHeader() { renderHeader() {
const {render, label, labelRemark, useMobileUI, env, popOverContainer} = const {
this.props; render,
label,
labelRemark,
useMobileUI,
env,
popOverContainer,
hasApiCenter
} = this.props;
const classPrefix = env?.theme?.classPrefix; const classPrefix = env?.theme?.classPrefix;
const {source} = this.state; const {source} = this.state;
const optionSourceList = ( const optionSourceList = (
@ -407,16 +414,17 @@ export default class OptionControl extends React.Component<
value: 'custom' value: 'custom'
}, },
{ {
label: '接口获取', label: '外部接口',
value: 'api' value: 'api'
} },
...(hasApiCenter ? [{label: 'API中心', value: 'apicenter'}] : [])
// { // {
// label: '表单实体', // label: '表单实体',
// value: 'form' // value: 'form'
// } // }
] as Array<{ ] as Array<{
label: string; label: string;
value: 'custom' | 'api' | 'form'; value: 'custom' | 'api' | 'apicenter';
}> }>
).map(item => ({ ).map(item => ({
...item, ...item,
@ -698,7 +706,7 @@ export default class OptionControl extends React.Component<
renderApiPanel() { renderApiPanel() {
const {render} = this.props; const {render} = this.props;
const {source, api, labelField, valueField} = this.state; const {source, api, labelField, valueField} = this.state;
if (source !== 'api') { if (source === 'custom') {
return null; return null;
} }
@ -711,6 +719,7 @@ export default class OptionControl extends React.Component<
visibleOn: 'data.autoComplete !== false', visibleOn: 'data.autoComplete !== false',
value: api, value: api,
onChange: this.handleAPIChange, onChange: this.handleAPIChange,
sourceType: source,
footer: [ footer: [
{ {
label: tipedLabel( label: tipedLabel(

View File

@ -26,7 +26,7 @@ import type {Option} from 'amis';
import type {FormControlProps} from 'amis-core'; import type {FormControlProps} from 'amis-core';
import {SchemaApi} from 'amis/lib/Schema'; import {SchemaApi} from 'amis/lib/Schema';
export type OptionControlItem = Option & {checked?: boolean, _key?: string}; export type OptionControlItem = Option & {checked?: boolean; _key?: string};
export interface OptionControlProps extends FormControlProps { export interface OptionControlProps extends FormControlProps {
className?: string; className?: string;
@ -37,14 +37,14 @@ export interface OptionControlState {
api: SchemaApi; api: SchemaApi;
labelField: string; labelField: string;
valueField: string; valueField: string;
source: 'custom' | 'api'; source: 'custom' | 'api' | 'apicenter';
modalVisible: boolean modalVisible: boolean;
} }
const defaultOption: OptionControlItem = { const defaultOption: OptionControlItem = {
label: '', label: '',
value: '' value: ''
} };
export default class TreeOptionControl extends React.Component< export default class TreeOptionControl extends React.Component<
OptionControlProps, OptionControlProps,
@ -69,9 +69,11 @@ export default class TreeOptionControl extends React.Component<
} }
transformOptions(props: OptionControlProps) { transformOptions(props: OptionControlProps) {
const {data: {options}} = props; const {
data: {options}
} = props;
if (!options || !options.length) { if (!options || !options.length) {
return [{...defaultOption}] return [{...defaultOption}];
} }
return options; return options;
} }
@ -87,7 +89,10 @@ export default class TreeOptionControl extends React.Component<
if (option.children && option.children.length) { if (option.children && option.children.length) {
option.children = this.pretreatOptions(option.children); option.children = this.pretreatOptions(option.children);
} }
option.value = option.value == null || option.value === '' ? option.label : option.value; option.value =
option.value == null || option.value === ''
? option.label
: option.value;
return option; return option;
}); });
} }
@ -109,7 +114,7 @@ export default class TreeOptionControl extends React.Component<
data.options = this.pretreatOptions(options); data.options = this.pretreatOptions(options);
} }
if (source === 'api') { if (source === 'api' || source === 'apicenter') {
const {api, labelField, valueField} = this.state; const {api, labelField, valueField} = this.state;
data.source = api; data.source = api;
data.labelField = labelField; data.labelField = labelField;
@ -123,7 +128,7 @@ export default class TreeOptionControl extends React.Component<
* *
*/ */
@autobind @autobind
handleSourceChange(source: 'custom' | 'api') { handleSourceChange(source: 'custom' | 'api' | 'apicenter') {
this.setState({source: source}, this.onChange); this.setState({source: source}, this.onChange);
} }
@ -134,23 +139,27 @@ export default class TreeOptionControl extends React.Component<
labelRemark, labelRemark,
useMobileUI, useMobileUI,
env, env,
popOverContainer popOverContainer,
hasApiCenter
} = this.props; } = this.props;
const classPrefix = env?.theme?.classPrefix; const classPrefix = env?.theme?.classPrefix;
const {source} = this.state; const {source} = this.state;
const optionSourceList = ([ const optionSourceList = (
[
{ {
label: '自定义选项', label: '自定义选项',
value: 'custom' value: 'custom'
}, },
{ {
label: '接口获取', label: '外部接口',
value: 'api' value: 'api'
} },
...(hasApiCenter ? [{label: 'API中心', value: 'apicenter'}] : [])
] as Array<{ ] as Array<{
label: string; label: string;
value: 'custom' | 'api'; value: 'custom' | 'api' | 'apicenter';
}>).map(item => ({ }>
).map(item => ({
...item, ...item,
onClick: () => this.handleSourceChange(item.value) onClick: () => this.handleSourceChange(item.value)
})); }));
@ -200,7 +209,7 @@ export default class TreeOptionControl extends React.Component<
); );
} }
handleEditLabelOrValue (value: string, path: string, key: string) { handleEditLabelOrValue(value: string, path: string, key: string) {
const options = cloneDeep(this.state.options); const options = cloneDeep(this.state.options);
const {path: nodePath} = this.getNodePath(path); const {path: nodePath} = this.getNodePath(path);
set(options, `${nodePath}.${key}`, value); set(options, `${nodePath}.${key}`, value);
@ -216,12 +225,12 @@ export default class TreeOptionControl extends React.Component<
const path = pathStr.split('-'); const path = pathStr.split('-');
if (path.length === 1) { if (path.length === 1) {
options.splice(index, 1); options.splice(index, 1);
} } else {
else {
const {parentPath} = this.getNodePath(pathStr); const {parentPath} = this.getNodePath(pathStr);
const parentNode = get(options, parentPath, {}); const parentNode = get(options, parentPath, {});
parentNode?.children?.splice(index, 1); parentNode?.children?.splice(index, 1);
if (!parentNode?.children.length) { // 去除僵尸子节点 if (!parentNode?.children.length) {
// 去除僵尸子节点
delete parentNode.children; delete parentNode.children;
} }
set(options, parentPath, parentNode); set(options, parentPath, parentNode);
@ -231,7 +240,7 @@ export default class TreeOptionControl extends React.Component<
@autobind @autobind
getNodePath(pathStr: string) { getNodePath(pathStr: string) {
let pathArr = pathStr.split('-'); let pathArr = pathStr.split('-');
if(pathArr.length === 1) { if (pathArr.length === 1) {
return { return {
path: pathArr, path: pathArr,
parentPath: '' parentPath: ''
@ -251,8 +260,7 @@ export default class TreeOptionControl extends React.Component<
const path = pathStr.split('-'); const path = pathStr.split('-');
if (path.length === 1) { if (path.length === 1) {
options.splice(+path[0] + 1, 0, {...defaultOption}); // 加在后面一项 options.splice(+path[0] + 1, 0, {...defaultOption}); // 加在后面一项
} } else {
else {
const index = path[path.length - 1]; const index = path[path.length - 1];
const {parentPath} = this.getNodePath(pathStr); const {parentPath} = this.getNodePath(pathStr);
const parentNode = get(options, parentPath, {}); const parentNode = get(options, parentPath, {});
@ -264,7 +272,9 @@ export default class TreeOptionControl extends React.Component<
@autobind @autobind
addChildOption(pathStr: string) { addChildOption(pathStr: string) {
if (pathStr.split('-').length >= 7) { if (pathStr.split('-').length >= 7) {
toast.warning('层级过深,建议使用【接口获取】管理选项', {closeButton: true}); toast.warning('层级过深,建议使用【接口获取】管理选项', {
closeButton: true
});
return; return;
} }
const options = cloneDeep(this.state.options); const options = cloneDeep(this.state.options);
@ -272,8 +282,7 @@ export default class TreeOptionControl extends React.Component<
const node = get(options, path) || []; const node = get(options, path) || [];
if (node.children) { if (node.children) {
node.children.push({...defaultOption}); node.children.push({...defaultOption});
} } else {
else {
node.children = [{...defaultOption}]; node.children = [{...defaultOption}];
} }
set(options, path, node); set(options, path, node);
@ -292,22 +301,26 @@ export default class TreeOptionControl extends React.Component<
if (option.children && option.children.length) { if (option.children && option.children.length) {
const parent = cloneDeep(option); const parent = cloneDeep(option);
delete parent.children; delete parent.children;
return <div className={cx('ae-TreeOptionControlItem-parent')} key={`parent${path}${key}${option.label}`}> return (
<div
className={cx('ae-TreeOptionControlItem-parent')}
key={`parent${path}${key}${option.label}`}
>
{this.renderOptions(parent, key, indexes)} {this.renderOptions(parent, key, indexes)}
<div <div
className={cx('ae-TreeOptionControlItem-son')} className={cx('ae-TreeOptionControlItem-son')}
key={`son${path}${key}${option.label}`} key={`son${path}${key}${option.label}`}
data-level={path} data-level={path}
> >
{ {option.children.map((option: any, key: number) => {
option.children.map((option: any, key: number) => { return this.renderOptions(option, key, indexes.concat(key));
return this.renderOptions(option, key, indexes.concat(key)) })}
})
}
</div> </div>
</div> </div>
);
} }
return <div return (
<div
className="ae-TreeOptionControlItem" className="ae-TreeOptionControlItem"
key={`child${path}${key}${option.label}`} key={`child${path}${key}${option.label}`}
data-path={path} data-path={path}
@ -320,7 +333,8 @@ export default class TreeOptionControl extends React.Component<
value={option.label} value={option.label}
placeholder="选项名称" placeholder="选项名称"
clearable={false} clearable={false}
onBlur={(event: any) => { // 这里使用onBlur替代onChange 减少渲染次数 onBlur={(event: any) => {
// 这里使用onBlur替代onChange 减少渲染次数
this.handleEditLabelOrValue(event.target.value, path, 'label'); this.handleEditLabelOrValue(event.target.value, path, 'label');
}} }}
/> />
@ -373,6 +387,7 @@ export default class TreeOptionControl extends React.Component<
</Button> </Button>
</div> </div>
</div> </div>
);
} }
@autobind @autobind
dragRef(ref: any) { dragRef(ref: any) {
@ -390,9 +405,7 @@ export default class TreeOptionControl extends React.Component<
} }
} }
initDragging() { initDragging() {
const rootSortable = new Sortable( const rootSortable = new Sortable(this.drag as HTMLElement, {
this.drag as HTMLElement,
{
group: 'TreeOptionControlGroup', group: 'TreeOptionControlGroup',
animation: 150, animation: 150,
handle: '.ae-TreeOptionControlItem-dragBar', handle: '.ae-TreeOptionControlItem-dragBar',
@ -400,7 +413,10 @@ export default class TreeOptionControl extends React.Component<
onEnd: (e: any) => { onEnd: (e: any) => {
const options = cloneDeep(this.state.options); const options = cloneDeep(this.state.options);
const {oldIndex, newIndex} = e; const {oldIndex, newIndex} = e;
[options[newIndex], options[oldIndex]] = [options[oldIndex], options[newIndex]]; [options[newIndex], options[oldIndex]] = [
options[oldIndex],
options[newIndex]
];
this.setState({options}, () => this.rereshBindDrag()); this.setState({options}, () => this.rereshBindDrag());
}, },
onMove: (e: any) => { onMove: (e: any) => {
@ -408,10 +424,11 @@ export default class TreeOptionControl extends React.Component<
// 暂时不支持跨级拖拽 // 暂时不支持跨级拖拽
return from.dataset.level === to.dataset.level; return from.dataset.level === to.dataset.level;
} }
} });
);
this.sortables.push(rootSortable); this.sortables.push(rootSortable);
const parents = this.drag?.querySelectorAll('.ae-TreeOptionControlItem-son'); const parents = this.drag?.querySelectorAll(
'.ae-TreeOptionControlItem-son'
);
if (!parents) { if (!parents) {
return; return;
} }
@ -432,7 +449,10 @@ export default class TreeOptionControl extends React.Component<
const {parentPath} = this.getNodePath(nodePath); const {parentPath} = this.getNodePath(nodePath);
const children = get(options, `${parentPath}.children`) || []; const children = get(options, `${parentPath}.children`) || [];
if (children) { if (children) {
[children[oldIndex], children[newIndex]] = [children[newIndex], children[oldIndex]]; [children[oldIndex], children[newIndex]] = [
children[newIndex],
children[oldIndex]
];
set(options, `${parentPath}.children`, children); set(options, `${parentPath}.children`, children);
this.setState({options}); this.setState({options});
} }
@ -476,7 +496,9 @@ export default class TreeOptionControl extends React.Component<
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<div className="ae-TreeOptionControl-content" ref={this.dragRef}> <div className="ae-TreeOptionControl-content" ref={this.dragRef}>
{options.map((option, key) => this.renderOptions(option, key, [key]))} {options.map((option, key) =>
this.renderOptions(option, key, [key])
)}
</div> </div>
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>
@ -484,14 +506,18 @@ export default class TreeOptionControl extends React.Component<
onClick={() => { onClick={() => {
this.hideModal(); this.hideModal();
}} }}
></Button> >
</Button>
<Button <Button
level="primary" level="primary"
onClick={() => { onClick={() => {
this.onChange(); this.onChange();
this.hideModal(true); this.hideModal(true);
}} }}
></Button> >
</Button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>
); );
@ -515,7 +541,7 @@ export default class TreeOptionControl extends React.Component<
renderApiPanel() { renderApiPanel() {
const {render} = this.props; const {render} = this.props;
const {source, api, labelField, valueField} = this.state; const {source, api, labelField, valueField} = this.state;
if (source !== 'api') { if (source === 'custom') {
return null; return null;
} }
@ -528,6 +554,7 @@ export default class TreeOptionControl extends React.Component<
visibleOn: 'data.autoComplete !== false', visibleOn: 'data.autoComplete !== false',
value: api, value: api,
onChange: this.handleAPIChange, onChange: this.handleAPIChange,
sourceType: source,
footer: [ footer: [
{ {
label: tipedLabel( label: tipedLabel(
@ -569,9 +596,11 @@ export default class TreeOptionControl extends React.Component<
onClick={() => { onClick={() => {
this.setState({ this.setState({
modalVisible: true modalVisible: true
}) });
}} }}
></Button> >
</Button>
{this.renderModal()} {this.renderModal()}
</div> </div>
</div> </div>