From ad9c8d73b2bf7ac33352c168afa5aebaf5d34b94 Mon Sep 17 00:00:00 2001 From: liaoxuezhi <2698393+2betop@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:55:00 +0800 Subject: [PATCH] Mege Feat-X Into Master (#11252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Editor 支持插件弹出面板 (#11055) * feat: Editor 支持插件弹出面板 (#11059) * chore: 暴露 RendererThumb * feat: resultBox item项支持点击事件 (#11060) * feat: resultBox item项支持点击事件 * feat: resultBox item项支持点击事件 * feat: resultBox item项点击事件增加testid * feat: resultBox item项点击事件更新jest快照 --------- Co-authored-by: hezhihang * chore: 调整 Page 渲染器导出方便外部覆盖 * chore: ConfirmBox 支持配置 onEntered 和 onExit 事件 * chore: checkboxes radios 选项 children 必须是数组且有长度才切到分组展示 * feat: 折叠面板,无标题时显示折叠箭头。组件存在于表单中,面板新增切换展示方式按钮 (#11067) * feat: 折叠面板,无标题时显示折叠箭头。组件存在于表单中,面板新增切换展示方式按钮 * feat: 调整面板组件默认值 * feat: 调整无标题时,折叠箭头样式。调整面板配置 * 更新jest快照 * feat: page侧栏支持左右位置 (#11090) Co-authored-by: hezhihang * chore: checkboxes radios 选项 children 必须是数组且有长度才切到分组展示 * feat: AvailableRenderersPanel 支持外围扩展 * feat: AvailableRenderersPanel 支持外围扩展 * feat: editor 插件 scaffoldForm 支持异步获取 * chore: add cross-env * chore: 避免 Table 数据更新死循环 * chore: 调整 ts 定义 * feat: 给配置面板加些标记方便外围扩充 * chore: checkboxes radios 选项 children 必须是数组且有长度才切到分组展示 * chore: Options 相关逻辑支持外部扩展 * feat: mapping支持接口变量是records,提供外围扩充函数 (#11105) * feat: mapping支持接口变量是records 的情况 * feat: 提供外围扩充函数 * feat: 修改函数名 * feat: 补充props 类型定义 * chore: 调整用例 * feat: 自定义样式扩展 (#11140) Co-authored-by: hezhihang * feat: tabs支持删除动作,外观样式支持主题 * typo error * chore: 支持更多的场景 * feat: 穿梭选择器增加点击选项事件 * feat: 锚点导航调整为sticky布局 (#11143) * feat: 锚点导航调整为sticky布局 * bugfix * 更新快照 * 更新单测 * 更新单测 * bugfix * 优化样式 --------- Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com> * 动作面板支持外部注册 (#11149) * 动作面板支持外部注册 * 修复someTree报错 * chore: debug 面板调整 (#11152) * feat:事件动作面板支持查看调用链 * feat: 新增autoCheckChildren和onlyChildren属性配置面板,更改穿梭框事件点击位置 * feat: 预留外围扩充函数 * fix: 调整文案和属性名 * 修复初始化弹窗问题 * fix:自定义样式根节点不生效、样式修改时schema更新不正确,tabs动作仅支持hash删除 (#11145) Co-authored-by: hezhihang * feat: 日期\日期范围组件可配禁止输入 (#11197) * feat:验证器手机和电话号支持国家代号,数字框新增显示上下按钮,修改自定义样式实现。 (#11198) * fix: 穿梭器面板中onlyChildren属性默认开启 * feat: 验证器中手机号和电话号支持国家代号 * feat: 数字框编辑器新增显示上下按钮开关,修改自定义样式实现 * 修复插件重复注册&输入序号支持表达式 * 移除无用代码 * feat: tree支持节点行为配置、点击事件、虚拟列表高度、工具栏、操作栏等功能 * feat: 虚拟列表支持高度自适应 * feat: 虚拟列表支持高度自适应 * feat: 虚拟列表支持高度自适应 * feat: 虚拟列表支持高度自适应 * feat: 表格列新增textOverflow属性 * feat: 新增文本未超出,不呼出功能 * feat: 修改dom计算的位置 * feat: 增加限制条件 * feat: 删除sonTarget属性 * feat: 合并renderTextOver * feat: 留出外围扩充位置 * feat: 步骤条新增iconPositon属性,修复简单模式下箭头布局错乱 (#11250) * feat: 替换按钮配置面板自定义样式 * feat: 步骤条新增iconPositon属性,修复简单模式下箭头布局错乱 --------- Co-authored-by: hzh11012 <43038692+hzh11012@users.noreply.github.com> Co-authored-by: hezhihang Co-authored-by: F-jianchao <161407305+F-jianchao@users.noreply.github.com> Co-authored-by: hsm-lv <80095014+hsm-lv@users.noreply.github.com> Co-authored-by: fujianchao Co-authored-by: qkiroc <30946345+qkiroc@users.noreply.github.com> Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com> Co-authored-by: z418577198 <418577198@qq.com> Co-authored-by: lvxiaojiao Co-authored-by: zhangzhijun03 Co-authored-by: Allen --- docs/zh-CN/components/crud.md | 1 + docs/zh-CN/components/form/input-tree.md | 500 ++- docs/zh-CN/components/form/treeselect.md | 55 + docs/zh-CN/components/page.md | 25 + docs/zh-CN/components/tabs.md | 124 + .../__tests__/renderers/wrapControl.test.tsx | 6 +- packages/amis-core/package.json | 7 +- packages/amis-core/src/actions/Action.ts | 4 + packages/amis-core/src/index.tsx | 8 +- packages/amis-core/src/renderers/Item.tsx | 2 + packages/amis-core/src/renderers/Options.tsx | 2198 +++++------ .../amis-core/src/renderers/wrapControl.tsx | 6 +- packages/amis-core/src/store/formItem.ts | 2 +- packages/amis-core/src/utils/api.ts | 4 +- .../amis-core/src/utils/attachmentAdpator.ts | 2 + packages/amis-core/src/utils/debug.tsx | 179 +- packages/amis-core/src/utils/index.ts | 1 + packages/amis-core/src/utils/loopTooMuch.ts | 23 + packages/amis-core/src/utils/position.ts | 21 +- packages/amis-core/src/utils/validations.ts | 6 +- packages/amis-editor-core/package.json | 5 +- .../scss/control/_event-action.scss | 55 +- packages/amis-editor-core/scss/editor.scss | 1 + .../style-control/_single-theme-css-code.scss | 83 + .../amis-editor-core/src/component/Editor.tsx | 16 +- .../src/component/ModalForm.tsx | 161 + .../Panel/AvailableRenderersPanel.tsx | 3 +- .../src/component/Preview.tsx | 6 +- packages/amis-editor-core/src/deepSplice.ts | 130 + packages/amis-editor-core/src/index.ts | 8 +- packages/amis-editor-core/src/manager.ts | 28 +- packages/amis-editor-core/src/plugin.ts | 5 + packages/amis-editor-core/src/store/editor.ts | 36 + packages/amis-editor/package.json | 5 +- .../amis-editor/src/component/BaseControl.ts | 11 +- packages/amis-editor/src/index.tsx | 3 + packages/amis-editor/src/plugin/AnchorNav.tsx | 2 + packages/amis-editor/src/plugin/Button.tsx | 14 +- packages/amis-editor/src/plugin/CRUD.tsx | 22 +- .../amis-editor/src/plugin/CRUD2/BaseCRUD.tsx | 16 +- packages/amis-editor/src/plugin/Calendar.tsx | 25 +- packages/amis-editor/src/plugin/Chart.tsx | 11 +- packages/amis-editor/src/plugin/Collapse.tsx | 24 +- .../amis-editor/src/plugin/CollapseGroup.tsx | 18 + packages/amis-editor/src/plugin/Dialog.tsx | 11 +- packages/amis-editor/src/plugin/Drawer.tsx | 12 +- .../src/plugin/Form/ButtonGroupSelect.tsx | 32 +- .../src/plugin/Form/ChainedSelect.tsx | 26 +- .../amis-editor/src/plugin/Form/Checkbox.tsx | 35 +- .../src/plugin/Form/Checkboxes.tsx | 32 +- .../src/plugin/Form/CodeEditor.tsx | 27 +- .../amis-editor/src/plugin/Form/Combo.tsx | 12 +- .../src/plugin/Form/DiffEditor.tsx | 19 +- packages/amis-editor/src/plugin/Form/Form.tsx | 53 +- .../amis-editor/src/plugin/Form/InputCity.tsx | 33 +- .../amis-editor/src/plugin/Form/InputDate.tsx | 36 +- .../src/plugin/Form/InputDateRange.tsx | 37 +- .../src/plugin/Form/InputExcel.tsx | 21 +- .../amis-editor/src/plugin/Form/InputFile.tsx | 26 +- .../src/plugin/Form/InputImage.tsx | 15 +- .../amis-editor/src/plugin/Form/InputKV.tsx | 24 +- .../src/plugin/Form/InputNumber.tsx | 71 +- .../src/plugin/Form/InputRange.tsx | 19 +- .../src/plugin/Form/InputRating.tsx | 25 +- .../src/plugin/Form/InputTable.tsx | 13 +- .../amis-editor/src/plugin/Form/InputTag.tsx | 25 +- .../amis-editor/src/plugin/Form/InputText.tsx | 32 +- .../amis-editor/src/plugin/Form/InputTree.tsx | 307 +- packages/amis-editor/src/plugin/Form/Item.tsx | 13 +- .../src/plugin/Form/ListSelect.tsx | 26 +- .../src/plugin/Form/LocationPicker.tsx | 29 +- .../src/plugin/Form/MatrixCheckboxes.tsx | 17 +- .../src/plugin/Form/NestedSelect.tsx | 31 +- .../amis-editor/src/plugin/Form/Radios.tsx | 33 +- .../amis-editor/src/plugin/Form/Select.tsx | 44 +- .../amis-editor/src/plugin/Form/Switch.tsx | 24 +- .../src/plugin/Form/TabsTransfer.tsx | 29 +- .../amis-editor/src/plugin/Form/Textarea.tsx | 28 +- .../amis-editor/src/plugin/Form/Transfer.tsx | 55 +- .../src/plugin/Form/TransferPicker.tsx | 28 +- .../src/plugin/Form/TreeSelect.tsx | 214 +- packages/amis-editor/src/plugin/Mapping.tsx | 1 + packages/amis-editor/src/plugin/Nav.tsx | 12 +- .../src/plugin/Others/TableCell.tsx | 35 +- packages/amis-editor/src/plugin/Page.tsx | 56 +- packages/amis-editor/src/plugin/Panel.tsx | 2 + packages/amis-editor/src/plugin/Progress.tsx | 22 +- packages/amis-editor/src/plugin/SearchBox.tsx | 17 +- packages/amis-editor/src/plugin/Service.tsx | 11 +- packages/amis-editor/src/plugin/Steps.tsx | 5 + packages/amis-editor/src/plugin/Table.tsx | 31 +- packages/amis-editor/src/plugin/Table2.tsx | 4 +- packages/amis-editor/src/plugin/Tabs.tsx | 142 +- packages/amis-editor/src/plugin/Wizard.tsx | 62 +- .../event-control/action-config-dialog.tsx | 62 +- .../event-control/action-config-panel.tsx | 15 +- .../event-control/actionsPanelManager.ts | 111 + .../componentActionsPanel/clear.tsx | 21 + .../componentActionsPanel/component.tsx | 10 + .../componentActionsPanel/helper.tsx | 135 + .../componentActionsPanel/index.ts | 11 + .../componentActionsPanel/reload.tsx | 177 + .../componentActionsPanel/reset.tsx | 21 + .../componentActionsPanel/setValue.tsx | 482 +++ .../componentActionsPanel/staticStatus.tsx | 64 + .../componentActionsPanel/submit.tsx | 60 + .../componentActionsPanel/usability.tsx | 122 + .../componentActionsPanel/validate.tsx | 56 + .../validateFormItem.tsx | 52 + .../componentActionsPanel/visibility.tsx | 104 + .../actionsPanelPlugins/index.tsx | 5 + .../modalActionsPanel/closeDialog.tsx | 7 + .../modalActionsPanel/index.ts | 3 + .../modalActionsPanel/openDialog.tsx | 125 + .../modalActionsPanel/toast.tsx | 130 + .../otherActionsPanel/copy.tsx | 52 + .../otherActionsPanel/custom.tsx | 42 + .../otherActionsPanel/index.ts | 2 + .../pageActionsPanel/goBack.tsx | 11 + .../pageActionsPanel/index.ts | 3 + .../pageActionsPanel/refresh.tsx | 11 + .../pageActionsPanel/url.tsx | 71 + .../serverActionsPanel/ajax.tsx | 94 + .../serverActionsPanel/download.tsx | 23 + .../serverActionsPanel/index.ts | 2 + .../event-control/comp-action-select.tsx | 15 +- .../src/renderer/event-control/constants.ts | 47 + .../event-control/eventControlConfigHelper.ts | 656 ++++ .../src/renderer/event-control/helper.tsx | 2695 +------------- .../src/renderer/event-control/index.tsx | 256 +- .../style-control/SingleThemeCssCode.tsx | 3297 +++++++++++++++++ packages/amis-editor/src/tpl/common.tsx | 15 + packages/amis-editor/src/tpl/style.tsx | 45 +- packages/amis-editor/src/util.ts | 23 + .../amis-theme-editor-helper/package.json | 3 +- packages/amis-ui/package.json | 5 +- .../scss/components/_collapse-group.scss | 11 + .../amis-ui/scss/components/_collapse.scss | 9 + packages/amis-ui/scss/components/_debug.scss | 66 +- packages/amis-ui/scss/components/_page.scss | 15 + .../amis-ui/scss/components/_popoverable.scss | 20 + packages/amis-ui/scss/components/_steps.scss | 7 +- packages/amis-ui/scss/components/_tabs.scss | 12 +- .../amis-ui/scss/components/form/_tree.scss | 24 +- packages/amis-ui/src/components/Collapse.tsx | 68 +- .../amis-ui/src/components/ConfirmBox.tsx | 11 +- .../amis-ui/src/components/DatePicker.tsx | 6 +- .../src/components/DateRangePicker.tsx | 6 +- .../src/components/PickerContainer.tsx | 2 +- packages/amis-ui/src/components/PopUp.tsx | 14 +- packages/amis-ui/src/components/Radios.tsx | 2 +- packages/amis-ui/src/components/ResultBox.tsx | 33 +- packages/amis-ui/src/components/Steps.tsx | 27 +- packages/amis-ui/src/components/Tabs.tsx | 14 +- packages/amis-ui/src/components/Transfer.tsx | 2 + .../amis-ui/src/components/TransferPicker.tsx | 4 +- packages/amis-ui/src/components/Tree.tsx | 129 +- .../Form/__snapshots__/fieldSet.test.tsx.snap | 21 + .../Form/__snapshots__/inputTag.test.tsx.snap | 10 + .../__snapshots__/nestedSelect.test.tsx.snap | 3 + .../Form/__snapshots__/select.test.tsx.snap | 2 + .../tabsTransferPicker.test.tsx.snap | 1 + .../amis/__tests__/renderers/Tabs.test.tsx | 88 + .../amis/__tests__/renderers/Tree.test.tsx | 411 +- .../__snapshots__/Page.test.tsx.snap | 2 +- packages/amis/package.json | 3 +- .../amis/src/renderers/Form/Checkboxes.tsx | 2 +- .../amis/src/renderers/Form/InputDate.tsx | 4 + .../amis/src/renderers/Form/InputText.tsx | 49 +- .../amis/src/renderers/Form/InputTree.tsx | 304 +- packages/amis/src/renderers/Form/Transfer.tsx | 9 +- .../src/renderers/Form/TransferPicker.tsx | 19 + .../amis/src/renderers/Form/TreeSelect.tsx | 238 +- packages/amis/src/renderers/Mapping.tsx | 28 +- packages/amis/src/renderers/Page.tsx | 35 +- packages/amis/src/renderers/PopOver.tsx | 67 +- packages/amis/src/renderers/Steps.tsx | 7 + .../amis/src/renderers/Table/TableCell.tsx | 2 +- packages/amis/src/renderers/Table/index.tsx | 8 +- packages/amis/src/renderers/Tabs.tsx | 266 +- 180 files changed, 12027 insertions(+), 4951 deletions(-) create mode 100644 packages/amis-core/src/utils/loopTooMuch.ts create mode 100644 packages/amis-editor-core/scss/style-control/_single-theme-css-code.scss create mode 100644 packages/amis-editor-core/src/component/ModalForm.tsx create mode 100644 packages/amis-editor-core/src/deepSplice.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelManager.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/clear.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/component.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/helper.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/index.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/reload.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/reset.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/setValue.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/staticStatus.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/submit.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/usability.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/validate.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/validateFormItem.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/componentActionsPanel/visibility.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/index.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/modalActionsPanel/closeDialog.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/modalActionsPanel/index.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/modalActionsPanel/openDialog.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/modalActionsPanel/toast.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/otherActionsPanel/copy.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/otherActionsPanel/custom.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/otherActionsPanel/index.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/pageActionsPanel/goBack.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/pageActionsPanel/index.ts create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/pageActionsPanel/refresh.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/pageActionsPanel/url.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/serverActionsPanel/ajax.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/serverActionsPanel/download.tsx create mode 100644 packages/amis-editor/src/renderer/event-control/actionsPanelPlugins/serverActionsPanel/index.ts create mode 100644 packages/amis-editor/src/renderer/event-control/constants.ts create mode 100644 packages/amis-editor/src/renderer/event-control/eventControlConfigHelper.ts create mode 100644 packages/amis-editor/src/renderer/style-control/SingleThemeCssCode.tsx diff --git a/docs/zh-CN/components/crud.md b/docs/zh-CN/components/crud.md index 48cac667b..b47ac32e7 100755 --- a/docs/zh-CN/components/crud.md +++ b/docs/zh-CN/components/crud.md @@ -3999,6 +3999,7 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据, | filterable | `boolean` \| [`QuickFilterConfig`](./crud#quickfilterconfig) | `false` | 是否可快速搜索,`options`属性为静态选项,支持设置`source`属性从接口获取选项 | | quickEdit | `boolean` \| [`QuickEditConfig`](./crud#quickeditconfig) | - | 快速编辑,一般需要配合`quickSaveApi`接口使用 | | quickEditEnabledOn | `SchemaExpression` | - | 开启快速编辑条件[表达式](../../docs/concepts/expression) | | +| textOverflow | `string` | `default` | 文本溢出后展示形式,默认换行处理。可选值 `ellipsis` 溢出隐藏展示, `noWrap` 不换行展示(仅在列为静态文本时生效) | `6.9.0` | #### QuickFilterConfig diff --git a/docs/zh-CN/components/form/input-tree.md b/docs/zh-CN/components/form/input-tree.md index 887c2fe48..af590e3ac 100755 --- a/docs/zh-CN/components/form/input-tree.md +++ b/docs/zh-CN/components/form/input-tree.md @@ -249,6 +249,8 @@ order: 59 `cascade`默认为 false,子节点禁止反选,值不包含子节点值,配置`"cascade": true`,子节点可以反选,值包含父子节点值(1.9.0 之前的版本 cascade 配置为 true 的效果为:选中父节点不默认选中子节点) +> 6.9.0 以上版本 autoCancelParent 配置为 true 的效果为:取消子节点,自动去除父节点的值(仅在多选和 cascade 为 true 时生效) + ```schema: scope="body" { "type": "form", @@ -292,7 +294,7 @@ order: 59 { "type": "divider" }, - { + { "type": "input-tree", "name": "tree2", "label": "子节点可以反选,值包含父子节点值", @@ -326,6 +328,45 @@ order: 59 "value": "c" } ] + }, + { + "type": "divider" + }, + { + "type": "input-tree", + "name": "tree3", + "label": "子节点可以反选,值包含父子节点值,取消子节点,自动去除父节点的值", + "multiple": true, + "cascade": true, + "autoCancelParent": true, + "options": [ + { + "label": "A", + "value": "a" + }, + { + "label": "B", + "value": "b", + "children": [ + { + "label": "B-1", + "value": "b-1" + }, + { + "label": "B-2", + "value": "b-2" + }, + { + "label": "B-3", + "value": "b-3" + } + ] + }, + { + "label": "C", + "value": "c" + } + ] } ] } @@ -1086,6 +1127,338 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A 适用于需选择的数据/信息源较多时,用户可直观的知道自己所选择的数据/信息的场景。未配置 searchApi 是前端检索,配置之后就只能通过后端检索。 +## 节点行为配置 + +> 6.9.0 以上版本 + +设置`nodeBehavior`属性,可以更改节点的行为,默认为选中行为,支持配置多个行为。 + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "body": [ + { + "type": "input-tree", + "name": "tree1", + "label": "选中", + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2, + } + ] + }, + { + "label": "file C", + "value": 3 + } + ] + }, + { + "type": "input-tree", + "name": "tree2", + "label": "展开", + "nodeBehavior": ["unfold"], + "options": [ + { + "label": "Folder A", + "value": 4, + "children": [ + { + "label": "file A", + "value": 5, + } + ] + }, + { + "label": "file C", + "value": 6 + } + ] + }, + { + "type": "input-tree", + "name": "tree3", + "label": "选中+展开", + "nodeBehavior": ["check", "unfold"], + "options": [ + { + "label": "Folder A", + "value": 7, + "children": [ + { + "label": "file A", + "value": 8, + } + ] + }, + { + "label": "file C", + "value": 9 + } + ] + } + ] +} +``` + +## 自定义选项操作 + +> 6.9.0 以上版本 + +> 使用`itemActions`属性,自定义下拉选项的操作。 + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "body": [ + { + "type": "input-tree", + "name": "tree", + "label": "Tree", + "iconField": "icon", + "options": [ + { + "label": "采购单", + "value": "order", + "tag": "数据模型", + "icon": "fa fa-database", + "children": [ + { + "label": "ID", + "value": "id", + "tag": "数字", + "icon": "fa fa-check" + }, + { + "label": "采购人", + "value": "name", + "tag": "字符串", + "icon": "fa fa-check" + }, + { + "label": "采购时间", + "value": "time", + "tag": "日期时间", + "icon": "fa fa-check" + } + ] + } + ], + "itemActions": [ + { + "type": "button", + "icon": "fa fa-plus", + "level": "link", + "size": "xs", + "onEvent": { + "click": { + "weight": 0, + "actions": [ + { + "ignoreError": false, + "actionType": "toast", + "args": { + "msgType": "info", + "position": "top-right", + "closeButton": true, + "showIcon": true, + "msg": "自定义操作", + "className": "theme-toast-action-scope" + } + } + ] + } + } + } + ] + } + ] +} +``` + +## 工具栏区域 + +> 6.9.0 以上版本 +> 使用`toolbar`属性,自定义工具栏区域。(仅开启检索时生效) + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "body": [ + { + "type": "input-tree", + "name": "tree", + "label": "Tree", + "searchable": true, + "toolbar": [ + { + "type": "button", + "label": "弹窗", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "dialog", + "dialog": { + "type": "dialog", + "title": "未命名弹窗", + "body": [ + { + "type": "tpl", + "tpl": "弹窗内容" + } + ], + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "取消" + }, + { + "type": "button", + "actionType": "confirm", + "label": "确定", + "primary": true + } + ] + } + } + ] + } + } + } + ], + "options": [ + { + "label": "Folder A", + "value": 1, + "collapsed": true, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file D", + "value": 4 + } + ] + } + ] +} +``` + +## 虚拟列表 + +> 6.9.0 以上版本, 开启 heightAuto 后,虚拟列表将自适应高度 + +```schema: scope="body" +{ + "type": "page", + "aside": [ + { + "type": "flex", + "direction": "column", + "isFixedHeight": true, + "style": { + "height": "300px" + }, + "items": [ + { + "type": "input-tree", + "id": "tree", + "name": "tree", + "label": false, + "heightAuto": true, + "virtualThreshold": 5, + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file C", + "value": 4 + }, + { + "label": "file D", + "value": 5 + }, + { + "label": "file E", + "value": 6 + }, + { + "label": "file F", + "value": 7 + }, + { + "label": "file G", + "value": 8 + }, + { + "label": "file H", + "value": 9 + }, + { + "label": "file I", + "value": 10 + }, + { + "label": "file J", + "value": 11 + }, + { + "label": "file K", + "value": 12 + }, + { + "label": "file L", + "value": 13 + } + ], + "wrapperCustomStyle": { + "root": { + "height": "100%" + } + } + } + ] + } + ], + "body": [ + { + "type": "tpl", + "tpl": "开启heightAuto后,设置tree的高度,虚拟列表将自适应" + } + ] +} +``` + ## 属性表 当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置 @@ -1137,6 +1510,11 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A | menuTpl | `string` | | 选项自定义渲染 HTML 片段 | `2.8.0` | | enableDefaultIcon | `boolean` | `true` | 是否为选项添加默认的前缀 Icon,父节点默认为`folder`,叶节点默认为`file` | `2.8.0` | | heightAuto | `boolean` | `false` | 默认高度会有个 maxHeight,即超过一定高度就会内部滚动,如果希望自动增长请设置此属性 | `3.0.0` | +| nodeBehavior | `Array<'unfold' \| 'check' \| ''>` | `['check']` | 节点行为配置,支持配置多个行为 | `6.9.0` | +| autoCancelParent | `boolean` | `false` | 子节点取消时自动取消父节点的值,仅在多选且 cascade 为 true 时生效 | `6.9.0` | +| toolbar | `SchemaNode` | | 工具栏区域,仅开启检索时生效 | `6.9.0` | +| toolbarClassName | `string` | | 工具栏区域类名 | `6.9.0` | +| itemActions | `SchemaNode` | | 节点操作栏区域 | `6.9.0` | ## 事件表 @@ -1151,6 +1529,7 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A | editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值
`item: object` 编辑的节点信息
`items: object[]`选项集合 | 编辑节点提交时触发 | | deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值
`item: object` 删除的节点信息
`items: object[]`选项集合 | 删除节点提交时触发 | | deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值
`result: object` deferApi 懒加载远程请求成功后返回的数据
`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 | +| itemClick (6.9.0 以上版本) | `value: any`表单项的值,值格式取决于具体配置
`item: object` 点击的节点信息 | 节点点击时触发 | | add(不推荐) | `[name]: object` 新增的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 | | edit(不推荐) | `[name]: object` 编辑的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 | | delete(不推荐) | `[name]: object` 删除的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 | @@ -1440,6 +1819,60 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A } ``` +### itemClick + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "debug": true, + "body": [ + { + "type": "input-tree", + "name": "tree", + "label": "Tree", + "nodeBehavior": [], + "onEvent": { + "itemClick": { + "actions": [ + { + "actionType": "toast", + "args": { + "msg": "${event.data.tree|json}" + } + } + ] + } + }, + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file C", + "value": 4 + }, + { + "label": "file D", + "value": 5 + } + ] + } + ] +} +``` + ## 动作表 当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。 @@ -1455,6 +1888,7 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A | clear | - | 清空 | | reset | - | 将值重置为初始值。6.3.0 及以下版本为`resetValue` | | setValue | `value: string` \| `string[]` 更新的值 | 更新数据,开启`multiple`支持设置多项,开启`joinValues`时,多值用`,`分隔,否则多值用数组 | +| search | `keyword: string` 检索的值 | 检索数据 | ### clear @@ -1589,3 +2023,67 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A ] } ``` + +### search + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "debug": true, + "body": [ + { + "type": "search-box", + "name": "keyword", + "className": "mb-4", + "style": { + "width": "100%" + }, + "onEvent": { + "change": { + "actions": [ + { + "componentId": "tree", + "groupType": "component", + "actionType": "search", + "args": { + "keyword": "${event.data.value}" + } + } + ] + } + } + }, + { + "type": "input-tree", + "id": "tree", + "name": "tree", + "label": false, + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file C", + "value": 4 + }, + { + "label": "file D", + "value": 5 + } + ] + } + ] +} +``` diff --git a/docs/zh-CN/components/form/treeselect.md b/docs/zh-CN/components/form/treeselect.md index 546eaaed6..d403afa21 100755 --- a/docs/zh-CN/components/form/treeselect.md +++ b/docs/zh-CN/components/form/treeselect.md @@ -418,6 +418,7 @@ order: 60 | editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值
`item: object` 编辑的节点信息
`items: object[]`选项集合 | 编辑节点提交时触发 | | deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值
`item: object` 删除的节点信息
`items: object[]`选项集合 | 删除节点提交时触发 | | deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值
`result: object` deferApi 懒加载远程请求成功后返回的数据
`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 | +| itemClick (6.9.0 以上版本) | `value: any`表单项的值,值格式取决于具体配置
`item: object` 点击的节点信息 | 节点点击时触发 | | add(不推荐) | `[name]: object` 新增的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 | | edit(不推荐) | `[name]: object` 编辑的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 | | delete(不推荐) | `[name]: object` 删除的节点信息
`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 | @@ -812,6 +813,60 @@ order: 60 } ``` +### itemClick + +```schema: scope="body" +{ + "type": "form", + "api": "/api/mock2/form/saveForm", + "debug": true, + "body": [ + { + "type": "input-tree", + "name": "tree", + "label": "Tree", + "nodeBehavior": [], + "onEvent": { + "itemClick": { + "actions": [ + { + "actionType": "toast", + "args": { + "msg": "${event.data.tree|json}" + } + } + ] + } + }, + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file C", + "value": 4 + }, + { + "label": "file D", + "value": 5 + } + ] + } + ] +} +``` + ## 动作表 当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。 diff --git a/docs/zh-CN/components/page.md b/docs/zh-CN/components/page.md index 85f996e0c..4f807b964 100755 --- a/docs/zh-CN/components/page.md +++ b/docs/zh-CN/components/page.md @@ -251,6 +251,30 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`)**、** 通过配置 `asideSticky` 来开关,默认是开启状态。 +## aside 展示位置 + +通过配置 `asidePosition`,可以控制侧边栏的展示位置。 + +```schema +{ + "type": "page", + "asideResizor": true, + "asidePosition": "right", + "aside": [ + { + "type": "tpl", + "tpl": "这是侧边栏部分" + } + ], + "body": [ + { + "type": "tpl", + "tpl": "这是内容区" + } + ] +} +``` + ## 属性表 | 属性名 | 类型 | 默认值 | 说明 | @@ -264,6 +288,7 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`)**、** | asideMinWidth | `number` | | 页面边栏区域的最小宽度 | | asideMaxWidth | `number` | | 页面边栏区域的最大宽度 | | asideSticky | `boolean` | true | 用来控制边栏固定与否 | +| asidePosition | `"left" \| "right"` | `"left"` | 页面边栏区域的位置 | | toolbar | [SchemaNode](../../docs/types/schemanode) | | 往页面的右上角加内容,需要注意的是,当有 title 时,该区域在右上角,没有时该区域在顶部 | | body | [SchemaNode](../../docs/types/schemanode) | | 往页面的内容区域加内容 | | className | `string` | | 外层 dom 类名 | diff --git a/docs/zh-CN/components/tabs.md b/docs/zh-CN/components/tabs.md index c7c6cbe6d..11e23d5ab 100755 --- a/docs/zh-CN/components/tabs.md +++ b/docs/zh-CN/components/tabs.md @@ -881,6 +881,7 @@ order: 68 | 事件名称 | 事件参数 | 说明 | | -------- | ------------------------------------ | ---------------- | | change | `value: number \| string` 选项卡索引 | 切换选项卡时触发 | +| delete | `value: number \| string` 选项卡索引 | 删除选项卡时触发 | ### change @@ -918,6 +919,43 @@ order: 68 } ``` +### delete + +```schema: scope="body" +{ + "type": "tabs", + "closable": true, + "mode": "line", + "tabs": [ + { + "title": "选项卡1", + "body": "选项卡内容1" + }, + { + "title": "选项卡2", + "body": "选项卡内容2" + }, + { + "title": "选项卡3", + "body": "选项卡内容3" + } + ], + "onEvent": { + "delete": { + "actions": [ + { + "actionType": "toast", + "args": { + "msgType": "info", + "msg": "删除选项卡${event.data.value}" + } + } + ] + } + } +} +``` + ## 动作表 当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。 @@ -925,6 +963,7 @@ order: 68 | 动作名称 | 动作配置 | 说明 | | --------------- | ---------------------------------------- | ---------------- | | changeActiveKey | `activeKey: number \| string` 选项卡索引 | 激活指定的选项卡 | +| deleteTab | `deleteHash: string` 选项卡 hash | 删除指定的选项卡 | ### changeActiveKey @@ -1007,3 +1046,88 @@ order: 68 } ] ``` + +### deleteTab + +可以尝试点击下方按钮,实现选项卡删除。 + +```schema: scope="body" +[ + { + "type": "action", + "label": "删除选项卡1", + "className": "mr-3 mb-3", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "deleteTab", + "componentId": "tabs-change-receiver", + "args": { + "deleteHash": "tab1" + } + } + ] + } + } + }, + { + "type": "action", + "label": "删除选项卡2", + "className": "mr-3 mb-3", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "deleteTab", + "componentId": "tabs-change-receiver", + "args": { + "deleteHash": "tab2" + } + } + ] + } + } + }, + { + "type": "action", + "label": "删除选项卡3", + "className": "mr-3 mb-3", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "deleteTab", + "componentId": "tabs-change-receiver", + "args": { + "deleteHash": "tab3" + } + } + ] + } + } + }, + { + "id": "tabs-change-receiver", + "type": "tabs", + "mode": "line", + "tabs": [ + { + "title": "选项卡1", + "hash": "tab1", + "body": "选项卡内容1" + }, + { + "title": "选项卡2", + "hash": "tab2", + "body": "选项卡内容2" + }, + { + "title": "选项卡3", + "hash": "tab3", + "body": "选项卡内容3" + } + ] + } +] +``` diff --git a/packages/amis-core/__tests__/renderers/wrapControl.test.tsx b/packages/amis-core/__tests__/renderers/wrapControl.test.tsx index 2fbfeebbf..a6b46cdeb 100644 --- a/packages/amis-core/__tests__/renderers/wrapControl.test.tsx +++ b/packages/amis-core/__tests__/renderers/wrapControl.test.tsx @@ -28,7 +28,7 @@ describe('constructor', () => { OriginComponent.defaultProps = { name: 'test' }; - const WrappedComponent = wrapControl(OriginComponent as any); + const WrappedComponent = wrapControl({}, OriginComponent as any); renderComponent(WrappedComponent, {$schema: {}}); expect(screen.getByText('FormItemStore')).toBeInTheDocument(); @@ -39,7 +39,7 @@ describe('constructor', () => { // 用于断言formItem已被注册 return
{props.formItem?.storeType}
; }; - const WrappedComponent = wrapControl(OriginComponent as any); + const WrappedComponent = wrapControl({}, OriginComponent as any); renderComponent(WrappedComponent, {$schema: {name: 'test'}}); expect(screen.getByText('FormItemStore')).toBeInTheDocument(); @@ -50,7 +50,7 @@ describe('constructor', () => { // 用于断言formItem已被注册 return
{props.formItem?.storeType}
; }; - const WrappedComponent = wrapControl(OriginComponent as any); + const WrappedComponent = wrapControl({}, OriginComponent as any); renderComponent(WrappedComponent, {$schema: {}}); expect(screen.queryByText('FormItemStore')).not.toBeInTheDocument(); diff --git a/packages/amis-core/package.json b/packages/amis-core/package.json index c20307c7a..858b68268 100644 --- a/packages/amis-core/package.json +++ b/packages/amis-core/package.json @@ -35,8 +35,8 @@ "typescript": "^4.6.4" }, "scripts": { - "build": "npm run clean-dist && NODE_ENV=production rollup -c ", - "build-esm": "npm run clean-dist && NODE_ENV=production rollup -c rollup.esm.config.js", + "build": "npm run clean-dist && cross-env NODE_ENV=production rollup -c ", + "build-esm": "npm run clean-dist && cross-env NODE_ENV=production rollup -c rollup.esm.config.js", "dev": "rollup -c -w", "test": "jest", "update-snapshot": "jest --updateSnapshot", @@ -49,7 +49,8 @@ ], "dependencies": { "@rc-component/mini-decimal": "^1.0.1", - "amis-formula": "^6.9.0", + "cross-env": "^7.0.3", + "amis-formula": "*", "classnames": "2.3.2", "file-saver": "^2.0.2", "hoist-non-react-statics": "^3.3.2", diff --git a/packages/amis-core/src/actions/Action.ts b/packages/amis-core/src/actions/Action.ts index d139500e6..2edc9a9d3 100644 --- a/packages/amis-core/src/actions/Action.ts +++ b/packages/amis-core/src/actions/Action.ts @@ -10,6 +10,7 @@ import {IContinueAction} from './ContinueAction'; import {ILoopAction} from './LoopAction'; import {IParallelAction} from './ParallelAction'; import {ISwitchAction} from './SwitchAction'; +import {debug} from '../utils/debug'; // 循环动作执行状态 export enum LoopStatus { @@ -343,6 +344,9 @@ export const runAction = async ( console.group?.(`run action ${action.actionType}`); console.debug(`[${action.actionType}] action args, data`, args, data); + debug('action', `run action ${action.actionType} with args`, args); + debug('action', `run action ${action.actionType} with data`, data); + let stopped = false; const actionResult = await actionInstrance.run( { diff --git a/packages/amis-core/src/index.tsx b/packages/amis-core/src/index.tsx index 99315d0c0..139e77d30 100644 --- a/packages/amis-core/src/index.tsx +++ b/packages/amis-core/src/index.tsx @@ -81,7 +81,11 @@ import type { FormControlProps, FormItemProps } from './renderers/Item'; -import {OptionsControl, registerOptionsControl} from './renderers/Options'; +import { + OptionsControl, + registerOptionsControl, + OptionsControlBase +} from './renderers/Options'; import type {OptionsControlProps} from './renderers/Options'; import type {FormOptionsControl} from './renderers/Options'; import {Schema} from './types'; @@ -217,6 +221,7 @@ export { ErrorBoundary, addSchemaFilter, OptionsControlProps, + OptionsControlBase, FormOptionsControl, FormControlProps, FormBaseControl, @@ -241,6 +246,7 @@ export { envOverwrite, getGlobalOptions, setGlobalOptions, + wrapFetcher, SchemaRenderer }; diff --git a/packages/amis-core/src/renderers/Item.tsx b/packages/amis-core/src/renderers/Item.tsx index 5a8d5e3c4..ab62a8da5 100644 --- a/packages/amis-core/src/renderers/Item.tsx +++ b/packages/amis-core/src/renderers/Item.tsx @@ -481,6 +481,7 @@ export interface FormItemBasicConfig extends Partial { renderDescription?: boolean; test?: RegExp | TestFunc; storeType?: string; + formItemStoreType?: string; validations?: string; strictMode?: boolean; @@ -2205,6 +2206,7 @@ export function asFormItem(config: Omit) { } return wrapControl( + config, hoistNonReactStatic( class extends FormItemWrap { static defaultProps: any = { diff --git a/packages/amis-core/src/renderers/Options.tsx b/packages/amis-core/src/renderers/Options.tsx index aec8770dc..7e1e8d7d4 100644 --- a/packages/amis-core/src/renderers/Options.tsx +++ b/packages/amis-core/src/renderers/Options.tsx @@ -287,10 +287,1109 @@ export const detectProps = itemDetectProps.concat([ 'hideSelected' ]); +export class OptionsControlBase< + T extends OptionsProps = OptionsProps, + S = any +> extends React.Component { + toDispose: Array<() => void> = []; + + input: any; + mounted = false; + + constructor(props: T, readonly config: OptionsConfig) { + super(props); + + const { + initFetch, + formItem, + source, + data, + setPrinstineValue, + defaultValue, + multiple, + joinValues, + extractValue, + addHook, + formInited, + valueField, + options, + value, + defaultCheckAll + } = props; + + if (!formItem) { + return; + } + + formItem.setOptions( + normalizeOptions(options, undefined, valueField), + this.changeOptionValue, + data + ); + + this.toDispose.push( + reaction( + () => JSON.stringify([formItem.loading, formItem.filteredOptions]), + () => this.mounted && this.forceUpdate() + ) + ); + + // 默认全选。这里会和默认值\回填值逻辑冲突,所以如果有配置source则不执行默认全选 + if ( + multiple && + defaultCheckAll && + formItem.filteredOptions?.length && + !source + ) { + this.defaultCheckAll(); + } + + let loadOptions: boolean = initFetch !== false; + let setInitValue: Function | null = null; + + if (joinValues === false && defaultValue) { + setInitValue = () => { + const selectedOptions = extractValue + ? formItem + .getSelectedOptions(value) + .map( + (selectedOption: Option) => + selectedOption[valueField || 'value'] + ) + : formItem.getSelectedOptions(value); + setPrinstineValue( + multiple ? selectedOptions.concat() : selectedOptions[0] + ); + }; + } + + if (loadOptions && config.autoLoadOptionsFromSource !== false) { + this.toDispose.push( + formInited || !addHook + ? formItem.addInitHook(async () => { + await this.reload(); + setInitValue?.(); + }) + : addHook(async (data: any) => { + await this.initOptions(data); + setInitValue?.(); + }, 'init') + ); + } else { + setInitValue?.(); + } + } + + componentDidMount() { + this.mounted = true; + this.normalizeValue(); + } + + shouldComponentUpdate(nextProps: OptionsProps) { + if (this.config.strictMode === false || nextProps.strictMode === false) { + return true; + } else if (nextProps.source || nextProps.autoComplete) { + return true; + } else if (nextProps.formItem?.expressionsInOptions) { + return true; + } else if (anyChanged(detectProps, this.props, nextProps)) { + return true; + } + + return false; + } + + componentDidUpdate(prevProps: OptionsProps) { + const props = this.props; + const formItem = props.formItem as IFormItemStore; + + if (!props.source && prevProps.options !== props.options && formItem) { + formItem.setOptions( + normalizeOptions(props.options || [], undefined, props.valueField), + this.changeOptionValue, + props.data + ); + this.normalizeValue(); + } else if ( + this.config.autoLoadOptionsFromSource !== false && + (props.formInited || typeof props.formInited === 'undefined') && + props.source && + formItem && + (prevProps.source !== props.source || prevProps.data !== props.data) + ) { + if (isPureVariable(props.source as string)) { + const prevOptions = resolveVariableAndFilter( + prevProps.source as string, + prevProps.data, + '| raw' + ); + const options = resolveVariableAndFilter( + props.source as string, + props.data, + '| raw' + ); + + if (prevOptions !== options) { + formItem.loadOptionsFromDataScope( + props.source as string, + props.data, + this.changeOptionValue + ); + + this.normalizeValue(); + } + } else if ( + isEffectiveApi(props.source, props.data) && + isApiOutdated( + prevProps.source, + props.source, + prevProps.data, + props.data + ) + ) { + formItem + .loadOptions( + props.source, + props.data, + undefined, + true, + this.changeOptionValue + ) + .then(() => this.normalizeValue()); + } + } + + if (prevProps.value !== props.value || formItem?.expressionsInOptions) { + formItem?.syncOptions(undefined, props.data); + } + } + + componentWillUnmount() { + this.props.removeHook?.(this.reload, 'init'); + this.mounted = false; + this.toDispose.forEach(fn => fn()); + this.toDispose = []; + } + + // 不推荐使用,缺少组件值 + async oldDispatchOptionEvent(eventName: string, eventData: any = '') { + const {dispatchEvent, options} = this.props; + const rendererEvent = await dispatchEvent( + eventName, + resolveEventData( + this.props, + {value: eventData, options, items: options} // 为了保持名字统一 + ) + ); + // 返回阻塞标识 + return !!rendererEvent?.prevented; + } + + async dispatchOptionEvent(eventName: string, eventData: any = '') { + const {dispatchEvent, options, value} = this.props; + const rendererEvent = await dispatchEvent( + eventName, + resolveEventData( + this.props, + {value, options, items: options, ...eventData} // 为了保持名字统一 + ) + ); + // 返回阻塞标识 + return !!rendererEvent?.prevented; + } + + doAction(action: ActionObject, data: object, throwErrors: boolean) { + const {resetValue, onChange} = this.props; + const actionType = action?.actionType as string; + + if (actionType === 'clear') { + onChange?.(''); + } else if (actionType === 'reset') { + onChange?.(resetValue ?? ''); + } + } + + // 当前值,跟设置预期的值格式不一致时自动转换。 + normalizeValue() { + const { + joinValues, + extractValue, + value, + multiple, + formItem, + valueField, + enableNodePath, + pathSeparator, + onChange + } = this.props; + + if (!formItem || joinValues !== false || !formItem.options.length) { + return; + } + + if ( + extractValue === false && + (typeof value === 'string' || typeof value === 'number') + ) { + const selectedOptions = formItem.getSelectedOptions(value); + onChange?.(multiple ? selectedOptions.concat() : selectedOptions[0]); + } else if ( + extractValue === true && + value && + !( + (Array.isArray(value) && + value.every( + (val: any) => typeof val === 'string' || typeof val === 'number' + )) || + typeof value === 'string' || + typeof value === 'number' + ) + ) { + const selectedOptions = formItem + .getSelectedOptions(value) + .map((selectedOption: Option) => selectedOption[valueField || 'value']); + onChange?.(multiple ? selectedOptions.concat() : selectedOptions[0]); + } + } + + getWrappedInstance() { + return this.input; + } + + @autobind + inputRef(ref: any) { + this.input = ref; + } + + @autobind + async handleToggle( + option: Option, + submitOnChange?: boolean, + changeImmediately?: boolean + ) { + const {onChange, formItem, value} = this.props; + + if (!formItem) { + return; + } + + let newValue: string | Array