fix: PageMaker315专项问题修复 (#6570)

Co-authored-by: wutong25 <wutong25@baidu.com>
This commit is contained in:
igrowp 2023-04-11 15:11:55 +08:00 committed by GitHub
parent ec2fca5dda
commit 17662f5337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1706 additions and 335 deletions

View File

@ -5,6 +5,9 @@ $category-2-height: px2rem(32px);
$tooltip-bottom: '[data-tooltip][data-position=' bottom ']:hover:after';
.editor-right-panel {
--select-base-default-fontSize: 12px;
--fonts-size-7: 12px;
position: relative;
flex: 0 0 auto;
width: $right-panel-width;
@ -222,7 +225,6 @@ $tooltip-bottom: '[data-tooltip][data-position=' bottom ']:hover:after';
height: 100%;
max-width: calc(100% - 48px); // 避免被内容元素撑开
border-left: none;
transform: scale(1); // 内部元素fixed定位需要
.editorPanel-tabs-pane {
position: relative;
@ -274,13 +276,16 @@ $tooltip-bottom: '[data-tooltip][data-position=' bottom ']:hover:after';
overflow: hidden;
margin: calc(0rem - (var(--gap-base)));
// 老动作入口
.old-action-btn {
.old-action-tooltip-warpper {
position: absolute;
left: 12px;
top: px2rem(15px);
width: 91%;
z-index: 9999;
// 老动作入口
.old-action-btn {
width: 100%;
}
}
// tab导航

View File

@ -120,3 +120,40 @@
color: var(--primary);
}
}
.ae-AdaptorControl {
&-func {
&-header,
&-footer {
padding-left: 8px;
font-size: 12px;
}
&-header {
margin-bottom: -20px;
}
&-footer {
margin-top: -20px;
}
&-arg {
padding: 0;
height: auto;
font-size: 12px;
vertical-align: baseline;
color: var(--primary);
}
&-editor {
margin-bottom: 0;
.cxd-EditorControl {
padding-top: 20px;
padding-bottom: 20px;
}
.cxd-MonacoEditor-placeholder {
line-height: 18px;
font-size: 12px;
}
}
}
&-desc-tooltip {
max-width: 500px;
}
}

View File

@ -70,7 +70,7 @@
}
&-EditLabel {
width: px2rem(36px);
flex: 0 0 2.25rem;
padding-right: 0;
}

View File

@ -100,3 +100,9 @@
}
}
}
.ae-ValidationControl-label-code {
background-color: #666;
padding: 0 4px;
border-radius: 2px;
}

View File

