Merge pull request #8075 from lurunze1226/feat-select-add-controls

feat(amis-editor): Select, Checkboxes, InputTree & TreeSelect组件新增、编辑、删除控件
This commit is contained in:
wutong 2023-09-18 10:45:09 +08:00 committed by GitHub
commit aef454881f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 492 additions and 189 deletions

View File

@ -191,27 +191,16 @@ export class CheckboxesControlPlugin extends BasePlugin {
getSchemaTpl('optionControlV2', {
multiple: true
}),
getSchemaTpl('creatable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('createBtnLabel'), getSchemaTpl('addApi')]
}
/** 新增选项 */
getSchemaTpl('optionAddControl', {
manager: this.manager
}),
getSchemaTpl('editable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('editApi')]
}
/** 编辑选项 */
getSchemaTpl('optionEditControl', {
manager: this.manager
}),
getSchemaTpl('removable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('deleteApi')]
}
})
/** 删除选项 */
getSchemaTpl('optionDeleteControl')
]
},
getSchemaTpl('status', {isFormItem: true}),

View File

@ -449,41 +449,54 @@ export class TreeControlPlugin extends BasePlugin {
label: '只可选择叶子节点',
name: 'onlyLeaf'
}),
getSchemaTpl('creatable', {
formType: 'extend',
hiddenOnDefault: true,
label: '可新增',
form: {
body: [
getSchemaTpl('switch', {
label: '顶层可新增',
value: true,
name: 'rootCreatable'
}),
{
type: 'input-text',
label: '顶层文案',
value: '添加一级节点',
name: 'rootCreateTip',
hiddenOn: '!data.rootCreatable'
},
getSchemaTpl('addApi')
]
}
/** 新增选项 */
getSchemaTpl('optionAddControl', {
manager: this.manager,
replace: true,
collections: [
getSchemaTpl('switch', {
label: '顶层可新增',
value: true,
name: 'rootCreatable'
}),
{
type: 'input-text',
label: '根节点文案',
value: '添加一级节点',
name: 'rootCreateTip',
hiddenOn: '!data.rootCreatable'
},
{
type: 'input-text',
label: '新增文案提示',
value: '添加子节点',
name: 'createTip'
}
]
}),
getSchemaTpl('editable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('editApi')]
}
/** 编辑选项 */
getSchemaTpl('optionEditControl', {
manager: this.manager,
collections: [
{
type: 'input-text',
label: '编辑文案提示',
value: '编辑该节点',
name: 'editTip'
}
]
}),
getSchemaTpl('removable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('deleteApi')]
}
/** 删除选项 */
getSchemaTpl('optionDeleteControl', {
manager: this.manager,
collections: [
{
type: 'input-text',
label: '删除文案提示',
value: '移除该节点',
name: 'removeTip'
}
]
})
]
},

View File

