From 17662f5337bd2e134880cf8118b783a335ab5a30 Mon Sep 17 00:00:00 2001 From: igrowp <34541195+igrowp@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:11:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20PageMaker315=E4=B8=93=E9=A1=B9=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E5=A4=8D=20(#6570)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wutong25 --- .../amis-editor-core/scss/_rightPanel.scss | 11 +- .../scss/control/_api-control.scss | 37 ++ .../scss/control/_option-control.scss | 2 +- .../scss/control/_validation-control.scss | 6 + .../src/component/PopOverForm.tsx | 3 +- .../src/plugin/BasicToolbar.tsx | 7 +- .../amis-editor-core/src/plugin/Unknown.tsx | 5 +- packages/amis-editor/src/icons/index.tsx | 2 + .../src/icons/layout/layout-fixed.svg | 16 +- packages/amis-editor/src/index.tsx | 3 + packages/amis-editor/src/locale/en-US.ts | 14 +- packages/amis-editor/src/locale/zh-CN.ts | 16 +- packages/amis-editor/src/plugin/Button.tsx | 3 +- packages/amis-editor/src/plugin/Carousel.tsx | 58 ++- packages/amis-editor/src/plugin/Date.tsx | 48 +- packages/amis-editor/src/plugin/Datetime.tsx | 48 +- .../src/plugin/Form/ButtonGroupSelect.tsx | 3 +- .../amis-editor/src/plugin/Form/InputDate.tsx | 27 +- .../src/plugin/Form/InputDateRange.tsx | 149 +++++- .../src/plugin/Form/InputTable.tsx | 40 +- .../amis-editor/src/plugin/Form/Radios.tsx | 13 +- .../amis-editor/src/plugin/Form/Select.tsx | 1 + .../amis-editor/src/plugin/Form/Static.tsx | 25 +- packages/amis-editor/src/plugin/Icon.tsx | 144 ++++++ packages/amis-editor/src/plugin/List.tsx | 10 +- packages/amis-editor/src/plugin/Mapping.tsx | 12 +- .../amis-editor/src/plugin/Others/Action.tsx | 41 +- packages/amis-editor/src/plugin/SearchBox.tsx | 172 +++++++ packages/amis-editor/src/plugin/Table.tsx | 9 +- packages/amis-editor/src/plugin/Tag.tsx | 36 ++ packages/amis-editor/src/plugin/Time.tsx | 49 +- .../src/renderer/APIAdaptorControl.tsx | 451 ++++++++++++++++++ .../amis-editor/src/renderer/APIControl.tsx | 29 +- .../src/renderer/ActionApiControl.tsx | 26 +- .../amis-editor/src/renderer/BadgeControl.tsx | 26 +- .../src/renderer/ExpressionFormulaControl.tsx | 14 +- .../src/renderer/FormulaControl.tsx | 4 + .../src/renderer/OptionControl.tsx | 259 ++++++---- .../src/renderer/StatusControl.tsx | 32 +- .../src/renderer/ValidationControl.tsx | 57 ++- .../event-control/action-config-panel.tsx | 7 +- .../src/renderer/event-control/helper.tsx | 84 +++- .../TextareaFormulaControl.tsx | 8 + .../src/renderer/textarea-formula/utils.ts | 4 +- packages/amis-editor/src/tpl/options.tsx | 12 +- packages/amis-editor/src/tpl/style.tsx | 10 + packages/amis-editor/src/util.ts | 8 + 47 files changed, 1706 insertions(+), 335 deletions(-) create mode 100644 packages/amis-editor/src/plugin/Icon.tsx create mode 100644 packages/amis-editor/src/plugin/SearchBox.tsx create mode 100644 packages/amis-editor/src/renderer/APIAdaptorControl.tsx diff --git a/packages/amis-editor-core/scss/_rightPanel.scss b/packages/amis-editor-core/scss/_rightPanel.scss index 703d7316e..5e43b6f52 100644 --- a/packages/amis-editor-core/scss/_rightPanel.scss +++ b/packages/amis-editor-core/scss/_rightPanel.scss @@ -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导航 diff --git a/packages/amis-editor-core/scss/control/_api-control.scss b/packages/amis-editor-core/scss/control/_api-control.scss index 14732c664..4d5977581 100644 --- a/packages/amis-editor-core/scss/control/_api-control.scss +++ b/packages/amis-editor-core/scss/control/_api-control.scss @@ -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; + } +} \ No newline at end of file diff --git a/packages/amis-editor-core/scss/control/_option-control.scss b/packages/amis-editor-core/scss/control/_option-control.scss index 8fee2e275..cbef0f2cb 100644 --- a/packages/amis-editor-core/scss/control/_option-control.scss +++ b/packages/amis-editor-core/scss/control/_option-control.scss @@ -70,7 +70,7 @@ } &-EditLabel { - width: px2rem(36px); + flex: 0 0 2.25rem; padding-right: 0; } diff --git a/packages/amis-editor-core/scss/control/_validation-control.scss b/packages/amis-editor-core/scss/control/_validation-control.scss index be14d8109..927106bad 100644 --- a/packages/amis-editor-core/scss/control/_validation-control.scss +++ b/packages/amis-editor-core/scss/control/_validation-control.scss @@ -100,3 +100,9 @@ } } } + +.ae-ValidationControl-label-code { + background-color: #666; + padding: 0 4px; + border-radius: 2px; +} \ No newline at end of file diff --git a/packages/amis-editor-core/src/component/PopOverForm.tsx b/packages/amis-editor-core/src/component/PopOverForm.tsx index f70e5f9d4..dd8b0d43d 100644 --- a/packages/amis-editor-core/src/component/PopOverForm.tsx +++ b/packages/amis-editor-core/src/component/PopOverForm.tsx @@ -69,7 +69,8 @@ export class PopOverForm extends React.Component { render( this.buildSchema(), { - data: createObject(store.ctx, popOverFormContext?.value) + data: createObject(store.ctx, popOverFormContext?.value), + manager }, { ...manager.env, diff --git a/packages/amis-editor-core/src/plugin/BasicToolbar.tsx b/packages/amis-editor-core/src/plugin/BasicToolbar.tsx index c8dedfdbb..03f29343d 100644 --- a/packages/amis-editor-core/src/plugin/BasicToolbar.tsx +++ b/packages/amis-editor-core/src/plugin/BasicToolbar.tsx @@ -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 ( diff --git a/packages/amis-editor-core/src/plugin/Unknown.tsx b/packages/amis-editor-core/src/plugin/Unknown.tsx index be26cd8d0..eab79c214 100644 --- a/packages/amis-editor-core/src/plugin/Unknown.tsx +++ b/packages/amis-editor-core/src/plugin/Unknown.tsx @@ -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' }; } diff --git a/packages/amis-editor/src/icons/index.tsx b/packages/amis-editor/src/icons/index.tsx index 999e6aa13..02dcc1dde 100644 --- a/packages/amis-editor/src/icons/index.tsx +++ b/packages/amis-editor/src/icons/index.tsx @@ -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); diff --git a/packages/amis-editor/src/icons/layout/layout-fixed.svg b/packages/amis-editor/src/icons/layout/layout-fixed.svg index 2272371ee..e9d14333b 100644 --- a/packages/amis-editor/src/icons/layout/layout-fixed.svg +++ b/packages/amis-editor/src/icons/layout/layout-fixed.svg @@ -1,6 +1,12 @@ - - - - - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/packages/amis-editor/src/index.tsx b/packages/amis-editor/src/index.tsx index 402436565..28b46437a 100644 --- a/packages/amis-editor/src/index.tsx +++ b/packages/amis-editor/src/index.tsx @@ -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'; diff --git a/packages/amis-editor/src/locale/en-US.ts b/packages/amis-editor/src/locale/en-US.ts index 20065c5d6..35f5aaa1e 100644 --- a/packages/amis-editor/src/locale/en-US.ts +++ b/packages/amis-editor/src/locale/en-US.ts @@ -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.Example', '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', { '
When the value is__ When undefined, it means to delete the corresponding field. You can combine {"&": " $$"} to achieve the blacklist effect', '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.Example' }); diff --git a/packages/amis-editor/src/locale/zh-CN.ts b/packages/amis-editor/src/locale/zh-CN.ts index 27f70f4bb..3b9953178 100644 --- a/packages/amis-editor/src/locale/zh-CN.ts +++ b/packages/amis-editor/src/locale/zh-CN.ts @@ -2840,8 +2840,6 @@ extendLocale('zh-CN', { '', 'ce3fd44456123f571e9d083b98da9fcb': '', - 'fea0f3f456153564218a9eefb78d8cec': - '当多列的分组名称设置一致时,表格会在显示表头的上层显示超级表头,示例', 'f8fc21a9fd40881e8fd3d7f15919465c': '如果当前字段有值,请不要设置,否则覆盖。支持使用 \\${xxx} 来获取变量,或者用 lodash.template 语法来写模板逻辑。详情', '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', { '
当值为 __undefined时,表示删除对应的字段,可以结合{"&": "\\$$"}来达到黑名单效果。
', 'cb65841ea7dec5ae0af20b3f5e52abfc': '原始数据打平', '6922790f45faf064e063069816e4d2ec': - '开启后,会将所有原始数据打平设置到 data 中,并在此基础上定制' + '开启后,会将所有原始数据打平设置到 data 中,并在此基础上定制', + '9791b05a4df9d72f1a01b81fa695fbc6': + '当多列的分组名称设置一致时,表格会在显示表头的上层显示超级表头,示例' }); diff --git a/packages/amis-editor/src/plugin/Button.tsx b/packages/amis-editor/src/plugin/Button.tsx index 791b0778b..14f48eb7b 100644 --- a/packages/amis-editor/src/plugin/Button.tsx +++ b/packages/amis-editor/src/plugin/Button.tsx @@ -295,7 +295,8 @@ export class ButtonPlugin extends BasePlugin { getSchemaTpl('icon', { name: 'rightIcon', label: '右侧图标' - }) + }), + getSchemaTpl('badge') ] }, getSchemaTpl('status', { diff --git a/packages/amis-editor/src/plugin/Carousel.tsx b/packages/amis-editor/src/plugin/Carousel.tsx index 9a963d227..31bbbb955 100644 --- a/packages/amis-editor/src/plugin/Carousel.tsx +++ b/packages/amis-editor/src/plugin/Carousel.tsx @@ -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', diff --git a/packages/amis-editor/src/plugin/Date.tsx b/packages/amis-editor/src/plugin/Date.tsx index 78f465f7e..95d5ffb8c 100644 --- a/packages/amis-editor/src/plugin/Date.tsx +++ b/packages/amis-editor/src/plugin/Date.tsx @@ -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( + '显示格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: dateFormatOptions, pipeIn: defaultValue('YYYY-MM-DD') }, { type: 'input-text', name: 'valueFormat', - label: '数据日期格式', - description: '请参考 moment 中的格式用法。', + label: tipedLabel( + '值格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: valueDateFormatOptions, pipeIn: defaultValue('X') }, getSchemaTpl('placeholder', { diff --git a/packages/amis-editor/src/plugin/Datetime.tsx b/packages/amis-editor/src/plugin/Datetime.tsx index 6d977c416..edf72afd2 100644 --- a/packages/amis-editor/src/plugin/Datetime.tsx +++ b/packages/amis-editor/src/plugin/Datetime.tsx @@ -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( + '显示格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: dateFormatOptions, pipeIn: defaultValue('YYYY-MM-DD HH:mm:ss') }, { type: 'input-text', name: 'valueFormat', - label: '数据日期格式', - description: '请参考 moment 中的格式用法。', + label: tipedLabel( + '值格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: valueDateFormatOptions, pipeIn: defaultValue('X') }, getSchemaTpl('placeholder', { diff --git a/packages/amis-editor/src/plugin/Form/ButtonGroupSelect.tsx b/packages/amis-editor/src/plugin/Form/ButtonGroupSelect.tsx index 9b7bee1a1..5bad470ed 100644 --- a/packages/amis-editor/src/plugin/Form/ButtonGroupSelect.tsx +++ b/packages/amis-editor/src/plugin/Form/ButtonGroupSelect.tsx @@ -163,7 +163,8 @@ export class ButtonGroupControlPlugin extends BasePlugin { }), getSchemaTpl('buttonLevel', { label: '按钮选中样式', - name: 'btnActiveLevel' + name: 'btnActiveLevel', + pipeIn: defaultValue('primary') }) ] }, diff --git a/packages/amis-editor/src/plugin/Form/InputDate.tsx b/packages/amis-editor/src/plugin/Form/InputDate.tsx index 818be5ca7..9f76be766 100644 --- a/packages/amis-editor/src/plugin/Form/InputDate.tsx +++ b/packages/amis-editor/src/plugin/Form/InputDate.tsx @@ -294,7 +294,32 @@ export class DateControlPlugin extends BasePlugin { '值格式', '提交数据前将根据设定格式化数据,请参考 moment 中的格式用法。' ), - 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', diff --git a/packages/amis-editor/src/plugin/Form/InputDateRange.tsx b/packages/amis-editor/src/plugin/Form/InputDateRange.tsx index 995206d07..ceb84671f 100644 --- a/packages/amis-editor/src/plugin/Form/InputDateRange.tsx +++ b/packages/amis-editor/src/plugin/Form/InputDateRange.tsx @@ -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 { '值格式', '提交数据前将根据设定格式化数据,请参考 moment 中的格式用法。' ), - 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 { '请参考 moment 中的格式用法。' ), 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', { diff --git a/packages/amis-editor/src/plugin/Form/InputTable.tsx b/packages/amis-editor/src/plugin/Form/InputTable.tsx index fb1e68760..e846180fe 100644 --- a/packages/amis-editor/src/plugin/Form/InputTable.tsx +++ b/packages/amis-editor/src/plugin/Form/InputTable.tsx @@ -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); diff --git a/packages/amis-editor/src/plugin/Form/Radios.tsx b/packages/amis-editor/src/plugin/Form/Radios.tsx index 0d589b7a4..a87244ec6 100644 --- a/packages/amis-editor/src/plugin/Form/Radios.tsx +++ b/packages/amis-editor/src/plugin/Form/Radios.tsx @@ -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}) diff --git a/packages/amis-editor/src/plugin/Form/Select.tsx b/packages/amis-editor/src/plugin/Form/Select.tsx index 0cbbbf1f3..082d50823 100644 --- a/packages/amis-editor/src/plugin/Form/Select.tsx +++ b/packages/amis-editor/src/plugin/Form/Select.tsx @@ -253,6 +253,7 @@ export class SelectControlPlugin extends BasePlugin { title: '选项', body: [ getSchemaTpl('optionControlV2'), + getSchemaTpl('selectFirst'), getSchemaTpl( 'loadingConfig', { diff --git a/packages/amis-editor/src/plugin/Form/Static.tsx b/packages/amis-editor/src/plugin/Form/Static.tsx index ca5970d5f..1972fea90 100644 --- a/packages/amis-editor/src/plugin/Form/Static.tsx +++ b/packages/amis-editor/src/plugin/Form/Static.tsx @@ -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'), diff --git a/packages/amis-editor/src/plugin/Icon.tsx b/packages/amis-editor/src/plugin/Icon.tsx new file mode 100644 index 000000000..c37808dcd --- /dev/null +++ b/packages/amis-editor/src/plugin/Icon.tsx @@ -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); diff --git a/packages/amis-editor/src/plugin/List.tsx b/packages/amis-editor/src/plugin/List.tsx index 572e72b03..3ec2c2d89 100644 --- a/packages/amis-editor/src/plugin/List.tsx +++ b/packages/amis-editor/src/plugin/List.tsx @@ -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)); } }); diff --git a/packages/amis-editor/src/plugin/Mapping.tsx b/packages/amis-editor/src/plugin/Mapping.tsx index d134a53d5..215ca6182 100644 --- a/packages/amis-editor/src/plugin/Mapping.tsx +++ b/packages/amis-editor/src/plugin/Mapping.tsx @@ -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: { diff --git a/packages/amis-editor/src/plugin/Others/Action.tsx b/packages/amis-editor/src/plugin/Others/Action.tsx index d7a84d45e..a98513852 100644 --- a/packages/amis-editor/src/plugin/Others/Action.tsx +++ b/packages/amis-editor/src/plugin/Others/Action.tsx @@ -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); } diff --git a/packages/amis-editor/src/plugin/SearchBox.tsx b/packages/amis-editor/src/plugin/SearchBox.tsx new file mode 100644 index 000000000..ec3b5278f --- /dev/null +++ b/packages/amis-editor/src/plugin/SearchBox.tsx @@ -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); diff --git a/packages/amis-editor/src/plugin/Table.tsx b/packages/amis-editor/src/plugin/Table.tsx index a4b7d5b92..5b468b908 100644 --- a/packages/amis-editor/src/plugin/Table.tsx +++ b/packages/amis-editor/src/plugin/Table.tsx @@ -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)); } }); diff --git a/packages/amis-editor/src/plugin/Tag.tsx b/packages/amis-editor/src/plugin/Tag.tsx index 484d23f53..3581c38fb 100644 --- a/packages/amis-editor/src/plugin/Tag.tsx +++ b/packages/amis-editor/src/plugin/Tag.tsx @@ -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' }) ] }, diff --git a/packages/amis-editor/src/plugin/Time.tsx b/packages/amis-editor/src/plugin/Time.tsx index 288b43457..8f5a8257d 100644 --- a/packages/amis-editor/src/plugin/Time.tsx +++ b/packages/amis-editor/src/plugin/Time.tsx @@ -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( + '显示格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: timeFormatOptions, pipeIn: defaultValue('HH:mm:ss') }, { type: 'input-text', name: 'valueFormat', - label: '数据日期格式', - description: '请参考 moment 中的格式用法。', + label: tipedLabel( + '值格式', + '请参考 moment 中的格式用法。' + ), + clearable: true, + options: dateFormatOptions, pipeIn: defaultValue('X') }, getSchemaTpl('placeholder', { diff --git a/packages/amis-editor/src/renderer/APIAdaptorControl.tsx b/packages/amis-editor/src/renderer/APIAdaptorControl.tsx new file mode 100644 index 000000000..d6c11a1b6 --- /dev/null +++ b/packages/amis-editor/src/renderer/APIAdaptorControl.tsx @@ -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 = { + params: [] + }; + + constructor(props: APIAdaptorControlProps) { + super(props); + this.state = { + switch: !!this.props.value + }; + } + + componentDidUpdate(prevProps: Readonly): 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: [ + 'function ', + '(', + ...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 + ? [] : [''] + ) + ] + }).flat(), + ') {' + ] + }, + { + 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: '}', + 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 ? [ + + + + ] : [] + ] + }); + } + + render() { + const {className} = this.props; + + return ( +
+ {this.renderSwitch()} + {this.renderEditor()} +
+ ) + } +} +@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 语言直接录入发送适配器的函数体,在该函数体内,您可以对 api 进行处理或者返回新的内容,最后需要 return api