@ -69,7 +69,8 @@ export class PopOverForm extends React.Component<PopOverFormProps> {
render(
this.buildSchema(),
{
data: createObject(store.ctx, popOverFormContext?.value)
data: createObject(store.ctx, popOverFormContext?.value),
manager
},
{
...manager.env,

View File

@ -461,7 +461,7 @@ export class BasicToolbarPlugin extends BasePlugin {
onSelect: () => store.redo()
});
// menus.push('|');
menus.push('|');
/** 可使用「点选(默认向后插入)」替代 */
/*
@ -519,10 +519,8 @@ export class BasicToolbarPlugin extends BasePlugin {
}
}
/** 「点选(默认向后插入)」+ 「删除」可以替换 「更改类型」 */
/*
menus.push({
label: '更改类型',
label: '替换组件',
disabled:
!node.host ||
node.info?.typeMutable === false ||
@ -531,7 +529,6 @@ export class BasicToolbarPlugin extends BasePlugin {
!node.replaceable,
onSelect: () => manager.showReplacePanel(id)
});
*/
}
if (

View File

@ -21,14 +21,15 @@ export class UnkownRendererPlugin extends BasePlugin {
return;
} else if (
renderer.name === 'card-item' ||
renderer.name === 'list-item-field'
renderer.name === 'list-item-field' ||
renderer.name === 'card-item-field'
) {
return;
}
// 复制部分信息出去
return {
name: 'Unkown',
name: 'Unknown',
$schema: '/schemas/UnkownSchema.json'
};
}

View File

@ -118,6 +118,7 @@ import picker from './form/picker.svg';
import quarter from './form/quarter.svg';
import radios from './form/radios.svg';
import searchBox from './form/select.svg';
import select from './form/select.svg';
import staticIcon from './form/static.svg';
import subForm from './form/sub-form.svg';
@ -282,6 +283,7 @@ registerIcon('nested-select-plugin', nestedSelect);
registerIcon('picker-plugin', picker);
registerIcon('quarter-plugin', quarter);
registerIcon('radios-plugin', radios);
registerIcon('search-box-plugin', searchBox);
registerIcon('select-plugin', select);
registerIcon('static-plugin', staticIcon);
registerIcon('sub-form-plugin', subForm);

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg t="1614838454815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12115" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
<path d="M904 1024H120c-66.168 0-120-53.832-120-120V120C0 53.832 53.832 0 120 0h784c66.168 0 120 53.832 120 120v784c0 66.168-53.832 120-120 120zM120 4C56.038 4 4 56.038 4 120v784c0 63.962 52.038 116 116 116h784c63.962 0 116-52.038 116-116V120c0-63.962-52.038-116-116-116H120z" fill="currentColor" p-id="12116"></path>
<path d="M402 832h-130c-44.112 0-80-35.888-80-80V272c0-44.112 35.888-80 80-80h130c44.112 0 80 35.888 80 80v480c0 44.112-35.888 80-80 80z m-130-580c-11.028 0-20 8.972-20 20v480c0 11.028 8.972 20 20 20h130c11.028 0 20-8.972 20-20V272c0-11.028-8.972-20-20-20h-130z" fill="currentColor" p-id="12117"></path>
<path d="M752 482h-130c-44.112 0-80-35.888-80-80v-130c0-44.112 35.888-80 80-80h130c44.112 0 80 35.888 80 80v130c0 44.112-35.888 80-80 80z m-130-230c-11.028 0-20 8.972-20 20v130c0 11.028 8.972 20 20 20h130c11.028 0 20-8.972 20-20v-130c0-11.028-8.972-20-20-20h-130z" fill="currentColor" p-id="12118"></path>
<path d="M752 832h-130c-44.112 0-80-35.888-80-80v-130c0-44.112 35.888-80 80-80h130c44.112 0 80 35.888 80 80v130c0 44.112-35.888 80-80 80z m-130-230c-11.028 0-20 8.972-20 20v130c0 11.028 8.972 20 20 20h130c11.028 0 20-8.972 20-20v-130c0-11.028-8.972-20-20-20h-130z" fill="#205dd9" p-id="12119"></path></svg>
<svg width="17px" height="16px" viewBox="0 0 17 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="布局容器交互稿" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-63.000000, -251.000000)">
<g id="常见布局备份-3" transform="translate(63.500000, 251.000000)">
<rect id="矩形" x="0" y="0" width="16" height="16"></rect>
<rect id="矩形" stroke="currentColor" stroke-linejoin="round" x="10.5" y="10" width="4" height="4"></rect>
<polyline id="路径" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" points="8.52861305 14 1.5 14 1.5 2 14.5 2 14.5 8"></polyline>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 856 B

View File

@ -108,6 +108,7 @@ import './plugin/Markdown';
import './plugin/Nav';
import './plugin/Operation';
import './plugin/Page';
import './plugin/Icon';
import './plugin/Pagination';
import './plugin/Panel';
import './plugin/Plain';
@ -115,6 +116,7 @@ import './plugin/Progress';
import './plugin/Property';
import './plugin/QRCode';
import './plugin/Reset';
import './plugin/SearchBox';
import './plugin/Service';
import './plugin/Status';
import './plugin/Steps';
@ -154,6 +156,7 @@ import './renderer/NavDefaultActive';
import './renderer/MapSourceControl';
import './renderer/TimelineItemControl';
import './renderer/APIControl';
import './renderer/APIAdaptorControl';
import './renderer/ValidationControl';
import './renderer/ValidationItem';
import './renderer/SwitchMoreControl';

View File

@ -1843,8 +1843,6 @@ extendLocale('en-US', {
'348097cc50579e489f0bcb5433637d3a':
'With this option enabled, you can sort them according to the current column sequence (backend sequence).',
'9db64f772c11c614ee00bb3cc066f46f': 'Column group name',
'fea0f3f456153564218a9eefb78d8cec':
'When the group name of multiple column groups keeps consistent, the table displays the super table header at the top layer of the table header displayed.<a href="https://baidu.github.io/amis/crud/header-group" target="_blank">Example</a>',
'19c4f5e98ad302574202de30dddbaf66': 'Enable quick edit',
'15c3796e07e33afc7252df751f610c5d': 'Whether to save immediately',
'ba5a0a1ff2c438ae7719ca48b0ce3af7': 'Enable “View more display options”',
@ -3773,6 +3771,14 @@ extendLocale('en-US', {
'8985ea173dce8f9bee667b3cdf0b7bdf':
'This configuration item only applies to the "Add" button in the table operation bar',
'3f64a567662a24714768237a3a6d0de7': 'New button below the table',
'9dd651411c1cb25e19249bb4ea8878c3': 'Animation interval (ms)',
'46bc66b19c2b589ebd24d1c583325080': 'Animation duration (ms)',
'9cb33a16b57ef10b79ae76a66379d66f': 'Arrows are always displayed',
'0bf60b32f9db93b87e08763b1c815469': 'quantity',
'98e04bf7cb91497e4225d272e3a331c8': 'Custom Arrows',
'7076ef56f5c4f13d3c9bf87d3536352f': 'left arrow',
'fce3880b7a24a47f02a16331a294b255': 'Right arrow',
'f4f965513462fcc9fe6fe896a9c249d8': 'Multi-picture display',
'522cddc343d72db3db80cf3d71f99210':
'The API return format is incorrect. Please click the example on the right side of the interface address to view the CRUD data interface structure requirements',
'5323ab3e5c12066101244f0577c30e22': 'Custom container area',
@ -3842,5 +3848,7 @@ extendLocale('en-US', {
'<div>When the value is__ When undefined, it means to delete the corresponding field. You can combine {"&": " $$"} to achieve the blacklist effect</ div>',
'cb65841ea7dec5ae0af20b3f5e52abfc': 'Raw data leveling',
'6922790f45faf064e063069816e4d2ec':
'After opening, all the original data will be flattened and set in the data, and customized on this basis'
'After opening, all the original data will be flattened and set in the data, and customized on this basis',
'9791b05a4df9d72f1a01b81fa695fbc6':
'When the grouping names of multiple columns are consistent, the table will display the super header on the upper layer of the display header.<a href=“ https://baidu.github.io/amis/zh-CN/components/table#%E8%B6%85%E7%BA%A7%E8%A1%A8%E5%A4%B4 " target="_ Blank ">Example</a>'
});

View File

@ -2840,8 +2840,6 @@ extendLocale('zh-CN', {
'<span class="label label-warning">四</span>',
'ce3fd44456123f571e9d083b98da9fcb':
'<span class="label label-primary">五</span>',
'fea0f3f456153564218a9eefb78d8cec':
'当多列的分组名称设置一致时,表格会在显示表头的上层显示超级表头,<a href="https://baidu.github.io/amis/crud/header-group" target="_blank">示例</a>',
'f8fc21a9fd40881e8fd3d7f15919465c':
'如果当前字段有值,请不要设置,否则覆盖。支持使用 <code>\\${xxx}</code> 来获取变量,或者用 lodash.template 语法来写模板逻辑。<a target="_blank" href="/amis/zh-CN/docs/concepts/template">详情</a>',
'2c8a99d35cb5704994cabcc61a4c3a4a':
@ -3348,7 +3346,7 @@ extendLocale('zh-CN', {
'9e1bafbb00018beacc8f579c8ddfaa36': '设置组件「',
'6c6e12c54723170f214527bedaf81f7d': '动作类型',
'1b7e6b2dbf3b7f4b1baf2c42e49a995d': '组件变量',
'2eb4c7ac45befad0f1f9c750bda57166': '内存变量',
'2eb4c7ac45befad0f1f9c750bda57166': '应用临时变量',
'844a7a7aacc5be82d0fd6225edc6bf63': '请选择变量',
'85451d2eb59327a23e8f745161066d4a': '请输入变量值',
'3d4d83f05a12364e2522fcfb265d8ce8':
@ -3357,6 +3355,14 @@ extendLocale('zh-CN', {
'5720057e62e80f7a04489dc4c035b4f1': '取消按钮图标',
'8985ea173dce8f9bee667b3cdf0b7bdf': '此配置项只作用于表格操作栏的“新增”按钮',
'3f64a567662a24714768237a3a6d0de7': '表格下方新增按钮',
'9dd651411c1cb25e19249bb4ea8878c3': '动画间隔(ms)',
'46bc66b19c2b589ebd24d1c583325080': '动画时长(ms)',
'9cb33a16b57ef10b79ae76a66379d66f': '箭头一直显示',
'0bf60b32f9db93b87e08763b1c815469': '数量',
'98e04bf7cb91497e4225d272e3a331c8': '自定义箭头',
'7076ef56f5c4f13d3c9bf87d3536352f': '左箭头',
'fce3880b7a24a47f02a16331a294b255': '右箭头',
'f4f965513462fcc9fe6fe896a9c249d8': '多图展示',
'522cddc343d72db3db80cf3d71f99210':
'API返回格式不正确请点击接口地址右侧示例查看CRUD数据接口结构要求',
'9b39126b20e519bb1c6e9054f4b55784':
@ -3423,5 +3429,7 @@ extendLocale('zh-CN', {
'<div>当值为 __undefined时表示删除对应的字段可以结合{"&": "\\$$"}来达到黑名单效果。</div>',
'cb65841ea7dec5ae0af20b3f5e52abfc': '原始数据打平',
'6922790f45faf064e063069816e4d2ec':
'开启后,会将所有原始数据打平设置到 data 中,并在此基础上定制'
'开启后,会将所有原始数据打平设置到 data 中,并在此基础上定制',
'9791b05a4df9d72f1a01b81fa695fbc6':
'当多列的分组名称设置一致时,表格会在显示表头的上层显示超级表头,<a href="https://baidu.github.io/amis/zh-CN/components/table#%E8%B6%85%E7%BA%A7%E8%A1%A8%E5%A4%B4" target="_blank">示例</a>'
});

View File

@ -295,7 +295,8 @@ export class ButtonPlugin extends BasePlugin {
getSchemaTpl('icon', {
name: 'rightIcon',
label: '右侧图标'
})
}),
getSchemaTpl('badge')
]
},
getSchemaTpl('status', {

View File

@ -209,26 +209,26 @@ export class CarouselPlugin extends BasePlugin {
label: '自动轮播',
pipeIn: defaultValue(true)
},
{
getSchemaTpl('valueFormula', {
rendererSchema: {
type: 'input-number'
},
mode: 'vertical',
name: 'interval',
type: 'input-range',
label: '动画间隔',
min: 1,
max: 100,
step: 1,
unit: 's',
pipeIn: (value: any) => (value ?? 3000) / 1000,
pipeOut: (value: any, originValue: any, data: any) => value * 1000
},
label: '动画间隔(ms)',
valueType: 'number',
pipeIn: defaultValue(5000)
}),
{
name: 'duration',
type: 'input-range',
label: '动画时长',
type: 'input-number',
label: '动画时长(ms)',
mode: 'inline',
className: 'w-full',
min: 100,
max: 2000,
step: 10,
pipeIn: defaultValue(500),
unit: 'ms'
size: 'sm',
pipeIn: defaultValue(500)
},
{
name: 'animation',
@ -288,6 +288,34 @@ export class CarouselPlugin extends BasePlugin {
}
]
},
{
name: 'alwaysShowArrow',
type: 'switch',
mode: 'inline',
className: 'w-full',
label: '箭头一直显示',
hiddenOn: '!~this.controls.indexOf("arrows")',
pipeIn: defaultValue(false)
},
{
type: 'ae-switch-more',
bulk: true,
mode: 'normal',
name: 'multiple',
label: '多图展示',
formType: 'extend',
form: {
body: [
{
name: 'multiple.count',
label: '数量',
type: 'input-number',
min: 2,
step: 1
}
]
}
},
{
name: 'width',
type: 'input-text',

View File

@ -1,7 +1,35 @@
import {registerEditorPlugin} from 'amis-editor-core';
import {registerEditorPlugin, tipedLabel} from 'amis-editor-core';
import {BaseEventContext, BasePlugin} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
const dateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
},
{
label: 'x(毫秒时间戳)',
value: 'x'
},
{
label: 'YYYY-MM-DD',
value: 'YYYY-MM-DD'
},
{
label: 'YYYY/MM/DD',
value: 'YYYY/MM/DD'
},
{
label: 'YYYY年MM月DD日',
value: 'YYYY年MM月DD日'
}
];
const valueDateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
}
];
export class DatePlugin extends BasePlugin {
static scene = ['layout'];
// 关联渲染器名字
@ -40,20 +68,28 @@ export class DatePlugin extends BasePlugin {
{
type: 'input-date',
name: 'value',
label: '日期值'
label: '日期值'
},
{
type: 'input-text',
name: 'format',
label: '显示日期格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'显示格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: dateFormatOptions,
pipeIn: defaultValue('YYYY-MM-DD')
},
{
type: 'input-text',
name: 'valueFormat',
label: '数据日期格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'值格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: valueDateFormatOptions,
pipeIn: defaultValue('X')
},
getSchemaTpl('placeholder', {

View File

@ -1,8 +1,36 @@
import {registerEditorPlugin} from 'amis-editor-core';
import {registerEditorPlugin, tipedLabel} from 'amis-editor-core';
import {BaseEventContext} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {DatePlugin} from './Date';
const dateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
},
{
label: 'x(毫秒时间戳)',
value: 'x'
},
{
label: 'YYYY-MM-DD HH:mm:ss',
value: 'YYYY-MM-DD HH:mm:ss'
},
{
label: 'YYYY/MM/DD HH:mm:ss',
value: 'YYYY/MM/DD HH:mm:ss'
},
{
label: 'YYYY年MM月DD日 HH时mm分ss秒',
value: 'YYYY年MM月DD日 HH时mm分ss秒'
}
];
const valueDateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
}
];
export class DatetimePlugin extends DatePlugin {
static scene = ['layout'];
// 关联渲染器名字
@ -35,20 +63,28 @@ export class DatetimePlugin extends DatePlugin {
{
type: 'input-datetime',
name: 'value',
label: '日期时间值'
label: '日期时间值'
},
{
type: 'input-text',
name: 'format',
label: '显示日期时间格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'显示格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: dateFormatOptions,
pipeIn: defaultValue('YYYY-MM-DD HH:mm:ss')
},
{
type: 'input-text',
name: 'valueFormat',
label: '数据日期格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'值格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: valueDateFormatOptions,
pipeIn: defaultValue('X')
},
getSchemaTpl('placeholder', {

View File

@ -163,7 +163,8 @@ export class ButtonGroupControlPlugin extends BasePlugin {
}),
getSchemaTpl('buttonLevel', {
label: '按钮选中样式',
name: 'btnActiveLevel'
name: 'btnActiveLevel',
pipeIn: defaultValue('primary')
})
]
},

View File

@ -294,7 +294,32 @@ export class DateControlPlugin extends BasePlugin {
'值格式',
'提交数据前将根据设定格式化数据,请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
pipeIn: defaultValue('X')
pipeIn: defaultValue('YYYY-MM-DD'),
clearable: true,
onChange: (
value: string,
oldValue: any,
model: any,
form: any
) => {
const type = form.data.type.split('-')[1];
model.setOptions(DateType[type].formatOptions);
// 时间日期类组件 input-time 需要更加关注 timeFormat 和 inputFormat 属性区别
// inputFormat 表示输入框内的显示格式; timeFormat表示选择下拉弹窗中展示"HH、mm、ss"的组合
if (type === 'time') {
const timeFormatObj = DateType[type].formatOptions.find(
item => item.value === value
);
const timeFormat = timeFormatObj
? (timeFormatObj as any).timeFormat
: 'HH:mm:ss';
form.setValues({
timeFormat: timeFormat
});
}
},
options:
DateType[this.scaffold.type.split('-')[1]].formatOptions
},
{
type: 'input-text',

View File

@ -8,6 +8,16 @@ import {FormulaDateType} from '../../renderer/FormulaControl';
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
import {getRendererByName} from 'amis-core';
const formatX = [
{
label: 'X(时间戳)',
value: 'X'
},
{
label: 'x(毫秒时间戳)',
value: 'x'
}
];
const DateType: {
[key: string]: {
format: string;
@ -15,6 +25,8 @@ const DateType: {
ranges: string[];
sizeMutable?: boolean;
type?: string;
timeFormat?: string;
formatOptions: Array<{label: string; value: string; timeFormat?: string}>;
};
} = {
date: {
@ -28,11 +40,27 @@ const DateType: {
'thismonth',
'prevmonth',
'prevquarter'
],
formatOptions: [
...formatX,
{
label: 'YYYY-MM-DD',
value: 'YYYY-MM-DD'
},
{
label: 'YYYY/MM/DD',
value: 'YYYY/MM/DD'
},
{
label: 'YYYY年MM月DD日',
value: 'YYYY年MM月DD日'
}
]
},
datetime: {
...getRendererByName('input-datetime-range'),
format: 'YYYY-MM-DD HH:mm:ss',
timeFormat: 'HH:mm:ss',
placeholder: '请选择日期时间范围',
ranges: [
'yesterday',
@ -41,31 +69,102 @@ const DateType: {
'thismonth',
'prevmonth',
'prevquarter'
],
formatOptions: [
...formatX,
{
label: 'YYYY-MM-DD HH:mm:ss',
value: 'YYYY-MM-DD HH:mm:ss'
},
{
label: 'YYYY/MM/DD HH:mm:ss',
value: 'YYYY/MM/DD HH:mm:ss'
},
{
label: 'YYYY年MM月DD日 HH时mm分ss秒',
value: 'YYYY年MM月DD日 HH时mm分ss秒'
}
]
},
time: {
...getRendererByName('input-time-range'),
format: 'HH:mm',
timeFormat: 'HH:mm:ss',
placeholder: '请选择时间范围',
ranges: []
ranges: [],
formatOptions: [
{
label: 'HH:mm',
value: 'HH:mm',
timeFormat: 'HH:mm'
},
{
label: 'HH:mm:ss',
value: 'HH:mm:ss',
timeFormat: 'HH:mm:ss'
},
{
label: 'HH时mm分',
value: 'HH时mm分',
timeFormat: 'HH:mm'
},
{
label: 'HH时mm分ss秒',
value: 'HH时mm分ss秒',
timeFormat: 'HH:mm:ss'
}
]
},
month: {
...getRendererByName('input-month-range'),
format: 'YYYY-MM',
placeholder: '请选择月份范围',
ranges: []
ranges: [],
formatOptions: [
...formatX,
{
label: 'YYYY-MM',
value: 'YYYY-MM'
},
{
label: 'MM',
value: 'MM'
},
{
label: 'M',
value: 'M'
}
]
},
quarter: {
...getRendererByName('input-quarter-range'),
format: 'YYYY [Q]Q',
placeholder: '请选择季度范围',
ranges: ['thisquarter', 'prevquarter']
ranges: ['thisquarter', 'prevquarter'],
formatOptions: [
...formatX,
{
label: 'YYYY-[Q]Q',
value: 'YYYY-[Q]Q'
},
{
label: 'Q',
value: 'Q'
}
]
},
year: {
...getRendererByName('input-year-range'),
format: 'YYYY',
placeholder: '请选择年范围',
ranges: ['thisyear', 'lastYear']
ranges: ['thisyear', 'lastYear'],
formatOptions: [
...formatX,
{
label: 'YYYY',
value: 'YYYY'
}
]
}
};
@ -218,6 +317,7 @@ export class DateRangeControlPlugin extends BasePlugin {
const type: string = value.split('-')[1];
form.setValues({
inputFormat: DateType[type]?.format,
timeFormat: DateType[type]?.timeFormat,
placeholder: DateType[type]?.placeholder,
format: type === 'time' ? 'HH:mm' : 'X',
minDate: '',
@ -238,7 +338,20 @@ export class DateRangeControlPlugin extends BasePlugin {
'值格式',
'提交数据前将根据设定格式化数据,请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
pipeIn: defaultValue('X')
pipeIn: defaultValue('X'),
clearable: true,
onChange: (
value: string,
oldValue: any,
model: any,
form: any
) => {
model.setOptions(
DateType[form.data.type.split('-')[1]].formatOptions
);
},
options:
DateType[this.scaffold.type.split('-')[1]].formatOptions
},
{
type: 'input-text',
@ -248,19 +361,19 @@ export class DateRangeControlPlugin extends BasePlugin {
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
pipeIn: defaultValue('YYYY-MM-DD'),
clearable: true
// onChange: (
// value: string,
// oldValue: any,
// model: any,
// form: any
// ) => {
// model.setOptions(
// DateType[form.data.type.split('-')[1]].formatOptions
// );
// },
// options:
// DateType[this.scaffold.type.split('-')[1]].formatOptions
clearable: true,
onChange: (
value: string,
oldValue: any,
model: any,
form: any
) => {
model.setOptions(
DateType[form.data.type.split('-')[1]].formatOptions
);
},
options:
DateType[this.scaffold.type.split('-')[1]].formatOptions
},
getSchemaTpl('utc'),
getSchemaTpl('clearable', {

View File

@ -15,9 +15,10 @@ import {
tipedLabel,
getI18nEnabled,
repeatArray,
mockValue
mockValue,
EditorNodeType
} from 'amis-editor-core';
import {setVariable} from 'amis-core';
import {setVariable, someTree} from 'amis-core';
import {ValidatorTag} from '../../validator';
import {
getEventControlConfig,
@ -833,6 +834,41 @@ export class TableControlPlugin extends BasePlugin {
};
}
}
async buildDataSchemas(node: EditorNodeType, region?: EditorNodeType) {
const itemsSchema: any = {
$id: 'inputTableRow',
type: 'object',
properties: {}
};
const columns: EditorNodeType = node.children.find(
item => item.isRegion && item.region === 'columns'
);
for (let current of columns?.children) {
const schema = current.schema;
if (schema.name) {
itemsSchema.properties[schema.name] = current.info?.plugin
?.buildDataSchemas
? await current.info.plugin.buildDataSchemas(current, region)
: {
type: 'string',
title: schema.label || schema.name
};
}
}
if (region?.region === 'columns') {
return itemsSchema;
}
return {
$id: 'inputTable',
type: 'array',
title: '表格表单数据',
items: itemsSchema
};
}
}
registerEditorPlugin(TableControlPlugin);

View File

@ -124,8 +124,7 @@ export class RadiosControlPlugin extends BasePlugin {
getSchemaTpl('valueFormula', {
rendererSchema: context?.schema,
useSelectMode: true, // 改用 Select 设置模式
visibleOn:
'this.options && this.options.length > 0 && this.selectFirst !== true'
visibleOn: 'this.options && this.options.length > 0'
}),
// getSchemaTpl('autoFill')
getSchemaTpl('labelRemark'),
@ -135,15 +134,7 @@ export class RadiosControlPlugin extends BasePlugin {
},
{
title: '选项',
body: [
getSchemaTpl('optionControlV2'),
getSchemaTpl('switch', {
label: '默认选择第一个',
name: 'selectFirst',
horizontal: {justify: true, left: 5},
visibleOn: '!this.options'
})
]
body: [getSchemaTpl('optionControlV2'), getSchemaTpl('selectFirst')]
},
getSchemaTpl('status', {isFormItem: true}),
getSchemaTpl('validation', {tag: ValidatorTag.MultiSelect})

View File

@ -253,6 +253,7 @@ export class SelectControlPlugin extends BasePlugin {
title: '选项',
body: [
getSchemaTpl('optionControlV2'),
getSchemaTpl('selectFirst'),
getSchemaTpl(
'loadingConfig',
{

View File

@ -12,7 +12,7 @@ import {EditorNodeType} from 'amis-editor-core';
import {mockValue} from 'amis-editor-core';
// 快速编辑
setSchemaTpl('quickEdit', {
setSchemaTpl('quickEdit', (patch: any, manager: any) => ({
type: 'ae-switch-more',
mode: 'normal',
name: 'quickEdit',
@ -84,7 +84,7 @@ setSchemaTpl('quickEdit', {
block
level="primary"
onClick={() => {
this.manager.openSubEditor({
manager.openSubEditor({
title: '配置快速编辑类型',
value: value,
slot: {
@ -93,7 +93,7 @@ setSchemaTpl('quickEdit', {
body: ['$$'],
wrapWithPanel: false
},
onChange: value =>
onChange: (value: any) =>
onChange(
{
...value,
@ -111,10 +111,10 @@ setSchemaTpl('quickEdit', {
}
]
}
});
}));
// 查看更多
setSchemaTpl('morePopOver', {
setSchemaTpl('morePopOver', (patch: any, manager: any) => ({
type: 'ae-switch-more',
mode: 'normal',
name: 'popOver',
@ -212,10 +212,10 @@ setSchemaTpl('morePopOver', {
block
level="primary"
onClick={() => {
this.manager.openSubEditor({
manager.openSubEditor({
title: '配置查看更多展示内容',
value: value,
onChange: value => onChange(value, 'quickEdit')
onChange: (value: any) => onChange(value, 'quickEdit')
});
}}
>
@ -226,7 +226,7 @@ setSchemaTpl('morePopOver', {
}
]
}
});
}));
// 可复制
setSchemaTpl('copyable', {
@ -310,19 +310,18 @@ export class StaticControlPlugin extends BasePlugin {
getSchemaTpl('formItemName', {
required: false
}),
getSchemaTpl('label')
getSchemaTpl('label'),
// getSchemaTpl('value'),
/*
getSchemaTpl('valueFormula', {
name: 'tpl',
name: 'tpl'
// rendererSchema: {
// ...context?.schema,
// type: 'textarea', // 改用多行文本编辑
// value: context?.schema.tpl // 避免默认值丢失
// }
}),
getSchemaTpl('quickEdit'),
getSchemaTpl('morePopOver'),
getSchemaTpl('quickEdit', {}, this.manager),
getSchemaTpl('morePopOver', {}, this.manager),
getSchemaTpl('copyable'),
getSchemaTpl('labelRemark'),
getSchemaTpl('remark'),

View File

@ -0,0 +1,144 @@
import {registerEditorPlugin, RendererPluginEvent} from 'amis-editor-core';
import {BaseEventContext, BasePlugin} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control';
export class IconPlugin extends BasePlugin {
// 关联渲染器名字
rendererName = 'icon';
$schema = '/schemas/Icon.json';
// 组件名称
name = '图标';
isBaseComponent = true;
icon = 'fa fa-calendar';
panelTitle = '图标';
description = '用来展示一个图标,你可以配置不同的图标样式。';
docLink = '/amis/zh-CN/components/icon';
tags = ['展示'];
pluginIcon = 'button-plugin';
scaffold = {
type: 'icon',
icon: 'fa fa-spotify',
vendor: ''
};
previewSchema: any = {
type: 'icon',
icon: 'fa fa-spotify',
vendor: ''
};
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'click',
eventLabel: '点击',
description: '点击时触发',
dataSchema: [
{
type: 'object',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
]
},
{
eventName: 'mouseenter',
eventLabel: '鼠标移入',
description: '鼠标移入时触发',
dataSchema: [
{
type: 'object',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
]
},
{
eventName: 'mouseleave',
eventLabel: '鼠标移出',
description: '鼠标移出时触发',
dataSchema: [
{
type: 'object',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
]
}
];
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
return [
getSchemaTpl('tabs', [
{
title: '属性',
body: getSchemaTpl('collapseGroup', [
{
title: '基本',
body: [
getSchemaTpl('icon', {
label: '图标'
})
]
},
getSchemaTpl('status')
])
},
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
{
title: '自定义样式',
body: [
getSchemaTpl('theme:size', {
label: '尺寸',
name: 'css.className.font.fontSize'
}),
getSchemaTpl('theme:colorPicker', {
label: '颜色',
name: `css.className.font.color`,
labelMode: 'input'
}),
getSchemaTpl('theme:paddingAndMargin', {
label: '边距'
})
]
}
])
}
// {
// title: '事件',
// className: 'p-none',
// body: [
// getSchemaTpl('eventControl', {
// name: 'onEvent',
// ...getEventControlConfig(this.manager, context)
// })
// ]
// }
])
];
};
}
registerEditorPlugin(IconPlugin);

View File

@ -13,9 +13,9 @@ import {
} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {diff, JSONPipeOut, repeatArray} from 'amis-editor-core';
import {schemaArrayFormat, schemaToArray} from '../util';
export class ListPlugin extends BasePlugin {
static scene = ['layout'];
// 关联渲染器名字
rendererName = 'list';
$schema = '/schemas/ListSchema.json';
@ -240,13 +240,13 @@ export class ListPlugin extends BasePlugin {
value &&
this.manager.openSubEditor({
title: '配置头部',
value: value.header ?? defaultHeader,
value: schemaToArray(value.header ?? defaultHeader),
slot: {
type: 'container',
body: '$$'
},
onChange: newValue => {
newValue = {...value, header: newValue};
newValue = {...value, header: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
}
});
@ -268,13 +268,13 @@ export class ListPlugin extends BasePlugin {
value &&
this.manager.openSubEditor({
title: '配置底部',
value: value.footer ?? defaultFooter,
value: schemaToArray(value.footer ?? defaultFooter),
slot: {
type: 'container',
body: '$$'
},
onChange: newValue => {
newValue = {...value, footer: newValue};
newValue = {...value, footer: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
}
});

View File

@ -10,6 +10,7 @@ import {
ContextMenuEventContext,
ContextMenuItem
} from 'amis-editor-core';
import {schemaArrayFormat, schemaToArray} from '../util';
export class MappingPlugin extends BasePlugin {
static scene = ['layout'];
@ -184,21 +185,22 @@ export class MappingPlugin extends BasePlugin {
const store = manager.store;
const node = store.getNodeById(id);
const value = store.getValueOf(id);
const defaultItemSchema = {
type: 'tag',
label: `\${${this.getDisplayField(value)}}`
};
node &&
value &&
this.manager.openSubEditor({
title: '配置显示模板',
value: value.itemSchema || {
type: 'tag',
label: `\${${this.getDisplayField(value)}}`
},
value: schemaToArray(value.itemSchema ?? defaultItemSchema),
slot: {
type: 'container',
body: '$$'
},
onChange: (newValue: any) => {
newValue = {...value, itemSchema: newValue};
newValue = {...value, itemSchema: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
},
data: {

View File

@ -14,6 +14,10 @@ import {SchemaCollection} from 'amis/lib/Schema';
export class ActionPlugin extends BasePlugin {
panelTitle = '按钮';
rendererName = 'action';
name = '行为按钮';
$schema = '/schemas/ActionSchema.json';
panelBodyCreator = (context: BaseEventContext) => {
const isInDialog = /(?:\/|^)dialog\/.+$/.test(context.path);
const isInDropdown = /(?:\/|^)dropdown-button\/.+$/.test(context.path);
@ -365,35 +369,18 @@ export class ActionPlugin extends BasePlugin {
if (context.selections.length) {
return;
}
if (
~['action', 'button', 'submit', 'reset', 'sparkline'].indexOf(
context.info!.renderer.name!
)
) {
if (context.info!.renderer.name === 'action') {
let body: any = this.panelBodyCreator(context);
// sparkline 的 action 配置是放 clickAction 参数下的,所以需要加一层
if (context.info.renderer.name === 'sparkline') {
body = {
name: 'clickAction',
type: 'combo',
label: '',
noBorder: true,
multiLine: true,
items: body
};
}
// panels.push({
// key: 'action',
// icon: 'fa fa-gavel',
// title: '动作',
// render: this.manager.makeSchemaFormRender({
// body: body
// }),
// order: 100
// });
panels.push({
key: 'action',
icon: 'fa fa-gavel',
title: '动作',
render: this.manager.makeSchemaFormRender({
body: body
}),
order: 100
});
} else {
super.buildEditorPanel(context, panels);
}

View File

@ -0,0 +1,172 @@
import React from 'react';
import {registerEditorPlugin} from 'amis-editor-core';
import {
BaseEventContext,
BasePlugin,
RendererPluginEvent
} from 'amis-editor-core';
import {getSchemaTpl} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
import {SchemaObject} from 'amis/lib/Schema';
export class SearchBoxPlugin extends BasePlugin {
// 关联渲染器名字
rendererName = 'search-box';
$schema = '/schemas/SearchBoxSchema.json';
// 组件名称
name = '搜索框';
isBaseComponent = true;
description =
'用于展示一个简单搜索框,通常需要搭配其他组件使用。比如 page 配置 initApi 后可以用来实现简单数据过滤查找name keywords 会作为参数传递给 page 的 initApi。';
docLink = '/amis/zh-CN/components/search-box';
icon = 'fa fa-search';
pluginIcon = 'search-box-plugin';
scaffold: SchemaObject = {
type: 'search-box',
body: {
type: 'tpl',
tpl: '搜索框',
wrapperComponent: '',
inline: false
},
level: 'info'
};
previewSchema: any = {
...this.scaffold,
className: 'text-left',
showCloseButton: true
};
regions = [{key: 'body', label: '内容区', placeholder: '搜索框内容'}];
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'search',
eventLabel: '点击搜索',
description: '点击搜索图标时触发',
dataSchema: [
{
'event.data.keywords': {
type: 'string',
title: '搜索内容'
}
}
]
},
{
eventName: 'change',
eventLabel: '值变化',
description: '输入框值变化时触发',
dataSchema: [
{
'event.data.keywords': {
type: 'string',
title: '搜索内容'
},
'event.data.value': {
type: 'string',
title: '搜索内容' // 和keywords值相同
}
}
]
},
{
eventName: 'focus',
eventLabel: '获取焦点',
description: '输入框获取焦点时触发',
dataSchema: [
{
'event.data.keywords': {
type: 'string',
title: '搜索内容'
},
'event.data.value': {
type: 'string',
title: '搜索内容' // 和keywords值相同
}
}
]
},
{
eventName: 'blur',
eventLabel: '失去焦点',
description: '输入框失去焦点时触发',
dataSchema: [
{
'event.data.keywords': {
type: 'string',
title: '搜索内容'
},
'event.data.value': {
type: 'string',
title: '搜索内容' // 和keywords值相同
}
}
]
}
];
notRenderFormZone = true;
panelTitle = '搜索框';
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
return getSchemaTpl('tabs', [
{
title: '属性',
body: getSchemaTpl('collapseGroup', [
{
title: '基础',
body: [
getSchemaTpl('formItemName', {
required: true
}),
getSchemaTpl('switch', {
label: '可清除',
name: 'clearable'
}),
getSchemaTpl('switch', {
label: '清除后立即搜索',
name: 'clearAndSubmit'
}),
getSchemaTpl('switch', {
label: '立即搜索',
name: 'searchImediately'
}),
getSchemaTpl('switch', {
label: 'mini版本',
name: 'mini'
}),
getSchemaTpl('switch', {
label: '加强样式',
name: 'enhance',
visibleOn: '!data.mini'
}),
getSchemaTpl('placeholder')
]
},
getSchemaTpl('status')
])
},
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
getSchemaTpl('style:classNames', {isFormItem: false})
])
},
{
title: '事件',
className: 'p-none',
body: getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
})
}
]);
};
}
registerEditorPlugin(SearchBoxPlugin);

View File

@ -27,6 +27,7 @@ import {
getEventControlConfig,
getArgsWrapper
} from '../renderer/event-control/helper';
import {schemaArrayFormat, schemaToArray} from '../util';
export class TablePlugin extends BasePlugin {
// 关联渲染器名字
@ -744,13 +745,13 @@ export class TablePlugin extends BasePlugin {
value &&
this.manager.openSubEditor({
title: '配置头部',
value: value.header ?? defaultHeader,
value: schemaToArray(value.header ?? defaultHeader),
slot: {
type: 'container',
body: '$$'
},
onChange: newValue => {
newValue = {...value, header: newValue};
newValue = {...value, header: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
}
});
@ -772,13 +773,13 @@ export class TablePlugin extends BasePlugin {
value &&
this.manager.openSubEditor({
title: '配置底部',
value: value.footer ?? defaultFooter,
value: schemaToArray(value.footer ?? defaultFooter),
slot: {
type: 'container',
body: '$$'
},
onChange: newValue => {
newValue = {...value, footer: newValue};
newValue = {...value, footer: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
}
});

View File

@ -58,6 +58,10 @@ export class TagPlugin extends BasePlugin {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
},
label: {
type: 'string',
title: '标签名称'
}
}
}
@ -74,6 +78,10 @@ export class TagPlugin extends BasePlugin {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
},
label: {
type: 'string',
title: '标签名称'
}
}
}
@ -90,6 +98,30 @@ export class TagPlugin extends BasePlugin {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
},
label: {
type: 'string',
title: '标签名称'
}
}
}
]
},
{
eventName: 'close',
eventLabel: '点击关闭',
description: '点击关闭时触发',
dataSchema: [
{
type: 'object',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
},
label: {
type: 'string',
title: '标签名称'
}
}
}
@ -133,6 +165,10 @@ export class TagPlugin extends BasePlugin {
getSchemaTpl('icon', {
visibleOn: 'data.displayMode === "status"',
label: '前置图标'
}),
getSchemaTpl('switch', {
label: '可关闭',
name: 'closable'
})
]
},

View File

@ -1,8 +1,37 @@
import {registerEditorPlugin} from 'amis-editor-core';
import {registerEditorPlugin, tipedLabel} from 'amis-editor-core';
import {BaseEventContext} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {DatePlugin} from './Date';
const timeFormatOptions = [
{
label: 'HH:mm',
value: 'HH:mm',
timeFormat: 'HH:mm'
},
{
label: 'HH:mm:ss',
value: 'HH:mm:ss',
timeFormat: 'HH:mm:ss'
},
{
label: 'HH时mm分',
value: 'HH时mm分',
timeFormat: 'HH:mm'
},
{
label: 'HH时mm分ss秒',
value: 'HH时mm分ss秒',
timeFormat: 'HH:mm:ss'
}
];
// 暂仅提示时间戳待input-time的timeFormat支持表达式后增加其他类型
const dateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
}
];
export class TimePlugin extends DatePlugin {
// 关联渲染器名字
rendererName = 'time';
@ -37,20 +66,28 @@ export class TimePlugin extends DatePlugin {
name: 'value',
inputFormat: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
label: '时间值'
label: '时间值'
},
{
type: 'input-text',
name: 'format',
label: '显示时间格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'显示格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: timeFormatOptions,
pipeIn: defaultValue('HH:mm:ss')
},
{
type: 'input-text',
name: 'valueFormat',
label: '数据日期格式',
description: '请参考 moment 中的格式用法。',
label: tipedLabel(
'值格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
options: dateFormatOptions,
pipeIn: defaultValue('X')
},
getSchemaTpl('placeholder', {

View File

@ -0,0 +1,451 @@
/**
* @file API
*/
import React from 'react';
import cx from 'classnames';
import {autobind, getSchemaTpl, setSchemaTpl} from 'amis-editor-core';
import {tipedLabel} from 'amis-editor-core';
import {FormControlProps, render} from 'amis-core';
import {FormItem, Icon, TooltipWrapper} from 'amis';
import {TooltipObject} from 'amis-ui/lib/components/TooltipWrapper';
interface AdaptorFuncParam {
label: string;
tip?: string | TooltipObject;
}
export interface APIAdaptorControlProps extends FormControlProps {
/**
*
*/
params?: AdaptorFuncParam[];
/**
*
*/
mergeParams?: (params: AdaptorFuncParam[]) => AdaptorFuncParam[];
/**
* description
*/
editorDesc?: any;
/**
*
*/
defaultCode?: string;
/**
* placeHolder
*/
editorPlaceholder?: string;
/**
*
*/
switchTip?: string | React.ReactNode;
/**
*
*/
tooltipProps?: TooltipObject;
}
export interface APIAdaptorControlState {
switch: boolean;
}
export default class APIAdaptorControl extends React.Component<
APIAdaptorControlProps,
APIAdaptorControlState
> {
static defaultProps: Pick<APIAdaptorControlProps, 'params'> = {
params: []
};
constructor(props: APIAdaptorControlProps) {
super(props);
this.state = {
switch: !!this.props.value
};
}
componentDidUpdate(prevProps: Readonly<APIAdaptorControlProps>): void {
if (this.props.value !== prevProps.value) {
this.setState({
switch: !!this.props.value
});
}
}
@autobind
onChange(value: any = '') {
this.props.onChange?.(value);
}
// 生成tooltip 的参数
genTooltipProps(content: any, othersProps?: TooltipObject) {
return {
tooltipTheme: 'light',
trigger: 'hover',
rootClose: true,
placement: 'top',
tooltipClassName: 'ae-AdaptorControl-desc-tooltip',
...(typeof content === 'string'
? {content}
: {
content: ' ', // amis缺陷必须有这个字段否则显示不出来
children: () => content
}
),
...this.props.tooltipProps || {},
...othersProps || {}
}
}
renderEditor() {
if (!this.state.switch) {
return null;
}
const {
render,
params = [],
allowFullscreen,
value,
name,
editorPlaceholder,
editorDesc,
mergeParams
} = this.props;
const lastParams = typeof mergeParams === 'function'
? mergeParams(params) : params;
return render('api-adaptor-control-editor', [
{
type: 'container',
className: 'ae-AdaptorControl-func-header',
body: [
'<span class="mtk6">function&nbsp;</span>',
'<span class="mtk1 bracket-highlighting-0">(</span>',
...lastParams.map(({label, tip}, index) => {
return [
{
type: 'button',
level: 'link',
label,
className: 'ae-AdaptorControl-func-arg',
...tip ? {tooltip: this.genTooltipProps(tip)} : {}
},
...(index === lastParams.length - 1
? [] : ['<span class="mtk1">,&nbsp;</span>']
)
]
}).flat(),
'<span class="mtk1 bracket-highlighting-0">)&nbsp;{</span>'
]
},
{
label: '',
mode: 'normal',
name: '__editor_' + name,
type: 'js-editor',
className: 'ae-AdaptorControl-func-editor',
allowFullscreen,
value,
placeholder: editorPlaceholder || '',
onChange: (value: any) => {
this.onChange(value);
}
},
{
type: 'container',
body: '<span class="mtk1 bracket-highlighting-0">}</span>',
className: 'ae-AdaptorControl-func-footer'
},
{
type: 'container',
className: 'cxd-Form-description',
body: editorDesc
}
]);
}
renderSwitch() {
const {render, defaultCode = '', switchTip, name, value} = this.props;
return render('api-adaptor-control-switch', {
type: 'flex',
className: 'mb-2',
alignItems: 'center',
direction: 'row',
justify: 'flex-start',
items: [
{
type: 'switch',
label: '',
mode: 'inline',
name: '__editorSwitch_' + name,
key: 'switch',
className: 'mb-1',
value: this.state.switch,
onChange: (checked: any) => {
this.setState({switch: checked}, () => {
this.onChange(!checked ? '' : value || defaultCode);
});
}
},
...switchTip ? [
<TooltipWrapper
key="TooltipWrapper"
tooltip={this.genTooltipProps(switchTip, {
placement: 'right'
})}
>
<Icon
icon="editor-help"
className="icon"
color="#84868c"
/>
</TooltipWrapper>
] : []
]
});
}
render() {
const {className} = this.props;
return (
<div className={cx('ae-ApiAdaptorControl', className)}>
{this.renderSwitch()}
{this.renderEditor()}
</div>
)
}
}
@FormItem({
type: 'ae-apiAdaptorControl'
})
export class APIAdaptorControlRenderer extends APIAdaptorControl {}
/**
*
* @param code
* @param size width, height, tooltip时计算会偏移
* @returns
*/
const genCodeSchema = (code: string, size?: string[]) => ({
type: 'container',
...!size ? {}
: {style: {
width: size[0],
height: size[1]
}},
body: {
type: 'code',
language: 'typescript',
className: 'bg-white text-xs m-0',
value: code
}
});
// 请求适配器 示例代码
export const requestAdaptorDefaultCode =
`api.data.count = api.data.count + 1;
return api;`;
// 适配器 适配器 api 参数说明
export const adaptorApiStruct = `{
url: string; // 当前接口地址
method: 'get' | 'post' | 'put' | 'delete';
data?: Object; // 请求体
headers?: Object; // 请求头
...
}`;
export const adaptorApiStructTooltip =
render(genCodeSchema(adaptorApiStruct, ['350px', '128px']))
;
// 适配器 response 参数说明
export const adaptorResponseStruct = `{
data: Object; // 接口返回数据,
request: XMLHttpRequest;
headers?: Object; // 请求头
status: number; // 状态码 200, 404, 500..
statusText: string; // 状态信息
...
}`;
export const adaptorResponseStructTooltip =
render(genCodeSchema(adaptorResponseStruct, ['345px', '144px']))
;
// 接收适配器 示例代码
export const adaptorDefaultCode =
`// API响应或自定义处理后需要符合以下格式
return {
status: 0, // 0 表示请求成功,否则按错误处理
msg: '请求成功',
data: {
text: 'world',
items: [
{label: '张三', value: 1}
]
}
}`;
export const validateApiAdaptorDefaultCode =
`// 校验成功
return {
status: 0
};
// 校验失败
return {
status: 422,
errors: '当前用户已存在'
}`;
// 接收适配器 正确返回格式 示例
export const adaptorReturnStruct = `{
"status": 0,
"msg": "",
"data": {
// ...其他字段
}
}`;
// 接收适配器 正确返回格式说明
export const adaptorEditorDescSchema = {
type: 'container',
className: 'text-xs',
style: {
width: '458px',
height: '315px'
},
body: [
'接口返回数据需要符合以下格式, status、msg、data 为必要字段',
genCodeSchema(adaptorReturnStruct),
{
type: 'table',
className: 'mt-1 mb-0',
data: {
items: [
{
label: 'status',
desc: '返回 0 表示当前接口正确返回,否则按错误请求处理'
},
{
label: 'msg',
desc: '返回接口处理信息,主要用于表单提交或请求失败时的 toast 显示'
},
{
label: 'data',
desc: '必须返回一个具有 key-value 结构的对象'
}
]
},
columns: [
{
name: 'label',
label: '字段'
},
{
name: 'desc',
label: '说明'
}
]
}
]
};
// 表单项校验接收适配器 正确返回格式说明
export const validateApiAdaptorEditorDescSchema = {
type: 'container',
className: 'text-xs',
body: [
'校验接口返回格式字段说明:',
{
type: 'table',
className: 'mt-1 mb-0',
data: {
items: [
{
label: 'status',
desc: '返回 0 表示校验成功422 表示校验失败'
},
{
label: 'errors',
desc: '返回 status 为 422 时,显示的校验失败信息'
}
]
},
columns: [
{
name: 'label',
label: '字段'
},
{
name: 'desc',
label: '说明'
}
]
}
]
};
setSchemaTpl('apiRequestAdaptor', {
label: tipedLabel(
'发送适配器',
`可基于 JavaScript 语言直接录入发送适配器的函数体,在该函数体内,您可以对 <span style="color: #108CEE">api</span> 进行处理或者返回新的内容,最后需要 <span style="color: #108CEE">return</span> <span style="color: #108CEE">api</span>。<br><br/>
访<br/>
&nbsp;1. <span style="color: #108CEE">api</span>schema配置对象<br/>
&nbsp;2. <span style="color: #108CEE">api.data</span><br/>
&nbsp;3. <span style="color: #108CEE">api.query</span><br/>
&nbsp;4. <span style="color: #108CEE">api.body</span>POST/PUT/PATCH<br/>
&nbsp;5. <span style="color: #108CEE">api.headers</span><br/>
&nbsp;6. <span style="color: #108CEE">api.url</span><br/>`
),
name: 'requestAdaptor',
type: 'ae-apiAdaptorControl',
editorDesc: '必须将修改好的 api 对象 return 出去。',
editorPlaceholder: requestAdaptorDefaultCode,
params: [
{
label: 'api',
tip: adaptorApiStructTooltip
}
]
});
setSchemaTpl('apiAdaptor', {
label: tipedLabel(
'返回适配器',
`可基于 JavaScript 语言直接录入返回适配器的函数体,在函数体内,您可以对 <span style="color: #108CEE">payload</span> 进行处理或者返回新的内容,最后需要 <span style="color: #108CEE">return</span> 接口最终的返回结果。<br><br/>
访<br/>
&nbsp;1. <span style="color: #108CEE">payload</span><br/>
&nbsp;2. <span style="color: #108CEE">response</span>response对象<br/>
&nbsp;3. <span style="color: #108CEE">api</span>schema配置对象<br/>`
),
type: 'ae-apiAdaptorControl',
name: 'adaptor',
params: [
{
label: 'payload',
tip: '当前请求的响应 payload即 response.data'
},
{
label: 'response',
tip: adaptorResponseStructTooltip
},
{
label: 'api',
tip: adaptorApiStructTooltip
}
],
editorPlaceholder: adaptorDefaultCode,
switchTip: render(adaptorEditorDescSchema)
});
setSchemaTpl('validateApiAdaptor', {
...getSchemaTpl('apiAdaptor'),
editorPlaceholder: validateApiAdaptorDefaultCode,
switchTip: render(validateApiAdaptorEditorDescSchema)
});

View File

@ -523,7 +523,7 @@ export default class APIControl extends React.Component<
}
renderApiConfigTabs(submitOnChange: boolean = false) {
const {messageDesc, debug = false} = this.props;
const {messageDesc, debug = false, name} = this.props;
return {
type: 'form',
@ -840,17 +840,7 @@ export default class APIControl extends React.Component<
})
]
},
{
label: '发送适配器',
name: 'requestAdaptor',
type: 'js-editor',
mode: 'horizontal',
horizontal: {justify: true},
clasName: 'm-t-sm',
allowFullscreen: true,
description:
'函数签名:(api) => api 数据在 api.data 中,修改后返回 api 对象。'
},
getSchemaTpl('apiRequestAdaptor'),
{
type: 'switch',
label: tipedLabel(
@ -922,16 +912,11 @@ export default class APIControl extends React.Component<
}
]
},
{
label: '接收适配器',
name: 'adaptor',
type: 'js-editor',
mode: 'horizontal',
horizontal: {justify: true},
clasName: 'm-t-sm',
allowFullscreen: true,
description: '函数签名: (payload, response, api) => payload'
}
getSchemaTpl(
name === 'validateApi'
? 'validateApiAdaptor'
: 'apiAdaptor'
)
]
},
{

View File

@ -8,7 +8,7 @@ import {PickerContainer} from 'amis-ui';
import {getEnv} from 'mobx-state-tree';
import {normalizeApi, isEffectiveApi, isApiOutdated} from 'amis-core';
import {autobind, isObject, anyChanged, createObject} from 'amis-editor-core';
import {autobind, isObject, anyChanged, createObject, getSchemaTpl} from 'amis-editor-core';
import {tipedLabel} from 'amis-editor-core';
import type {SchemaObject, SchemaCollection, SchemaApi} from 'amis/lib/Schema';
@ -753,28 +753,8 @@ export default class APIControl extends React.Component<
}
]
},
{
label: '发送适配器',
name: 'requestAdaptor',
type: 'js-editor',
mode: 'horizontal',
horizontal: {justify: true},
clasName: 'm-t-sm',
allowFullscreen: true,
description:
'函数签名:(api) => api 数据在 api.data 中,修改后返回 api 对象。'
},
{
label: '接收适配器',
name: 'adaptor',
type: 'js-editor',
mode: 'horizontal',
horizontal: {justify: true},
clasName: 'm-t-sm',
allowFullscreen: true,
description:
'函数签名: (payload, response, api) => payload'
}
getSchemaTpl('apiRequestAdaptor'),
getSchemaTpl('apiAdaptor'),
]
},
{

View File

@ -72,6 +72,11 @@ export interface BadgeControlProps extends FormControlProps {
*
*/
level?: 'info' | 'warning' | 'success' | 'danger' | SchemaExpression;
/**
* option属性时
*/
onOptionChange?: (value: boolean | BadgeForm) => void
}
interface BadgeControlState {
@ -142,10 +147,14 @@ export default class BadgeControl extends React.Component<
}
transformBadgeValue(): BadgeForm {
const {data: ctx} = this.props;
const badge = ctx?.badge ?? {};
const {data: ctx, node} = this.props;
let badge = ctx?.badge ?? {};
// 避免获取到上层的size
const size = ctx?.badge?.size;
let size = ctx?.badge?.size;
if (node.type === 'button-group-select') {
badge = ctx?.option?.badge ?? {};
size = badge?.size;
}
const offset = [0, 0];
// 转换成combo可以识别的格式
@ -171,20 +180,25 @@ export default class BadgeControl extends React.Component<
@autobind
handleSwitchChange(checked: boolean): void {
const {onChange, disabled} = this.props;
const {onChange,onOptionChange, disabled} = this.props;
if (disabled) {
return;
}
this.setState({checked});
if (onOptionChange) {
return onOptionChange(checked);
}
onChange?.(checked ? {mode: 'dot'} : undefined);
}
handleSubmit(form: BadgeForm, action: any): void {
const {onBulkChange} = this.props;
const {onBulkChange, onOptionChange} = this.props;
if (action?.type === 'submit') {
if (onOptionChange) {
return onOptionChange(this.normalizeBadgeValue(form));
}
onBulkChange?.({badge: this.normalizeBadgeValue(form)});
}
}

View File

@ -105,11 +105,9 @@ export default class ExpressionFormulaControl extends React.Component<
@autobind
initFormulaPickerValue(value: string) {
let formulaPickerValue = value;
if (this.props.evalMode) {
formulaPickerValue =
value?.replace(/^\$\{(.*)\}$/, (match: string, p1: string) => p1) || '';
}
let formulaPickerValue =
value?.replace(/^\$\{(.*)\}$/, (match: string, p1: string) => p1) || '';
this.setState({
formulaPickerValue
});
@ -124,9 +122,9 @@ export default class ExpressionFormulaControl extends React.Component<
@autobind
handleConfirm(value = '') {
if (this.props.evalMode) {
value = value.replace(/^\$\{(.*)\}$/, (match: string, p1: string) => p1);
value = value ? `\${${value}}` : '';
const expressionReg = /^\$\{(.*)\}$/;
if (value && !expressionReg.test(value)) {
value = `\${${value}}`;
}
this.props?.onChange?.(value);
}

View File

@ -388,6 +388,8 @@ export default class FormulaControl extends React.Component<
'size',
'remark',
'labelRemark',
'static',
'staticOn',
'hidden',
'hiddenOn',
'visible',
@ -409,6 +411,8 @@ export default class FormulaControl extends React.Component<
'kilobitSeparator',
'value',
'inputControlClassName',
'css',
'validateApi',
'themeCss'
];

View File

@ -7,6 +7,7 @@ import {findDOMNode} from 'react-dom';
import cx from 'classnames';
import uniqBy from 'lodash/uniqBy';
import omit from 'lodash/omit';
import get from 'lodash/get';
import Sortable from 'sortablejs';
import {
FormItem,
@ -23,7 +24,7 @@ import {getSchemaTpl} from 'amis-editor-core';
import {tipedLabel} from 'amis-editor-core';
import type {Option} from 'amis';
import type {FormControlProps} from 'amis-core';
import {createObject, FormControlProps} from 'amis-core';
import type {TextControlSchema} from 'amis/lib/renderers/Form/inputText';
import type {OptionValue} from 'amis-core';
import {SchemaApi} from 'amis/lib/Schema';
@ -87,6 +88,27 @@ export default class OptionControl extends React.Component<
};
}
/**
*
*/
componentWillReceiveProps(nextProps: OptionControlProps) {
const options = get(nextProps, 'data.options')
? this.transformOptions(nextProps)
: [];
if (
JSON.stringify(
this.state.options.map(item => ({
...item,
editing: undefined
}))
) !== JSON.stringify(options)
) {
this.setState({
options
});
}
}
/**
*
*/
@ -147,7 +169,10 @@ export default class OptionControl extends React.Component<
label: item.label,
// 为了使用户编写label时同时生效到value
value: item.label === item.value ? null : item.value,
checked: !!~valueArray.indexOf(item[ctx?.valueField ?? 'value'])
checked: !!~valueArray.indexOf(item[ctx?.valueField ?? 'value']),
...(item?.badge ? {badge: item.badge} : {}),
...(item.hidden !== undefined ? {hidden: item.hidden} : {}),
...(item.hiddenOn !== undefined ? {hiddenOn: item.hiddenOn} : {})
}))
: [];
}
@ -165,7 +190,7 @@ export default class OptionControl extends React.Component<
valueField
} = ctx;
const checkedOptions = this.state.options
.filter(item => item.checked)
.filter(item => item.checked && item?.hidden !== true)
.map(item => omit(item, this.internalProps));
let value: Array<OptionValue> | OptionValue;
@ -217,10 +242,13 @@ export default class OptionControl extends React.Component<
if (source === 'custom') {
const {options} = this.state;
data.options = options.map(item => ({
...(item?.badge ? {badge: item.badge} : {}),
label: item.label,
value: item.value == null || item.value === '' ? item.label : item.value
value:
item.value == null || item.value === '' ? item.label : item.value,
...(item.hiddenOn !== undefined ? {hiddenOn: item.hiddenOn} : {})
}));
data.value = defaultValue || undefined;
data.value = defaultValue;
}
if (source === 'api' || source === 'apicenter') {
@ -352,14 +380,44 @@ export default class OptionControl extends React.Component<
this.setState({options});
}
/**
*
*/
toggleBadge(index: number, value: boolean | {}) {
const {options} = this.state;
// visible
if (typeof value === 'boolean') {
if (value) {
options[index].badge = {mode: 'dot'};
} else {
delete options[index].badge;
}
} else {
// 角标配置
options[index].badge = value;
}
this.setState({options}, () => this.onChange());
}
@autobind
handleEditLabel(index: number, value: string) {
const options = this.state.options.concat();
options.splice(index, 1, {...options[index], label: value});
this.setState({options}, () => this.onChange());
}
@autobind
handleHiddenValueChange(index: number, value: string) {
const options = this.state.options.concat();
const {hiddenOn, ...option} = options[index];
options.splice(index, 1, {
...option,
...(!value ? {} : {hiddenOn: value})
});
this.setState({options}, () => this.onChange());
}
@autobind
handleAdd() {
const {options} = this.state;
@ -392,9 +450,9 @@ export default class OptionControl extends React.Component<
}
@autobind
handleBatchAdd(values: {batchOption: string}, action: any) {
handleBatchAdd(values: {batchOption: string}[], action: any) {
const options = this.state.options.concat();
const addedOptions: Array<OptionControlItem> = values.batchOption
const addedOptions: Array<OptionControlItem> = values[0].batchOption
.split('\n')
.map(option => {
const item = option.trim();
@ -492,92 +550,110 @@ export default class OptionControl extends React.Component<
renderOption(props: any) {
const {checked, index, editing, multipleProps, closeDefaultCheck} = props;
const render = this.props.render;
const ctx: Partial<TextControlSchema> = this.props.data;
const {render, data: ctx, node} = this.props;
const isMultiple = ctx?.multiple === true || multipleProps;
const i18nEnabled = getI18nEnabled();
const label = this.transformOptionValue(props.label);
const value = this.transformOptionValue(props.value);
const valueType = this.getOptionValueType(props.value);
const showBadge = node.type === 'button-group-select';
const editDom = editing ? (
<div className="ae-OptionControlItem-extendMore">
{render('option', {
type: 'container',
className: 'ae-ExtendMore right mb-2',
body: [
{
type: 'button',
className: 'ae-OptionControlItem-closeBtn',
label: '×',
level: 'link',
onClick: () => this.toggleEdit(index)
},
{
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
placeholder: '请输入显示文本',
label: '文本',
mode: 'horizontal',
value: label,
labelClassName: 'ae-OptionControlItem-EditLabel',
valueClassName: 'ae-OptionControlItem-EditValue',
onChange: (v: string) => this.handleEditLabel(index, v)
},
{
type: 'input-group',
name: 'input-group',
label: '值',
labelClassName: 'ae-OptionControlItem-EditLabel',
valueClassName: 'ae-OptionControlItem-EditValue',
mode: 'horizontal',
body: [
{
type: 'select',
name: 'valueType',
value: valueType,
options: [
{
label: '文本',
value: 'text'
},
{
label: '数字',
value: 'number'
},
{
label: '布尔',
value: 'boolean'
}
],
checkAll: false,
onChange: (v: valueType) =>
this.handleValueTypeChange(index, v)
},
{
type: 'input-text',
placeholder: '默认与文本一致',
name: 'value',
value,
visibleOn: "this.optionValueType !== 'boolean'",
onChange: (v: string) => this.handleValueChange(index, v)
},
{
type: 'input-text',
placeholder: '默认与文本一致',
name: 'value',
value,
visibleOn: "this.optionValueType === 'boolean'",
onChange: (v: string) => this.handleValueChange(index, v),
options: [
{label: 'true', value: true},
{label: 'false', value: false}
]
}
]
}
]
})}
{render(
'option',
{
type: 'container',
className: 'ae-ExtendMore right mb-2',
body: [
{
type: 'button',
className: 'ae-OptionControlItem-closeBtn',
label: '×',
level: 'link',
onClick: () => this.toggleEdit(index)
},
{
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
placeholder: '请输入显示文本',
label: '文本',
mode: 'horizontal',
value: label,
name: 'optionLabel',
labelClassName: 'ae-OptionControlItem-EditLabel',
valueClassName: 'ae-OptionControlItem-EditValue',
onChange: (v: string) => this.handleEditLabel(index, v)
},
{
type: 'input-group',
name: 'input-group',
label: '值',
labelClassName: 'ae-OptionControlItem-EditLabel',
valueClassName: 'ae-OptionControlItem-EditValue',
mode: 'horizontal',
body: [
{
type: 'select',
name: 'optionValueType',
value: valueType,
options: [
{
label: '文本',
value: 'text'
},
{
label: '数字',
value: 'number'
},
{
label: '布尔',
value: 'boolean'
}
],
checkAll: false,
onChange: (v: valueType) =>
this.handleValueTypeChange(index, v)
},
{
type: 'input-text',
placeholder: '默认与文本一致',
name: 'optionValue',
value,
visibleOn: "this.optionValueType !== 'boolean'",
onChange: (v: string) => this.handleValueChange(index, v)
},
{
type: 'input-text',
placeholder: '默认与文本一致',
name: 'optionValue',
value,
visibleOn: "this.optionValueType === 'boolean'",
onChange: (v: string) => this.handleValueChange(index, v),
options: [
{label: 'true', value: true},
{label: 'false', value: false}
]
}
]
},
{
type: 'ae-expressionFormulaControl',
name: 'optionHiddenOn',
label: '隐藏',
labelClassName: 'ae-OptionControlItem-EditLabel',
valueClassName: 'ae-OptionControlItem-EditValue',
onChange: (v: string) => this.handleHiddenValueChange(index, v)
},
{
type: 'ae-badge',
visible: showBadge,
value: props?.badge,
onOptionChange: v => this.toggleBadge(index, v)
}
]
},
{data: createObject(ctx, {option: props})}
)}
</div>
) : null;
@ -606,6 +682,9 @@ export default class OptionControl extends React.Component<
});
}
const disabled = props?.hidden === true;
const tooltip = disabled ? '隐藏选项不能设为默认值' : '默认选中此项';
return (
<li className="ae-OptionControlItem" key={index}>
<div className="ae-OptionControlItem-Main">
@ -614,10 +693,11 @@ export default class OptionControl extends React.Component<
</a>
{!this.props.closeDefaultCheck &&
this.props.data.defaultCheckAll !== true && (
<span className="inline-flex" data-tooltip="默认选中此项">
<span className="inline-flex" data-tooltip={tooltip}>
<Checkbox
className="ae-OptionControlItem-checkbox"
checked={checked}
disabled={disabled}
type={isMultiple ? 'checkbox' : 'radio'}
onChange={(newChecked: any, shift?: boolean) =>
this.handleToggleDefaultValue(index, newChecked, shift)
@ -676,6 +756,7 @@ export default class OptionControl extends React.Component<
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
onConfirm: this.handleBatchAdd,
body: [
{
type: 'alert',
@ -805,9 +886,7 @@ export default class OptionControl extends React.Component<
</Button>
{/* {render('option-control-batchAdd', this.buildBatchAddSchema())} */}
{render('inner', this.buildBatchAddSchema(), {
onSubmit: this.handleBatchAdd
})}
{render('inner', this.buildBatchAddSchema())}
</div>
{/* {this.renderPopover()} */}

View File

@ -21,6 +21,10 @@ export interface StatusControlProps extends FormControlProps {
options?: Option[];
children?: SchemaCollection;
messages?: Pick<FormSchema, 'messages'>;
// 应用于不需要 bulkChange 的场景,如
noBulkChange?: boolean;
noBulkChangeData?: any;
onDataChange?: (value: any) => void;
}
type StatusFormData = {
@ -48,11 +52,19 @@ export class StatusControl extends React.Component<
}
initState() {
const {data: ctx = {}, expressionName, name, trueValue} = this.props;
const {data = {}, noBulkChange, noBulkChangeData, expressionName, name, trueValue} = this.props;
const formData: StatusFormData = {
statusType: 1,
expression: ''
};
let ctx = data;
if (noBulkChange && noBulkChangeData) {
ctx = noBulkChangeData
}
if (ctx[expressionName] || ctx[expressionName] === '') {
formData.statusType = 2;
formData.expression = ctx[expressionName];
@ -75,18 +87,19 @@ export class StatusControl extends React.Component<
handleSwitch(value: boolean) {
const {trueValue, falseValue} = this.props;
this.setState({checked: value == trueValue ? true : false}, () => {
const {onBulkChange, expressionName, name} = this.props;
onBulkChange &&
onBulkChange({
[name]: value == trueValue ? trueValue : falseValue,
[expressionName]: undefined
});
const {onBulkChange, noBulkChange, onDataChange, expressionName, name} = this.props;
const newData = {
[name]: value == trueValue ? trueValue : falseValue,
[expressionName]: undefined
};
!noBulkChange && onBulkChange && onBulkChange(newData);
onDataChange && onDataChange(newData);
});
}
@autobind
handleFormSubmit(values: StatusFormData) {
const {onBulkChange, name, expressionName} = this.props;
const {onBulkChange, noBulkChange, onDataChange, name, expressionName} = this.props;
const data: Record<string, any> = {
[name]: undefined,
[expressionName]: undefined
@ -102,7 +115,8 @@ export class StatusControl extends React.Component<
data[expressionName] = values.expression;
break;
}
onBulkChange && onBulkChange(data);
!noBulkChange && onBulkChange && onBulkChange(data);
onDataChange && onDataChange(data);
}
render() {

View File

@ -8,7 +8,7 @@ import remove from 'lodash/remove';
import cx from 'classnames';
import {FormItem} from 'amis';
import {autobind} from 'amis-editor-core';
import {autobind, getSchemaTpl, tipedLabel} from 'amis-editor-core';
import ValidationItem, {ValidatorData} from './ValidationItem';
import type {FormControlProps} from 'amis-core';
@ -323,10 +323,65 @@ export default class ValidationControl extends React.Component<
return (
<div className="ae-ValidationControl-rules" key="rules">
{rules}
{this.renderValidateApiControl()}
</div>
);
}
renderValidateApiControl() {
const {onBulkChange, render} = this.props;
return <div className='ae-ValidationControl-item'>
{render('validate-api-control', {
type: 'form',
wrapWithPanel: false,
className: 'w-full mb-2',
bodyClassName: 'p-none',
wrapperComponent: 'div',
mode: 'horizontal',
data: {
validateApi: this.props.data.validateApi,
switchStatus: this.props.data.validateApi !== undefined
},
preventEnterSubmit: true,
submitOnChange: true,
onSubmit: ({switchStatus, validateApi}: any) => {
onBulkChange && onBulkChange({
validateApi: !switchStatus ? undefined : validateApi
});
},
body: [
getSchemaTpl('switch', {
label: tipedLabel(
'接口校验',
`配置校验接口,对表单项进行远程校验,配置方式与普通接口一致<br />
1. <span class="ae-ValidationControl-label-code">{status: 0}</span> <br />
2. <span class="ae-ValidationControl-label-code">{status: 422}</span> <br />
3. errors <br />
<span class="ae-ValidationControl-label-code">{status: 422, errors: '错误提示消息'}</span>
`
),
name: 'switchStatus'
}),
{
type: 'container',
className: 'ae-ExtendMore ae-ValidationControl-item-input',
bodyClassName: 'w-full',
visibleOn: 'this.switchStatus',
body: [
getSchemaTpl('apiControl', {
name: 'validateApi',
renderLabel: true,
label: '',
mode: 'normal',
className: 'w-full'
})
]
}
]
})}
</div>
}
render() {
const {className} = this.props;

View File

@ -40,12 +40,7 @@ export default class ActionConfigPanel extends React.Component<RendererProps> {
return schema ? (
render('inner', schema as Schema, {
data,
onChange: (value: any, field: any) => {
onBulkChange({
[field]: value
});
}
data
})
) : data.__subActions ? (
<></>

View File

@ -334,6 +334,9 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
},
{
actionType: 'drawer'
},
{
actionType: 'confirmDialog'
}
],
schema: [
@ -354,6 +357,10 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
{
label: '抽屉',
value: 'drawer'
},
{
label: '确认对话框',
value: 'confirmDialog'
}
],
visibleOn: 'data.actionType === "openDialog"'
@ -397,7 +404,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
mode: 'horizontal',
required: true,
pipeIn: defaultValue({
title: '弹框标题',
title: '抽屉标题',
body: '对,你刚刚点击了'
}),
asFormItem: true,
@ -419,6 +426,36 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
{_i18n('a532be3ad5f3fda70d228b8542e81835')}
</Button>
)
},
{
name: 'confirmDialog',
type: 'container',
visibleOn: 'data.groupType === "confirmDialog"',
body: [
getArgsWrapper({
type: 'wrapper',
className: 'p-none',
body: [
{
name: 'msg',
label: '消息内容',
type: 'ae-textareaFormulaControl',
mode: 'horizontal',
variables: '${variables}',
size: 'lg',
required: true
},
{
name: 'title',
label: '标题内容',
type: 'ae-textareaFormulaControl',
variables: '${variables}',
mode: 'horizontal',
size: 'lg'
}
]
})
]
}
]
},
@ -1075,7 +1112,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
offText: '否',
mode: 'horizontal',
pipeIn: defaultValue(true),
visibleOn: `data.actionType === "reload" && ${IS_DATA_CONTAINER}`
visibleOn: `data.actionType === "reload" && data.__isScopeContainer`
},
{
type: 'switch',
@ -1088,7 +1125,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
offText: '否',
mode: 'horizontal',
pipeIn: defaultValue(true),
visibleOn: `data.__addParam && data.actionType === "reload" && ${IS_DATA_CONTAINER}`,
visibleOn: `data.__addParam && data.actionType === "reload" && data.__isScopeContainer`,
onChange: (value: string, oldVal: any, data: any, form: any) => {
form.setValueByName('__containerType', 'all');
}
@ -1099,7 +1136,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
mode: 'horizontal',
label: '',
pipeIn: defaultValue('all'),
visibleOn: `data.__addParam && data.__customData && data.actionType === "reload" && ${IS_DATA_CONTAINER}`,
visibleOn: `data.__addParam && data.__customData && data.actionType === "reload" && data.__isScopeContainer`,
options: [
{
label: '直接赋值',
@ -1127,7 +1164,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
label: '',
size: 'lg',
mode: 'horizontal',
visibleOn: `data.__addParam && data.__customData && data.__containerType === "all" && data.actionType === "reload" && ${IS_DATA_CONTAINER}`
visibleOn: `data.__addParam && data.__customData && data.__containerType === "all" && data.actionType === "reload" && data.__isScopeContainer`
},
*/
getSchemaTpl('formulaControl', {
@ -1137,7 +1174,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
size: 'lg',
mode: 'horizontal',
required: true,
visibleOn: `data.__addParam && data.__customData && data.__containerType === "all" && data.actionType === "reload" && ${IS_DATA_CONTAINER}`
visibleOn: `data.__addParam && data.__customData && data.__containerType === "all" && data.actionType === "reload" && data.__isScopeContainer`
}),
{
type: 'combo',
@ -1176,7 +1213,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
placeholder: '参数值'
})
],
visibleOn: `data.__addParam && data.__customData && data.__containerType === "appoint" && data.actionType === "reload" && ${IS_DATA_CONTAINER}`
visibleOn: `data.__addParam && data.__customData && data.__containerType === "appoint" && data.actionType === "reload" && data.__isScopeContainer`
},
{
type: 'radios',
@ -1187,7 +1224,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
'选择“合并”时,会将数据合并到目标组件的数据域。<br/>选择“覆盖”时,数据会直接覆盖目标组件的数据域。'
),
pipeIn: defaultValue('merge'),
visibleOn: `data.__addParam && data.actionType === "reload" && ${IS_DATA_CONTAINER}`,
visibleOn: `data.__addParam && data.actionType === "reload" && data.__isScopeContainer`,
options: [
{
label: '合并',
@ -1296,7 +1333,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
mode: 'horizontal',
label: '数据设置',
pipeIn: defaultValue('all'),
visibleOn: `${IS_DATA_CONTAINER}`,
visibleOn: 'data.__isScopeContainer',
options: [
{
label: '直接赋值',
@ -1396,7 +1433,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
placeholder: '字段值'
})
],
visibleOn: `${IS_DATA_CONTAINER} && data.__containerType === 'appoint' || data.__comboType === 'appoint'`
visibleOn: `data.__isScopeContainer && data.__containerType === 'appoint' || data.__comboType === 'appoint'`
},
{
type: 'combo',
@ -1466,7 +1503,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
label: '',
size: 'lg',
mode: 'horizontal',
visibleOn: `(${IS_DATA_CONTAINER} || ${SHOW_SELECT_PROP}) && data.__containerType === 'all'`,
visibleOn: `(data.__isScopeContainer || ${SHOW_SELECT_PROP}) && data.__containerType === 'all'`,
required: true
},
*/
@ -1476,7 +1513,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
variables: '${variables}',
size: 'lg',
mode: 'horizontal',
visibleOn: `(${IS_DATA_CONTAINER} || ${SHOW_SELECT_PROP}) && data.__containerType === 'all'`,
visibleOn: `(data.__isScopeContainer || ${SHOW_SELECT_PROP}) && data.__containerType === 'all'`,
required: true
}),
/*
@ -1490,7 +1527,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
label: '数据设置',
size: 'lg',
mode: 'horizontal',
visibleOn: `data.__rendererName && !${IS_DATA_CONTAINER} && data.__rendererName !== 'combo'`,
visibleOn: `data.__rendererName && !data.__isScopeContainer && data.__rendererName !== 'combo'`,
required: true
}
*/
@ -1500,7 +1537,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
variables: '${variables}',
size: 'lg',
mode: 'horizontal',
visibleOn: `data.__rendererName && !${IS_DATA_CONTAINER} && data.__rendererName !== 'combo' && data.__rendererName !== 'input-table'`,
visibleOn: `data.__rendererName && !data.__isScopeContainer && data.__rendererName !== 'combo' && data.__rendererName !== 'input-table'`,
required: true
})
]
@ -1842,7 +1879,8 @@ export const renderCmptSelect = (
__rendererLabel: '${label}',
__rendererName: '${type}',
__nodeId: '${id}',
__nodeSchema: '${schema}'
__nodeSchema: '${schema}',
__isScopeContainer: '${isScopeContainer}'
},
onChange: async (value: string, oldVal: any, data: any, form: any) => {
onChange?.(value, oldVal, data, form);
@ -1987,7 +2025,7 @@ export const COMMON_ACTION_SCHEMA_MAP: {
placeholder: '变量值'
})
],
visibleOn: `${IS_DATA_CONTAINER}`
visibleOn: 'data.__isScopeContainer'
},
{
type: 'combo',
@ -2050,7 +2088,7 @@ export const COMMON_ACTION_SCHEMA_MAP: {
label: '变量赋值',
size: 'lg',
mode: 'horizontal',
visibleOn: `!${IS_DATA_CONTAINER} && data.__rendererName !== 'combo'`,
visibleOn: `!data.__isScopeContainer && data.__rendererName !== 'combo'`,
required: true
}
*/
@ -2060,7 +2098,7 @@ export const COMMON_ACTION_SCHEMA_MAP: {
variables: '${variables}',
size: 'lg',
mode: 'horizontal',
visibleOn: `!${IS_DATA_CONTAINER} && data.__rendererName !== 'combo' && data.__rendererName !== 'input-table'`,
visibleOn: `!data.__isScopeContainer && data.__rendererName !== 'combo' && data.__rendererName !== 'input-table'`,
required: true
})
]
@ -2308,10 +2346,12 @@ export const getOldActionSchema = (
const isInDialog = /(?:\/|^)dialog\/.+$/.test(context.path);
return {
type: 'tooltip-wrapper',
className: 'old-action-tooltip-warpper',
content:
'温馨提示:添加下方事件动作后,下方事件动作将先于旧版动作执行,建议统一迁移至事件动作机制,帮助您实现更灵活的交互设计',
inline: true,
tooltipTheme: 'dark',
placement: 'bottom',
body: [
{
type: 'button',
@ -2477,7 +2517,7 @@ export const getOldActionSchema = (
visibleOn: 'data.actionType == "drawer"',
name: 'drawer',
pipeIn: defaultValue({
title: '弹框标题',
title: '抽屉标题',
body: '对,你刚刚点击了'
}),
asFormItem: true,
@ -2713,18 +2753,20 @@ export const getEventControlConfig = (
// 内置逻辑
if (action.supportComponents === 'byComponent') {
isSupport = hasActionType(actionType, actions);
node.scoped = isSupport;
}
} else if (Array.isArray(action.supportComponents)) {
isSupport = action.supportComponents.includes(node.type);
}
node.isScopeContainer = !!manager.dataSchema.getScope(
`${node.id}-${node.type}`
);
if (actionType === 'component' && !actions?.length) {
node.disabled = true;
}
if (isSupport) {
return true;
} else if (haveChild) {
node.disabled = true;
return true;
}
return false;

View File

@ -130,6 +130,14 @@ export class TextareaFormulaControl extends React.Component<
variables: variablesArr
});
}
if (this.state.value !== this.props.value) {
this.setState(
{
value: this.props.value
},
this.editorAutoMark
);
}
}
componentWillUnmount() {

View File

@ -10,8 +10,10 @@ import type {VariableItem} from 'amis-ui/lib/components/formula/Editor';
*/
export async function resolveVariablesFromScope(node: any, manager: any) {
await manager?.getContextSchemas(node);
// 获取当前组件内相关变量,如表单、增删改查
const dataPropsAsOptions: VariableItem[] =
manager?.dataSchema?.getDataPropsAsOptions();
await manager?.dataSchema?.getDataPropsAsOptions();
const variables: VariableItem[] =
manager?.variableManager?.getVariableFormulaOptions() || [];

View File

@ -281,8 +281,14 @@ setSchemaTpl('ref', () => {
setSchemaTpl('selectFirst', {
type: 'switch',
label: '是否默认选择第一个',
name: 'selectFirst'
label: '默认选择第一项',
name: 'selectFirst',
mode: 'horizontal',
horizontal: {
justify: true,
left: 8
},
inputClassName: 'is-inline '
});
setSchemaTpl('hideNodePathLabel', {
@ -321,7 +327,7 @@ setSchemaTpl('optionControlV2', {
mode: 'normal',
name: 'options',
type: 'ae-optionControl',
closeDefaultCheck: true // 关闭默认值设置
closeDefaultCheck: false // 关闭默认值设置
});
/**

View File

@ -565,6 +565,16 @@ setSchemaTpl('theme:shadow', (option: any = {}) => {
};
});
// 尺寸选择器
setSchemaTpl('theme:size', (option: any = {}) => {
return {
type: 'amis-theme-select',
label: false,
name: `css.className.size`,
...option
};
});
setSchemaTpl(
'theme:common',
(exclude: string[] | string, include: string[]) => {

View File

@ -279,3 +279,11 @@ export const isAuto = (value: any) => {
}
return false;
};
export const schemaToArray = (value: any) => {
return value && Array.isArray(value)? value : [value];
};
export const schemaArrayFormat = (value: any) => {
return value && Array.isArray(value) && value.length === 1 ? value[0] : value;
};