@ -1,44 +1,68 @@
import {EditorNodeType, getSchemaTpl} from 'amis-editor-core';
import {registerEditorPlugin} from 'amis-editor-core';
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
import React from 'react';
import omit from 'lodash/omit';
import {findObjectsWithKey} from 'amis-core';
import {Button, Icon} from 'amis-ui';
import {
registerEditorPlugin,
getSchemaTpl,
BasePlugin,
tipedLabel,
JSONPipeOut
} from 'amis-editor-core';
import {tipedLabel} from 'amis-editor-core';
import {ValidatorTag} from '../../validator';
import {getEventControlConfig} from '../../renderer/event-control/helper';
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
import {resolveOptionType} from '../../util';
import type {RendererProps} from 'amis';
import type {
EditorNodeType,
RendererPluginAction,
RendererPluginEvent,
BaseEventContext
} from 'amis-editor-core';
export class SelectControlPlugin extends BasePlugin {
static id = 'SelectControlPlugin';
static scene = ['layout'];
// 关联渲染器名字
name = '下拉框';
panelTitle = '下拉框';
rendererName = 'select';
icon = 'fa fa-th-list';
panelIcon = 'fa fa-th-list';
pluginIcon = 'select-plugin';
isBaseComponent = true;
panelJustify = true;
notRenderFormZone = true;
$schema = '/schemas/SelectControlSchema.json';
// 组件名称
name = '下拉框';
isBaseComponent = true;
icon = 'fa fa-th-list';
pluginIcon = 'select-plugin';
description = '支持多选,输入提示,可使用 source 获取选项';
docLink = '/amis/zh-CN/components/form/select';
tags = ['表单项'];
scaffold = {
type: 'select',
label: '选项',
name: 'select',
options: [
{
label: '选项A',
value: 'A'
},
{
label: '选项B',
value: 'B'
}
{label: '选项A', value: 'A'},
{label: '选项B', value: 'B'}
]
};
previewSchema: any = {
type: 'form',
className: 'text-left',
@ -51,10 +75,6 @@ export class SelectControlPlugin extends BasePlugin {
]
};
notRenderFormZone = true;
panelTitle = '下拉框';
// 事件定义
events: RendererPluginEvent[] = [
{
@ -243,7 +263,6 @@ export class SelectControlPlugin extends BasePlugin {
}
];
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
return getSchemaTpl('tabs', [
{
@ -310,51 +329,16 @@ export class SelectControlPlugin extends BasePlugin {
manager: this.manager,
onChange: (value: any) => {}
}),
getSchemaTpl('creatable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [
getSchemaTpl('createBtnLabel'),
getSchemaTpl('addApi')
// {
// label: '按钮位置',
// name: 'valueType',
// type: 'button-group-select',
// size: 'sm',
// tiled: true,
// value: 'asUpload',
// mode: 'row',
// options: [
// {
// label: '顶部',
// value: ''
// },
// {
// label: '底部',
// value: ''
// },
// ],
// },
]
}
/** 新增选项 */
getSchemaTpl('optionAddControl', {
manager: this.manager
}),
getSchemaTpl('editable', {
type: 'ae-Switch-More',
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('editApi')]
}
/** 编辑选项 */
getSchemaTpl('optionEditControl', {
manager: this.manager
}),
getSchemaTpl('removable', {
type: 'ae-Switch-More',
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('deleteApi')]
}
})
/** 删除选项 */
getSchemaTpl('optionDeleteControl')
]
},
{

View File

@ -447,41 +447,53 @@ export class TreeSelectControlPlugin extends BasePlugin {
label: '只可选择叶子节点',
name: 'onlyLeaf'
}),
getSchemaTpl('creatable', {
formType: 'extend',
hiddenOnDefault: true,
label: '可新增',
form: {
body: [
getSchemaTpl('switch', {
label: '顶层可新增',
value: true,
name: 'rootCreatable'
}),
{
type: 'input-text',
label: '顶层文案',
value: '添加一级节点',
name: 'rootCreateTip',
hiddenOn: '!data.rootCreatable'
},
getSchemaTpl('addApi')
]
}
/** 新增选项 */
getSchemaTpl('optionAddControl', {
manager: this.manager,
collections: [
getSchemaTpl('switch', {
label: '顶层可新增',
value: true,
name: 'rootCreatable'
}),
{
type: 'input-text',
label: '根节点文案',
value: '添加一级节点',
name: 'rootCreateTip',
hiddenOn: '!data.rootCreatable'
},
{
type: 'input-text',
label: '新增文案提示',
value: '添加子节点',
name: 'createTip'
}
]
}),
getSchemaTpl('editable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('editApi')]
}
/** 编辑选项 */
getSchemaTpl('optionEditControl', {
manager: this.manager,
collections: [
{
type: 'input-text',
label: '编辑文案提示',
value: '编辑该节点',
name: 'editTip'
}
]
}),
getSchemaTpl('removable', {
formType: 'extend',
hiddenOnDefault: true,
form: {
body: [getSchemaTpl('deleteApi')]
}
/** 删除选项 */
getSchemaTpl('optionDeleteControl', {
manager: this.manager,
collections: [
{
type: 'input-text',
label: '删除文案提示',
value: '移除该节点',
name: 'removeTip'
}
]
})
]
},

View File

@ -815,9 +815,38 @@ export default class OptionControl extends React.Component<
this.setState({valueField}, this.onChange);
}
/** 获取功能性字段控件 schema */
getFuncFieldSchema(): Record<string, any>[] {
const {labelField, valueField} = this.state;
return [
{
label: tipedLabel(
'显示字段',
'选项文本对应的数据字段,多字段合并请通过模板配置'
),
type: 'input-text',
name: 'labelField',
clearable: true,
value: labelField,
placeholder: '选项文本对应的字段',
onChange: this.handleLableFieldChange
},
{
label: '值字段',
type: 'input-text',
name: 'valueField',
clearable: true,
value: valueField,
placeholder: '值对应的字段',
onChange: this.handleValueFieldChange
}
];
}
renderApiPanel() {
const {render} = this.props;
const {source, api, labelField, valueField} = this.state;
const {source, api} = this.state;
return render(
'api',
@ -830,27 +859,7 @@ export default class OptionControl extends React.Component<
value: api,
onChange: this.handleAPIChange,
sourceType: source,
footer: [
{
label: tipedLabel(
'显示字段',
'选项文本对应的数据字段,多字段合并请通过模板配置'
),
type: 'input-text',
name: 'labelField',
value: labelField,
placeholder: '选项文本对应的字段',
onChange: this.handleLableFieldChange
},
{
label: '值字段',
type: 'input-text',
name: 'valueField',
value: valueField,
placeholder: '值对应的字段',
onChange: this.handleValueFieldChange
}
]
footer: this.getFuncFieldSchema()
})
);
}
@ -863,6 +872,7 @@ export default class OptionControl extends React.Component<
<div className={cx('ae-OptionControl', className)}>
{this.renderHeader()}
{/* 自定义选项 */}
{source === 'custom' ? (
<div className="ae-OptionControl-wrapper">
{Array.isArray(options) && options.length ? (
@ -890,21 +900,24 @@ export default class OptionControl extends React.Component<
</div>
) : null}
{/* API 接口 */}
{source === 'api' || source === 'apicenter'
? this.renderApiPanel()
: null}
{/* 上下文变量 */}
{source === 'variable'
? render(
'variable',
getSchemaTpl('sourceBindControl', {
label: false,
className: 'ae-ExtendMore'
}),
{
onChange: this.handleAPIChange
}
)
? render('variable', {
type: 'control',
label: false,
className: 'ae-ExtendMore',
body: [
getSchemaTpl('sourceBindControl', {
label: false,
onChange: this.handleAPIChange
})
].concat(this.getFuncFieldSchema())
})
: null}
</div>
);

View File

@ -1,15 +1,22 @@
import React from 'react';
import {
setSchemaTpl,
getSchemaTpl,
defaultValue,
getI18nEnabled
getI18nEnabled,
tipedLabel,
JSONPipeOut
} from 'amis-editor-core';
import {tipedLabel} from 'amis-editor-core';
import {findObjectsWithKey} from 'amis-core';
import {Button, Icon} from 'amis-ui';
import type {SchemaObject} from 'amis';
import assign from 'lodash/assign';
import cloneDeep from 'lodash/cloneDeep';
import omit from 'lodash/omit';
import type {RendererProps} from 'amis';
import type {EditorManager} from 'amis-editor-core';
setSchemaTpl('options', () => {
const i18nEnabled = getI18nEnabled();
return {
@ -244,10 +251,10 @@ setSchemaTpl('addApi', () => {
});
setSchemaTpl('createBtnLabel', {
label: '按钮名称',
label: '新增按钮名称',
name: 'createBtnLabel',
type: 'input-text',
placeholder: '新'
placeholder: '新增选项'
});
setSchemaTpl('editable', (schema: Partial<SchemaObject> = {}) => {
@ -269,6 +276,15 @@ setSchemaTpl('editApi', () =>
})
);
setSchemaTpl('editInitApi', () =>
getSchemaTpl('apiControl', {
label: '编辑初始化接口',
name: 'editInitApi',
mode: 'row',
visibleOn: 'data.editable'
})
);
setSchemaTpl('removable', (schema: Partial<SchemaObject> = {}) => {
return {
label: tipedLabel('可删除', '配置事件动作可插入或拦截默认交互'),
@ -446,3 +462,271 @@ setSchemaTpl('dataMap', {
})
]
});
export interface OptionControlParams {
manager: EditorManager;
/** switch-more 控制器的配置 */
controlSchema?: Record<string, any>;
/** 子表单中的配置集合 */
collections?: Record<string, any>[];
/** 是否替换除了addControls以外的其他属性 */
replace?: boolean;
}
/**
*
*/
setSchemaTpl('optionAddControl', (params: OptionControlParams) => {
const {manager, controlSchema = {}, collections = [], replace} = params || {};
const customFormItems = Array.isArray(collections)
? collections
: [collections];
return getSchemaTpl('creatable', {
formType: 'extend',
autoFocus: false,
hiddenOnDefault: false,
...controlSchema,
form: {
body: [
...(replace
? customFormItems
: [...customFormItems, getSchemaTpl('createBtnLabel')]),
getSchemaTpl('addApi'),
/** 用于关闭开关后清空相关配置 */
{
type: 'hidden',
name: 'addDialog'
},
{
name: 'addControls',
asFormItem: true,
label: false,
children: (props: RendererProps) => {
const {value, data: ctx, onBulkChange} = props || {};
const {addApi, createBtnLabel, addDialog, optionLabel} = ctx || {};
/** 新增表单弹窗 */
const scaffold = {
type: 'dialog',
title: createBtnLabel || `新增${optionLabel || '选项'}`,
...addDialog,
body: {
/** 标识符,用于 SubEditor 确认后找到对应的 Schema */
'amis-select-addControls': true,
'type': 'form',
'api': addApi,
/** 这里是为了兼容旧版比如type: text类型的组件会被渲染为input-text */
'controls': [
...(value
? Array.isArray(value)
? value
: [value]
: [
/** FIXME: 这里是没做任何配置时的默认 scaffold */
{
type: 'input-text',
name: 'label',
label: false,
required: true,
placeholder: '请输入名称'
}
])
]
}
};
return (
<Button
className="w-full flex flex-col items-center"
level="enhance"
size="sm"
onClick={() => {
manager.openSubEditor({
title: '配置新增表单',
value: scaffold,
onChange: (value, diff: any) => {
const pureSchema = JSONPipeOut(
value,
(key, propValue) =>
key.substring(0, 2) === '__' || key === 'id'
);
const addDialog = omit(pureSchema, [
'type',
'body',
'id'
]);
const targetForm = findObjectsWithKey(
pureSchema,
'amis-select-addControls'
);
const addApi = targetForm?.[0]?.api;
const addControls =
targetForm?.[0]?.controls ?? targetForm?.[0]?.body;
onBulkChange({addApi, addDialog, addControls});
}
});
}}
>
</Button>
);
}
}
// {
// label: '按钮位置',
// name: 'valueType',
// type: 'button-group-select',
// size: 'sm',
// tiled: true,
// value: 'asUpload',
// mode: 'row',
// options: [
// {
// label: '顶部',
// value: ''
// },
// {
// label: '底部',
// value: ''
// },
// ],
// }
]
}
});
});
/**
*
*/
setSchemaTpl('optionEditControl', (params: OptionControlParams) => {
const {manager, controlSchema = {}, collections = [], replace} = params || {};
const customFormItems = Array.isArray(collections)
? collections
: [collections];
return getSchemaTpl('editable', {
formType: 'extend',
autoFocus: false,
hiddenOnDefault: false,
...controlSchema,
form: {
body: [
...(replace ? customFormItems : [...customFormItems]),
getSchemaTpl('editInitApi'),
getSchemaTpl('editApi'),
/** 用于关闭开关后清空相关配置 */
{
type: 'hidden',
name: 'editDialog'
},
{
name: 'editControls',
asFormItem: true,
label: false,
children: (props: RendererProps) => {
const {value, data: ctx, onBulkChange} = props || {};
const {editApi, editInitApi, editDialog, optionLabel} = ctx || {};
/** 新增表单弹窗 */
const scaffold = {
type: 'dialog',
title: '编辑选项',
...editDialog,
body: {
/** 标识符,用于 SubEditor 确认后找到对应的 Schema */
'amis-select-editControls': true,
'type': 'form',
'api': editApi,
'initApi': editInitApi,
/** 这里是为了兼容旧版比如type: text类型的组件会被渲染为input-text */
'controls': [
...(value
? Array.isArray(value)
? value
: [value]
: [
/** FIXME: 这里是没做任何配置时的默认 scaffold */
{
type: 'input-text',
name: 'label',
label: false,
required: true,
placeholder: '请输入名称'
}
])
]
}
};
return (
<Button
className="w-full flex flex-col items-center"
level="enhance"
size="sm"
onClick={() => {
manager.openSubEditor({
title: '配置编辑表单',
value: scaffold,
onChange: (value, diff: any) => {
const pureSchema = JSONPipeOut(
value,
(key, propValue) =>
key.substring(0, 2) === '__' || key === 'id'
);
const editDialog = omit(pureSchema, [
'type',
'body',
'id'
]);
const targetForm = findObjectsWithKey(
pureSchema,
'amis-select-editControls'
);
const editApi = targetForm?.[0]?.api;
const editInitApi = targetForm?.[0]?.initApi;
const editControls =
targetForm?.[0]?.controls ?? targetForm?.[0]?.body;
onBulkChange({
editApi,
editInitApi,
editDialog,
editControls
});
}
});
}}
>
</Button>
);
}
}
]
}
});
});
/**
*
*/
setSchemaTpl('optionDeleteControl', (params: OptionControlParams) => {
const {manager, controlSchema = {}, collections = [], replace} = params || {};
const customFormItems = Array.isArray(collections)
? collections
: [collections];
return getSchemaTpl('removable', {
formType: 'extend',
autoFocus: false,
hiddenOnDefault: false,
...controlSchema,
form: {
body: [
...(replace ? customFormItems : [...customFormItems]),
getSchemaTpl('deleteApi')
]
}
});
});

View File

@ -708,6 +708,14 @@
margin-right: 0;
margin-bottom: 0;
}
.#{$ns}Checkboxes-addBtn {
> svg {
width: px2rem(14px);
height: px2rem(14px);
margin-right: var(--Checkbox-gap);
}
}
}
.#{$ns}RadiosControl-group,