+ 函数体内可访问的变量如下:
+  1. api:接口的schema配置对象
+  2. api.data:请求数据
+  3. api.query:请求查询参数
+  4. api.body:请求体(针对POST/PUT/PATCH)
+  5. api.headers:请求头
+  6. api.url:请求地址
` + ), + name: 'requestAdaptor', + type: 'ae-apiAdaptorControl', + editorDesc: '必须将修改好的 api 对象 return 出去。', + editorPlaceholder: requestAdaptorDefaultCode, + params: [ + { + label: 'api', + tip: adaptorApiStructTooltip + } + ] +}); + +setSchemaTpl('apiAdaptor', { + label: tipedLabel( + '返回适配器', + `可基于 JavaScript 语言直接录入返回适配器的函数体,在函数体内,您可以对 payload 进行处理或者返回新的内容,最后需要 return 接口最终的返回结果。

+ 函数体内可访问的变量如下:
+  1. payload:接口的返回结果
+  2. response:接口的response对象
+  3. api:接口的schema配置对象
` + ), + 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) +}); diff --git a/packages/amis-editor/src/renderer/APIControl.tsx b/packages/amis-editor/src/renderer/APIControl.tsx index 5a98930fc..2be9f9b82 100644 --- a/packages/amis-editor/src/renderer/APIControl.tsx +++ b/packages/amis-editor/src/renderer/APIControl.tsx @@ -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' + ) ] }, { diff --git a/packages/amis-editor/src/renderer/ActionApiControl.tsx b/packages/amis-editor/src/renderer/ActionApiControl.tsx index ed983ec50..11141e0f2 100644 --- a/packages/amis-editor/src/renderer/ActionApiControl.tsx +++ b/packages/amis-editor/src/renderer/ActionApiControl.tsx @@ -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'), ] }, { diff --git a/packages/amis-editor/src/renderer/BadgeControl.tsx b/packages/amis-editor/src/renderer/BadgeControl.tsx index 792943c99..db885b921 100644 --- a/packages/amis-editor/src/renderer/BadgeControl.tsx +++ b/packages/amis-editor/src/renderer/BadgeControl.tsx @@ -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)}); } } diff --git a/packages/amis-editor/src/renderer/ExpressionFormulaControl.tsx b/packages/amis-editor/src/renderer/ExpressionFormulaControl.tsx index bffb543f8..8d6274ddb 100644 --- a/packages/amis-editor/src/renderer/ExpressionFormulaControl.tsx +++ b/packages/amis-editor/src/renderer/ExpressionFormulaControl.tsx @@ -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); } diff --git a/packages/amis-editor/src/renderer/FormulaControl.tsx b/packages/amis-editor/src/renderer/FormulaControl.tsx index 4d99c4a67..5fc557d84 100644 --- a/packages/amis-editor/src/renderer/FormulaControl.tsx +++ b/packages/amis-editor/src/renderer/FormulaControl.tsx @@ -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' ]; diff --git a/packages/amis-editor/src/renderer/OptionControl.tsx b/packages/amis-editor/src/renderer/OptionControl.tsx index 19098b94d..466498418 100644 --- a/packages/amis-editor/src/renderer/OptionControl.tsx +++ b/packages/amis-editor/src/renderer/OptionControl.tsx @@ -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; @@ -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 = values.batchOption + const addedOptions: Array = 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 = 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 ? (
- {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})} + )}
) : null; @@ -606,6 +682,9 @@ export default class OptionControl extends React.Component< }); } + const disabled = props?.hidden === true; + const tooltip = disabled ? '隐藏选项不能设为默认值' : '默认选中此项'; + return (
  • @@ -614,10 +693,11 @@ export default class OptionControl extends React.Component< {!this.props.closeDefaultCheck && this.props.data.defaultCheckAll !== true && ( - + 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< 添加选项 {/* {render('option-control-batchAdd', this.buildBatchAddSchema())} */} - {render('inner', this.buildBatchAddSchema(), { - onSubmit: this.handleBatchAdd - })} + {render('inner', this.buildBatchAddSchema())}
    {/* {this.renderPopover()} */} diff --git a/packages/amis-editor/src/renderer/StatusControl.tsx b/packages/amis-editor/src/renderer/StatusControl.tsx index cd0ab6fce..546331100 100644 --- a/packages/amis-editor/src/renderer/StatusControl.tsx +++ b/packages/amis-editor/src/renderer/StatusControl.tsx @@ -21,6 +21,10 @@ export interface StatusControlProps extends FormControlProps { options?: Option[]; children?: SchemaCollection; messages?: Pick; + // 应用于不需要 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 = { [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() { diff --git a/packages/amis-editor/src/renderer/ValidationControl.tsx b/packages/amis-editor/src/renderer/ValidationControl.tsx index 9d136e8ad..9276aa9ee 100644 --- a/packages/amis-editor/src/renderer/ValidationControl.tsx +++ b/packages/amis-editor/src/renderer/ValidationControl.tsx @@ -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 (
    {rules} + {this.renderValidateApiControl()}
    ); } + renderValidateApiControl() { + const {onBulkChange, render} = this.props; + return
    + {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( + '接口校验', + `配置校验接口,对表单项进行远程校验,配置方式与普通接口一致
    + 1. 接口返回 {status: 0} 表示校验通过
    + 2. 接口返回 {status: 422} 表示校验不通过
    + 3. 若校验失败时需要显示错误提示信息,还需返回 errors 字段,示例
    + {status: 422, errors: '错误提示消息'} + ` + ), + 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' + }) + ] + } + ] + })} +
    + } + render() { const {className} = this.props; diff --git a/packages/amis-editor/src/renderer/event-control/action-config-panel.tsx b/packages/amis-editor/src/renderer/event-control/action-config-panel.tsx index 14d417f1b..d253f2603 100644 --- a/packages/amis-editor/src/renderer/event-control/action-config-panel.tsx +++ b/packages/amis-editor/src/renderer/event-control/action-config-panel.tsx @@ -40,12 +40,7 @@ export default class ActionConfigPanel extends React.Component { return schema ? ( render('inner', schema as Schema, { - data, - onChange: (value: any, field: any) => { - onBulkChange({ - [field]: value - }); - } + data }) ) : data.__subActions ? ( <> diff --git a/packages/amis-editor/src/renderer/event-control/helper.tsx b/packages/amis-editor/src/renderer/event-control/helper.tsx index a073dcc8a..4427641f1 100644 --- a/packages/amis-editor/src/renderer/event-control/helper.tsx +++ b/packages/amis-editor/src/renderer/event-control/helper.tsx @@ -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')} ) + }, + { + 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[] => { '选择“合并”时,会将数据合并到目标组件的数据域。
    选择“覆盖”时,数据会直接覆盖目标组件的数据域。' ), 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; diff --git a/packages/amis-editor/src/renderer/textarea-formula/TextareaFormulaControl.tsx b/packages/amis-editor/src/renderer/textarea-formula/TextareaFormulaControl.tsx index 8e8b6f26a..0b795e791 100644 --- a/packages/amis-editor/src/renderer/textarea-formula/TextareaFormulaControl.tsx +++ b/packages/amis-editor/src/renderer/textarea-formula/TextareaFormulaControl.tsx @@ -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() { diff --git a/packages/amis-editor/src/renderer/textarea-formula/utils.ts b/packages/amis-editor/src/renderer/textarea-formula/utils.ts index 0d2a9150e..a8d728eb5 100644 --- a/packages/amis-editor/src/renderer/textarea-formula/utils.ts +++ b/packages/amis-editor/src/renderer/textarea-formula/utils.ts @@ -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() || []; diff --git a/packages/amis-editor/src/tpl/options.tsx b/packages/amis-editor/src/tpl/options.tsx index 9910c53fb..6cfbbf4d0 100644 --- a/packages/amis-editor/src/tpl/options.tsx +++ b/packages/amis-editor/src/tpl/options.tsx @@ -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 // 关闭默认值设置 }); /** diff --git a/packages/amis-editor/src/tpl/style.tsx b/packages/amis-editor/src/tpl/style.tsx index d95e5c81d..99398c122 100644 --- a/packages/amis-editor/src/tpl/style.tsx +++ b/packages/amis-editor/src/tpl/style.tsx @@ -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[]) => { diff --git a/packages/amis-editor/src/util.ts b/packages/amis-editor/src/util.ts index 0b0e6a6cc..e32c39f86 100644 --- a/packages/amis-editor/src/util.ts +++ b/packages/amis-editor/src/util.ts @@ -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; +}; \ No newline at end of file