mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
Merge branch 'master' into bugfix-editor-paste
This commit is contained in:
commit
7eab13dcb3
121
docs/zh-CN/components/form/input-signature.md
Normal file
121
docs/zh-CN/components/form/input-signature.md
Normal file
@ -0,0 +1,121 @@
|
||||
---
|
||||
title: inputSignature 签名面板
|
||||
description:
|
||||
type: 0
|
||||
group: null
|
||||
menuName: inputSignature
|
||||
icon:
|
||||
order: 62
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "input-signature",
|
||||
"label": "手写签名",
|
||||
"height": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义按钮名称
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "input-signature",
|
||||
"height": 160,
|
||||
"confirmBtnLabel": "确定",
|
||||
"undoBtnLabel": "上一步",
|
||||
"clearBtnLabel": "重置"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义颜色
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "input-signature",
|
||||
"label": "手写签名",
|
||||
"height": 200,
|
||||
"color": "#ff0000",
|
||||
"bgColor": "#fff"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 配合图片组件实现实时预览
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "input-signature",
|
||||
"label": "手写签名",
|
||||
"height": 200
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"name": "signature"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 内嵌模式
|
||||
|
||||
在内嵌模式下,组件会以按钮的形式展示,点击按钮后弹出一个容器,用户可以在容器中完成签名。更适合在移动端使用。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "input-signature",
|
||||
"label": "手写签名",
|
||||
"embed": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------------- | --------- | ---------- | -------------------- |
|
||||
| width | `number` | | 组件宽度,最小 300 |
|
||||
| height | `number` | | 组件高度,最小 160 |
|
||||
| color | `string` | `#000` | 手写字体颜色 |
|
||||
| bgColor | `string` | `#EFEFEF` | 面板背景颜色 |
|
||||
| clearBtnLabel | `string` | `清空` | 清空按钮名称 |
|
||||
| undoBtnLabel | `string` | `撤销` | 撤销按钮名称 |
|
||||
| confirmBtnLabel | `string` | `确认` | 确认按钮名称 |
|
||||
| embed | `boolean` | | 是否内嵌 |
|
||||
| embedConfirmLabel | `string` | `确认` | 内嵌容器确认按钮名称 |
|
||||
| ebmedCancelLabel | `string` | `取消` | 内嵌容器取消按钮名称 |
|
||||
| embedBtnIcon | `string` | | 内嵌按钮图标 |
|
||||
| embedBtnLabel | `string` | `点击签名` | 内嵌按钮文案 |
|
@ -1144,17 +1144,17 @@ true false false [{label: 'A/B/C', value: 'a/b/c'},{label: 'A
|
||||
|
||||
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| change | `items: object[]`选项集合(< 3.6.0 及以下版本 不支持该参数)<br/>`[name]: string` 组件的值 | 选中值变化时触发 |
|
||||
| addConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 新增的节点信息<br/>`items: object[]`选项集合 | 新增节点提交时触发 |
|
||||
| editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`item: object` 编辑的节点信息<br/>`items: object[]`选项集合 | 编辑节点提交时触发 |
|
||||
| deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 删除的节点信息<br/>`items: object[]`选项集合 | 删除节点提交时触发 |
|
||||
| deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`result: object` deferApi 懒加载远程请求成功后返回的数据 <br/>`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 |
|
||||
| add(不推荐) | `[name]: object` 新增的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 |
|
||||
| edit(不推荐) | `[name]: object` 编辑的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 |
|
||||
| delete(不推荐) | `[name]: object` 删除的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 |
|
||||
| loadFinished(不推荐) | `[name]: object` deferApi 懒加载远程请求成功后返回的数据 | 懒加载接口远程请求成功时触发 |
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| change | `items: object[]`选项集合(3.6.0 及以上版本)<br/>`item: object`选中的节点(6.2.0 及以上版本)<br/>`[name]: string` 组件的值 | 选中值变化时触发 |
|
||||
| addConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 新增的节点信息<br/>`items: object[]`选项集合 | 新增节点提交时触发 |
|
||||
| editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`item: object` 编辑的节点信息<br/>`items: object[]`选项集合 | 编辑节点提交时触发 |
|
||||
| deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 删除的节点信息<br/>`items: object[]`选项集合 | 删除节点提交时触发 |
|
||||
| deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`result: object` deferApi 懒加载远程请求成功后返回的数据 <br/>`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 |
|
||||
| add(不推荐) | `[name]: object` 新增的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 |
|
||||
| edit(不推荐) | `[name]: object` 编辑的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 |
|
||||
| delete(不推荐) | `[name]: object` 删除的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 |
|
||||
| loadFinished(不推荐) | `[name]: object` deferApi 懒加载远程请求成功后返回的数据 | 懒加载接口远程请求成功时触发 |
|
||||
|
||||
### change
|
||||
|
||||
|
@ -123,6 +123,45 @@ order: 61
|
||||
}
|
||||
```
|
||||
|
||||
## Mini 版本
|
||||
|
||||
通过设置 `mini` 属性可以开启 mini 版本,适用于宽度窄的情况。同时通过 `advancedSettings` 可以定制弹窗中的配置面板。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "json-schema-editor",
|
||||
"name": "schema",
|
||||
"label": "字段类型",
|
||||
"mini": true,
|
||||
"style": {
|
||||
"width": 300
|
||||
},
|
||||
"advancedSettings": {
|
||||
"string": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "maxLength",
|
||||
"label": "Max Length"
|
||||
}
|
||||
],
|
||||
"number": [
|
||||
{
|
||||
"type": "input-number",
|
||||
"name": "max",
|
||||
"label": "Max"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 占位提示
|
||||
|
||||
> 2.8.0 及以上版本
|
||||
@ -160,6 +199,7 @@ order: 61
|
||||
| showRootInfo | `boolean` | false | 是否显示顶级类型信息 |
|
||||
| disabledTypes | `Array<string>` | | 用来禁用默认数据类型,默认类型有:string、number、interger、object、number、array、boolean、null |
|
||||
| definitions | `object` | | 用来配置预设类型 |
|
||||
| mini | `boolean` | | 用来开启迷你模式,适应于边栏面板,宽度较低的情况 |
|
||||
| placeholder | `SchemaEditorItemPlaceholder` | `{key: "字段名", title: "名称", description: "描述", default: "默认值", empty: "<空>",}` | 属性输入控件的占位提示文本 | `2.8.0` |
|
||||
|
||||
### SchemaEditorItemPlaceholder
|
||||
|
@ -409,19 +409,19 @@ order: 60
|
||||
|
||||
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| change | `[name]: string` 组件的值 <br/>`items: object[]`选项集合(< 3.6.0 及以下版本 不支持该参数) | 选中值变化时触发 |
|
||||
| blur | `[name]: string` 组件的值 <br/>`items: object[]`选项集合(< 3.6.4 及以下版本 不支持该参数) | 输入框失去焦点时触发 |
|
||||
| focus | `[name]: string` 组件的值 <br/>`items: object[]`选项集合(< 3.6.4 及以下版本 不支持该参数) | 输入框获取焦点时触发 |
|
||||
| addConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 新增的节点信息<br/>`items: object[]`选项集合 | 新增节点提交时触发 |
|
||||
| editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`item: object` 编辑的节点信息<br/>`items: object[]`选项集合 | 编辑节点提交时触发 |
|
||||
| deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 删除的节点信息<br/>`items: object[]`选项集合 | 删除节点提交时触发 |
|
||||
| deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`result: object` deferApi 懒加载远程请求成功后返回的数据 <br/>`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 |
|
||||
| add(不推荐) | `[name]: object` 新增的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 |
|
||||
| edit(不推荐) | `[name]: object` 编辑的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 |
|
||||
| delete(不推荐) | `[name]: object` 删除的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 |
|
||||
| loadFinished(不推荐) | `[name]: object` deferApi 懒加载远程请求成功后返回的数据 | 懒加载接口远程请求成功时触发 |
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| change | `[name]: string` 组件的值 <br/>`item: object`选中的节点(6.2.0 及以上版本)<br/>`items: object[]`选项集合(3.6.0 及以上版本) | 选中值变化时触发 |
|
||||
| blur | `[name]: string` 组件的值 <br/>``item: object`选中的节点(6.2.0 及以上版本)<br/>items: object[]`选项集合(3.6.4 及以上版本) | 输入框失去焦点时触发 |
|
||||
| focus | `[name]: string` 组件的值 <br/>`item: object`选中的节点(6.2.0 及以上版本)<br/>`items: object[]`选项集合(3.6.4 及以上版本) | 输入框获取焦点时触发 |
|
||||
| addConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 新增的节点信息<br/>`items: object[]`选项集合 | 新增节点提交时触发 |
|
||||
| editConfirm (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`item: object` 编辑的节点信息<br/>`items: object[]`选项集合 | 编辑节点提交时触发 |
|
||||
| deleteConfirm (3.6.4 及以上版本) | `[name]: string` 组件的值<br/>`item: object` 删除的节点信息<br/>`items: object[]`选项集合 | 删除节点提交时触发 |
|
||||
| deferLoadFinished (3.6.4 及以上版本) | `[name]: object` 组件的值<br/>`result: object` deferApi 懒加载远程请求成功后返回的数据 <br/>`items: object[]`选项集合 | 懒加载接口远程请求成功时触发 |
|
||||
| add(不推荐) | `[name]: object` 新增的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 新增节点提交时触发 |
|
||||
| edit(不推荐) | `[name]: object` 编辑的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 编辑节点提交时触发 |
|
||||
| delete(不推荐) | `[name]: object` 删除的节点信息<br/>`items: object[]`选项集合(< 2.3.2 及以下版本 为`options`) | 删除节点提交时触发 |
|
||||
| loadFinished(不推荐) | `[name]: object` deferApi 懒加载远程请求成功后返回的数据 | 懒加载接口远程请求成功时触发 |
|
||||
|
||||
### change
|
||||
|
||||
|
56
docs/zh-CN/components/pdf-viewer.md
Normal file
56
docs/zh-CN/components/pdf-viewer.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: PDF Viewer
|
||||
description:
|
||||
type: 0
|
||||
group: ⚙ 组件
|
||||
menuName: PDFViewer 渲染
|
||||
icon:
|
||||
order: 24
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "pdf-viewer",
|
||||
"id": "pdf-viewer",
|
||||
"src": "/examples/static/simple.pdf",
|
||||
"width": 500
|
||||
}
|
||||
```
|
||||
|
||||
## 配合文件上传实现预览功能
|
||||
|
||||
配置和 `input-file` 相同的 `name` 即可
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "",
|
||||
"wrapWithPanel": false,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-file",
|
||||
"name": "file",
|
||||
"label": "File",
|
||||
"asBlob": true,
|
||||
"accept": ".pdf"
|
||||
},
|
||||
{
|
||||
"type": "pdf-viewer",
|
||||
"id": "pdf-viewer",
|
||||
"name": "file",
|
||||
"width": 500
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ---------- | ------ | ------ | ---------- |
|
||||
| src | Api | | 文档地址 |
|
||||
| width | number | | 宽度 |
|
||||
| height | number | - | 高度 |
|
||||
| background | string | #fff | PDF 背景色 |
|
@ -1444,6 +1444,75 @@ run action ajax
|
||||
| copyFormat | `string` | `text/html` | 复制格式 |
|
||||
| content | [模板](../../docs/concepts/template) | - | 指定复制的内容。可用 `${xxx}` 取值 |
|
||||
|
||||
### 打印
|
||||
|
||||
> 6.2.0 及以后版本
|
||||
|
||||
打印页面中的某个组件,对应的组件需要配置 `testid`,如果要打印多个,可以使用 `"testids": ["x", "y"]` 来打印多个组件
|
||||
|
||||
```schema
|
||||
{
|
||||
type: 'page',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '打印',
|
||||
level: 'primary',
|
||||
className: 'mr-2',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'print',
|
||||
args: {
|
||||
testid: 'mycrud'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "crud",
|
||||
"api": "/api/mock2/sample",
|
||||
"testid": "mycrud",
|
||||
"syncLocation": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform(s)"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"label": "Engine version"
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "CSS grade"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------- | ---------- | ------ | ----------------- |
|
||||
| testid | `string` | | 组件的 testid |
|
||||
| testids | `string[]` | - | 多个组件的 testid |
|
||||
|
||||
### 发送邮件
|
||||
|
||||
通过配置`actionType: 'email'`和邮件属性实现发送邮件操作。
|
||||
|
@ -303,7 +303,16 @@ export default {
|
||||
body: {
|
||||
type: 'form',
|
||||
name: 'sample-edit-form',
|
||||
api: '/api/sample/$id',
|
||||
data:{
|
||||
env: 'test'
|
||||
},
|
||||
api: {
|
||||
method:'post',
|
||||
url:'/api/sample/$id',
|
||||
messages:{
|
||||
success: '成功了-${env}'
|
||||
}
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
|
@ -785,6 +785,16 @@ export const components = [
|
||||
wrapDoc
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
label: 'InputSignature 签名面板',
|
||||
path: '/zh-CN/components/form/input-signature',
|
||||
component: React.lazy(() =>
|
||||
import('../../docs/zh-CN/components/form/input-signature.md').then(
|
||||
wrapDoc
|
||||
)
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -987,6 +997,13 @@ export const components = [
|
||||
import('../../docs/zh-CN/components/office-viewer.md').then(wrapDoc)
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'PDFViewer 渲染',
|
||||
path: '/zh-CN/components/pdf-viewer',
|
||||
component: React.lazy(() =>
|
||||
import('../../docs/zh-CN/components/pdf-viewer.md').then(wrapDoc)
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Progress 进度条',
|
||||
path: '/zh-CN/components/progress',
|
||||
|
@ -130,6 +130,7 @@ import Tab3Schema from './Tabs/Tab3';
|
||||
import Loading from './Loading';
|
||||
import CodeSchema from './Code';
|
||||
import OfficeViewer from './OfficeViewer';
|
||||
import PdfViewer from './PdfViewer';
|
||||
import InputTableEvent from './EventAction/cmpt-event-action/InputTableEvent';
|
||||
import WizardPage from './WizardPage';
|
||||
|
||||
@ -912,6 +913,13 @@ export const examples = [
|
||||
component: makeSchemaRenderer(OfficeViewer)
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Pdf 预览',
|
||||
icon: 'fa fa-file-pdf',
|
||||
path: '/examples/pdf-viewer',
|
||||
component: makeSchemaRenderer(PdfViewer)
|
||||
},
|
||||
|
||||
{
|
||||
label: '多 loading',
|
||||
icon: 'fa fa-spinner',
|
||||
|
24
examples/components/PdfViewer.jsx
Normal file
24
examples/components/PdfViewer.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'form',
|
||||
id: 'form',
|
||||
debug: true,
|
||||
wrapWithPanel: false,
|
||||
body: [
|
||||
{
|
||||
type: 'input-file',
|
||||
name: 'file',
|
||||
label: '选择 PDF 文件预览效果(不会上传到服务器)',
|
||||
asBlob: true,
|
||||
accept: '.pdf'
|
||||
},
|
||||
{
|
||||
type: 'pdf-viewer',
|
||||
id: 'pdf-viewer',
|
||||
name: 'file',
|
||||
width: 500
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
@ -246,6 +246,8 @@ export default function (schema, schemaProps, showCode, envOverrides) {
|
||||
};
|
||||
});
|
||||
},
|
||||
// 是否开启测试 testid
|
||||
// enableTestid: true,
|
||||
...envOverrides
|
||||
};
|
||||
|
||||
|
BIN
examples/static/simple.pdf
Normal file
BIN
examples/static/simple.pdf
Normal file
Binary file not shown.
@ -241,7 +241,7 @@ fis.match('/examples/mod.js', {
|
||||
isMod: false
|
||||
});
|
||||
|
||||
fis.match('{markdown-it,moment-timezone}/**', {
|
||||
fis.match('{markdown-it,moment-timezone,pdfjs-dist}/**', {
|
||||
preprocessor: fis.plugin('js-require-file')
|
||||
});
|
||||
|
||||
@ -503,6 +503,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!amis-ui/lib/components/RichText.js',
|
||||
'!amis-ui/lib/components/Tinymce.js',
|
||||
'!amis-ui/lib/components/ColorPicker.js',
|
||||
'!amis-ui/lib/components/PdfViewer.js',
|
||||
'!react-color/**',
|
||||
'!material-colors/**',
|
||||
'!reactcss/**',
|
||||
@ -562,6 +563,11 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'tinycolor2/**'
|
||||
],
|
||||
|
||||
'pdf-viewer.js': [
|
||||
'amis-ui/lib/components/PdfViewer.js',
|
||||
'pdfjs-dist/build/pdf.worker.min.js'
|
||||
],
|
||||
|
||||
'cropperjs.js': ['cropperjs/**', 'react-cropper/**'],
|
||||
|
||||
'barcode.js': ['src/components/BarCode.tsx', 'jsbarcode/**'],
|
||||
@ -584,6 +590,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!mpegts.js/**',
|
||||
'!hls.js/**',
|
||||
'!froala-editor/**',
|
||||
'!pdfjs-dist/**',
|
||||
|
||||
'!amis-ui/lib/components/RichText.js',
|
||||
'!zrender/**',
|
||||
|
@ -8,5 +8,5 @@
|
||||
"packages/amis-editor"
|
||||
],
|
||||
"useWorkspaces": false,
|
||||
"version": "6.1.0"
|
||||
"version": "6.2.1"
|
||||
}
|
||||
|
@ -43,7 +43,8 @@
|
||||
"dependencies": {
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"postcss": "^8.4.14",
|
||||
"qs": "6.9.7"
|
||||
"qs": "6.9.7",
|
||||
"smooth-signature": "^1.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/generator": "^7.22.9",
|
||||
@ -97,6 +98,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-overlays": "5.1.1",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-pluginutils": "^2.8.2",
|
||||
"setprototypeof": "^1.2.0",
|
||||
"ts-jest": "^29.0.2",
|
||||
@ -149,4 +151,4 @@
|
||||
"printBasicPrototype": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-core",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2.1",
|
||||
"description": "amis-core",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -19,6 +19,7 @@
|
||||
"@types/jest": "^28.1.0",
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@types/react-is": "^18.2.4",
|
||||
"immutable": "^4.1.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
@ -47,7 +48,7 @@
|
||||
"esm"
|
||||
],
|
||||
"dependencies": {
|
||||
"amis-formula": "^6.1.0",
|
||||
"amis-formula": "*",
|
||||
"classnames": "2.3.2",
|
||||
"file-saver": "^2.0.2",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
@ -68,7 +69,8 @@
|
||||
"peerDependencies": {
|
||||
"amis-formula": "*",
|
||||
"react": ">=16.8.6",
|
||||
"react-dom": ">=16.8.6"
|
||||
"react-dom": ">=16.8.6",
|
||||
"react-is": ">=16.8.6"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
@ -104,4 +106,4 @@
|
||||
]
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
|
||||
window.open(mailto);
|
||||
} else if (action.actionType === 'dialog') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.openDialog(
|
||||
ctx,
|
||||
undefined,
|
||||
@ -183,7 +183,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
delegate || (this.context as any)
|
||||
);
|
||||
} else if (action.actionType === 'drawer') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.openDrawer(ctx, undefined, undefined, delegate);
|
||||
} else if (action.actionType === 'toast') {
|
||||
action.toast?.items?.forEach((item: any) => {
|
||||
@ -211,7 +211,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
);
|
||||
});
|
||||
} else if (action.actionType === 'ajax') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store
|
||||
.saveRemote(action.api as string, ctx, {
|
||||
successMessage:
|
||||
@ -341,11 +341,14 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
openFeedback(dialog: any, ctx: any) {
|
||||
return new Promise(resolve => {
|
||||
const store = this.store;
|
||||
store.setCurrentAction({
|
||||
type: 'button',
|
||||
actionType: 'dialog',
|
||||
dialog: dialog
|
||||
});
|
||||
store.setCurrentAction(
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'dialog',
|
||||
dialog: dialog
|
||||
},
|
||||
this.props.resolveDefinitions
|
||||
);
|
||||
store.openDialog(
|
||||
ctx,
|
||||
undefined,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import difference from 'lodash/difference';
|
||||
import omit from 'lodash/omit';
|
||||
import React from 'react';
|
||||
import {isValidElementType} from 'react-is';
|
||||
import LazyComponent from './components/LazyComponent';
|
||||
import {
|
||||
filterSchema,
|
||||
@ -15,7 +16,7 @@ import {IScopedContext, ScopedContext} from './Scoped';
|
||||
import {Schema, SchemaNode} from './types';
|
||||
import {DebugWrapper} from './utils/debug';
|
||||
import getExprProperties from './utils/filter-schema';
|
||||
import {anyChanged, chainEvents, autobind} from './utils/helper';
|
||||
import {anyChanged, chainEvents, autobind, TestIdBuilder} from './utils/helper';
|
||||
import {SimpleMap} from './utils/SimpleMap';
|
||||
import {bindEvent, dispatchEvent, RendererEvent} from './utils/renderer-event';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
@ -349,7 +350,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
||||
statusStore,
|
||||
dispatchEvent: this.dispatchEvent
|
||||
});
|
||||
} else if (typeof schema.component === 'function') {
|
||||
} else if (schema.component && isValidElementType(schema.component)) {
|
||||
const isSFC = !(schema.component.prototype instanceof React.Component);
|
||||
const {
|
||||
data: defaultData,
|
||||
@ -475,8 +476,14 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
||||
(props as any).static = isStatic;
|
||||
}
|
||||
|
||||
if (rest.env.enableTestid && props.id && !props.testid) {
|
||||
props.testid = props.id;
|
||||
// 优先使用组件自己的testid或者id,这个解决不了table行内的一些子元素
|
||||
// 每一行都会出现这个testid的元素,只在测试工具中直接使用nth拿序号
|
||||
if (rest.env.enableTestid) {
|
||||
if (props.testid || props.id || props.testIdBuilder == null) {
|
||||
if (!(props.testIdBuilder instanceof TestIdBuilder)) {
|
||||
props.testIdBuilder = new TestIdBuilder(props.testid || props.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自动解析变量模式,主要是方便直接引入第三方组件库,无需为了支持变量封装一层
|
||||
|
@ -344,6 +344,7 @@ export const runAction = async (
|
||||
{
|
||||
...action,
|
||||
args,
|
||||
rawData: actionConfig.data,
|
||||
data: action.actionType === 'reload' ? actionData : data, // 如果是刷新动作,则只传action.data
|
||||
...key
|
||||
},
|
||||
|
@ -76,7 +76,8 @@ export class DialogAction implements RendererAction {
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: action.dialog,
|
||||
reload: 'none'
|
||||
reload: 'none',
|
||||
data: action.rawData
|
||||
},
|
||||
action.data
|
||||
);
|
||||
@ -142,11 +143,20 @@ export class ConfirmAction implements RendererAction {
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
const type = action.dialog?.type ?? (action.args as any)?.type;
|
||||
let modal: any = action.dialog ?? action.args;
|
||||
|
||||
if (modal.$ref && renderer.props.resolveDefinitions) {
|
||||
modal = {
|
||||
...renderer.props.resolveDefinitions(modal.$ref),
|
||||
...modal
|
||||
};
|
||||
}
|
||||
|
||||
const type = modal?.type;
|
||||
|
||||
if (!type) {
|
||||
const confirmed = await event.context.env.confirm?.(
|
||||
filter(action.dialog?.msg, event.data) || action.args?.msg,
|
||||
filter(modal?.msg, event.data) || action.args?.msg,
|
||||
filter(action.dialog?.title, event.data) || action.args?.title,
|
||||
{
|
||||
closeOnEsc:
|
||||
@ -177,7 +187,8 @@ export class ConfirmAction implements RendererAction {
|
||||
event,
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: action.dialog ?? action.args,
|
||||
dialog: modal,
|
||||
data: action.rawData,
|
||||
reload: 'none',
|
||||
callback: (result: boolean) => resolve(result)
|
||||
},
|
||||
|
@ -38,7 +38,8 @@ export class DrawerAction implements RendererAction {
|
||||
{
|
||||
actionType: 'drawer',
|
||||
drawer: action.drawer,
|
||||
reload: 'none'
|
||||
reload: 'none',
|
||||
data: action.rawData
|
||||
},
|
||||
action.data
|
||||
);
|
||||
|
51
packages/amis-core/src/actions/PrintAction.ts
Normal file
51
packages/amis-core/src/actions/PrintAction.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {printElements} from '../utils/printElement';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {
|
||||
RendererAction,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
export interface IPrintAction extends ListenerAction {
|
||||
actionType: 'copy';
|
||||
args: {
|
||||
testid?: string;
|
||||
testids?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印动作
|
||||
*
|
||||
* @export
|
||||
* @class PrintAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class PrintAction implements RendererAction {
|
||||
async run(
|
||||
action: IPrintAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
if (action.args?.testid) {
|
||||
const element = document.querySelector(
|
||||
`[data-testid='${action.args.testid}']`
|
||||
);
|
||||
if (element) {
|
||||
printElements([element]);
|
||||
}
|
||||
} else if (action.args?.testids) {
|
||||
const elements: Element[] = [];
|
||||
action.args.testids.forEach(testid => {
|
||||
const element = document.querySelector(`[data-testid='${testid}']`);
|
||||
if (element) {
|
||||
elements.push(element);
|
||||
}
|
||||
});
|
||||
printElements(elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('print', new PrintAction());
|
@ -19,5 +19,6 @@ import './EmailAction';
|
||||
import './LinkAction';
|
||||
import './ToastAction';
|
||||
import './PageAction';
|
||||
import './PrintAction';
|
||||
|
||||
export * from './Action';
|
||||
|
@ -9,7 +9,8 @@ import {
|
||||
qsparse,
|
||||
string2regExp,
|
||||
parseQuery,
|
||||
isMobile
|
||||
isMobile,
|
||||
TestIdBuilder
|
||||
} from './utils/helper';
|
||||
import {
|
||||
fetcherResult,
|
||||
@ -72,6 +73,7 @@ export interface RendererProps
|
||||
env: RendererEnv;
|
||||
$path: string; // 当前组件所在的层级信息
|
||||
$schema: any; // 原始 schema 配置
|
||||
testIdBuilder?: TestIdBuilder;
|
||||
store?: IIRendererStore;
|
||||
syncSuperStore?: boolean;
|
||||
data: {
|
||||
|
@ -206,7 +206,8 @@ export {
|
||||
splitTarget,
|
||||
CustomStyle,
|
||||
enableDebug,
|
||||
disableDebug
|
||||
disableDebug,
|
||||
envOverwrite
|
||||
};
|
||||
|
||||
export function render(
|
||||
|
@ -51,6 +51,7 @@ import {isAlive} from 'mobx-state-tree';
|
||||
import type {LabelAlign} from './Item';
|
||||
import {injectObjectChain} from '../utils';
|
||||
import {reaction} from 'mobx';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
|
||||
export interface FormHorizontal {
|
||||
left?: number;
|
||||
@ -461,7 +462,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
];
|
||||
|
||||
hooks: {
|
||||
[propName: string]: Array<() => Promise<any>>;
|
||||
[propName: string]: Array<(...args: any) => Promise<any>>;
|
||||
} = {};
|
||||
asyncCancel: () => void;
|
||||
toDispose: Array<() => void> = [];
|
||||
@ -762,8 +763,25 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
const initedAt = store.initedAt;
|
||||
|
||||
store.setInited(true);
|
||||
const hooks: Array<(data: any) => Promise<any>> = this.hooks['init'] || [];
|
||||
await Promise.all(hooks.map(hook => hook(data)));
|
||||
const hooks = this.hooks['init'] || [];
|
||||
const groupedHooks = groupBy(hooks, item =>
|
||||
(item as any).__enforce === 'prev'
|
||||
? 'prev'
|
||||
: (item as any).__enforce === 'post'
|
||||
? 'post'
|
||||
: 'normal'
|
||||
);
|
||||
|
||||
await Promise.all((groupedHooks.prev || []).map(hook => hook(data)));
|
||||
// 有可能在前面的步骤中删除了钩子,所以需要重新验证一下
|
||||
await Promise.all(
|
||||
(groupedHooks.normal || []).map(
|
||||
hook => hooks.includes(hook) && hook(data)
|
||||
)
|
||||
);
|
||||
await Promise.all(
|
||||
(groupedHooks.post || []).map(hook => hooks.includes(hook) && hook(data))
|
||||
);
|
||||
|
||||
if (!isAlive(store)) {
|
||||
return;
|
||||
@ -976,9 +994,15 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
store.reset(onReset);
|
||||
}
|
||||
|
||||
addHook(fn: () => any, type: 'validate' | 'init' | 'flush' = 'validate') {
|
||||
addHook(
|
||||
fn: () => any,
|
||||
type: 'validate' | 'init' | 'flush' = 'validate',
|
||||
enforce?: 'prev' | 'post'
|
||||
) {
|
||||
this.hooks[type] = this.hooks[type] || [];
|
||||
this.hooks[type].push(type === 'flush' ? fn : promisify(fn));
|
||||
const hook = type === 'flush' ? fn : promisify(fn);
|
||||
(hook as any).__enforce = enforce;
|
||||
this.hooks[type].push(hook);
|
||||
return () => {
|
||||
this.removeHook(fn, type);
|
||||
fn = noop;
|
||||
@ -1205,7 +1229,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
return;
|
||||
}
|
||||
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
|
||||
if (action.actionType === 'reset-and-submit') {
|
||||
store.reset(this.handleReset(action));
|
||||
@ -1372,16 +1396,16 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
}
|
||||
});
|
||||
} else if (action.type === 'reset' || action.actionType === 'reset') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.reset(onReset);
|
||||
} else if (action.actionType === 'clear') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.clear(onReset);
|
||||
} else if (action.actionType === 'validate') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
return this.validate(true, throwErrors, true, true);
|
||||
} else if (action.actionType === 'dialog') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.openDialog(
|
||||
data,
|
||||
undefined,
|
||||
@ -1389,10 +1413,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
delegate || (this.context as any)
|
||||
);
|
||||
} else if (action.actionType === 'drawer') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
store.openDrawer(data);
|
||||
} else if (action.actionType === 'ajax') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
if (!isEffectiveApi(action.api)) {
|
||||
return env.alert(__(`当 actionType 为 ajax 时,请设置 api 属性`));
|
||||
}
|
||||
@ -1447,7 +1471,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
}
|
||||
});
|
||||
} else if (action.actionType === 'reload') {
|
||||
store.setCurrentAction(action);
|
||||
store.setCurrentAction(action, this.props.resolveDefinitions);
|
||||
if (action.target) {
|
||||
this.reloadTarget(filterTarget(action.target, data), data);
|
||||
} else {
|
||||
@ -1558,11 +1582,14 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
openFeedback(dialog: any, ctx: any) {
|
||||
return new Promise(resolve => {
|
||||
const {store} = this.props;
|
||||
store.setCurrentAction({
|
||||
type: 'button',
|
||||
actionType: 'dialog',
|
||||
dialog: dialog
|
||||
});
|
||||
store.setCurrentAction(
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'dialog',
|
||||
dialog: dialog
|
||||
},
|
||||
this.props.resolveDefinitions
|
||||
);
|
||||
store.openDialog(
|
||||
ctx,
|
||||
undefined,
|
||||
@ -1808,7 +1835,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
render,
|
||||
staticClassName,
|
||||
static: isStatic = false,
|
||||
loadingConfig
|
||||
loadingConfig,
|
||||
testid
|
||||
} = this.props;
|
||||
|
||||
const {restError} = store;
|
||||
|
@ -33,11 +33,19 @@ import {wrapControl} from './wrapControl';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {isApiOutdated, isEffectiveApi} from '../utils/api';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {dataMapping, setThemeClassName} from '../utils';
|
||||
import {
|
||||
dataMapping,
|
||||
getTreeAncestors,
|
||||
isEmpty,
|
||||
keyToPath,
|
||||
setThemeClassName,
|
||||
setVariable
|
||||
} from '../utils';
|
||||
import Overlay from '../components/Overlay';
|
||||
import PopOver from '../components/PopOver';
|
||||
import CustomStyle from '../components/CustomStyle';
|
||||
import classNames from 'classnames';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
export type LabelAlign = 'right' | 'left';
|
||||
|
||||
@ -383,6 +391,75 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
|
||||
* 远端校验表单项接口
|
||||
*/
|
||||
validateApi?: string | BaseApiObject;
|
||||
|
||||
/**
|
||||
* 自动填充,当选项被选择的时候,将选项中的其他值同步设置到表单内。
|
||||
*
|
||||
*/
|
||||
autoFill?:
|
||||
| {
|
||||
[propName: string]: string;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* 是否为参照录入模式,参照录入会展示候选值供用户选择,而不是直接填充。
|
||||
*/
|
||||
showSuggestion?: boolean;
|
||||
|
||||
/**
|
||||
* 自动填充 api
|
||||
*/
|
||||
api?: BaseApiObject | string;
|
||||
|
||||
/**
|
||||
* 是否展示数据格式错误提示,默认为不展示
|
||||
* @default true
|
||||
*/
|
||||
silent?: boolean;
|
||||
|
||||
/**
|
||||
* 填充时的数据映射
|
||||
*/
|
||||
fillMappinng?: {
|
||||
[propName: string]: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发条件,默认为 change
|
||||
*/
|
||||
trigger?: 'change' | 'foucs';
|
||||
|
||||
/**
|
||||
* 弹窗方式,当为参照录入时用可以配置
|
||||
*/
|
||||
mode?: 'popOver' | 'dialog' | 'drawer';
|
||||
|
||||
/**
|
||||
* 当参照录入为抽屉时可以配置弹出位置
|
||||
*/
|
||||
position?: string;
|
||||
|
||||
/**
|
||||
* 当为参照录入时可以配置弹出容器的大小
|
||||
*/
|
||||
size?: string;
|
||||
|
||||
/**
|
||||
* 参照录入展示的项
|
||||
*/
|
||||
columns?: Array<any>;
|
||||
|
||||
/**
|
||||
* 参照录入时的过滤条件
|
||||
*/
|
||||
filter?: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* @default fillIfNotSet
|
||||
* 初始化时是否把其他字段同步到表单内部。
|
||||
*/
|
||||
initAutoFill?: boolean | 'fillIfNotSet';
|
||||
}
|
||||
|
||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||
@ -439,7 +516,11 @@ export interface FormItemProps extends RendererProps {
|
||||
values: {[propName: string]: any},
|
||||
submitOnChange?: boolean
|
||||
) => void;
|
||||
addHook: (fn: Function, mode?: 'validate' | 'init' | 'flush') => () => void;
|
||||
addHook: (
|
||||
fn: Function,
|
||||
mode?: 'validate' | 'init' | 'flush',
|
||||
enforce?: 'prev' | 'post'
|
||||
) => () => void;
|
||||
removeHook: (fn: Function, mode?: 'validate' | 'init' | 'flush') => void;
|
||||
renderFormItems: (
|
||||
schema: Partial<FormSchemaBase>,
|
||||
@ -471,7 +552,6 @@ export interface FormItemProps extends RendererProps {
|
||||
// error string
|
||||
error?: string;
|
||||
showErrorMsg?: boolean;
|
||||
testid?: string;
|
||||
}
|
||||
|
||||
// 下发下去的属性
|
||||
@ -525,9 +605,12 @@ const getItemInputClassName = (props: FormItemProps) => {
|
||||
};
|
||||
|
||||
export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
reaction: Array<() => void> = [];
|
||||
lastSearchTerm: any;
|
||||
target: HTMLElement;
|
||||
mounted = false;
|
||||
initedOptionFilled = false;
|
||||
initedApiFilled = false;
|
||||
toDispose: Array<() => void> = [];
|
||||
|
||||
constructor(props: FormItemProps) {
|
||||
super(props);
|
||||
@ -536,28 +619,66 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
isOpened: false
|
||||
};
|
||||
|
||||
const {formItem: model} = props;
|
||||
const {formItem: model, formInited, addHook, initAutoFill} = props;
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model) {
|
||||
this.reaction.push(
|
||||
reaction(
|
||||
() => `${model.errors.join('')}${model.isFocused}${model.dialogOpen}`,
|
||||
() => this.forceUpdate()
|
||||
)
|
||||
);
|
||||
this.reaction.push(
|
||||
reaction(
|
||||
() => model?.filteredOptions,
|
||||
() => this.forceUpdate()
|
||||
)
|
||||
);
|
||||
this.reaction.push(
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() =>
|
||||
`${model.errors.join('')}${model.isFocused}${
|
||||
model.dialogOpen
|
||||
}${JSON.stringify(model.filteredOptions)}`,
|
||||
() => this.forceUpdate()
|
||||
)
|
||||
);
|
||||
|
||||
let onInit = () => {
|
||||
this.initedOptionFilled = true;
|
||||
initAutoFill !== false &&
|
||||
this.syncOptionAutoFill(
|
||||
model.getSelectedOptions(model.tmpValue),
|
||||
initAutoFill === 'fillIfNotSet'
|
||||
);
|
||||
this.initedApiFilled = true;
|
||||
initAutoFill !== false &&
|
||||
this.syncApiAutoFill(
|
||||
model.tmpValue ?? '',
|
||||
false,
|
||||
initAutoFill === 'fillIfNotSet'
|
||||
);
|
||||
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() => JSON.stringify(model.tmpValue),
|
||||
() => this.syncAutoFill(model.tmpValue)
|
||||
() =>
|
||||
this.mounted &&
|
||||
this.initedApiFilled &&
|
||||
this.syncApiAutoFill(model.tmpValue)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() => JSON.stringify(model.getSelectedOptions(model.tmpValue)),
|
||||
() =>
|
||||
this.mounted &&
|
||||
this.initedOptionFilled &&
|
||||
this.syncOptionAutoFill(model.getSelectedOptions(model.tmpValue))
|
||||
)
|
||||
);
|
||||
};
|
||||
this.toDispose.push(
|
||||
formInited || !addHook
|
||||
? model.addInitHook(onInit, 999)
|
||||
: addHook(onInit, 'init', 'post')
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.target = findDOMNode(this) as HTMLElement;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: FormItemProps) {
|
||||
@ -573,18 +694,15 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
props.data
|
||||
)
|
||||
) {
|
||||
this.syncAutoFill(model?.tmpValue, true);
|
||||
this.syncApiAutoFill(model?.tmpValue, true);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.target = findDOMNode(this) as HTMLElement;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.reaction.forEach(fn => fn());
|
||||
this.reaction = [];
|
||||
this.syncAutoFill.cancel();
|
||||
this.syncApiAutoFill.cancel();
|
||||
this.mounted = false;
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
}
|
||||
|
||||
@autobind
|
||||
@ -622,7 +740,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
trigger === type &&
|
||||
(mode === 'dialog' || mode === 'drawer')
|
||||
) {
|
||||
formItem?.openDialog(this.buildSchema(), data, result => {
|
||||
formItem?.openDialog(this.buildAutoFillSchema(), data, result => {
|
||||
if (!result?.selectedItems) {
|
||||
return;
|
||||
}
|
||||
@ -651,44 +769,76 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
onBulkChange?.(responseData);
|
||||
}
|
||||
|
||||
syncAutoFill = debounce(
|
||||
(term: any, reload?: boolean) => {
|
||||
(async (term: string, reload?: boolean) => {
|
||||
syncApiAutoFill = debounce(
|
||||
async (term: any, forceLoad?: boolean, skipIfExits = false) => {
|
||||
try {
|
||||
const {autoFill, onBulkChange, formItem, data} = this.props;
|
||||
|
||||
// 参照录入
|
||||
if (!autoFill || (autoFill && !autoFill?.hasOwnProperty('api'))) {
|
||||
if (
|
||||
!onBulkChange ||
|
||||
!formItem ||
|
||||
!autoFill ||
|
||||
(autoFill && !autoFill?.hasOwnProperty('api'))
|
||||
) {
|
||||
return;
|
||||
} else if (
|
||||
skipIfExits &&
|
||||
(!autoFill.fillMapping ||
|
||||
Object.keys(autoFill.fillMapping).some(
|
||||
key => typeof getVariable(data, key) !== 'undefined'
|
||||
))
|
||||
) {
|
||||
// 只要目标填充值有一个有值,就初始不自动填充
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoFill?.showSuggestion) {
|
||||
this.handleAutoFill('change');
|
||||
} else {
|
||||
// 自动填充
|
||||
const itemName = formItem?.name;
|
||||
const itemName = formItem.name;
|
||||
const ctx = createObject(data, {
|
||||
[itemName || '']: term
|
||||
[itemName || '']: term,
|
||||
__term: term
|
||||
});
|
||||
if (
|
||||
(onBulkChange &&
|
||||
isEffectiveApi(autoFill.api, ctx) &&
|
||||
this.lastSearchTerm !== term) ||
|
||||
reload
|
||||
forceLoad ||
|
||||
(isEffectiveApi(autoFill.api, ctx) && this.lastSearchTerm !== term)
|
||||
) {
|
||||
let result = await formItem?.loadAutoUpdateData(
|
||||
let result = await formItem.loadAutoUpdateData(
|
||||
autoFill.api,
|
||||
ctx,
|
||||
!!(autoFill.api as BaseApiObject)?.silent
|
||||
);
|
||||
|
||||
this.lastSearchTerm =
|
||||
(result && getVariable(result, itemName)) ?? term;
|
||||
|
||||
// 如果没有返回不应该处理
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoFill?.fillMapping) {
|
||||
result = dataMapping(autoFill.fillMapping, result);
|
||||
}
|
||||
result && onBulkChange?.(result);
|
||||
|
||||
if (result) {
|
||||
// 不能把自己给清了吧
|
||||
setVariable(
|
||||
result,
|
||||
itemName,
|
||||
getVariable(result, itemName) || formItem.tmpValue
|
||||
);
|
||||
|
||||
onBulkChange?.(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
})(term, reload).catch(e => console.error(e));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
250,
|
||||
{
|
||||
@ -697,7 +847,81 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
}
|
||||
);
|
||||
|
||||
buildSchema() {
|
||||
syncOptionAutoFill(selectedOptions: Array<any>, skipIfExits = false) {
|
||||
const {autoFill, multiple, onBulkChange, data} = this.props;
|
||||
const formItem = this.props.formItem as IFormItemStore;
|
||||
// 参照录入|自动填充
|
||||
if (autoFill?.hasOwnProperty('api')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
onBulkChange &&
|
||||
autoFill &&
|
||||
!isEmpty(autoFill) &&
|
||||
formItem.filteredOptions.length
|
||||
) {
|
||||
const toSync = dataMapping(
|
||||
autoFill,
|
||||
multiple
|
||||
? {
|
||||
items: selectedOptions.map(item =>
|
||||
createObject(
|
||||
{
|
||||
...data,
|
||||
ancestors: getTreeAncestors(
|
||||
formItem.filteredOptions,
|
||||
item,
|
||||
true
|
||||
)
|
||||
},
|
||||
item
|
||||
)
|
||||
)
|
||||
}
|
||||
: createObject(
|
||||
{
|
||||
...data,
|
||||
ancestors: getTreeAncestors(
|
||||
formItem.filteredOptions,
|
||||
selectedOptions[0],
|
||||
true
|
||||
)
|
||||
},
|
||||
selectedOptions[0]
|
||||
)
|
||||
);
|
||||
const tmpData = {...data};
|
||||
const result = {...toSync};
|
||||
|
||||
Object.keys(autoFill).forEach(key => {
|
||||
const keys = keyToPath(key);
|
||||
let value = getVariable(toSync, key);
|
||||
|
||||
if (skipIfExits) {
|
||||
const originValue = getVariable(data, key);
|
||||
if (typeof originValue !== 'undefined') {
|
||||
value = originValue;
|
||||
}
|
||||
}
|
||||
|
||||
setVariable(result, key, value);
|
||||
|
||||
// 如果左边的 key 是一个路径
|
||||
// 这里不希望直接把原始对象都给覆盖没了
|
||||
// 而是保留原始的对象,只修改指定的属性
|
||||
if (keys.length > 1 && isPlainObject(tmpData[keys[0]])) {
|
||||
// 存在情况:依次更新同一子路径的多个key,eg: a.b.c1 和 a.b.c2,所以需要同步更新data
|
||||
setVariable(tmpData, key, value);
|
||||
result[keys[0]] = tmpData[keys[0]];
|
||||
}
|
||||
});
|
||||
|
||||
onBulkChange(result);
|
||||
}
|
||||
}
|
||||
|
||||
buildAutoFillSchema() {
|
||||
const {
|
||||
render,
|
||||
autoFill,
|
||||
@ -775,26 +999,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
}
|
||||
]
|
||||
};
|
||||
const schema = {
|
||||
type: mode,
|
||||
className: 'auto-fill-dialog',
|
||||
title: __('FormItem.autoFillSuggest'),
|
||||
size,
|
||||
body: form,
|
||||
actions: [
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'cancel',
|
||||
label: __('cancel')
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
actionType: 'submit',
|
||||
level: 'primary',
|
||||
label: __('confirm')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (mode === 'popOver') {
|
||||
return (
|
||||
<Overlay
|
||||
@ -805,7 +1010,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
>
|
||||
<PopOver
|
||||
classPrefix={ns}
|
||||
className={cx(`${ns}auto-fill-popOver`, popOverClassName)}
|
||||
className={cx(`${ns}Autofill-popOver`, popOverClassName)}
|
||||
style={{
|
||||
minWidth: this.target ? this.target.offsetWidth : undefined
|
||||
}}
|
||||
@ -821,7 +1026,29 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
</Overlay>
|
||||
);
|
||||
} else {
|
||||
return schema;
|
||||
return {
|
||||
type: mode,
|
||||
className: 'auto-fill-dialog',
|
||||
title: __('FormItem.autoFillSuggest'),
|
||||
size,
|
||||
body: {
|
||||
...form,
|
||||
wrapWithPanel: false
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'cancel',
|
||||
label: __('cancel')
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
actionType: 'submit',
|
||||
level: 'primary',
|
||||
label: __('confirm')
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1738,7 +1965,8 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
return wrapControl(
|
||||
hoistNonReactStatic(
|
||||
class extends FormItemWrap {
|
||||
static defaultProps = {
|
||||
static defaultProps: any = {
|
||||
initAutoFill: 'fillIfNotSet',
|
||||
className: '',
|
||||
renderLabel: config.renderLabel,
|
||||
renderDescription: config.renderDescription,
|
||||
@ -1757,14 +1985,14 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
...((Control as any).propsList || [])
|
||||
];
|
||||
|
||||
static displayName = `FormItem${
|
||||
static displayName: string = `FormItem${
|
||||
config.type ? `(${config.type})` : ''
|
||||
}`;
|
||||
static ComposedComponent = Control;
|
||||
|
||||
ref: any;
|
||||
|
||||
constructor(props: FormItemProps) {
|
||||
constructor(props: FormControlProps) {
|
||||
super(props);
|
||||
this.refFn = this.refFn.bind(this);
|
||||
|
||||
@ -1859,7 +2087,7 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
getItemInputClassName(this.props)
|
||||
)}
|
||||
></Control>
|
||||
{isOpened ? this.buildSchema() : null}
|
||||
{isOpened ? this.buildAutoFillSchema() : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -196,19 +196,6 @@ export interface FormOptionsControl extends FormBaseControl {
|
||||
* 选项删除提示文字。
|
||||
*/
|
||||
deleteConfirmText?: string;
|
||||
|
||||
/**
|
||||
* 自动填充,当选项被选择的时候,将选项中的其他值同步设置到表单内。
|
||||
*/
|
||||
autoFill?: {
|
||||
[propName: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @default fillIfNotSet
|
||||
* 初始化时是否把其他字段同步到表单内部。
|
||||
*/
|
||||
initAutoFill?: boolean | 'fillIfNotSet';
|
||||
}
|
||||
|
||||
export interface OptionsBasicConfig extends FormItemBasicConfig {
|
||||
@ -309,7 +296,6 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
placeholder: 'Select.placeholder',
|
||||
resetValue: '',
|
||||
deleteConfirmText: 'deleteConfirm',
|
||||
initAutoFill: 'fillIfNotSet',
|
||||
...Control.defaultProps
|
||||
};
|
||||
static propsList: any = (Control as any).propsList
|
||||
@ -321,7 +307,6 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
|
||||
input: any;
|
||||
mounted = false;
|
||||
initedFilled = false;
|
||||
|
||||
constructor(props: OptionsProps) {
|
||||
super(props);
|
||||
@ -344,63 +329,36 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
defaultCheckAll
|
||||
} = props;
|
||||
|
||||
if (formItem) {
|
||||
formItem.setOptions(
|
||||
normalizeOptions(options, undefined, valueField),
|
||||
this.changeOptionValue,
|
||||
data
|
||||
);
|
||||
if (!formItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() => JSON.stringify([formItem.loading, formItem.filteredOptions]),
|
||||
() => this.mounted && this.forceUpdate()
|
||||
)
|
||||
);
|
||||
formItem.setOptions(
|
||||
normalizeOptions(options, undefined, valueField),
|
||||
this.changeOptionValue,
|
||||
data
|
||||
);
|
||||
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() =>
|
||||
JSON.stringify(formItem.getSelectedOptions(formItem.tmpValue)),
|
||||
() =>
|
||||
this.mounted &&
|
||||
this.initedFilled &&
|
||||
this.syncAutoFill(formItem.getSelectedOptions(formItem.tmpValue))
|
||||
)
|
||||
);
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() => JSON.stringify([formItem.loading, formItem.filteredOptions]),
|
||||
() => this.mounted && this.forceUpdate()
|
||||
)
|
||||
);
|
||||
|
||||
if (formInited || !addHook) {
|
||||
this.initedFilled = true;
|
||||
this.props.initAutoFill !== false &&
|
||||
this.syncAutoFill(
|
||||
formItem.getSelectedOptions(formItem.tmpValue),
|
||||
this.props.initAutoFill === 'fillIfNotSet'
|
||||
);
|
||||
} else if (addHook) {
|
||||
addHook(() => {
|
||||
this.initedFilled = true;
|
||||
this.props.initAutoFill !== false &&
|
||||
this.syncAutoFill(
|
||||
formItem.getSelectedOptions(formItem.tmpValue),
|
||||
this.props.initAutoFill === 'fillIfNotSet'
|
||||
);
|
||||
}, 'init');
|
||||
}
|
||||
|
||||
// 默认全选。这里会和默认值\回填值逻辑冲突,所以如果有配置source则不执行默认全选
|
||||
if (
|
||||
multiple &&
|
||||
defaultCheckAll &&
|
||||
formItem.filteredOptions?.length &&
|
||||
!source
|
||||
) {
|
||||
this.defaultCheckAll();
|
||||
}
|
||||
// 默认全选。这里会和默认值\回填值逻辑冲突,所以如果有配置source则不执行默认全选
|
||||
if (
|
||||
multiple &&
|
||||
defaultCheckAll &&
|
||||
formItem.filteredOptions?.length &&
|
||||
!source
|
||||
) {
|
||||
this.defaultCheckAll();
|
||||
}
|
||||
|
||||
let loadOptions: boolean = initFetch !== false;
|
||||
|
||||
if (formItem && joinValues === false && defaultValue) {
|
||||
if (joinValues === false && defaultValue) {
|
||||
const selectedOptions = extractValue
|
||||
? formItem
|
||||
.getSelectedOptions(value)
|
||||
@ -416,9 +374,11 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
|
||||
loadOptions &&
|
||||
config.autoLoadOptionsFromSource !== false &&
|
||||
(formInited || !addHook
|
||||
? this.reload()
|
||||
: addHook && addHook(this.initOptions, 'init'));
|
||||
this.toDispose.push(
|
||||
formInited || !addHook
|
||||
? formItem.addInitHook(this.reload)
|
||||
: addHook(this.initOptions, 'init')
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -507,6 +467,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.removeHook?.(this.reload, 'init');
|
||||
this.mounted = false;
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
}
|
||||
@ -549,80 +510,6 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
syncAutoFill(selectedOptions: Array<any>, skipIfExits = false) {
|
||||
const {autoFill, multiple, onBulkChange, data} = this.props;
|
||||
const formItem = this.props.formItem as IFormItemStore;
|
||||
// 参照录入|自动填充
|
||||
if (autoFill?.hasOwnProperty('api')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
onBulkChange &&
|
||||
autoFill &&
|
||||
!isEmpty(autoFill) &&
|
||||
formItem.filteredOptions.length
|
||||
) {
|
||||
const toSync = dataMapping(
|
||||
autoFill,
|
||||
multiple
|
||||
? {
|
||||
items: selectedOptions.map(item =>
|
||||
createObject(
|
||||
{
|
||||
...data,
|
||||
ancestors: getTreeAncestors(
|
||||
formItem.filteredOptions,
|
||||
item,
|
||||
true
|
||||
)
|
||||
},
|
||||
item
|
||||
)
|
||||
)
|
||||
}
|
||||
: createObject(
|
||||
{
|
||||
...data,
|
||||
ancestors: getTreeAncestors(
|
||||
formItem.filteredOptions,
|
||||
selectedOptions[0],
|
||||
true
|
||||
)
|
||||
},
|
||||
selectedOptions[0]
|
||||
)
|
||||
);
|
||||
const tmpData = {...data};
|
||||
const result = {...toSync};
|
||||
|
||||
Object.keys(autoFill).forEach(key => {
|
||||
const keys = keyToPath(key);
|
||||
let value = getVariable(toSync, key);
|
||||
|
||||
if (skipIfExits) {
|
||||
const originValue = getVariable(data, key);
|
||||
if (typeof originValue !== 'undefined') {
|
||||
value = originValue;
|
||||
}
|
||||
}
|
||||
|
||||
setVariable(result, key, value);
|
||||
|
||||
// 如果左边的 key 是一个路径
|
||||
// 这里不希望直接把原始对象都给覆盖没了
|
||||
// 而是保留原始的对象,只修改指定的属性
|
||||
if (keys.length > 1 && isPlainObject(tmpData[keys[0]])) {
|
||||
// 存在情况:依次更新同一子路径的多个key,eg: a.b.c1 和 a.b.c2,所以需要同步更新data
|
||||
setVariable(tmpData, key, value);
|
||||
result[keys[0]] = tmpData[keys[0]];
|
||||
}
|
||||
});
|
||||
|
||||
onBulkChange(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 当前值,跟设置预期的值格式不一致时自动转换。
|
||||
normalizeValue() {
|
||||
const {
|
||||
|
@ -63,7 +63,7 @@ export interface ControlOutterProps extends RendererProps {
|
||||
submitOnChange?: boolean;
|
||||
validate?: (value: any, values: any, name: string) => any;
|
||||
formItem?: IFormItemStore;
|
||||
addHook?: (fn: () => any, type?: 'validate' | 'init' | 'flush') => void;
|
||||
addHook?: (fn: () => any, type?: 'validate' | 'init' | 'flush') => () => void;
|
||||
removeHook?: (fn: () => any, type?: 'validate' | 'init' | 'flush') => void;
|
||||
$schema: {
|
||||
pipeIn?: (value: any, data: any) => any;
|
||||
@ -306,6 +306,8 @@ export function wrapControl<
|
||||
};
|
||||
addHook?.(this.hook2);
|
||||
}
|
||||
|
||||
formItem?.init();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: OuterProps) {
|
||||
@ -468,7 +470,7 @@ export function wrapControl<
|
||||
|
||||
setInitialValue(value: any) {
|
||||
const model = this.model!;
|
||||
const {formStore: form, canAccessSuperData, data} = this.props;
|
||||
const {formStore: form, data, canAccessSuperData} = this.props;
|
||||
const isExp = isExpression(value);
|
||||
|
||||
if (isExp) {
|
||||
|
@ -548,11 +548,17 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
self.submiting = true;
|
||||
|
||||
try {
|
||||
yield validate(hooks, true, true, failedMessage, validateErrCb);
|
||||
const valid = yield validate(
|
||||
hooks,
|
||||
true,
|
||||
true,
|
||||
failedMessage,
|
||||
validateErrCb
|
||||
);
|
||||
|
||||
if (fn) {
|
||||
const diff = difference(self.data, self.pristine);
|
||||
const result = yield fn(
|
||||
const result: any = yield fn(
|
||||
createObject(
|
||||
createObject(self.data.__super, {
|
||||
diff: diff,
|
||||
@ -578,7 +584,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
failedMessage?: string,
|
||||
validateErrCb?: () => void
|
||||
) => Promise<boolean> = flow(function* validate(
|
||||
hooks?: Array<() => Promise<any>>,
|
||||
hooks?: Array<(data: any) => Promise<any>>,
|
||||
forceValidate?: boolean,
|
||||
throwErrors?: boolean,
|
||||
failedMessage?: string,
|
||||
@ -624,10 +630,30 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
}
|
||||
}
|
||||
|
||||
if (hooks && hooks.length) {
|
||||
for (let i = 0, len = hooks.length; i < len; i++) {
|
||||
yield hooks[i]();
|
||||
try {
|
||||
if (hooks && hooks.length) {
|
||||
for (let i = 0, len = hooks.length; i < len; i++) {
|
||||
const msg = yield hooks[i](self.data);
|
||||
|
||||
if (typeof msg == 'string' && msg) {
|
||||
throw new Error(msg);
|
||||
} else if (msg === false) {
|
||||
// 不提示直接不通过校验
|
||||
throw new ValidateError(
|
||||
failedMessage || self.__('Form.validateFailed'),
|
||||
self.errors
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (throwErrors) {
|
||||
throw e;
|
||||
} else {
|
||||
toastValidateError(e.message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self.valid) {
|
||||
|
@ -28,7 +28,8 @@ import {
|
||||
eachTree,
|
||||
mapTree,
|
||||
setVariable,
|
||||
cloneObject
|
||||
cloneObject,
|
||||
promisify
|
||||
} from '../utils/helper';
|
||||
import {flattenTree} from '../utils/helper';
|
||||
import find from 'lodash/find';
|
||||
@ -91,6 +92,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
itemId: '', // 因为 name 可能会重名,所以加个 id 进来,如果有需要用来定位具体某一个
|
||||
unsetValueOnInvisible: false,
|
||||
itemsRef: types.optional(types.array(types.string), []),
|
||||
inited: false,
|
||||
validated: false,
|
||||
validating: false,
|
||||
multiple: false,
|
||||
@ -318,6 +320,8 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||
let loadAutoUpdateCancel: Function | null = null;
|
||||
|
||||
const initHooks: Array<(store: any) => any> = [];
|
||||
|
||||
function config({
|
||||
name,
|
||||
extraName,
|
||||
@ -1495,6 +1499,19 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
self.isControlled = !!value;
|
||||
}
|
||||
|
||||
const init: () => Promise<void> = flow(function* init() {
|
||||
const hooks = initHooks.sort(
|
||||
(a: any, b: any) => (a.__weight || 0) - (b.__weight || 0)
|
||||
);
|
||||
try {
|
||||
for (let hook of hooks) {
|
||||
yield hook(self);
|
||||
}
|
||||
} finally {
|
||||
self.inited = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
focus,
|
||||
blur,
|
||||
@ -1523,7 +1540,24 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
addSubFormItem,
|
||||
removeSubFormItem,
|
||||
loadAutoUpdateData,
|
||||
setIsControlled
|
||||
setIsControlled,
|
||||
|
||||
init,
|
||||
|
||||
addInitHook(fn: (store: any) => any, weight = 0) {
|
||||
fn = promisify(fn);
|
||||
initHooks.push(fn);
|
||||
(fn as any).__weight = weight;
|
||||
return () => {
|
||||
const idx = initHooks.indexOf(fn);
|
||||
~idx && initHooks.splice(idx, 1);
|
||||
};
|
||||
},
|
||||
|
||||
beforeDestroy: () => {
|
||||
// 销毁
|
||||
initHooks.splice(0, initHooks.length);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -155,7 +155,21 @@ export const iRendererStore = StoreNode.named('iRendererStore')
|
||||
self.data = data;
|
||||
},
|
||||
|
||||
setCurrentAction(action: object) {
|
||||
setCurrentAction(action: any, resolveDefinitions?: (schema: any) => any) {
|
||||
// 处理 $ref
|
||||
resolveDefinitions &&
|
||||
['dialog', 'drawer'].forEach(key => {
|
||||
if (action[key]?.$ref) {
|
||||
action = {
|
||||
...action,
|
||||
[key]: {
|
||||
...resolveDefinitions(action[key].$ref),
|
||||
...action[key]
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
self.action = action;
|
||||
self.dialogData = false;
|
||||
self.drawerOpen = false;
|
||||
@ -174,11 +188,11 @@ export const iRendererStore = StoreNode.named('iRendererStore')
|
||||
}
|
||||
|
||||
const data = createObjectFromChain(chain);
|
||||
|
||||
if (self.action.dialog && self.action.dialog.data) {
|
||||
const mappingData = self.action.data ?? self.action.dialog?.data;
|
||||
if (mappingData) {
|
||||
self.dialogData = createObjectFromChain([
|
||||
top?.context,
|
||||
dataMapping(self.action.dialog.data, data)
|
||||
dataMapping(mappingData, data)
|
||||
]);
|
||||
|
||||
const clonedAction = {
|
||||
@ -223,10 +237,11 @@ export const iRendererStore = StoreNode.named('iRendererStore')
|
||||
|
||||
const data = createObjectFromChain(chain);
|
||||
|
||||
if (self.action.drawer.data) {
|
||||
const mappingData = self.action.data ?? self.action.drawer.data;
|
||||
if (mappingData) {
|
||||
self.drawerData = createObjectFromChain([
|
||||
top?.context,
|
||||
dataMapping(self.action.drawer.data, data)
|
||||
dataMapping(mappingData, data)
|
||||
]);
|
||||
|
||||
const clonedAction = {
|
||||
|
@ -6,6 +6,8 @@ import {ServerError} from '../utils/errors';
|
||||
import {normalizeApiResponseData} from '../utils/api';
|
||||
import {replaceText} from '../utils/replaceText';
|
||||
import {concatData} from '../utils/concatData';
|
||||
import {envOverwrite} from '../envOverwrite';
|
||||
import {filter} from '../utils';
|
||||
|
||||
export const ServiceStore = iRendererStore
|
||||
.named('ServiceStore')
|
||||
@ -54,7 +56,7 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
|
||||
function updateMessage(msg?: string, error: boolean = false) {
|
||||
self.msg = (msg && String(msg)) || '';
|
||||
self.msg = (msg && filter(msg, self.data)) || '';
|
||||
self.error = error;
|
||||
}
|
||||
|
||||
@ -445,6 +447,7 @@ export const ServiceStore = iRendererStore
|
||||
} else {
|
||||
if (json.data) {
|
||||
const env = getEnv(self);
|
||||
json.data = envOverwrite(json.data, env.locale);
|
||||
json.data = replaceText(
|
||||
json.data,
|
||||
env.replaceText,
|
||||
|
@ -1288,7 +1288,7 @@ export const TableStore = iRendererStore
|
||||
typeof column.pristine.width === 'number'
|
||||
? `width: ${column.pristine.width}px;`
|
||||
: column.pristine.width
|
||||
? `width: ${column.pristine.width};`
|
||||
? `width: ${column.pristine.width};min-width: ${column.pristine.width};`
|
||||
: '' // todo 可能需要让修改过列宽的保持相应宽度,目前这样相当于重置了
|
||||
}`;
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
import type {JSONSchema7} from 'json-schema';
|
||||
import {ListenerAction} from './actions/Action';
|
||||
import {debounceConfig, trackConfig} from './utils/renderer-event';
|
||||
import type {TestIdBuilder} from './utils/helper';
|
||||
|
||||
export interface Option {
|
||||
/**
|
||||
@ -571,6 +572,10 @@ export type SchemaClassName =
|
||||
[propName: string]: boolean | undefined | null | SchemaExpression;
|
||||
};
|
||||
export interface BaseSchemaWithoutType {
|
||||
/**
|
||||
* 组件唯一 id,主要用于页面设计器中定位 json 节点
|
||||
*/
|
||||
$$id?: string;
|
||||
/**
|
||||
* 容器 css 类名
|
||||
*/
|
||||
@ -689,6 +694,8 @@ export interface BaseSchemaWithoutType {
|
||||
* 可以组件级别用来关闭移动端样式
|
||||
*/
|
||||
useMobileUI?: boolean;
|
||||
|
||||
testIdBuilder?: TestIdBuilder;
|
||||
}
|
||||
|
||||
export type OperatorType =
|
||||
|
@ -2,3 +2,10 @@ export const chromeVersion = (function getChromeVersion() {
|
||||
const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
return raw ? parseInt(raw[2], 10) : false;
|
||||
})();
|
||||
|
||||
export const isSafari =
|
||||
navigator.vendor &&
|
||||
navigator.vendor.indexOf('Apple') > -1 &&
|
||||
navigator.userAgent &&
|
||||
navigator.userAgent.indexOf('CriOS') == -1 &&
|
||||
navigator.userAgent.indexOf('FxiOS') == -1;
|
||||
|
@ -2282,18 +2282,49 @@ export function replaceUrlParams(path: string, params: Record<string, any>) {
|
||||
|
||||
export const TEST_ID_KEY: 'data-testid' = 'data-testid';
|
||||
|
||||
export function buildTestId(testid?: string, data?: PlainObject) {
|
||||
if (!testid) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
[TEST_ID_KEY]: filter(testid, data)
|
||||
};
|
||||
}
|
||||
export class TestIdBuilder {
|
||||
testId?: string;
|
||||
|
||||
export function getTestId(testid?: string, data?: PlainObject) {
|
||||
if (!testid) {
|
||||
return undefined;
|
||||
static fast(testId: string) {
|
||||
return {
|
||||
[TEST_ID_KEY]: testId
|
||||
};
|
||||
}
|
||||
|
||||
// 为空就表示没有启用testId,后续一直返回都将是空
|
||||
constructor(testId?: string) {
|
||||
this.testId = testId;
|
||||
}
|
||||
|
||||
// 生成子区域的testid生成器
|
||||
getChild(childPath: string | number, data?: object) {
|
||||
if (this.testId == null) {
|
||||
return new TestIdBuilder();
|
||||
}
|
||||
|
||||
return new TestIdBuilder(
|
||||
data
|
||||
? filter(`${this.testId}-${childPath}`, data)
|
||||
: `${this.testId}-${childPath}`
|
||||
);
|
||||
}
|
||||
|
||||
// 获取当前组件的testid
|
||||
getTestId(data?: object) {
|
||||
if (this.testId == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
[TEST_ID_KEY]: data ? filter(this.testId, data) : this.testId
|
||||
};
|
||||
}
|
||||
|
||||
getTestIdValue(data?: object) {
|
||||
if (this.testId == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return data ? filter(this.testId, data) : this.testId;
|
||||
}
|
||||
return buildTestId(testid, data)[TEST_ID_KEY];
|
||||
}
|
||||
|
76
packages/amis-core/src/utils/printElement.ts
Normal file
76
packages/amis-core/src/utils/printElement.ts
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 打印元素,参考 https://github.com/szepeshazi/print-elements 里的实现
|
||||
* 原理就是遍历节点加上打印样式,然后打印完了再清理掉
|
||||
* 对代码做了改造和优化
|
||||
*/
|
||||
|
||||
const hideFromPrintClass = 'pe-no-print';
|
||||
const preservePrintClass = 'pe-preserve-print';
|
||||
const preserveAncestorClass = 'pe-preserve-ancestor';
|
||||
const bodyElementName = 'BODY';
|
||||
|
||||
function hide(element: Element) {
|
||||
if (!element.classList.contains(preservePrintClass)) {
|
||||
element.classList.add(hideFromPrintClass);
|
||||
}
|
||||
}
|
||||
|
||||
function preserve(element: Element, isStartingElement: boolean) {
|
||||
element.classList.remove(hideFromPrintClass);
|
||||
element.classList.add(preservePrintClass);
|
||||
if (!isStartingElement) {
|
||||
element.classList.add(preserveAncestorClass);
|
||||
}
|
||||
}
|
||||
|
||||
function clean(element: Element) {
|
||||
element.classList.remove(hideFromPrintClass);
|
||||
element.classList.remove(preservePrintClass);
|
||||
element.classList.remove(preserveAncestorClass);
|
||||
}
|
||||
|
||||
function walkSiblings(element: Element, callback: (element: Element) => void) {
|
||||
let sibling = element.previousElementSibling;
|
||||
while (sibling) {
|
||||
callback(sibling);
|
||||
sibling = sibling.previousElementSibling;
|
||||
}
|
||||
sibling = element.nextElementSibling;
|
||||
while (sibling) {
|
||||
callback(sibling);
|
||||
sibling = sibling.nextElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function attachPrintClasses(element: Element, isStartingElement: boolean) {
|
||||
preserve(element, isStartingElement);
|
||||
walkSiblings(element, hide);
|
||||
}
|
||||
|
||||
function cleanup(element: Element, isStartingElement: boolean) {
|
||||
clean(element);
|
||||
walkSiblings(element, clean);
|
||||
}
|
||||
|
||||
function walkTree(
|
||||
element: Element,
|
||||
callback: (element: Element, isStartingElement: boolean) => void
|
||||
) {
|
||||
let currentElement: Element | null = element;
|
||||
callback(currentElement, true);
|
||||
currentElement = currentElement.parentElement;
|
||||
while (currentElement && currentElement.nodeName !== bodyElementName) {
|
||||
callback(currentElement, false);
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
export function printElements(elements: Element[]) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
walkTree(elements[i], attachPrintClasses);
|
||||
}
|
||||
window.print();
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
walkTree(elements[i], cleanup);
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* 身份证号码校验,(没有校验城市和区县)
|
||||
*
|
||||
@ -142,7 +144,9 @@ function verifyRegion(province: string, city: string, country: string) {
|
||||
}
|
||||
|
||||
function verifyBirthday(birthday: any) {
|
||||
return !isNaN(+birthday);
|
||||
const min = moment(new Date('1850-01-01')); // 还有1850年前的人活着吗?
|
||||
const max = moment().endOf('day'); // 最大值是今天
|
||||
return !isNaN(+birthday) && moment(birthday).isBetween(min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor-core",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2.1",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
@ -175,6 +175,11 @@
|
||||
height: 48px;
|
||||
color: #151b26;
|
||||
}
|
||||
|
||||
&.editor-tab-s-icon > svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,3 +224,59 @@
|
||||
perspective: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.ae-DialogList {
|
||||
&-wrap {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
list-style: none;
|
||||
margin: 8px 0 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 4px 0;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--colors-neutral-text-2);
|
||||
|
||||
a {
|
||||
font-size: inherit;
|
||||
color: var(--icon-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--icon-onHover-color);
|
||||
background: var(--colors-neutral-bg-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// li + li {
|
||||
// border-top: var(--borderWidth) solid var(--borderColor);
|
||||
// }
|
||||
|
||||
li:hover {
|
||||
color: var(--Layout-fontColor--onHover);
|
||||
background: $hover-bg-color;
|
||||
// border-top: var(--borderWidth) solid var(--borderColor);
|
||||
// border-bottom: var(--borderWidth) solid var(--borderColor);
|
||||
}
|
||||
|
||||
&-placeholder {
|
||||
color: #b4b6ba;
|
||||
padding-top: px2rem(10px);
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ $tooltip-bottom: '[data-tooltip][data-position=' bottom ']:hover:after';
|
||||
.config-form-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 16px 12px;
|
||||
padding: 10px 12px;
|
||||
|
||||
// 带底部操作按钮的属性配置面板
|
||||
&.with-actions {
|
||||
|
@ -309,6 +309,10 @@
|
||||
height: calc(100% - 88px);
|
||||
border-top: 1px solid #e8e9eb;
|
||||
.action-tree-control {
|
||||
& > div {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
padding: 0;
|
||||
|
@ -64,6 +64,7 @@
|
||||
align-items: unset;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background: #f2f2f4;
|
||||
border-radius: 0 !important;
|
||||
@include collapse-header-text();
|
||||
|
||||
i {
|
||||
|
@ -54,6 +54,7 @@
|
||||
@import './style-control/size';
|
||||
@import './style-control/style-common';
|
||||
@import './style-control/theme-css-code';
|
||||
@import './style-control/flex-layout';
|
||||
|
||||
/* 组件样式 */
|
||||
@import './components/button';
|
||||
@ -1071,7 +1072,7 @@
|
||||
.ae-CodePanel {
|
||||
// 左侧面板Header
|
||||
.panel-header {
|
||||
margin: 12px 0;
|
||||
margin: 10px 0;
|
||||
flex: 0 0 22px;
|
||||
padding: 0 12px;
|
||||
font-family: PingFangSC-Medium;
|
||||
|
@ -0,0 +1,29 @@
|
||||
.ae-FlexLayout {
|
||||
&-wrap {
|
||||
display: grid;
|
||||
grid-row-gap: 10px;
|
||||
grid-column-gap: 10px;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
height: 36px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
border-color: #528eff;
|
||||
}
|
||||
}
|
||||
|
||||
&-itemColumn {
|
||||
height: 100%;
|
||||
background-color: #999;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import {PluginEventListener, RendererPluginAction} from '../plugin';
|
||||
import {reGenerateID} from '../util';
|
||||
import {SubEditor} from './SubEditor';
|
||||
import Breadcrumb from './Breadcrumb';
|
||||
import {destroy} from 'mobx-state-tree';
|
||||
import {destroy, isAlive} from 'mobx-state-tree';
|
||||
import {ScaffoldModal} from './ScaffoldModal';
|
||||
import {PopOverForm} from './PopOverForm';
|
||||
import {ContextMenuPanel} from './Panel/ContextMenuPanel';
|
||||
@ -255,7 +255,7 @@ export default class Editor extends Component<EditorProps> {
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
this.manager.dispose();
|
||||
destroy(this.store);
|
||||
setTimeout(() => destroy(this.store), 4);
|
||||
}
|
||||
|
||||
// 快捷功能键
|
||||
|
@ -7,6 +7,7 @@ import {Icon} from 'amis';
|
||||
import {autobind, noop} from '../util';
|
||||
import {PluginEvent, ResizeMoveEventContext} from '../plugin';
|
||||
import {EditorManager} from '../manager';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
|
||||
export interface HighlightBoxProps {
|
||||
store: EditorStoreType;
|
||||
@ -20,315 +21,271 @@ export interface HighlightBoxProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
@observer
|
||||
export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
mainRef = React.createRef<HTMLDivElement>();
|
||||
export default observer(function ({
|
||||
className,
|
||||
store,
|
||||
id,
|
||||
title,
|
||||
children,
|
||||
node,
|
||||
toolbarContainer,
|
||||
onSwitch,
|
||||
manager
|
||||
}: HighlightBoxProps) {
|
||||
const handleWResizerMouseDown = React.useCallback(
|
||||
(e: MouseEvent) => startResize(e, 'horizontal'),
|
||||
[]
|
||||
);
|
||||
|
||||
@autobind
|
||||
handleWResizerMouseDown(e: MouseEvent) {
|
||||
return this.startResize(e, 'horizontal');
|
||||
}
|
||||
const handleHResizerMouseDown = React.useCallback(
|
||||
(e: MouseEvent) => startResize(e, 'vertical'),
|
||||
[]
|
||||
);
|
||||
|
||||
@autobind
|
||||
handleHResizerMouseDown(e: MouseEvent) {
|
||||
return this.startResize(e, 'vertical');
|
||||
}
|
||||
const handleResizerMouseDown = React.useCallback(
|
||||
(e: MouseEvent) => startResize(e, 'both'),
|
||||
[]
|
||||
);
|
||||
|
||||
@autobind
|
||||
handleResizerMouseDown(e: MouseEvent) {
|
||||
return this.startResize(e, 'both');
|
||||
}
|
||||
const startResize = React.useCallback(
|
||||
(e: MouseEvent, direction: 'horizontal' | 'vertical' | 'both') => {
|
||||
const isLeftButton =
|
||||
(e.button === 1 && window.event !== null) || e.button === 0;
|
||||
if (!isLeftButton || e.defaultPrevented) return;
|
||||
|
||||
startResize(
|
||||
e: MouseEvent,
|
||||
direction: 'horizontal' | 'vertical' | 'both' = 'horizontal'
|
||||
) {
|
||||
const isLeftButton =
|
||||
(e.button === 1 && window.event !== null) || e.button === 0;
|
||||
if (!isLeftButton || e.defaultPrevented) return;
|
||||
|
||||
e.preventDefault();
|
||||
const {manager, id, node, store} = this.props;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = document.querySelector(`[data-editor-id="${id}"]`);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
manager.disableHover = true;
|
||||
|
||||
const event = manager[
|
||||
direction === 'both'
|
||||
? 'onSizeChangeStart'
|
||||
: direction === 'vertical'
|
||||
? 'onHeightChangeStart'
|
||||
: 'onWidthChangeStart'
|
||||
](e, {
|
||||
dom: target as HTMLElement,
|
||||
node: node,
|
||||
store: store,
|
||||
resizer:
|
||||
direction === 'both'
|
||||
? this.resizerDom
|
||||
: direction === 'vertical'
|
||||
? this.hResizerDom
|
||||
: this.wResizerDom
|
||||
}) as PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
e.preventDefault();
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
>;
|
||||
|
||||
const pluginOnMove = event.data?.onMove;
|
||||
const pluginonEnd = event.data?.onEnd;
|
||||
const target = document.querySelector(`[data-editor-id="${id}"]`);
|
||||
|
||||
if (!pluginOnMove && !pluginonEnd) {
|
||||
return;
|
||||
}
|
||||
this.mainRef.current?.setAttribute('data-resizing', '');
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
manager.disableHover = true;
|
||||
|
||||
const onMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
pluginOnMove?.(e);
|
||||
};
|
||||
const event = manager[
|
||||
direction === 'both'
|
||||
? 'onSizeChangeStart'
|
||||
: direction === 'vertical'
|
||||
? 'onHeightChangeStart'
|
||||
: 'onWidthChangeStart'
|
||||
](e, {
|
||||
dom: target as HTMLElement,
|
||||
node: node,
|
||||
store: store,
|
||||
resizer:
|
||||
direction === 'both'
|
||||
? resizerDom.current!
|
||||
: direction === 'vertical'
|
||||
? hResizerDom.current!
|
||||
: wResizerDom.current!
|
||||
}) as PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>;
|
||||
|
||||
const onUp = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
manager.disableHover = false;
|
||||
this.mainRef.current?.removeAttribute('data-resizing');
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onUp);
|
||||
document.body.style.cursor = 'default';
|
||||
const pluginOnMove = event.data?.onMove;
|
||||
const pluginonEnd = event.data?.onEnd;
|
||||
|
||||
// 阻止 click 事件触发。
|
||||
let captureClick = (e: MouseEvent) => {
|
||||
window.removeEventListener('click', captureClick, true);
|
||||
if (!pluginOnMove && !pluginonEnd) {
|
||||
return;
|
||||
}
|
||||
mainRef.current?.setAttribute('data-resizing', '');
|
||||
|
||||
const onMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
pluginOnMove?.(e);
|
||||
};
|
||||
window.addEventListener('click', captureClick, true);
|
||||
setTimeout(
|
||||
() => window.removeEventListener('click', captureClick, true),
|
||||
350
|
||||
);
|
||||
|
||||
pluginonEnd?.(e);
|
||||
};
|
||||
const onUp = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
manager.disableHover = false;
|
||||
mainRef.current?.removeAttribute('data-resizing');
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onUp);
|
||||
document.body.style.cursor = 'default';
|
||||
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('mouseup', onUp);
|
||||
document.body.style.cursor =
|
||||
direction === 'both'
|
||||
? 'nwse-resize'
|
||||
: direction === 'vertical'
|
||||
? 'ns-resize'
|
||||
: 'ew-resize';
|
||||
}
|
||||
// 阻止 click 事件触发。
|
||||
let captureClick = (e: MouseEvent) => {
|
||||
window.removeEventListener('click', captureClick, true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
window.addEventListener('click', captureClick, true);
|
||||
setTimeout(
|
||||
() => window.removeEventListener('click', captureClick, true),
|
||||
350
|
||||
);
|
||||
|
||||
wResizerDom: HTMLElement;
|
||||
pluginonEnd?.(e);
|
||||
};
|
||||
|
||||
@autobind
|
||||
wResizerRef(ref: any) {
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('mouseup', onUp);
|
||||
document.body.style.cursor =
|
||||
direction === 'both'
|
||||
? 'nwse-resize'
|
||||
: direction === 'vertical'
|
||||
? 'ns-resize'
|
||||
: 'ew-resize';
|
||||
},
|
||||
[]
|
||||
);
|
||||
const wResizerDom = React.useRef<HTMLElement>();
|
||||
const wResizerRef = React.useCallback((ref: HTMLElement) => {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleWResizerMouseDown);
|
||||
ref.addEventListener('mousedown', handleWResizerMouseDown);
|
||||
} else {
|
||||
this.wResizerDom?.removeEventListener(
|
||||
wResizerDom.current?.removeEventListener(
|
||||
'mousedown',
|
||||
this.handleWResizerMouseDown
|
||||
handleWResizerMouseDown
|
||||
);
|
||||
}
|
||||
|
||||
this.wResizerDom = ref;
|
||||
}
|
||||
wResizerDom.current = ref;
|
||||
}, []);
|
||||
|
||||
hResizerDom: HTMLElement;
|
||||
|
||||
@autobind
|
||||
hResizerRef(ref: any) {
|
||||
const hResizerDom = React.useRef<HTMLElement>();
|
||||
const hResizerRef = React.useCallback((ref: HTMLElement) => {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleHResizerMouseDown);
|
||||
ref.addEventListener('mousedown', handleHResizerMouseDown);
|
||||
} else {
|
||||
this.hResizerDom?.removeEventListener(
|
||||
hResizerDom.current?.removeEventListener(
|
||||
'mousedown',
|
||||
this.handleHResizerMouseDown
|
||||
handleHResizerMouseDown
|
||||
);
|
||||
}
|
||||
|
||||
this.hResizerDom = ref;
|
||||
}
|
||||
hResizerDom.current = ref;
|
||||
}, []);
|
||||
|
||||
resizerDom: HTMLElement;
|
||||
|
||||
@autobind
|
||||
resizerRef(ref: any) {
|
||||
const resizerDom = React.useRef<HTMLElement>();
|
||||
const resizerRef = React.useCallback((ref: HTMLElement) => {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleResizerMouseDown);
|
||||
ref.addEventListener('mousedown', handleResizerMouseDown);
|
||||
} else {
|
||||
this.resizerDom?.removeEventListener(
|
||||
resizerDom.current?.removeEventListener(
|
||||
'mousedown',
|
||||
this.handleResizerMouseDown
|
||||
handleResizerMouseDown
|
||||
);
|
||||
}
|
||||
|
||||
this.resizerDom = ref;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleMouseEnter() {
|
||||
const manager = this.props.manager;
|
||||
resizerDom.current = ref;
|
||||
}, []);
|
||||
|
||||
const handleMouseEnter = React.useCallback(() => {
|
||||
if (manager.disableHover) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.store.setHoverId(this.props.id);
|
||||
}
|
||||
|
||||
// 特殊布局元素和自由容器直接子元素直接拖拽调整位置
|
||||
@autobind
|
||||
handleDragStart(e: React.DragEvent) {
|
||||
const {manager, id} = this.props;
|
||||
store.setHoverId(id);
|
||||
}, []);
|
||||
|
||||
const handleDragStart = React.useCallback((e: React.DragEvent) => {
|
||||
if (manager.disableHover) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.startDrag(id, e);
|
||||
}, []);
|
||||
|
||||
const mainRef = React.createRef<HTMLDivElement>();
|
||||
const toolbars = store.sortedToolbars;
|
||||
const secondaryToolbars = store.sortedSecondaryToolbars;
|
||||
const specialToolbars = store.sortedSpecialToolbars;
|
||||
const isActive = store.isActive(id);
|
||||
const curFreeContainerId = store.parentIsFreeContainer();
|
||||
const isHover =
|
||||
store.isHoved(id) ||
|
||||
store.dropId === id ||
|
||||
store.insertOrigId === id ||
|
||||
curFreeContainerId === id;
|
||||
const isDraggableContainer = store.draggableContainer(id);
|
||||
|
||||
// todo 干掉这个逻辑
|
||||
// 获取当前高亮画布宽度
|
||||
const aePreviewOffsetWidth = document.getElementById(
|
||||
'aePreviewHighlightBox'
|
||||
)!.offsetWidth;
|
||||
|
||||
if (!isAlive(node)) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
// @autobind
|
||||
// handleMouseLeave() {
|
||||
// this.props.store.setHoverId(this.props.id);
|
||||
// }
|
||||
// 判断是否在最右侧(考虑组件头部工具栏被遮挡的问题)
|
||||
const isRightElem = aePreviewOffsetWidth - node.x < 176; // 跳过icode代码检查
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
store,
|
||||
id,
|
||||
title,
|
||||
children,
|
||||
node,
|
||||
toolbarContainer,
|
||||
onSwitch
|
||||
} = this.props;
|
||||
const toolbars = store.sortedToolbars;
|
||||
const secondaryToolbars = store.sortedSecondaryToolbars;
|
||||
const specialToolbars = store.sortedSpecialToolbars;
|
||||
const isActive = store.isActive(id);
|
||||
const curFreeContainerId = store.parentIsFreeContainer();
|
||||
const isHover =
|
||||
store.isHoved(id) ||
|
||||
store.dropId === id ||
|
||||
store.insertOrigId === id ||
|
||||
curFreeContainerId === id;
|
||||
const isDraggableContainer = store.draggableContainer(id);
|
||||
|
||||
// 获取当前高亮画布宽度
|
||||
const aePreviewOffsetWidth = document.getElementById(
|
||||
'aePreviewHighlightBox'
|
||||
)!.offsetWidth;
|
||||
// 判断是否在最右侧(考虑组件头部工具栏被遮挡的问题)
|
||||
const isRightElem = aePreviewOffsetWidth - node.x < 176; // 跳过icode代码检查
|
||||
|
||||
/* bca-disable */ return (
|
||||
<div
|
||||
className={cx(
|
||||
'ae-Editor-hlbox',
|
||||
{
|
||||
shake: id === store.insertOrigId,
|
||||
selected: isActive || ~store.selections.indexOf(id),
|
||||
hover: isHover,
|
||||
regionOn: node.childRegions.some(region =>
|
||||
store.isRegionHighlighted(region.id, region.region)
|
||||
),
|
||||
isFreeContainerElem: !!curFreeContainerId || isDraggableContainer
|
||||
},
|
||||
className
|
||||
)}
|
||||
data-hlbox-id={id}
|
||||
style={{
|
||||
display: node.w && node.h ? 'block' : 'none',
|
||||
top: node.y,
|
||||
left: node.x,
|
||||
width: node.w,
|
||||
height: node.h
|
||||
}}
|
||||
ref={this.mainRef}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
draggable={!!curFreeContainerId || isDraggableContainer}
|
||||
onDragStart={this.handleDragStart}
|
||||
>
|
||||
{isActive ? (
|
||||
<div
|
||||
className={`ae-Editor-toolbarPopover ${
|
||||
isRightElem ? 'is-right-elem' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="ae-Editor-nav">
|
||||
{node.host ? (
|
||||
<div
|
||||
className="ae-Editor-tip parent"
|
||||
onClick={() => onSwitch?.(node.host.id)}
|
||||
>
|
||||
{node.host.label}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div key="tip" className="ae-Editor-tip current">
|
||||
{title}
|
||||
/* bca-disable */
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'ae-Editor-hlbox',
|
||||
{
|
||||
shake: id === store.insertOrigId,
|
||||
selected: isActive || ~store.selections.indexOf(id),
|
||||
hover: isHover,
|
||||
regionOn: node.childRegions.some(region =>
|
||||
store.isRegionHighlighted(region.id, region.region)
|
||||
),
|
||||
isFreeContainerElem: !!curFreeContainerId || isDraggableContainer
|
||||
},
|
||||
className
|
||||
)}
|
||||
data-hlbox-id={id}
|
||||
style={{
|
||||
display: node.w && node.h ? 'block' : 'none',
|
||||
top: node.y,
|
||||
left: node.x,
|
||||
width: node.w,
|
||||
height: node.h
|
||||
}}
|
||||
ref={mainRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
draggable={!!curFreeContainerId || isDraggableContainer}
|
||||
onDragStart={handleDragStart}
|
||||
>
|
||||
{isActive ? (
|
||||
<div
|
||||
className={`ae-Editor-toolbarPopover ${
|
||||
isRightElem ? 'is-right-elem' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="ae-Editor-nav">
|
||||
{node.host ? (
|
||||
<div
|
||||
className="ae-Editor-tip parent"
|
||||
onClick={() => onSwitch?.(node.host.id)}
|
||||
>
|
||||
{node.host.label}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.firstChild ? (
|
||||
<div
|
||||
className="ae-Editor-tip child"
|
||||
onClick={() => onSwitch?.(node.firstChild.id)}
|
||||
>
|
||||
{node.firstChild.label}
|
||||
</div>
|
||||
) : null}
|
||||
<div key="tip" className="ae-Editor-tip current">
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className="ae-Editor-toolbar" key="toolbar">
|
||||
{toolbars.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
draggable={item.draggable}
|
||||
onDragStart={item.onDragStart}
|
||||
data-id={item.id}
|
||||
data-tooltip={item.tooltip || undefined}
|
||||
data-position={item.placement || 'top'}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.iconSvg ? (
|
||||
<Icon className="icon" icon={item.iconSvg} />
|
||||
) : ~item.icon!.indexOf('<') ? (
|
||||
<span dangerouslySetInnerHTML={{__html: item.icon!}} />
|
||||
) : (
|
||||
<i className={item.icon} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{node.firstChild ? (
|
||||
<div
|
||||
className="ae-Editor-tip child"
|
||||
onClick={() => onSwitch?.(node.firstChild.id)}
|
||||
>
|
||||
{node.firstChild.label}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isActive && secondaryToolbars.length ? (
|
||||
<div
|
||||
className="ae-Editor-toolbar sencondary"
|
||||
key="sencondary-toolbar"
|
||||
>
|
||||
{secondaryToolbars.map(item => (
|
||||
<div className="ae-Editor-toolbar" key="toolbar">
|
||||
{toolbars.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={item.className}
|
||||
draggable={item.draggable}
|
||||
onDragStart={item.onDragStart}
|
||||
data-id={item.id}
|
||||
data-tooltip={item.tooltip || undefined}
|
||||
data-position={item.placement || 'top'}
|
||||
@ -344,52 +301,76 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isActive && specialToolbars.length ? (
|
||||
<div className="ae-Editor-toolbar special" key="special-toolbar">
|
||||
{specialToolbars.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={item.className}
|
||||
data-id={item.id}
|
||||
data-tooltip={item.tooltip || undefined}
|
||||
data-position={item.placement || 'top'}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.iconSvg ? (
|
||||
<Icon className="icon" icon={item.iconSvg} />
|
||||
) : ~item.icon!.indexOf('<') ? (
|
||||
<span dangerouslySetInnerHTML={{__html: item.icon!}} />
|
||||
) : (
|
||||
<i className={item.icon} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{isActive && secondaryToolbars.length ? (
|
||||
<div className="ae-Editor-toolbar sencondary" key="sencondary-toolbar">
|
||||
{secondaryToolbars.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={item.className}
|
||||
data-id={item.id}
|
||||
data-tooltip={item.tooltip || undefined}
|
||||
data-position={item.placement || 'top'}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.iconSvg ? (
|
||||
<Icon className="icon" icon={item.iconSvg} />
|
||||
) : ~item.icon!.indexOf('<') ? (
|
||||
<span dangerouslySetInnerHTML={{__html: item.icon!}} />
|
||||
) : (
|
||||
<i className={item.icon} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{children}
|
||||
{isActive && specialToolbars.length ? (
|
||||
<div className="ae-Editor-toolbar special" key="special-toolbar">
|
||||
{specialToolbars.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={item.className}
|
||||
data-id={item.id}
|
||||
data-tooltip={item.tooltip || undefined}
|
||||
data-position={item.placement || 'top'}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.iconSvg ? (
|
||||
<Icon className="icon" icon={item.iconSvg} />
|
||||
) : ~item.icon!.indexOf('<') ? (
|
||||
<span dangerouslySetInnerHTML={{__html: item.icon!}} />
|
||||
) : (
|
||||
<i className={item.icon} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.widthMutable ? (
|
||||
<>
|
||||
<span className="ae-border-WResizer" ref={this.wResizerRef}></span>
|
||||
<span className="ae-WResizer" ref={this.wResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
{children}
|
||||
|
||||
{node.heightMutable ? (
|
||||
<>
|
||||
<span className="ae-border-HResizer" ref={this.hResizerRef}></span>
|
||||
<span className="ae-HResizer" ref={this.hResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
{node.widthMutable ? (
|
||||
<>
|
||||
<span className="ae-border-WResizer" ref={wResizerRef}></span>
|
||||
<span className="ae-WResizer" ref={wResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{node.widthMutable && node.heightMutable ? (
|
||||
<span className="ae-Resizer" ref={this.resizerRef}></span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
{node.heightMutable ? (
|
||||
<>
|
||||
<span className="ae-border-HResizer" ref={hResizerRef}></span>
|
||||
<span className="ae-HResizer" ref={hResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{node.widthMutable && node.heightMutable ? (
|
||||
<span className="ae-Resizer" ref={resizerRef}></span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -80,7 +80,7 @@ export class NodeWrapper extends React.Component<NodeWrapperProps> {
|
||||
|
||||
// 自动合并假数据
|
||||
if (isObject(mockProps) && !isEmpty(mockProps)) {
|
||||
rest = merge({}, rest, mockProps);
|
||||
rest = merge(rest, mockProps);
|
||||
}
|
||||
|
||||
if ($$editor.renderRenderer) {
|
||||
|
117
packages/amis-editor-core/src/component/Panel/DialogList.tsx
Normal file
117
packages/amis-editor-core/src/component/Panel/DialogList.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import {ClassNamesFn} from 'amis-core';
|
||||
import {observer} from 'mobx-react';
|
||||
import React from 'react';
|
||||
import {EditorStoreType} from '../../store/editor';
|
||||
import {modalsToDefinitions, translateSchema} from '../../util';
|
||||
import {Button, Icon, ListMenu, PopOverContainer, confirm} from 'amis';
|
||||
|
||||
export interface DialogListProps {
|
||||
classnames: ClassNamesFn;
|
||||
store: EditorStoreType;
|
||||
}
|
||||
|
||||
export default observer(function DialogList({
|
||||
classnames: cx,
|
||||
store
|
||||
}: DialogListProps) {
|
||||
const modals = store.modals;
|
||||
|
||||
const handleAddDialog = React.useCallback(() => {
|
||||
const modal = {
|
||||
type: 'dialog',
|
||||
title: '未命名弹窗',
|
||||
definitions: modalsToDefinitions(store.modals),
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '弹窗内容'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
store.openSubEditor({
|
||||
title: '编辑弹窗',
|
||||
value: modal,
|
||||
onChange: ({definitions, ...modal}: any, diff: any) => {
|
||||
store.addModal(modal, definitions);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleEditDialog = React.useCallback((event: React.UIEvent<any>) => {
|
||||
const index = parseInt(event.currentTarget.getAttribute('data-index')!, 10);
|
||||
const dialog = store.modals[index];
|
||||
store.openSubEditor({
|
||||
title: '编辑弹窗',
|
||||
value: {
|
||||
type: 'dialog',
|
||||
...(dialog as any),
|
||||
definitions: modalsToDefinitions(store.modals)
|
||||
},
|
||||
onChange: ({definitions, ...modal}: any, diff: any) => {
|
||||
store.updateModal(dialog.$$id!, modal, definitions);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleDelDialog = React.useCallback(
|
||||
async (event: React.UIEvent<any>) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const index = parseInt(
|
||||
event.currentTarget
|
||||
.closest('[data-index]')!
|
||||
.getAttribute('data-index')!,
|
||||
10
|
||||
);
|
||||
const dialog = store.modals[index];
|
||||
const refsCount = store.countModalActionRefs(dialog.$$id!);
|
||||
|
||||
const confirmed = await confirm(
|
||||
refsCount
|
||||
? `当前弹窗已关联 ${refsCount} 个事件,删除后,所配置的事件动作将一起被删除。`
|
||||
: '',
|
||||
`确认删除弹窗「${dialog.editorSetting?.displayName || dialog.title}」?`
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
store.removeModal(dialog.$$id!);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx('ae-DialogList-wrap', 'hoverShowScrollBar')}>
|
||||
<Button size="sm" level="enhance" block onClick={handleAddDialog}>
|
||||
新增弹窗
|
||||
</Button>
|
||||
{modals.length ? (
|
||||
<ul className="ae-DialogList">
|
||||
{modals.map((modal, index) => (
|
||||
<li
|
||||
className="ae-DialogList-item"
|
||||
data-index={index}
|
||||
key={modal.$$id || index}
|
||||
onClick={handleEditDialog}
|
||||
>
|
||||
<span>
|
||||
{`${
|
||||
modal.editorSetting?.displayName ||
|
||||
modal.title ||
|
||||
'未命名弹窗'
|
||||
}`}
|
||||
</span>
|
||||
<a onClick={handleDelDialog} className="ae-DialogList-iconBtn">
|
||||
<Icon className="icon" icon="delete-bold-btn" />
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="ae-DialogList-placeholder">暂无弹窗</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -7,6 +7,7 @@ import {Icon, InputBox, Tab, Tabs} from 'amis';
|
||||
import {EditorNodeType} from '../../store/node';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
import type {Schema} from 'amis';
|
||||
import DialogList from './DialogList';
|
||||
|
||||
@observer
|
||||
export class OutlinePanel extends React.Component<PanelProps> {
|
||||
@ -42,8 +43,8 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
e: React.MouseEvent<HTMLAnchorElement>,
|
||||
option: Schema
|
||||
) {
|
||||
const store = this.props.store;
|
||||
store.setPreviewDialogId(option.$$id);
|
||||
// const store = this.props.store;
|
||||
// store.setPreviewDialogId(option.$$id);
|
||||
}
|
||||
|
||||
@autobind
|
||||
@ -61,9 +62,9 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
const store = this.props.store;
|
||||
if (key && isAlive(store)) {
|
||||
store.changeOutlineTabsKey(key);
|
||||
if (key === 'component-outline') {
|
||||
store.setPreviewDialogId();
|
||||
}
|
||||
// if (key === 'component-outline') {
|
||||
// store.setPreviewDialogId();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,15 +251,13 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
}
|
||||
|
||||
renderDialogItem(option: any, index: number) {
|
||||
const store = this.props.store;
|
||||
const children = store.root.children;
|
||||
const isSelectedDialog = option.$$id === store.previewDialogId;
|
||||
// const store = this.props.store;
|
||||
// const children = store.root.children;
|
||||
// const isSelectedDialog = option.$$id === store.previewDialogId;
|
||||
|
||||
const dialogLabel = this.getDialogLabel(option, false);
|
||||
|
||||
return children?.length && isSelectedDialog ? (
|
||||
this.renderItem(children[0], index, 'dialog')
|
||||
) : (
|
||||
return (
|
||||
<li className={cx('ae-Outline-node')} key={index}>
|
||||
<a onClick={e => this.handleDialogNodeClick(e, option)}>
|
||||
<span className="ae-Outline-node-text">
|
||||
@ -277,7 +276,6 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
const {store} = this.props;
|
||||
const outlineTabsKey = store.outlineTabsKey || 'component-outline';
|
||||
const options = store.outline;
|
||||
const dialogOptions = store.dialogOutlineList;
|
||||
|
||||
return (
|
||||
<div className="ae-Outline-panel">
|
||||
@ -336,46 +334,16 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
)}
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab
|
||||
className={'ae-outline-tabs-panel'}
|
||||
key={'dialog-outline'}
|
||||
eventKey={'dialog-outline'}
|
||||
title={'弹窗大纲'}
|
||||
>
|
||||
<InputBox
|
||||
className="editor-InputSearch"
|
||||
value={curSearchElemKey}
|
||||
onChange={this.handleSearchElemKeyChange}
|
||||
placeholder={'查询页面元素'}
|
||||
clearable={false}
|
||||
{store.isSubEditor ? null : (
|
||||
<Tab
|
||||
className={'ae-outline-tabs-panel'}
|
||||
key={'dialog-outline'}
|
||||
eventKey={'dialog-outline'}
|
||||
title={'弹窗列表'}
|
||||
>
|
||||
{curSearchElemKey ? (
|
||||
<a onClick={this.clearSearchElemKey}>
|
||||
<Icon icon="close" className="icon" />
|
||||
</a>
|
||||
) : (
|
||||
<Icon icon="editor-search" className="icon" />
|
||||
)}
|
||||
</InputBox>
|
||||
<hr className="margin-top" />
|
||||
<div
|
||||
className={cx('ae-Outline', 'hoverShowScrollBar', {
|
||||
'ae-Outline--draging': store.dragging
|
||||
})}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleDrop}
|
||||
>
|
||||
{dialogOptions.length ? (
|
||||
<ul className="ae-Outline-list">
|
||||
{dialogOptions.map((option, index) =>
|
||||
this.renderDialogItem(option, index)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
<div>暂无数据</div>
|
||||
)}
|
||||
</div>
|
||||
</Tab>
|
||||
<DialogList store={store} classnames={cx} />
|
||||
</Tab>
|
||||
)}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Html, render, TooltipWrapper, buildTestId} from 'amis';
|
||||
import {Html, render, TestIdBuilder, TooltipWrapper} from 'amis';
|
||||
import {observer} from 'mobx-react';
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
@ -18,6 +18,7 @@ type PanelProps = {
|
||||
};
|
||||
searchRendererType: string;
|
||||
className?: string;
|
||||
testIdBuilder?: TestIdBuilder;
|
||||
};
|
||||
|
||||
type PanelStates = {
|
||||
@ -98,7 +99,7 @@ export default class RenderersPanel extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const {store, searchRendererType, className} = this.props;
|
||||
const {store, searchRendererType, className, testIdBuilder} = this.props;
|
||||
const grouped = this.props.groupedRenderers || {};
|
||||
const keys = Object.keys(grouped);
|
||||
|
||||
@ -189,7 +190,7 @@ export default class RenderersPanel extends React.Component<
|
||||
onDragStart={(e: React.DragEvent) =>
|
||||
this.handleDragStart(e, item.name)
|
||||
}
|
||||
{...buildTestId(testid)}
|
||||
{...testIdBuilder?.getChild(testid).getTestId()}
|
||||
>
|
||||
<div
|
||||
className="icon-box"
|
||||
|
@ -79,29 +79,6 @@ export default class Preview extends Component<PreviewProps> {
|
||||
this.currentDom.addEventListener('mousedown', this.handeMouseDown);
|
||||
|
||||
this.props.manager.on('after-update', this.handlePanelChange);
|
||||
|
||||
const store = this.props.store;
|
||||
// 添加弹窗事件或弹窗列表进行弹窗切换后自动选中对应的弹窗
|
||||
this.dialogReaction = reactionWithOldValue(
|
||||
() =>
|
||||
store.root.children?.length
|
||||
? `${store.root.children[0]?.type}:${store.root.children[0]?.id}`
|
||||
: '',
|
||||
(info, preInfo) => {
|
||||
if (preInfo !== '') {
|
||||
// 如果为'' 说明是从预览切换回来的,不需要调整activId
|
||||
const type = info.split(':')[0];
|
||||
if (type === 'dialog' || type === 'drawer') {
|
||||
const dialogId = info.split(':')[1];
|
||||
store.changeOutlineTabsKey('dialog-outline');
|
||||
store.setPreviewDialogId(dialogId);
|
||||
store.setActiveId(dialogId);
|
||||
} else {
|
||||
store.setActiveId(store.getRootId());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -6,6 +6,7 @@ import {observer} from 'mobx-react';
|
||||
import {EditorManager} from '../manager';
|
||||
import {EditorNodeType} from '../store/node';
|
||||
import {autobind} from '../util';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
|
||||
export const AddBTNSvg = `<svg viewBox="0 0 12 12">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
@ -27,66 +28,62 @@ export interface HighlightBoxProps {
|
||||
isOnlyChildRegion: boolean;
|
||||
}
|
||||
|
||||
@observer
|
||||
export default class RegionHighlightBox extends React.Component<HighlightBoxProps> {
|
||||
// 点击清空当前区域中的所有元素
|
||||
@autobind
|
||||
handleClick() {
|
||||
const {manager, id, name} = this.props;
|
||||
// 改成 sfc 函数组件 可以消除一个警告 但是不知道为什么
|
||||
// 可以消除 Can't perform a React state update on an unmounted component
|
||||
export default observer(function (props: HighlightBoxProps) {
|
||||
const {manager, store, id, name, title, node, isOnlyChildRegion} = props;
|
||||
const handleClick = React.useCallback(() => {
|
||||
manager.emptyRegion(id, name);
|
||||
}
|
||||
}, [id, name, manager]);
|
||||
|
||||
render() {
|
||||
const {store, id, name, title, node, isOnlyChildRegion} = this.props;
|
||||
let isHiglight = store.isRegionHighlighted(id, name);
|
||||
let isHiglightHover = store.isRegionHighlightHover(id, name);
|
||||
let isDragEnter = store.isRegionDragEnter(id, name);
|
||||
const host = store.getNodeById(id)!;
|
||||
const dx = node.x - host.x;
|
||||
const dy = node.y - host.y;
|
||||
|
||||
let isHiglight = store.isRegionHighlighted(id, name);
|
||||
let isHiglightHover = store.isRegionHighlightHover(id, name);
|
||||
let isDragEnter = store.isRegionDragEnter(id, name);
|
||||
const host = store.getNodeById(id)!;
|
||||
const dx = node.x - host.x;
|
||||
const dy = node.y - host.y;
|
||||
|
||||
return (
|
||||
return (
|
||||
<div
|
||||
data-renderer={node.host.info.renderer.name}
|
||||
data-region={name}
|
||||
className={cx(
|
||||
'ae-Editor-rhlbox',
|
||||
isDragEnter ? 'is-dragenter' : '',
|
||||
!isOnlyChildRegion && isHiglightHover ? 'region-hover' : '',
|
||||
isOnlyChildRegion || isHiglight ? 'is-highlight' : '',
|
||||
dx < 87 && dy < 21 && node.x < 190 ? 'region-label-within' : ''
|
||||
)}
|
||||
style={{
|
||||
width: node.w,
|
||||
height: node.h,
|
||||
borderWidth: `${Math.max(0, dy)}px ${Math.max(
|
||||
0,
|
||||
host.w - dx - node.w
|
||||
)}px ${Math.max(0, host.h - dy - node.h)}px ${Math.max(0, dx)}px`
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-renderer={node.host.info.renderer.name}
|
||||
data-region={name}
|
||||
className={cx(
|
||||
'ae-Editor-rhlbox',
|
||||
isDragEnter ? 'is-dragenter' : '',
|
||||
!isOnlyChildRegion && isHiglightHover ? 'region-hover' : '',
|
||||
isOnlyChildRegion || isHiglight ? 'is-highlight' : '',
|
||||
dx < 87 && dy < 21 && node.x < 190 ? 'region-label-within' : ''
|
||||
)}
|
||||
style={{
|
||||
width: node.w,
|
||||
height: node.h,
|
||||
borderWidth: `${Math.max(0, dy)}px ${Math.max(
|
||||
0,
|
||||
host.w - dx - node.w
|
||||
)}px ${Math.max(0, host.h - dy - node.h)}px ${Math.max(0, dx)}px`
|
||||
}}
|
||||
data-node-id={id}
|
||||
data-node-region={name}
|
||||
className={`region-tip ${
|
||||
isOnlyChildRegion ? 'is-only-child-region' : ''
|
||||
} ignore-hover-elem`}
|
||||
>
|
||||
<div
|
||||
data-node-id={id}
|
||||
data-node-region={name}
|
||||
className={`region-tip ${
|
||||
isOnlyChildRegion ? 'is-only-child-region' : ''
|
||||
} ignore-hover-elem`}
|
||||
>
|
||||
{title}
|
||||
<span className="margin-space">|</span>
|
||||
{title}
|
||||
<span className="margin-space">|</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="clear-icon-btn"
|
||||
data-tooltip={'点击清空当前区域'}
|
||||
data-position={'bottom'}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Icon icon="clear-btn" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="clear-icon-btn"
|
||||
title={''}
|
||||
data-tooltip={'点击清空当前区域'}
|
||||
data-position={'bottom'}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon icon="clear-btn" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -224,7 +224,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
label: '确认',
|
||||
label: '保存',
|
||||
level: 'primary'
|
||||
},
|
||||
{
|
||||
|
157
packages/amis-editor-core/src/component/base/SchemaForm.tsx
Normal file
157
packages/amis-editor-core/src/component/base/SchemaForm.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
import {EditorNodeType} from '../../store/node';
|
||||
import {EditorManager} from '../../manager';
|
||||
import {diff, getThemeConfig} from '../../util';
|
||||
import {createObjectFromChain, render} from 'amis';
|
||||
import omit from 'lodash/omit';
|
||||
import cx from 'classnames';
|
||||
|
||||
export function SchemaFrom({
|
||||
propKey,
|
||||
body,
|
||||
definitions,
|
||||
controls,
|
||||
onChange,
|
||||
value,
|
||||
env,
|
||||
api,
|
||||
popOverContainer,
|
||||
submitOnChange,
|
||||
node,
|
||||
manager,
|
||||
justify,
|
||||
ctx,
|
||||
pipeIn,
|
||||
pipeOut
|
||||
}: {
|
||||
propKey?: string;
|
||||
env: any;
|
||||
body?: Array<any>;
|
||||
/**
|
||||
* @deprecated 用 body 代替
|
||||
*/
|
||||
controls?: Array<any>;
|
||||
definitions?: any;
|
||||
value: any;
|
||||
api?: any;
|
||||
onChange: (
|
||||
value: any,
|
||||
diff: any,
|
||||
filter: (schema: any, value: any, id: string, diff?: any) => any
|
||||
) => void;
|
||||
popOverContainer?: () => HTMLElement | void;
|
||||
submitOnChange?: boolean;
|
||||
node?: EditorNodeType;
|
||||
manager: EditorManager;
|
||||
panelById?: string;
|
||||
justify?: boolean;
|
||||
ctx?: any;
|
||||
pipeIn?: (value: any) => any;
|
||||
pipeOut?: (value: any, oldValue: any) => any;
|
||||
}) {
|
||||
const schema = React.useMemo(() => {
|
||||
let containerKey = 'body';
|
||||
|
||||
if (Array.isArray(controls)) {
|
||||
body = controls;
|
||||
containerKey = 'controls';
|
||||
}
|
||||
|
||||
body = Array.isArray(body) ? body.concat() : [];
|
||||
|
||||
if (submitOnChange === false) {
|
||||
body.push({
|
||||
type: 'submit',
|
||||
label: '保存',
|
||||
level: 'primary',
|
||||
block: true,
|
||||
className: 'ae-Settings-actions'
|
||||
});
|
||||
}
|
||||
const schema = {
|
||||
key: propKey,
|
||||
definitions,
|
||||
[containerKey]: body,
|
||||
className: cx(
|
||||
'config-form-content',
|
||||
'ae-Settings-content',
|
||||
'hoverShowScrollBar',
|
||||
submitOnChange === false ? 'with-actions' : ''
|
||||
),
|
||||
wrapperComponent: 'div',
|
||||
type: 'form',
|
||||
title: '',
|
||||
mode: 'normal',
|
||||
api,
|
||||
wrapWithPanel: false,
|
||||
submitOnChange: submitOnChange !== false,
|
||||
messages: {
|
||||
validateFailed: ''
|
||||
}
|
||||
};
|
||||
|
||||
if (justify) {
|
||||
schema.mode = 'horizontal';
|
||||
schema.horizontal = {
|
||||
left: 4,
|
||||
right: 8,
|
||||
justify: true
|
||||
};
|
||||
}
|
||||
return schema;
|
||||
}, [body, controls, submitOnChange]);
|
||||
|
||||
value = value || {};
|
||||
const finalValue = pipeIn ? pipeIn(value) : value;
|
||||
const themeConfig = React.useMemo(() => getThemeConfig(), []);
|
||||
const submitSubscribers = React.useRef<Array<Function>>([]);
|
||||
const subscribeSubmit = React.useCallback(
|
||||
(
|
||||
fn: (schema: any, value: any, id: string, diff?: any) => any,
|
||||
once = false
|
||||
) => {
|
||||
let raw = fn;
|
||||
const unsubscribe = () => {
|
||||
submitSubscribers.current = submitSubscribers.current.filter(
|
||||
item => ((item as any).__raw ?? item) !== raw
|
||||
);
|
||||
};
|
||||
|
||||
if (once) {
|
||||
fn = (schema: any, value: any, id: string, diff?: any) => {
|
||||
const ret = raw(schema, value, id, diff);
|
||||
unsubscribe();
|
||||
return ret;
|
||||
};
|
||||
(fn as any).__raw = raw;
|
||||
}
|
||||
submitSubscribers.current.push(fn);
|
||||
return unsubscribe;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return render(
|
||||
schema,
|
||||
{
|
||||
onFinished: async (newValue: any) => {
|
||||
newValue = pipeOut ? await pipeOut(newValue, value) : newValue;
|
||||
const diffValue = diff(value, newValue);
|
||||
onChange(newValue, diffValue, (schema, value, id, diff) => {
|
||||
return submitSubscribers.current.reduce((schema, fn) => {
|
||||
return fn(schema, value, id, diff);
|
||||
}, schema);
|
||||
});
|
||||
},
|
||||
data: createObjectFromChain([ctx, themeConfig, finalValue]),
|
||||
node: node,
|
||||
manager: manager,
|
||||
popOverContainer,
|
||||
subscribeSchemaSubmit: subscribeSubmit
|
||||
},
|
||||
{
|
||||
...omit(env, 'replaceText')
|
||||
// theme: 'cxd' // 右侧属性配置面板固定使用cxd主题展示
|
||||
}
|
||||
);
|
||||
}
|
@ -3,7 +3,6 @@ import {isAlive} from 'mobx-state-tree';
|
||||
import React from 'react';
|
||||
import {NodeWrapper} from './NodeWrapper';
|
||||
import {PanelProps, RegionConfig, RendererInfo} from '../plugin';
|
||||
import cx from 'classnames';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import {RegionWrapper} from './RegionWrapper';
|
||||
import find from 'lodash/find';
|
||||
@ -13,25 +12,14 @@ import {EditorNodeContext, EditorNodeType} from '../store/node';
|
||||
import {EditorManager} from '../manager';
|
||||
import flatten from 'lodash/flatten';
|
||||
import {render as reactRender, unmountComponentAtNode} from 'react-dom';
|
||||
import {
|
||||
autobind,
|
||||
diff,
|
||||
getThemeConfig,
|
||||
JSONGetById,
|
||||
JSONPipeIn,
|
||||
JSONPipeOut,
|
||||
JSONUpdate,
|
||||
getFixDialogType,
|
||||
appTranslate
|
||||
} from '../util';
|
||||
import {createObjectFromChain} from 'amis-core';
|
||||
import {autobind, JSONGetById, JSONUpdate, appTranslate} from '../util';
|
||||
import {ErrorBoundary} from 'amis-core';
|
||||
import {CommonConfigWrapper} from './CommonConfigWrapper';
|
||||
import type {Schema} from 'amis';
|
||||
import type {DataScope} from 'amis-core';
|
||||
import type {RendererConfig} from 'amis-core';
|
||||
import type {SchemaCollection} from 'amis';
|
||||
import omit from 'lodash/omit';
|
||||
import {SchemaFrom} from './base/SchemaForm';
|
||||
|
||||
// 创建 Node Store 并构建成树
|
||||
export function makeWrapper(
|
||||
@ -233,288 +221,6 @@ function replaceDialogtoRef(
|
||||
return replacedSchema;
|
||||
}
|
||||
|
||||
// 添加definitions
|
||||
function addDefinitions(
|
||||
schema: Schema,
|
||||
definitions: Schema,
|
||||
dialogMaxIndex: number,
|
||||
selectDialog: any
|
||||
) {
|
||||
let newSchema;
|
||||
let dialogRefsName = '';
|
||||
if (dialogMaxIndex) {
|
||||
Object.keys(definitions).forEach(ref => {
|
||||
const dialog = definitions[ref];
|
||||
if (dialog.$$id === selectDialog) {
|
||||
dialogRefsName = ref;
|
||||
}
|
||||
});
|
||||
}
|
||||
let dialogType = getFixDialogType(schema, selectDialog);
|
||||
let newDefinitions = {...definitions};
|
||||
if (!dialogRefsName) {
|
||||
dialogRefsName = dialogMaxIndex
|
||||
? `${dialogType}-ref-${dialogMaxIndex + 1}`
|
||||
: `${dialogType}-ref-1`;
|
||||
}
|
||||
let dialogBody = JSONGetById(schema, selectDialog);
|
||||
// 防止definition被查找到替换为$ref重新生成一下
|
||||
newDefinitions[dialogRefsName] = JSONPipeIn(
|
||||
JSONPipeOut({
|
||||
...dialogBody,
|
||||
type: dialogType
|
||||
})
|
||||
);
|
||||
newSchema = {
|
||||
...schema,
|
||||
definitions: newDefinitions
|
||||
};
|
||||
return {
|
||||
dialogRefsName,
|
||||
newSchema
|
||||
};
|
||||
}
|
||||
|
||||
// 选择现有弹窗后definitions设置和弹窗schema的同步
|
||||
function currentDialogOnchagne(
|
||||
manager: EditorManager,
|
||||
diffs: any,
|
||||
newValue?: any
|
||||
) {
|
||||
const {store} = manager;
|
||||
let schema = store.schema;
|
||||
let definitions = schema.definitions || {};
|
||||
let dialogMaxIndex: number = 0;
|
||||
Object.keys(definitions).forEach(k => {
|
||||
if (k.includes('ref-')) {
|
||||
let index = Number(k.split('-')[2]);
|
||||
dialogMaxIndex = Math.max(dialogMaxIndex, index);
|
||||
}
|
||||
});
|
||||
if (diffs?.length) {
|
||||
let replacedSchema = null;
|
||||
let editRefsName = '';
|
||||
for (const diff of diffs) {
|
||||
const {path, kind, item, rhs} = diff;
|
||||
// 添加选择现有弹窗事件
|
||||
if (
|
||||
kind === 'A' &&
|
||||
path.length > 1 &&
|
||||
path?.[path.length - 1] === 'actions' &&
|
||||
item.kind === 'N' &&
|
||||
item.rhs?.__selectDialog
|
||||
) {
|
||||
const {newSchema, dialogRefsName} = addDefinitions(
|
||||
schema,
|
||||
definitions,
|
||||
dialogMaxIndex,
|
||||
item.rhs?.__selectDialog
|
||||
);
|
||||
replacedSchema = replaceDialogtoRef(
|
||||
newSchema,
|
||||
item.rhs?.__selectDialog,
|
||||
dialogRefsName
|
||||
);
|
||||
if (item.rhs?.__relatedDialogId) {
|
||||
replacedSchema = replaceDialogtoRef(
|
||||
replacedSchema,
|
||||
item.rhs?.__relatedDialogId,
|
||||
dialogRefsName
|
||||
);
|
||||
}
|
||||
return replacedSchema;
|
||||
}
|
||||
// 编辑弹窗,从新建弹窗切换到现有弹窗,原始弹窗id
|
||||
else if (
|
||||
kind === 'N' &&
|
||||
path?.length > 1 &&
|
||||
path?.[path.length - 1] === '__selectDialog'
|
||||
) {
|
||||
const {newSchema, dialogRefsName} = addDefinitions(
|
||||
schema,
|
||||
definitions,
|
||||
dialogMaxIndex,
|
||||
rhs
|
||||
);
|
||||
editRefsName = dialogRefsName;
|
||||
replacedSchema = replaceDialogtoRef(newSchema, rhs, dialogRefsName);
|
||||
}
|
||||
// 编辑弹窗,从新建弹窗切换到现有弹窗,新生成弹窗id
|
||||
else if (
|
||||
kind === 'N' &&
|
||||
path?.length > 1 &&
|
||||
path?.[path.length - 1] === '__relatedDialogId'
|
||||
) {
|
||||
replacedSchema = replaceDialogtoRef(
|
||||
replacedSchema!,
|
||||
rhs,
|
||||
editRefsName!
|
||||
);
|
||||
return replacedSchema;
|
||||
}
|
||||
// 编辑弹窗,选择了其他现有弹窗,原始弹窗id
|
||||
else if (
|
||||
kind === 'E' &&
|
||||
path?.length > 1 &&
|
||||
path?.[path.length - 1] === '__selectDialog'
|
||||
) {
|
||||
const {newSchema, dialogRefsName} = addDefinitions(
|
||||
schema,
|
||||
definitions,
|
||||
dialogMaxIndex,
|
||||
rhs
|
||||
);
|
||||
editRefsName = dialogRefsName;
|
||||
replacedSchema = replaceDialogtoRef(newSchema, rhs, dialogRefsName);
|
||||
}
|
||||
// 编辑弹窗,选择了其他现有弹窗,新生成弹窗id
|
||||
else if (
|
||||
kind === 'E' &&
|
||||
path?.length > 1 &&
|
||||
path?.[path.length - 1] === '__relatedDialogId'
|
||||
) {
|
||||
replacedSchema = replaceDialogtoRef(
|
||||
replacedSchema!,
|
||||
rhs,
|
||||
editRefsName!
|
||||
);
|
||||
return replacedSchema;
|
||||
}
|
||||
}
|
||||
return replacedSchema;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function SchemaFrom({
|
||||
propKey,
|
||||
body,
|
||||
definitions,
|
||||
controls,
|
||||
onChange,
|
||||
value,
|
||||
env,
|
||||
api,
|
||||
popOverContainer,
|
||||
submitOnChange,
|
||||
node,
|
||||
manager,
|
||||
justify,
|
||||
ctx,
|
||||
pipeIn,
|
||||
pipeOut
|
||||
}: {
|
||||
propKey: string;
|
||||
env: any;
|
||||
body?: Array<any>;
|
||||
/**
|
||||
* @deprecated 用 body 代替
|
||||
*/
|
||||
controls?: Array<any>;
|
||||
definitions?: any;
|
||||
value: any;
|
||||
api?: any;
|
||||
onChange: (value: any, diff: any) => void;
|
||||
popOverContainer?: () => HTMLElement | void;
|
||||
submitOnChange?: boolean;
|
||||
node?: EditorNodeType;
|
||||
manager: EditorManager;
|
||||
panelById?: string;
|
||||
justify?: boolean;
|
||||
ctx?: any;
|
||||
pipeIn?: (value: any) => any;
|
||||
pipeOut?: (value: any, oldValue: any) => any;
|
||||
}) {
|
||||
let containerKey = 'body';
|
||||
|
||||
if (Array.isArray(controls)) {
|
||||
body = controls;
|
||||
containerKey = 'controls';
|
||||
}
|
||||
|
||||
body = Array.isArray(body) ? body.concat() : [];
|
||||
|
||||
if (submitOnChange === false) {
|
||||
body.push({
|
||||
type: 'submit',
|
||||
label: '保存',
|
||||
level: 'primary',
|
||||
block: true,
|
||||
className: 'ae-Settings-actions'
|
||||
});
|
||||
}
|
||||
const schema = {
|
||||
key: propKey,
|
||||
definitions,
|
||||
[containerKey]: body,
|
||||
className: cx(
|
||||
'config-form-content',
|
||||
'ae-Settings-content',
|
||||
'hoverShowScrollBar',
|
||||
submitOnChange === false ? 'with-actions' : ''
|
||||
),
|
||||
wrapperComponent: 'div',
|
||||
type: 'form',
|
||||
title: '',
|
||||
mode: 'normal',
|
||||
api,
|
||||
wrapWithPanel: false,
|
||||
submitOnChange: submitOnChange !== false,
|
||||
messages: {
|
||||
validateFailed: ''
|
||||
}
|
||||
};
|
||||
|
||||
if (justify) {
|
||||
schema.mode = 'horizontal';
|
||||
schema.horizontal = {
|
||||
left: 4,
|
||||
right: 8,
|
||||
justify: true
|
||||
};
|
||||
}
|
||||
|
||||
value = value || {};
|
||||
const finalValue = pipeIn ? pipeIn(value) : value;
|
||||
const themeConfig = getThemeConfig();
|
||||
|
||||
return render(
|
||||
schema,
|
||||
{
|
||||
onFinished: async (newValue: any) => {
|
||||
newValue = pipeOut ? await pipeOut(newValue, value) : newValue;
|
||||
const diffValue = diff(value, newValue);
|
||||
onChange(newValue, diffValue);
|
||||
|
||||
// 如果是选择现有弹窗,需要提取Definitions,在这里一起做变更
|
||||
const store = manager.store;
|
||||
const schema = store.schema;
|
||||
let newSchema = currentDialogOnchagne(manager, diffValue, newValue);
|
||||
if (newSchema) {
|
||||
const schemaDiff = diff(schema, newSchema);
|
||||
store.definitionOnchangeValue(newSchema, schemaDiff);
|
||||
}
|
||||
// 添加弹窗事件后自动选中弹窗
|
||||
if (store.activeDialogPath) {
|
||||
let activeId = store.getSchemaByPath(
|
||||
store.activeDialogPath.split('/').filter(item => item !== '')
|
||||
)?.$$id;
|
||||
activeId && store.setPreviewDialogId(activeId);
|
||||
store.setActiveDialogPath('');
|
||||
}
|
||||
},
|
||||
data: createObjectFromChain([ctx, themeConfig, finalValue]),
|
||||
node: node,
|
||||
manager: manager,
|
||||
popOverContainer
|
||||
},
|
||||
{
|
||||
...omit(env, 'replaceText')
|
||||
// theme: 'cxd' // 右侧属性配置面板固定使用cxd主题展示
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function makeSchemaFormRender(
|
||||
manager: EditorManager,
|
||||
schema: {
|
||||
|
@ -1,15 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<svg width="16px" height="16px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="页面编辑器" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="补充icon" transform="translate(-20.000000, -35.000000)">
|
||||
<g id="组件" transform="translate(20.000000, 35.000000)">
|
||||
<rect id="矩形" x="0" y="0" width="48" height="48"></rect>
|
||||
<g id="4.图标元件/7.通用/7.分类/分类-线备份-3" transform="translate(14.000000, 14.000000)" stroke-width="1.25">
|
||||
<rect id="矩形" stroke="currentColor" fill="currentColor" opacity="0" x="0.625" y="0.625" width="18.75" height="18.75"></rect>
|
||||
<rect id="矩形" stroke="currentColor" stroke-linejoin="round" x="2.70106592" y="2.70231592" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-3" stroke="currentColor" stroke-linejoin="round" x="11.4563618" y="2.70231592" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-5" stroke="currentColor" stroke-linejoin="round" x="2.70106592" y="11.4510659" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-4" stroke="currentColor" stroke-linejoin="round" x="11.4563618" y="11.4510659" width="5.84786816" height="5.84786816"></rect>
|
||||
<g id="4.图标元件/7.通用/7.分类/分类-线备份-3" transform="translate(14.000000, 14.000000)"
|
||||
stroke-width="1.25">
|
||||
<rect id="矩形" stroke="currentColor" fill="currentColor" opacity="0" x="0.625"
|
||||
y="0.625" width="18.75" height="18.75"></rect>
|
||||
<rect id="矩形" stroke="currentColor" stroke-linejoin="round" x="2.70106592"
|
||||
y="2.70231592" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-3" stroke="currentColor" stroke-linejoin="round" x="11.4563618"
|
||||
y="2.70231592" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-5" stroke="currentColor" stroke-linejoin="round" x="2.70106592"
|
||||
y="11.4510659" width="5.84786816" height="5.84786816"></rect>
|
||||
<rect id="矩形备份-4" stroke="currentColor" stroke-linejoin="round" x="11.4563618"
|
||||
y="11.4510659" width="5.84786816" height="5.84786816"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
@ -36,6 +36,7 @@ import type {EditorStoreType} from './store/editor';
|
||||
import {AvailableRenderersPlugin} from './plugin/AvailableRenderers';
|
||||
import ShortcutKey from './component/base/ShortcutKey';
|
||||
import WidthDraggableContainer from './component/base/WidthDraggableContainer';
|
||||
import {SchemaFrom} from './component/base/SchemaForm';
|
||||
|
||||
export const version = '__buildVersion';
|
||||
|
||||
@ -59,5 +60,6 @@ export {
|
||||
ContainerWrapper,
|
||||
AvailableRenderersPlugin,
|
||||
ShortcutKey,
|
||||
SchemaFrom,
|
||||
WidthDraggableContainer
|
||||
};
|
||||
|
@ -873,7 +873,11 @@ export class EditorManager {
|
||||
* @param rendererIdOrSchema
|
||||
* 备注:可以根据渲染器ID添加新元素,也可以根据现有schema片段添加新元素
|
||||
*/
|
||||
async addElem(rendererIdOrSchema: string | any, reGenerateId?: boolean) {
|
||||
async addElem(
|
||||
rendererIdOrSchema: string | any,
|
||||
reGenerateId?: boolean,
|
||||
activeChild: boolean = true
|
||||
) {
|
||||
if (!rendererIdOrSchema) {
|
||||
return;
|
||||
}
|
||||
@ -1029,7 +1033,7 @@ export class EditorManager {
|
||||
},
|
||||
reGenerateId
|
||||
);
|
||||
if (child) {
|
||||
if (child && activeChild) {
|
||||
// mobx 修改数据是异步的
|
||||
setTimeout(() => {
|
||||
store.setActiveId(child.$$id);
|
||||
@ -1323,10 +1327,15 @@ export class EditorManager {
|
||||
* @param diff
|
||||
*/
|
||||
@autobind
|
||||
panelChangeValue(value: any, diff?: any) {
|
||||
panelChangeValue(
|
||||
value: any,
|
||||
diff?: any,
|
||||
changeFilter?: (schema: any, value: any, id: string, diff?: any) => any,
|
||||
id = this.store.activeId
|
||||
) {
|
||||
const store = this.store;
|
||||
const context: ChangeEventContext = {
|
||||
...this.buildEventContext(store.activeId),
|
||||
...this.buildEventContext(id),
|
||||
value,
|
||||
diff
|
||||
};
|
||||
@ -1336,9 +1345,12 @@ export class EditorManager {
|
||||
return;
|
||||
}
|
||||
|
||||
store.changeValue(value, diff);
|
||||
store.changeValue(value, diff, changeFilter, id);
|
||||
|
||||
this.trigger('after-update', context);
|
||||
this.trigger('after-update', {
|
||||
...context,
|
||||
schema: context.node.schema // schema 是新的,因为修改完了
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -983,7 +983,7 @@ export interface RendererPluginAction {
|
||||
schema?: any; // 动作配置schema
|
||||
supportComponents?: string[] | string; // 如果schema中包含选择组件,可以指定该动作支持的组件类型,用于组件数树过滤
|
||||
innerArgs?: string[]; // 动作专属配置参数,主要是为了区分特性字段和附加参数
|
||||
descDetail?: (info: any) => string | JSX.Element; // 动作详细描述
|
||||
descDetail?: (info: any, context: any, props: any) => string | JSX.Element; // 动作详细描述
|
||||
outputVarDataSchema?: any | any[]; // 动作出参的结构定义
|
||||
actions?: SubRendererPluginAction[]; // 分支动作(配置面板包含多种动作的情况)
|
||||
children?: RendererPluginAction[]; // 子类型,for动作树
|
||||
|
@ -21,8 +21,8 @@ import {
|
||||
guid,
|
||||
appTranslate,
|
||||
JSONGetByPath,
|
||||
getDialogActions,
|
||||
getFixDialogType
|
||||
addModal,
|
||||
mergeDefinitions
|
||||
} from '../../src/util';
|
||||
import {
|
||||
InsertEventContext,
|
||||
@ -56,6 +56,8 @@ import {EditorNode, EditorNodeType} from './node';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import {matchSorter} from 'match-sorter';
|
||||
import debounce from 'lodash/debounce';
|
||||
import type {DialogSchema} from '../../../amis/src/renderers/Dialog';
|
||||
import type {DrawerSchema} from '../../../amis/src/renderers/Drawer';
|
||||
|
||||
export interface SchemaHistory {
|
||||
versionId: number;
|
||||
@ -124,6 +126,16 @@ export interface TargetName {
|
||||
editorId: string;
|
||||
}
|
||||
|
||||
export type EditorModalBody = (DialogSchema | DrawerSchema) & {
|
||||
// 节点 ID
|
||||
$$id?: string;
|
||||
// 如果是公共弹窗,在 definitions 中的 key
|
||||
$$ref?: string;
|
||||
|
||||
// 弹出方式
|
||||
actionType?: string;
|
||||
};
|
||||
|
||||
export const MainStore = types
|
||||
.model('EditorRoot', {
|
||||
isMobile: false,
|
||||
@ -138,8 +150,6 @@ export const MainStore = types
|
||||
hoverId: '',
|
||||
hoverRegion: '',
|
||||
activeId: '',
|
||||
previewDialogId: '', // 选择要进行编辑的弹窗id
|
||||
activeDialogPath: '', // 记录选中设计的弹窗path
|
||||
activeRegion: '', // 记录当前激活的子区域
|
||||
mouseMoveRegion: '', // 记录当前鼠标hover到的区域,后续需要优化(合并MouseMoveRegion和hoverRegion)
|
||||
|
||||
@ -236,15 +246,6 @@ export const MainStore = types
|
||||
// 给编辑状态时的
|
||||
get filteredSchema() {
|
||||
let schema = self.schema;
|
||||
if (self.previewDialogId) {
|
||||
let originDialogSchema = this.getSchema(self.previewDialogId);
|
||||
schema = {
|
||||
...originDialogSchema,
|
||||
type:
|
||||
originDialogSchema.type ||
|
||||
getFixDialogType(self.schema, self.previewDialogId)
|
||||
};
|
||||
}
|
||||
return filterSchemaForEditor(
|
||||
getEnv(self).schemaFilter?.(schema) ?? schema
|
||||
);
|
||||
@ -1011,10 +1012,53 @@ export const MainStore = types
|
||||
},
|
||||
|
||||
// 获取弹窗大纲列表
|
||||
get dialogOutlineList() {
|
||||
get modals(): Array<EditorModalBody> {
|
||||
const schema = self.schema;
|
||||
let actions = getDialogActions(schema, 'list');
|
||||
return actions;
|
||||
const modals: Array<DialogSchema | DrawerSchema> = [];
|
||||
Object.keys(schema.definitions || {}).forEach(key => {
|
||||
const definition = schema.definitions[key];
|
||||
if (['dialog', 'drawer'].includes(definition.type)) {
|
||||
modals.push({
|
||||
...definition,
|
||||
$$ref: key
|
||||
});
|
||||
}
|
||||
});
|
||||
JSONTraverse(schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value)
|
||||
) {
|
||||
const key = value === 'drawer' ? 'drawer' : 'dialog';
|
||||
const body = host[key] || host['args'];
|
||||
if (body && !body.$ref) {
|
||||
modals.push({
|
||||
...body,
|
||||
actionType: value
|
||||
});
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
return modals;
|
||||
},
|
||||
|
||||
get modalOptions() {
|
||||
return this.modals.map((modal: EditorModalBody) => {
|
||||
return {
|
||||
label: `${
|
||||
modal.editorSetting?.displayName || modal.title || '未命名弹窗'
|
||||
}`,
|
||||
tip:
|
||||
modal.actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗',
|
||||
value: modal.$$id,
|
||||
$$ref: modal.$$ref
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
@ -1235,14 +1279,6 @@ export const MainStore = types
|
||||
// }
|
||||
},
|
||||
|
||||
setActiveDialogPath(path: string) {
|
||||
self.activeDialogPath = path;
|
||||
},
|
||||
|
||||
setPreviewDialogId(id?: string) {
|
||||
self.previewDialogId = id ? id : '';
|
||||
},
|
||||
|
||||
setSelections(ids: Array<string>) {
|
||||
self.activeId = '';
|
||||
self.activeRegion = '';
|
||||
@ -1400,11 +1436,23 @@ export const MainStore = types
|
||||
this.changeLeftPanelOpenStatus(true);
|
||||
},
|
||||
|
||||
changeValue(value: Schema, diff?: any) {
|
||||
if (!self.activeId) {
|
||||
changeValue(
|
||||
value: Schema,
|
||||
diff?: any,
|
||||
changeFilter?: (schema: any, value: any, id: string, diff?: any) => any,
|
||||
id = self.activeId
|
||||
) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
this.changeValueById(self.activeId, value, diff);
|
||||
this.changeValueById(
|
||||
id,
|
||||
value,
|
||||
diff,
|
||||
undefined,
|
||||
undefined,
|
||||
changeFilter
|
||||
);
|
||||
},
|
||||
|
||||
definitionOnchangeValue(value: Schema, diff?: any) {
|
||||
@ -1416,7 +1464,8 @@ export const MainStore = types
|
||||
value: Schema,
|
||||
diff?: any,
|
||||
replace?: boolean,
|
||||
noTrace?: boolean
|
||||
noTrace?: boolean,
|
||||
changeFilter?: (schema: any, value: any, id: string, diff?: any) => any
|
||||
) {
|
||||
const origin = JSONGetById(self.schema, id);
|
||||
|
||||
@ -1427,11 +1476,12 @@ export const MainStore = types
|
||||
// 通常 Panel 和 codeEditor 过来都有 diff 信息
|
||||
if (diff) {
|
||||
const result = patchDiff(origin, diff);
|
||||
this.traceableSetSchema(
|
||||
JSONUpdate(self.schema, id, JSONPipeIn(result), true),
|
||||
noTrace
|
||||
);
|
||||
let schema = JSONUpdate(self.schema, id, JSONPipeIn(result), true);
|
||||
schema = changeFilter?.(schema, value, id, diff) || schema;
|
||||
this.traceableSetSchema(schema, noTrace);
|
||||
} else {
|
||||
let schema = JSONUpdate(self.schema, id, JSONPipeIn(value), replace);
|
||||
schema = changeFilter?.(schema, value, id) || schema;
|
||||
this.traceableSetSchema(
|
||||
JSONUpdate(self.schema, id, JSONPipeIn(value), replace),
|
||||
noTrace
|
||||
@ -1627,6 +1677,132 @@ export const MainStore = types
|
||||
self.jsonSchemaUri = schemaUri;
|
||||
},
|
||||
|
||||
addModal(modal?: DialogSchema | DrawerSchema, definitions?: any) {
|
||||
const [schema] = addModal(self.schema, modal, definitions);
|
||||
this.traceableSetSchema(schema);
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算具有指定ID的模态动作引用的数量
|
||||
*
|
||||
* @param id 模态动作的ID
|
||||
* @returns 返回模态动作引用的数量
|
||||
*/
|
||||
countModalActionRefs(id: string) {
|
||||
let count = 0;
|
||||
const host = JSONGetParentById(self.schema, id);
|
||||
|
||||
if (host?.actionType) {
|
||||
// 有 type 说明是旧的动作按钮,按钮本身就是某种动作,不需要再计算
|
||||
// 没有 type 说明是 onEvent 里面的动作,引用的数量也就是自己是 1
|
||||
return host.type ? 0 : 1;
|
||||
} else if (host !== self.schema.definitions) {
|
||||
return count;
|
||||
}
|
||||
|
||||
const modalKey = Object.keys(host).find(key => host[key]?.$$id === id);
|
||||
JSONTraverse(self.schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value) &&
|
||||
host[value === 'drawer' ? 'drawer' : 'dialog']?.$ref === modalKey
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
removeModal(id: string) {
|
||||
let schema = self.schema;
|
||||
const host = JSONGetParentById(schema, id);
|
||||
if (host === schema.definitions) {
|
||||
const modalKey = Object.keys(host).find(
|
||||
key => host[key]?.$$id === id
|
||||
);
|
||||
JSONTraverse(schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value) &&
|
||||
host[value === 'drawer' ? 'drawer' : 'dialog']?.$ref === modalKey
|
||||
) {
|
||||
schema = JSONDelete(schema, host.$$id);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
schema = JSONDelete(schema, id);
|
||||
} else {
|
||||
schema = JSONDelete(schema, host.$$id);
|
||||
}
|
||||
this.traceableSetSchema(schema);
|
||||
},
|
||||
|
||||
updateModal(
|
||||
id: string,
|
||||
modal: DialogSchema | DrawerSchema,
|
||||
definitions?: any
|
||||
) {
|
||||
let schema = self.schema;
|
||||
const parent = JSONGetParentById(schema, id);
|
||||
|
||||
if (!parent) {
|
||||
throw new Error('modal not found');
|
||||
}
|
||||
|
||||
if (definitions && isPlainObject(definitions)) {
|
||||
schema = mergeDefinitions(schema, definitions, modal);
|
||||
}
|
||||
|
||||
const newHostKey =
|
||||
((modal as any).actionType || modal.type) === 'drawer'
|
||||
? 'drawer'
|
||||
: 'dialog';
|
||||
|
||||
schema = JSONUpdate(schema, id, modal);
|
||||
|
||||
// 如果编辑的是公共弹窗
|
||||
if (!parent.actionType) {
|
||||
const modalKey = Object.keys(parent).find(
|
||||
key => parent[key]?.$$id === id
|
||||
);
|
||||
|
||||
// 所有引用的地方都要更新
|
||||
JSONTraverse(schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value) &&
|
||||
host[value === 'drawer' ? 'drawer' : 'dialog']?.$ref ===
|
||||
modalKey &&
|
||||
newHostKey !== (value === 'drawer' ? 'drawer' : 'dialog')
|
||||
) {
|
||||
schema = JSONUpdate(schema, host.$$id, {
|
||||
actionType: (modal as any).actionType || modal.type,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
[newHostKey]: host[value === 'drawer' ? 'drawer' : 'dialog']
|
||||
});
|
||||
}
|
||||
return value;
|
||||
});
|
||||
} else {
|
||||
// 内嵌弹窗只用改自己就行了
|
||||
schema = JSONUpdate(schema, parent.$$id, {
|
||||
actionType: (modal as any).actionType || modal.type,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
[newHostKey]: JSONPipeIn(modal)
|
||||
});
|
||||
}
|
||||
|
||||
this.traceableSetSchema(schema);
|
||||
|
||||
// todo 更新弹出方式的配置
|
||||
},
|
||||
|
||||
openSubEditor(context: SubEditorContext) {
|
||||
const activeId = self.activeId;
|
||||
|
||||
|
@ -15,6 +15,7 @@ import isEqual from 'lodash/isEqual';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import debounce from 'lodash/debounce';
|
||||
import merge from 'lodash/merge';
|
||||
import {EditorModalBody} from './store/editor';
|
||||
|
||||
const {
|
||||
guid,
|
||||
@ -1393,129 +1394,116 @@ export const scrollToActive = debounce((selector: string) => {
|
||||
}
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* 获取弹窗事件
|
||||
* @param schema 遍历的schema
|
||||
* @param listType 列表形式,弹窗list或label value形式的数据源
|
||||
* @param filterId 要过滤弹窗的id
|
||||
*/
|
||||
export const getDialogActions = (
|
||||
schema: Schema,
|
||||
listType: 'list' | 'source',
|
||||
filterId?: string
|
||||
) => {
|
||||
let dialogActions: any[] = [];
|
||||
JSONTraverse(
|
||||
schema,
|
||||
(value: any, key: string, object: any) => {
|
||||
// definitions中的弹窗
|
||||
if (key === 'type' && value === 'page') {
|
||||
const definitions = object.definitions;
|
||||
if (definitions) {
|
||||
Object.keys(definitions).forEach(key => {
|
||||
if (key.includes('ref-')) {
|
||||
if (listType === 'list') {
|
||||
dialogActions.push(definitions[key]);
|
||||
} else {
|
||||
const dialog = definitions[key];
|
||||
const dialogTypeName =
|
||||
dialog.type === 'drawer'
|
||||
? '抽屉式弹窗'
|
||||
: dialog.dialogType
|
||||
? '确认对话框'
|
||||
: '弹窗';
|
||||
dialogActions.push({
|
||||
label: `${dialog.title || '-'}(${dialogTypeName})`,
|
||||
value: dialog.$$id
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (
|
||||
(key === 'actionType' && value === 'dialog') ||
|
||||
(key === 'actionType' && value === 'drawer') ||
|
||||
(key === 'actionType' && value === 'confirmDialog')
|
||||
) {
|
||||
const dialogBodyMap = new Map([
|
||||
[
|
||||
'dialog',
|
||||
{
|
||||
title: '弹窗',
|
||||
body: 'dialog'
|
||||
}
|
||||
],
|
||||
[
|
||||
'drawer',
|
||||
{
|
||||
title: '抽屉式弹窗',
|
||||
body: 'drawer'
|
||||
}
|
||||
],
|
||||
[
|
||||
'confirmDialog',
|
||||
{
|
||||
title: '确认对话框',
|
||||
// 兼容历史args参数
|
||||
body: ['dialog', 'args']
|
||||
}
|
||||
]
|
||||
]);
|
||||
let dialogBody = dialogBodyMap.get(value)?.body!;
|
||||
let dialogBodyContent = Array.isArray(dialogBody)
|
||||
? object[dialogBody[0]] || object[dialogBody[1]]
|
||||
: object[dialogBody];
|
||||
export function addModal(schema: any, modal: any, definitions?: any) {
|
||||
schema = {...schema, definitions: {...schema.definitions}};
|
||||
|
||||
if (
|
||||
dialogBodyMap.has(value) &&
|
||||
dialogBodyContent &&
|
||||
!dialogBodyContent.$ref
|
||||
) {
|
||||
if (listType == 'list') {
|
||||
// 没有 type: dialog的历史数据兼容一下
|
||||
dialogActions.push({
|
||||
...dialogBodyContent,
|
||||
type: Array.isArray(dialogBody) ? 'dialog' : dialogBody
|
||||
});
|
||||
} else {
|
||||
// 新建弹窗切换到现有弹窗把自身过滤掉
|
||||
if (!filterId || (filterId && filterId !== dialogBodyContent.id)) {
|
||||
dialogActions.push({
|
||||
label: `${dialogBodyContent?.title || '-'}(${
|
||||
dialogBodyMap.get(value)?.title
|
||||
})`,
|
||||
value: dialogBodyContent.$$id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(value, key) => key.toString().startsWith('__')
|
||||
);
|
||||
return dialogActions;
|
||||
};
|
||||
// 如果有传入definitions,则合并到schema中
|
||||
if (definitions && isPlainObject(definitions)) {
|
||||
schema = mergeDefinitions(schema, definitions, modal);
|
||||
}
|
||||
|
||||
let idx = 1;
|
||||
while (true) {
|
||||
if (!schema.definitions[`modal-ref-${idx}`]) {
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
modal = {
|
||||
type: 'dialog',
|
||||
body: [{type: 'tpl', tpl: '这是一个弹窗'}],
|
||||
title: `未命名弹窗${idx}`,
|
||||
...modal,
|
||||
$$id: guid()
|
||||
} as any;
|
||||
schema.definitions[`modal-ref-${idx}`] = JSONPipeIn(modal);
|
||||
|
||||
return [schema, `modal-ref-${idx}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取弹窗的类型,来源于事件或definitions,由于历史数据可能没有type: dialog,在这里兼容一下
|
||||
* @param json
|
||||
* @param previewDialogId
|
||||
* 弹窗转成 definitions 定义
|
||||
* 这样打开子弹窗的时候,可以把父级的弹窗列表透传到子弹窗里面去
|
||||
*
|
||||
* 这样子弹窗里面打开弹窗才能选到外面的弹窗
|
||||
* @param modals
|
||||
* @param definitions
|
||||
* @returns
|
||||
*/
|
||||
export const getFixDialogType = (json: Schema, dialogId: string) => {
|
||||
const dialogBodyMap = {
|
||||
dialog: 'dialog',
|
||||
drawer: 'drawer',
|
||||
confirmDialog: 'dialog'
|
||||
export function modalsToDefinitions(
|
||||
modals: Array<EditorModalBody>,
|
||||
definitions: any = {}
|
||||
) {
|
||||
let schema = {
|
||||
definitions
|
||||
};
|
||||
let parentSchema = JSONGetParentById(json, dialogId);
|
||||
// 事件中的弹窗
|
||||
if (parentSchema.actionType) {
|
||||
return dialogBodyMap[parentSchema.actionType as keyof typeof dialogBodyMap];
|
||||
}
|
||||
// definitions中的弹窗
|
||||
else {
|
||||
let dialogRefSchema = JSONGetById(parentSchema, dialogId);
|
||||
return dialogRefSchema.type;
|
||||
}
|
||||
};
|
||||
modals.forEach((modal, idx) => {
|
||||
if (modal.$$ref) {
|
||||
schema.definitions[modal.$$ref] = JSONPipeIn(modal);
|
||||
} else {
|
||||
[schema] = addModal(schema, {...modal, $$originId: modal.$$id});
|
||||
}
|
||||
});
|
||||
return schema.definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从子弹窗的 definitions 合并回来到主弹窗的 definitions
|
||||
*
|
||||
* @param originSchema
|
||||
* @param definitions
|
||||
* @param modal
|
||||
* @returns
|
||||
*/
|
||||
export function mergeDefinitions(
|
||||
originSchema: any,
|
||||
definitions: any,
|
||||
modal: any
|
||||
) {
|
||||
const refs: Array<string> = [];
|
||||
JSONTraverse(modal, (value, key) => {
|
||||
if (key === '$ref') {
|
||||
refs.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
let schema = originSchema;
|
||||
Object.keys(definitions).forEach(key => {
|
||||
// 弹窗里面用到了才更新
|
||||
if (!refs.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 要修改就复制一份,避免污染原始数据
|
||||
if (schema === originSchema) {
|
||||
schema = {...schema, definitions: {...schema.definitions}};
|
||||
}
|
||||
|
||||
const {$$originId, ...def} = definitions[key];
|
||||
|
||||
if ($$originId) {
|
||||
const parent = JSONGetParentById(schema, $$originId);
|
||||
if (!parent) {
|
||||
throw new Error('Can not find modal action.');
|
||||
}
|
||||
|
||||
const modalType = def.type === 'drawer' ? 'drawer' : 'dialog';
|
||||
schema = JSONUpdate(schema, parent.$$id, {
|
||||
...parent,
|
||||
__actionModals: undefined,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
actionType: def.actionType ?? modalType,
|
||||
[modalType]: JSONPipeIn({
|
||||
$ref: key
|
||||
})
|
||||
});
|
||||
schema.definitions[key] = JSONPipeIn(def);
|
||||
} else {
|
||||
schema.definitions[key] = JSONPipeIn(def);
|
||||
}
|
||||
});
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
@ -15,19 +15,18 @@ import {InputBoxProps} from '../../../amis-ui/src/components/InputBox';
|
||||
import cx from 'classnames';
|
||||
import {getEnv} from 'mobx-state-tree';
|
||||
import {pick} from 'lodash';
|
||||
import {LocaleProps} from 'amis-core';
|
||||
|
||||
/** 语料 key 正则表达式 */
|
||||
export const corpusKeyReg =
|
||||
/^i18n:[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/;
|
||||
|
||||
interface InputTextI18nProps extends InputBoxProps {
|
||||
interface InputTextI18nProps extends InputBoxProps, LocaleProps {
|
||||
classPrefix: string;
|
||||
i18nEnabled?: boolean;
|
||||
disabled?: boolean;
|
||||
locale?: string;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
translate?: (value: any) => any;
|
||||
onI18nChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2.1",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -41,7 +41,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"amis-editor-core": "^6.1.0",
|
||||
"amis-editor-core": "^6.2.1",
|
||||
"amis-postcss": "1.0.0",
|
||||
"amis-theme-editor-helper": "*",
|
||||
"i18n-runtime": "*",
|
||||
|
69
packages/amis-editor/src/component/ModalSettingPanel.tsx
Normal file
69
packages/amis-editor/src/component/ModalSettingPanel.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import {
|
||||
EditorManager,
|
||||
EditorStoreType,
|
||||
SchemaFrom,
|
||||
getSchemaTpl
|
||||
} from 'amis-editor-core';
|
||||
import {observer} from 'mobx-react';
|
||||
import React from 'react';
|
||||
|
||||
export interface ModalSettingPanelProps {
|
||||
store: EditorStoreType;
|
||||
manager: EditorManager;
|
||||
popOverContainer: any;
|
||||
}
|
||||
|
||||
export default observer(function ({
|
||||
store,
|
||||
manager,
|
||||
popOverContainer
|
||||
}: ModalSettingPanelProps) {
|
||||
const body = React.useMemo(() => {
|
||||
return [
|
||||
getSchemaTpl('collapseGroup', [
|
||||
{
|
||||
title: '弹窗入参',
|
||||
body: [
|
||||
{
|
||||
type: 'json-schema-editor',
|
||||
name: 'inputParams',
|
||||
label: false,
|
||||
mini: true,
|
||||
disabledTypes: ['array'],
|
||||
evalMode: true,
|
||||
// variables: '${variables}',
|
||||
addButtonText: '添加入参'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
];
|
||||
}, []);
|
||||
const node = store.root.firstChild;
|
||||
const value = store.getValueOf(node.id);
|
||||
const onChange = React.useCallback((value: any, diff: any) => {
|
||||
manager.panelChangeValue(value, diff, undefined, node.id);
|
||||
}, []);
|
||||
const env = React.useMemo(
|
||||
() => ({...manager.env, session: 'left-panel-form'}),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ae-Outline-panel">
|
||||
<div className="panel-header">弹窗参数</div>
|
||||
|
||||
<SchemaFrom
|
||||
body={body}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
submitOnChange={true}
|
||||
env={env}
|
||||
// popOverContainer={popOverContainer}
|
||||
node={node}
|
||||
manager={manager}
|
||||
justify={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
3
packages/amis-editor/src/icons/form/input-signature.svg
Normal file
3
packages/amis-editor/src/icons/form/input-signature.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg t="1709803052887" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1488" width="16px" height="16px">
|
||||
<path d="M793.6 960H230.4C140.8 960 64 883.2 64 793.6V230.4C64 140.8 140.8 64 230.4 64h563.2C883.2 64 960 140.8 960 230.4v563.2c0 89.6-76.8 166.4-166.4 166.4z m108.8-729.6c0-64-51.2-115.2-115.2-115.2H230.4c-64 0-115.2 51.2-115.2 115.2v563.2c0 64 51.2 115.2 115.2 115.2h563.2c64 0 115.2-51.2 115.2-115.2V230.4z m-499.2 563.2h390.4v57.6H403.2v-57.6zM512 678.4v57.6H288v-19.2l-19.2 19.2-38.4-38.4 467.2-467.2 38.4 38.4-409.6 409.6H512z" fill="currentColor" p-id="1489"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 631 B |
@ -105,6 +105,7 @@ import inputRepeat from './form/input-repeat.svg';
|
||||
import inputRichText from './form/input-rich-text.svg';
|
||||
import inputTag from './form/input-tag.svg';
|
||||
import inputText from './form/input-text.svg';
|
||||
import InputSignature from './form/input-signature.svg';
|
||||
|
||||
import inputTime from './form/input-time.svg';
|
||||
import inputTree from './form/input-tree.svg';
|
||||
@ -158,6 +159,7 @@ import layout_fixed_top from './layout/layout-fixed-top.svg';
|
||||
// 其他类 icon
|
||||
import inputAddFx from './other/+fx.svg';
|
||||
import inputFx from './other/fx.svg';
|
||||
import modalSetting from './other/modal-setting.svg';
|
||||
|
||||
// 属性配置面板/显示类型
|
||||
import block from './display/block.svg';
|
||||
@ -282,6 +284,7 @@ registerIcon('input-rich-text-plugin', inputRichText);
|
||||
registerIcon('input-tag-plugin', inputTag);
|
||||
registerIcon('input-text-plugin', inputText);
|
||||
registerIcon('input-time-range-plugin', inputTimeRange);
|
||||
registerIcon('input-signature-plugin', InputSignature);
|
||||
|
||||
registerIcon('input-time-plugin', inputTime);
|
||||
registerIcon('input-tree-plugin', inputTree);
|
||||
@ -313,6 +316,7 @@ registerIcon('formula-plugin', formula);
|
||||
registerIcon('property-sheet-plugin', propertySheet);
|
||||
registerIcon('tooltip-plugin', tooltip);
|
||||
registerIcon('divider-plugin', divider);
|
||||
registerIcon('modal-setting', modalSetting);
|
||||
|
||||
// 常见布局组件 icon x 13
|
||||
registerIcon('layout-absolute-plugin', layout_absolute);
|
||||
|
11
packages/amis-editor/src/icons/other/modal-setting.svg
Normal file
11
packages/amis-editor/src/icons/other/modal-setting.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.2 KiB |
@ -32,6 +32,7 @@ import './renderer/style-control/BoxShadow';
|
||||
import './renderer/style-control/Background';
|
||||
import './renderer/style-control/Display';
|
||||
import './renderer/style-control/InsetBoxModel';
|
||||
import './renderer/style-control/FlexLayout';
|
||||
import './renderer/RangePartsControl';
|
||||
import './renderer/DataBindingControl';
|
||||
import './renderer/DataMappingControl';
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
|
||||
import {diff, JSONPipeOut, repeatArray} from 'amis-editor-core';
|
||||
import set from 'lodash/set';
|
||||
import merge from 'lodash/merge';
|
||||
import {escapeFormula, resolveArrayDatasource} from '../util';
|
||||
|
||||
export class CardsPlugin extends BasePlugin {
|
||||
@ -574,7 +575,12 @@ export class CardsPlugin extends BasePlugin {
|
||||
props.className = `${props.className || ''} ae-Editor-list`;
|
||||
props.itemsClassName = `${props.itemsClassName || ''} cards-items`;
|
||||
if (props.card && !props.card.className?.includes('listItem')) {
|
||||
props.card.className = `${props.card.className || ''} ae-Editor-listItem`;
|
||||
props.card = merge(
|
||||
{
|
||||
className: `${props.card.className || ''} ae-Editor-listItem`
|
||||
},
|
||||
props.card
|
||||
);
|
||||
}
|
||||
|
||||
// 列表类型内的文本元素显示原始公式
|
||||
|
@ -1,3 +1,6 @@
|
||||
import React from 'react';
|
||||
import {Button} from 'amis';
|
||||
import {Icon} from 'amis-editor-core';
|
||||
import {
|
||||
ActiveEventContext,
|
||||
BaseEventContext,
|
||||
@ -11,6 +14,8 @@ import {
|
||||
RendererPluginEvent
|
||||
} from 'amis-editor-core';
|
||||
import {getEventControlConfig} from '../renderer/event-control';
|
||||
import {EditorNodeType} from 'packages/amis-editor-core/lib';
|
||||
import {defaultFlexColumnSchema} from './Layout/FlexPluginBase';
|
||||
|
||||
export class ContainerPlugin extends LayoutBasePlugin {
|
||||
static id = 'ContainerPlugin';
|
||||
@ -32,8 +37,12 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
type: 'container',
|
||||
body: [],
|
||||
style: {
|
||||
position: 'static',
|
||||
display: 'block'
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
inset: 'auto',
|
||||
flexWrap: 'nowrap',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
size: 'none',
|
||||
wrapperBody: false
|
||||
@ -123,6 +132,183 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
}
|
||||
];
|
||||
|
||||
onActive(event: PluginEvent<ActiveEventContext>) {
|
||||
const context = event.context;
|
||||
|
||||
if (context.info?.plugin !== this || !context.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = context.node!;
|
||||
const isFlexItem = this.manager?.isFlexItem(node.id);
|
||||
if (isFlexItem && context.node.parent?.children?.length > 1) {
|
||||
let isColumnFlex = String(node.schema?.style?.flexDirection).includes(
|
||||
'column'
|
||||
);
|
||||
console.log(
|
||||
'isColumnFlex',
|
||||
isColumnFlex,
|
||||
node.schema?.style?.flexDirection
|
||||
);
|
||||
// context?.node.setHeightMutable(isColumnFlex);
|
||||
context?.node.setWidthMutable(!isColumnFlex);
|
||||
}
|
||||
}
|
||||
|
||||
afterUpdate(event: PluginEvent<ActiveEventContext>) {
|
||||
const node = event.context?.node;
|
||||
}
|
||||
|
||||
onWidthChangeStart(
|
||||
event: PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const context = event.context;
|
||||
const node = context.node;
|
||||
const host = node.host;
|
||||
|
||||
const dom = context.dom;
|
||||
const parent = dom.parentElement as HTMLElement;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
console.log('on width change start');
|
||||
const resizer = context.resizer;
|
||||
const frameRect = parent.getBoundingClientRect();
|
||||
const rect = dom.getBoundingClientRect();
|
||||
const isFlexItem = this.manager?.isFlexItem(node.id);
|
||||
const schema = node.schema;
|
||||
const index = node.index;
|
||||
const isFlexSize =
|
||||
schema.style?.flex === '1 1 auto' &&
|
||||
(schema.style?.position === 'static' ||
|
||||
schema.style?.position === 'relative');
|
||||
|
||||
let flexGrow = 1;
|
||||
let width = 0;
|
||||
|
||||
event.setData({
|
||||
onMove: (e: MouseEvent) => {
|
||||
const children = parent.children;
|
||||
|
||||
width = e.pageX - rect.left;
|
||||
flexGrow = Math.max(
|
||||
1,
|
||||
Math.min(12, Math.round((12 * width) / frameRect.width))
|
||||
);
|
||||
|
||||
resizer.setAttribute(
|
||||
'data-value',
|
||||
isFlexSize ? `${flexGrow}` : width + 'px'
|
||||
);
|
||||
|
||||
if (isFlexSize) {
|
||||
// 需重新计算flex下各子组件占比,按照12等分计算
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (i !== index) {
|
||||
let width = children[i].clientWidth;
|
||||
if (width > 0) {
|
||||
let grow = Math.max(
|
||||
1,
|
||||
Math.min(12, Math.round((12 * width) / frameRect.width))
|
||||
);
|
||||
host.children[i]?.updateState({
|
||||
style: {
|
||||
...host.children[i].schema.style,
|
||||
flexGrow: grow
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
node.updateState({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
flexGrow: +flexGrow
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isFlexItem) {
|
||||
node.updateState({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
flex: '0 0 150px',
|
||||
flexBasis: `${width}px`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
node.updateState({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
width: `${width}px`
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
node.calculateHighlightBox();
|
||||
});
|
||||
},
|
||||
onEnd: () => {
|
||||
resizer.removeAttribute('data-value');
|
||||
|
||||
if (isFlexSize) {
|
||||
host?.children.forEach((item: EditorNodeType) => {
|
||||
item.updateSchema({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
flexGrow: item.state.style?.flexGrow ?? 1
|
||||
}
|
||||
});
|
||||
item.updateState({}, true);
|
||||
});
|
||||
} else {
|
||||
if (isFlexItem) {
|
||||
node.updateSchema({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
flex: `0 0 150px`,
|
||||
flexBasis: `${width}px`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
node.updateSchema({
|
||||
style: {
|
||||
...node.schema.style,
|
||||
width: `${width}px`
|
||||
}
|
||||
});
|
||||
}
|
||||
node.updateState({}, true);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
node.calculateHighlightBox();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onHeightChangeStart(
|
||||
event: PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
console.log('on height change start');
|
||||
// return this.onSizeChangeStart(event, 'vertical');
|
||||
}
|
||||
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
const curRendererSchema = context?.schema;
|
||||
const isRowContent =
|
||||
@ -132,6 +318,19 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
const isFreeContainer = curRendererSchema?.isFreeContainer || false;
|
||||
const isFlexItem = this.manager?.isFlexItem(context?.id);
|
||||
const isFlexColumnItem = this.manager?.isFlexColumnItem(context?.id);
|
||||
const node = context.node;
|
||||
|
||||
const parent = node.parent?.schema;
|
||||
const draggableContainer = this.manager.draggableContainer(context?.id);
|
||||
const canAppendSiblings =
|
||||
parent &&
|
||||
isFlexItem &&
|
||||
!draggableContainer &&
|
||||
this.manager?.canAppendSiblings();
|
||||
|
||||
const newItemSchema = isFlexColumnItem
|
||||
? defaultFlexColumnSchema('', false)
|
||||
: defaultFlexColumnSchema();
|
||||
|
||||
const displayTpl = [
|
||||
getSchemaTpl('layout:display'),
|
||||
@ -154,53 +353,122 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
{
|
||||
title: '属性',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
// {
|
||||
// title: '基本',
|
||||
// body: [
|
||||
// {
|
||||
// name: 'wrapperComponent',
|
||||
// label: '容器标签',
|
||||
// type: 'select',
|
||||
// searchable: true,
|
||||
// options: [
|
||||
// 'div',
|
||||
// 'p',
|
||||
// 'h1',
|
||||
// 'h2',
|
||||
// 'h3',
|
||||
// 'h4',
|
||||
// 'h5',
|
||||
// 'h6',
|
||||
// 'article',
|
||||
// 'aside',
|
||||
// 'code',
|
||||
// 'footer',
|
||||
// 'header',
|
||||
// 'section'
|
||||
// ],
|
||||
// pipeIn: defaultValue('div'),
|
||||
// validations: {
|
||||
// isAlphanumeric: true,
|
||||
// matchRegexp: '/^(?!.*script).*$/' // 禁用一下script标签
|
||||
// },
|
||||
// validationErrors: {
|
||||
// isAlpha: 'HTML标签不合法,请重新输入',
|
||||
// matchRegexp: 'HTML标签不合法,请重新输入'
|
||||
// },
|
||||
// validateOnChange: false
|
||||
// },
|
||||
// getSchemaTpl('layout:padding')
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
title: '基本',
|
||||
body: [
|
||||
{
|
||||
name: 'wrapperComponent',
|
||||
label: '容器标签',
|
||||
type: 'select',
|
||||
searchable: true,
|
||||
options: [
|
||||
'div',
|
||||
'p',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'article',
|
||||
'aside',
|
||||
'code',
|
||||
'footer',
|
||||
'header',
|
||||
'section'
|
||||
],
|
||||
pipeIn: defaultValue('div'),
|
||||
validations: {
|
||||
isAlphanumeric: true,
|
||||
matchRegexp: '/^(?!.*script).*$/' // 禁用一下script标签
|
||||
},
|
||||
validationErrors: {
|
||||
isAlpha: 'HTML标签不合法,请重新输入',
|
||||
matchRegexp: 'HTML标签不合法,请重新输入'
|
||||
},
|
||||
validateOnChange: false
|
||||
canAppendSiblings && {
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
className: 'grid grid-cols-2 gap-4 mb-4',
|
||||
body: [
|
||||
{
|
||||
children: (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
this.manager.appendSiblingSchema(
|
||||
newItemSchema,
|
||||
true,
|
||||
true
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
className="icon"
|
||||
icon={
|
||||
isFlexColumnItem
|
||||
? 'top-arrow-to-top'
|
||||
: 'left-arrow-to-left'
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
{isFlexColumnItem ? '上方' : '左侧'}插入一
|
||||
{isFlexColumnItem ? '行' : '列'}
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
this.manager.appendSiblingSchema(
|
||||
newItemSchema,
|
||||
false,
|
||||
true
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
className="icon"
|
||||
icon={
|
||||
isFlexColumnItem
|
||||
? 'arrow-to-bottom'
|
||||
: 'arrow-to-right'
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
{isFlexColumnItem ? '下方' : '右侧'}插入一
|
||||
{isFlexColumnItem ? '行' : '列'}
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
getSchemaTpl('layout:padding')
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '布局',
|
||||
body: [
|
||||
getSchemaTpl('layout:position', {
|
||||
visibleOn: '!data.stickyStatus'
|
||||
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: 'themeCss.baseControlClassName.padding-and-margin:default'
|
||||
}),
|
||||
getSchemaTpl('layout:originPosition'),
|
||||
getSchemaTpl('layout:inset', {
|
||||
mode: 'vertical'
|
||||
getSchemaTpl('theme:border', {
|
||||
name: `themeCss.baseControlClassName.border:default`
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
name: 'themeCss.baseControlClassName.background:default',
|
||||
label: '背景',
|
||||
needCustom: true,
|
||||
needGradient: true,
|
||||
needImage: true,
|
||||
labelMode: 'input'
|
||||
}),
|
||||
|
||||
// 自由容器不需要 display 相关配置项
|
||||
@ -263,7 +531,12 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
getSchemaTpl('layout:isFixedWidth', {
|
||||
visibleOn: `${!isFlexItem || isFlexColumnItem}`,
|
||||
onChange: (value: boolean) => {
|
||||
context?.node.setWidthMutable(value);
|
||||
if (
|
||||
!isFlexItem ||
|
||||
context.node.parent?.children?.length > 1
|
||||
) {
|
||||
context?.node.setWidthMutable(value);
|
||||
}
|
||||
}
|
||||
}),
|
||||
getSchemaTpl('layout:width', {
|
||||
@ -295,15 +568,27 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
'data.style && data.style.display !== "flex" && data.style.display !== "inline-flex"'
|
||||
})
|
||||
: null,
|
||||
getSchemaTpl('layout:z-index'),
|
||||
getSchemaTpl('layout:z-index')
|
||||
]
|
||||
},
|
||||
getSchemaTpl('status'),
|
||||
{
|
||||
title: '高级',
|
||||
body: [
|
||||
getSchemaTpl('layout:position', {
|
||||
visibleOn: '!data.stickyStatus'
|
||||
}),
|
||||
getSchemaTpl('layout:originPosition'),
|
||||
getSchemaTpl('layout:inset', {
|
||||
mode: 'vertical'
|
||||
}),
|
||||
getSchemaTpl('layout:sticky', {
|
||||
visibleOn:
|
||||
'data.style && (data.style.position !== "fixed" && data.style.position !== "absolute")'
|
||||
}),
|
||||
getSchemaTpl('layout:stickyPosition')
|
||||
]
|
||||
},
|
||||
getSchemaTpl('status')
|
||||
}
|
||||
])
|
||||
},
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Button, Drawer, Modal} from 'amis-ui';
|
||||
import {Button, Drawer, Icon, Modal} from 'amis-ui';
|
||||
import {
|
||||
registerEditorPlugin,
|
||||
BaseEventContext,
|
||||
@ -11,12 +11,19 @@ import {
|
||||
defaultValue,
|
||||
EditorNodeType,
|
||||
isEmpty,
|
||||
getI18nEnabled
|
||||
getI18nEnabled,
|
||||
BuildPanelEventContext,
|
||||
BasicPanelItem,
|
||||
PluginEvent,
|
||||
ChangeEventContext,
|
||||
JSONPipeOut
|
||||
} from 'amis-editor-core';
|
||||
import {getEventControlConfig} from '../renderer/event-control/helper';
|
||||
import omit from 'lodash/omit';
|
||||
import type {RendererConfig, Schema} from 'amis-core';
|
||||
import {ModalProps} from 'amis-ui/src/components/Modal';
|
||||
import ModalSettingPanel from '../component/ModalSettingPanel';
|
||||
import find from 'lodash/find';
|
||||
|
||||
interface InlineModalProps extends ModalProps {
|
||||
type: string;
|
||||
@ -139,19 +146,45 @@ export class DialogPlugin extends BasePlugin {
|
||||
{
|
||||
title: '基本',
|
||||
body: [
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
{
|
||||
type: 'input-text',
|
||||
label: '组件名称',
|
||||
name: 'editorSetting.displayName'
|
||||
},
|
||||
|
||||
{
|
||||
type: 'radios',
|
||||
label: '弹出方式',
|
||||
name: 'actionType',
|
||||
pipeIn: (value: any, store: any, data: any) =>
|
||||
value ?? data.type,
|
||||
inline: false,
|
||||
options: [
|
||||
{
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
},
|
||||
{
|
||||
label: '抽屉',
|
||||
value: 'drawer'
|
||||
},
|
||||
{
|
||||
label: '确认对话框',
|
||||
value: 'confirmDialog'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: '标题',
|
||||
type: 'input-text',
|
||||
name: 'title'
|
||||
},
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
{
|
||||
label: '确认按钮文案',
|
||||
type: 'input-text',
|
||||
name: 'confirmText'
|
||||
},
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
{
|
||||
label: '取消按钮文案',
|
||||
type: 'input-text',
|
||||
@ -223,12 +256,43 @@ export class DialogPlugin extends BasePlugin {
|
||||
{
|
||||
title: '基本',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
label: '组件名称',
|
||||
name: 'editorSetting.displayName'
|
||||
},
|
||||
|
||||
{
|
||||
type: 'radios',
|
||||
label: '弹出方式',
|
||||
name: 'actionType',
|
||||
pipeIn: (value: any, store: any, data: any) =>
|
||||
value ?? data.type,
|
||||
inline: false,
|
||||
options: [
|
||||
{
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
},
|
||||
{
|
||||
label: '抽屉',
|
||||
value: 'drawer'
|
||||
},
|
||||
{
|
||||
label: '确认对话框',
|
||||
value: 'confirmDialog'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
|
||||
{
|
||||
label: '标题',
|
||||
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
|
||||
name: 'title'
|
||||
},
|
||||
|
||||
getSchemaTpl('switch', {
|
||||
label: '展示关闭按钮',
|
||||
name: 'showCloseButton',
|
||||
@ -474,6 +538,37 @@ export class DialogPlugin extends BasePlugin {
|
||||
]);
|
||||
};
|
||||
|
||||
afterUpdate(event: PluginEvent<ChangeEventContext>) {
|
||||
const context = event.context;
|
||||
|
||||
// 当弹出方式改变的时候,切换渲染器类型
|
||||
if (
|
||||
context.info.renderer.type &&
|
||||
['dialog', 'drawer'].includes(context.info.renderer.type) &&
|
||||
context.diff?.some(change => change.path?.join('.') === 'actionType')
|
||||
) {
|
||||
const change: any = find(
|
||||
context.diff,
|
||||
change => change.path?.join('.') === 'actionType'
|
||||
)!;
|
||||
|
||||
let value = change?.rhs;
|
||||
const newType = value === 'drawer' ? 'drawer' : 'dialog';
|
||||
|
||||
if (
|
||||
newType !== context.schema.type &&
|
||||
this.manager.replaceChild(context.id, {
|
||||
...context.schema,
|
||||
type: value === 'drawer' ? 'drawer' : 'dialog'
|
||||
})
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.manager.rebuild();
|
||||
}, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildSubRenderers() {}
|
||||
|
||||
async buildDataSchemas(
|
||||
@ -483,7 +578,10 @@ export class DialogPlugin extends BasePlugin {
|
||||
) {
|
||||
const renderer = this.manager.store.getNodeById(node.id)?.getComponent();
|
||||
const data = omit(renderer.props.$schema.data, '$$id');
|
||||
let dataSchema: any = {};
|
||||
const inputParams = JSONPipeOut(renderer.props.$schema.inputParams);
|
||||
let dataSchema: any = {
|
||||
...inputParams?.properties
|
||||
};
|
||||
|
||||
if (renderer.props.$schema.data === undefined || !isEmpty(data)) {
|
||||
// 静态数据
|
||||
@ -515,6 +613,7 @@ export class DialogPlugin extends BasePlugin {
|
||||
return {
|
||||
$id: 'dialog',
|
||||
type: 'object',
|
||||
...inputParams,
|
||||
title: node.schema?.label || node.schema?.name,
|
||||
properties: dataSchema
|
||||
};
|
||||
@ -548,6 +647,33 @@ export class DialogPlugin extends BasePlugin {
|
||||
].filter((item: any) => item)
|
||||
};
|
||||
}
|
||||
|
||||
buildEditorPanel(
|
||||
context: BuildPanelEventContext,
|
||||
panels: Array<BasicPanelItem>
|
||||
) {
|
||||
if (
|
||||
this.manager.store.isSubEditor &&
|
||||
['dialog', 'drawer'].includes(this.manager.store.schema?.type)
|
||||
) {
|
||||
panels.push({
|
||||
key: 'modal-setting',
|
||||
icon: '', // 'fa fa-code',
|
||||
title: (
|
||||
<span
|
||||
className="editor-tab-icon editor-tab-s-icon"
|
||||
editor-tooltip="弹窗参数"
|
||||
>
|
||||
<Icon icon="modal-setting" />
|
||||
</span>
|
||||
),
|
||||
position: 'left',
|
||||
component: ModalSettingPanel,
|
||||
order: -99999
|
||||
});
|
||||
}
|
||||
super.buildEditorPanel(context, panels);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorPlugin(DialogPlugin);
|
||||
|
@ -9,7 +9,8 @@ import {
|
||||
noop,
|
||||
EditorNodeType,
|
||||
isEmpty,
|
||||
getI18nEnabled
|
||||
getI18nEnabled,
|
||||
JSONPipeOut
|
||||
} from 'amis-editor-core';
|
||||
import {getEventControlConfig} from '../renderer/event-control/helper';
|
||||
import {tipedLabel} from 'amis-editor-core';
|
||||
@ -124,6 +125,35 @@ export class DrawerPlugin extends BasePlugin {
|
||||
{
|
||||
title: '基本',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
label: '组件名称',
|
||||
name: 'editorSetting.displayName'
|
||||
},
|
||||
|
||||
{
|
||||
type: 'radios',
|
||||
label: '弹出方式',
|
||||
name: 'actionType',
|
||||
pipeIn: (value: any, store: any, data: any) =>
|
||||
value ?? data.type,
|
||||
inline: false,
|
||||
options: [
|
||||
{
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
},
|
||||
{
|
||||
label: '抽屉',
|
||||
value: 'drawer'
|
||||
},
|
||||
{
|
||||
label: '确认对话框',
|
||||
value: 'confirmDialog'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
{
|
||||
label: '标题',
|
||||
@ -355,7 +385,10 @@ export class DrawerPlugin extends BasePlugin {
|
||||
) {
|
||||
const renderer = this.manager.store.getNodeById(node.id)?.getComponent();
|
||||
const data = omit(renderer.props.$schema.data, '$$id');
|
||||
let dataSchema: any = {};
|
||||
const inputParams = JSONPipeOut(renderer.props.$schema.inputParams);
|
||||
let dataSchema: any = {
|
||||
...inputParams?.properties
|
||||
};
|
||||
|
||||
if (renderer.props.$schema.data === undefined || !isEmpty(data)) {
|
||||
// 静态数据
|
||||
@ -387,6 +420,7 @@ export class DrawerPlugin extends BasePlugin {
|
||||
return {
|
||||
$id: 'drawer',
|
||||
type: 'object',
|
||||
...inputParams,
|
||||
title: node.schema?.label || node.schema?.name,
|
||||
properties: dataSchema
|
||||
};
|
||||
|
@ -4,7 +4,8 @@ import {EditorNodeType, registerEditorPlugin} from 'amis-editor-core';
|
||||
import {BaseEventContext, BasePlugin} from 'amis-editor-core';
|
||||
import {getSchemaTpl} from 'amis-editor-core';
|
||||
import {escapeFormula} from '../util';
|
||||
import {set} from 'lodash';
|
||||
import merge from 'lodash/merge';
|
||||
import set from 'lodash/set';
|
||||
|
||||
export class EachPlugin extends BasePlugin {
|
||||
static id = 'EachPlugin';
|
||||
@ -362,10 +363,13 @@ export class EachPlugin extends BasePlugin {
|
||||
props.value = [{}, {}];
|
||||
|
||||
props.className = `${props.className || ''} ae-Editor-list`;
|
||||
if (props.items && !props.items.className?.includes('listItem')) {
|
||||
props.items.className = `${
|
||||
props.items.className || ''
|
||||
} ae-Editor-eachItem`;
|
||||
if (props.items && !props.items.className?.includes('eachItem')) {
|
||||
props.items = merge(
|
||||
{
|
||||
className: `${props.items.className || ''} ae-Editor-eachItem`
|
||||
},
|
||||
props.items
|
||||
);
|
||||
}
|
||||
|
||||
return props;
|
||||
|
155
packages/amis-editor/src/plugin/Form/InputSignature.tsx
Normal file
155
packages/amis-editor/src/plugin/Form/InputSignature.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import {EditorNodeType, getSchemaTpl} from 'amis-editor-core';
|
||||
import {registerEditorPlugin} from 'amis-editor-core';
|
||||
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
|
||||
import {ValidatorTag} from '../../validator';
|
||||
import {getEventControlConfig} from '../../renderer/event-control/helper';
|
||||
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
|
||||
|
||||
export class SignaturePlugin extends BasePlugin {
|
||||
static id = 'SignaturePlugin';
|
||||
// 关联渲染器名字
|
||||
rendererName = 'input-signature';
|
||||
$schema = '/schemas/InputSignatureSchema.json';
|
||||
|
||||
// 组件名称
|
||||
name = '签名面板';
|
||||
isBaseComponent = true;
|
||||
icon = 'fa fa-star-o';
|
||||
pluginIcon = 'input-signature-plugin';
|
||||
description = '手写签名面板';
|
||||
docLink = '/amis/zh-CN/components/form/input-signature';
|
||||
tags = ['表单项'];
|
||||
scaffold = {
|
||||
type: 'input-signature',
|
||||
label: '签名',
|
||||
name: 'signature'
|
||||
};
|
||||
previewSchema: any = {
|
||||
type: 'form',
|
||||
className: 'text-left',
|
||||
mode: 'horizontal',
|
||||
wrapWithPanel: false,
|
||||
body: [
|
||||
{
|
||||
...this.scaffold,
|
||||
embed: true
|
||||
}
|
||||
]
|
||||
};
|
||||
notRenderFormZone = true;
|
||||
|
||||
panelTitle = '签名面板';
|
||||
|
||||
// 事件定义
|
||||
events: RendererPluginEvent[] = [];
|
||||
|
||||
// 动作定义
|
||||
actions: RendererPluginAction[] = [];
|
||||
|
||||
panelJustify = true;
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
return getSchemaTpl('tabs', [
|
||||
{
|
||||
title: '属性',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
{
|
||||
title: '基本',
|
||||
body: [
|
||||
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
|
||||
getSchemaTpl('formItemName', {
|
||||
required: true
|
||||
}),
|
||||
getSchemaTpl('label'),
|
||||
getSchemaTpl('labelRemark'),
|
||||
{
|
||||
name: 'embed',
|
||||
label: '弹窗展示',
|
||||
type: 'ae-switch-more',
|
||||
mode: 'normal',
|
||||
formType: 'extend',
|
||||
title: '弹窗展示',
|
||||
bulk: true,
|
||||
form: {
|
||||
body: [
|
||||
{
|
||||
label: '按钮文案',
|
||||
name: 'embedBtnLabel',
|
||||
type: 'input-text'
|
||||
},
|
||||
getSchemaTpl('icon', {
|
||||
label: '按钮图标',
|
||||
name: 'embedBtnIcon'
|
||||
}),
|
||||
{
|
||||
label: '确认按钮',
|
||||
name: 'embedConfirmLabel',
|
||||
type: 'input-text'
|
||||
},
|
||||
{
|
||||
label: '确认按钮',
|
||||
name: 'ebmedCancelLabel',
|
||||
type: 'input-text'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '确认按钮',
|
||||
name: 'confirmBtnLabel',
|
||||
type: 'input-text'
|
||||
},
|
||||
{
|
||||
label: '撤销按钮',
|
||||
name: 'undoBtnLabel',
|
||||
type: 'input-text'
|
||||
},
|
||||
{
|
||||
label: '清空按钮',
|
||||
name: 'clearBtnLabel',
|
||||
type: 'input-text'
|
||||
}
|
||||
]
|
||||
},
|
||||
getSchemaTpl('status', {
|
||||
isFormItem: true,
|
||||
unsupportStatic: true
|
||||
}),
|
||||
getSchemaTpl('validation', {
|
||||
tag: ValidatorTag.Check
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
title: '外观',
|
||||
body: [
|
||||
getSchemaTpl('collapseGroup', [
|
||||
getSchemaTpl('style:formItem', {
|
||||
renderer: context.info.renderer
|
||||
}),
|
||||
getSchemaTpl('style:classNames', {unsupportStatic: true})
|
||||
])
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '事件',
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getSchemaTpl('eventControl', {
|
||||
name: 'onEvent',
|
||||
...getEventControlConfig(this.manager, context)
|
||||
})
|
||||
]
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
buildDataSchemas(node: EditorNodeType, region: EditorNodeType) {
|
||||
return {
|
||||
type: 'number',
|
||||
title: node.schema?.label || node.schema?.name,
|
||||
originalValue: node.schema?.value // 记录原始值,循环引用检测需要
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorPlugin(SignaturePlugin);
|
@ -1,4 +1,9 @@
|
||||
import {getSchemaTpl, valuePipeOut} from 'amis-editor-core';
|
||||
import {
|
||||
defaultValue,
|
||||
getSchemaTpl,
|
||||
undefinedPipeOut,
|
||||
valuePipeOut
|
||||
} from 'amis-editor-core';
|
||||
import {registerEditorPlugin, tipedLabel} from 'amis-editor-core';
|
||||
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
|
||||
import {ValidatorTag} from '../../validator';
|
||||
@ -9,7 +14,8 @@ import type {
|
||||
RendererPluginAction,
|
||||
RendererPluginEvent
|
||||
} from 'amis-editor-core';
|
||||
import {isExpression} from 'amis-core';
|
||||
import {isExpression, isPureVariable} from 'amis-core';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
export class SwitchControlPlugin extends BasePlugin {
|
||||
static id = 'SwitchControlPlugin';
|
||||
@ -123,49 +129,58 @@ export class SwitchControlPlugin extends BasePlugin {
|
||||
body: [getSchemaTpl('onText'), getSchemaTpl('offText')]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
type: 'ae-switch-more',
|
||||
bulk: true,
|
||||
hiddenOnDefault: false,
|
||||
mode: 'normal',
|
||||
label: tipedLabel(
|
||||
'值格式',
|
||||
'默认勾选后的值 true,未勾选的值 false'
|
||||
),
|
||||
label: '值格式',
|
||||
formType: 'extend',
|
||||
form: {
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
label: '勾选后的值',
|
||||
type: 'ae-valueFormat',
|
||||
name: 'trueValue',
|
||||
value: true,
|
||||
pipeOut: valuePipeOut,
|
||||
label: '开启时',
|
||||
pipeIn: defaultValue(true),
|
||||
pipeOut: undefinedPipeOut,
|
||||
onChange: (
|
||||
value: string,
|
||||
oldValue: string,
|
||||
value: any,
|
||||
oldValue: any,
|
||||
model: any,
|
||||
form: any
|
||||
) => {
|
||||
if (oldValue === form.getValueByName('value')) {
|
||||
form.setValueByName('value', value);
|
||||
const {value: defaultValue, trueValue} =
|
||||
form?.data || {};
|
||||
if (isPureVariable(defaultValue)) {
|
||||
return;
|
||||
}
|
||||
if (trueValue === defaultValue && trueValue !== value) {
|
||||
form.setValues({value});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
label: '未勾选的值',
|
||||
type: 'ae-valueFormat',
|
||||
name: 'falseValue',
|
||||
value: false,
|
||||
pipeOut: valuePipeOut,
|
||||
label: '关闭时',
|
||||
pipeIn: defaultValue(false),
|
||||
pipeOut: undefinedPipeOut,
|
||||
onChange: (
|
||||
value: string,
|
||||
oldValue: string,
|
||||
value: any,
|
||||
oldValue: any,
|
||||
model: any,
|
||||
form: any
|
||||
) => {
|
||||
if (oldValue === form.getValueByName('value')) {
|
||||
form.setValueByName('value', value);
|
||||
const {value: defaultValue, falseValue} =
|
||||
form?.data || {};
|
||||
if (isPureVariable(defaultValue)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
falseValue === defaultValue &&
|
||||
falseValue !== value
|
||||
) {
|
||||
form.setValues({value});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,18 +204,18 @@ export class SwitchControlPlugin extends BasePlugin {
|
||||
}),
|
||||
*/
|
||||
getSchemaTpl('valueFormula', {
|
||||
rendererSchema: context?.schema,
|
||||
rendererSchema: {
|
||||
...omit(context?.schema, ['trueValue', 'falseValue']),
|
||||
type: 'switch'
|
||||
},
|
||||
needDeleteProps: ['option'],
|
||||
rendererWrapper: true, // 浅色线框包裹一下,增加边界感
|
||||
// valueType: 'boolean',
|
||||
valueType: 'boolean',
|
||||
pipeIn: (value: any, data: any) => {
|
||||
const {trueValue = true, falseValue = false} =
|
||||
data.data || {};
|
||||
return value === trueValue
|
||||
? true
|
||||
: value === falseValue
|
||||
? false
|
||||
: value;
|
||||
if (isPureVariable(value)) {
|
||||
return value;
|
||||
}
|
||||
return value === (data?.data?.trueValue ?? true);
|
||||
},
|
||||
pipeOut: (value: any, origin: any, data: any) => {
|
||||
// 如果是表达式,直接返回
|
||||
|
@ -90,7 +90,8 @@ export class TreeSelectControlPlugin extends BasePlugin {
|
||||
eventLabel: '获取焦点',
|
||||
description: '输入框获取焦点时触发',
|
||||
dataSchema: (manager: EditorManager) => {
|
||||
const {value, items} = resolveOptionEventDataSchame(manager);
|
||||
const {value, items, itemSchema} =
|
||||
resolveOptionEventDataSchame(manager);
|
||||
|
||||
return [
|
||||
{
|
||||
@ -101,6 +102,11 @@ export class TreeSelectControlPlugin extends BasePlugin {
|
||||
title: '数据',
|
||||
properties: {
|
||||
value,
|
||||
item: {
|
||||
type: 'object',
|
||||
title: '选中的项',
|
||||
properties: itemSchema
|
||||
},
|
||||
items
|
||||
}
|
||||
}
|
||||
@ -114,7 +120,8 @@ export class TreeSelectControlPlugin extends BasePlugin {
|
||||
eventLabel: '失去焦点',
|
||||
description: '输入框失去焦点时触发',
|
||||
dataSchema: (manager: EditorManager) => {
|
||||
const {value, items} = resolveOptionEventDataSchame(manager);
|
||||
const {value, items, itemSchema} =
|
||||
resolveOptionEventDataSchame(manager);
|
||||
|
||||
return [
|
||||
{
|
||||
@ -125,6 +132,11 @@ export class TreeSelectControlPlugin extends BasePlugin {
|
||||
title: '数据',
|
||||
properties: {
|
||||
value,
|
||||
item: {
|
||||
type: 'object',
|
||||
title: '选中的项',
|
||||
properties: itemSchema
|
||||
},
|
||||
items
|
||||
}
|
||||
}
|
||||
|
@ -514,6 +514,7 @@ export class ImagePlugin extends BasePlugin {
|
||||
|
||||
resizer.removeAttribute('data-value');
|
||||
node.updateSchema(state);
|
||||
node.updateState({}, true);
|
||||
requestAnimationFrame(() => {
|
||||
node.calculateHighlightBox();
|
||||
});
|
||||
|
@ -1,9 +1,15 @@
|
||||
/**
|
||||
* @file Flex 常见布局 1:3
|
||||
*/
|
||||
import {PlainObject} from 'amis-core';
|
||||
import {LayoutBasePlugin, PluginEvent} from 'amis-editor-core';
|
||||
import {getSchemaTpl, tipedLabel} from 'amis-editor-core';
|
||||
import React from 'react';
|
||||
import {
|
||||
JSONPipeOut,
|
||||
LayoutBasePlugin,
|
||||
PluginEvent,
|
||||
reGenerateID
|
||||
} from 'amis-editor-core';
|
||||
import {getSchemaTpl} from 'amis-editor-core';
|
||||
import {Button, PlainObject} from 'amis';
|
||||
import type {
|
||||
BaseEventContext,
|
||||
EditorNodeType,
|
||||
@ -12,20 +18,29 @@ import type {
|
||||
BasicToolbarItem,
|
||||
PluginInterface
|
||||
} from 'amis-editor-core';
|
||||
import {Icon} from 'amis-editor-core';
|
||||
import {JSONChangeInArray, JSONPipeIn, repeatArray} from 'amis-editor-core';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
|
||||
// 默认的列容器Schema
|
||||
export const defaultFlexColumnSchema = (title?: string) => {
|
||||
export const defaultFlexColumnSchema = (
|
||||
title?: string,
|
||||
disableFlexBasis: boolean = true
|
||||
) => {
|
||||
let style: PlainObject = {
|
||||
position: 'static',
|
||||
display: 'block',
|
||||
flex: '1 1 auto',
|
||||
flexGrow: 1
|
||||
};
|
||||
if (disableFlexBasis) {
|
||||
style.flexBasis = 0;
|
||||
}
|
||||
return {
|
||||
type: 'container',
|
||||
body: [],
|
||||
size: 'none',
|
||||
style: {
|
||||
position: 'static',
|
||||
display: 'block',
|
||||
flex: '1 1 auto',
|
||||
flexGrow: 1,
|
||||
flexBasis: 0
|
||||
},
|
||||
style,
|
||||
wrapperBody: false,
|
||||
isFixedHeight: false,
|
||||
isFixedWidth: false
|
||||
@ -88,9 +103,66 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
|
||||
panelJustify = true; // 右侧配置项默认左右展示
|
||||
|
||||
// 设置分栏的默认布局比例
|
||||
setFlexLayout = (node: EditorNodeType, value: string) => {
|
||||
if (/^[\d:]+$/.test(value) && isAlive(node)) {
|
||||
let list = value.trim().split(':');
|
||||
let children = node.children || [];
|
||||
|
||||
if (String(node.schema?.style?.flexDirection).includes('column')) {
|
||||
list = list.reverse();
|
||||
node.updateSchemaStyle({
|
||||
flexDirection: 'row'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新flex布局
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
child.updateSchemaStyle({
|
||||
flexGrow: +list[i],
|
||||
width: undefined,
|
||||
flexBasis: 0,
|
||||
flex: '1 1 auto'
|
||||
});
|
||||
}
|
||||
|
||||
// 增加或删除列
|
||||
if (children.length < list.length) {
|
||||
for (let i = 0; i < list.length - children.length; i++) {
|
||||
let newColumnSchema = defaultFlexColumnSchema();
|
||||
newColumnSchema.style.flexGrow = +list[i];
|
||||
this.manager.addElem(newColumnSchema, true, false);
|
||||
}
|
||||
} else if (children.length > list.length) {
|
||||
// 如果删除的列里面存在元素,截断生成新的flex放在组件后面
|
||||
const newSchema = JSONPipeIn(JSONPipeOut(node.schema));
|
||||
newSchema.items = newSchema.items.slice(list.length);
|
||||
|
||||
node.updateSchema({
|
||||
items: node.schema.items.slice(0, list.length)
|
||||
});
|
||||
|
||||
if (
|
||||
(newSchema.items as PlainObject[]).some(
|
||||
(item, index) => item.body?.length
|
||||
)
|
||||
) {
|
||||
const parent = node.parent;
|
||||
this.manager.addChild(
|
||||
parent.id,
|
||||
parent.region,
|
||||
newSchema,
|
||||
parent?.children?.[node.index + 1]?.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
resetFlexBasis = (node: EditorNodeType, flexSetting: PlainObject = {}) => {
|
||||
let schema = node.schema;
|
||||
|
||||
if (
|
||||
String(flexSetting.flexDirection).includes('column') &&
|
||||
!schema?.style?.height
|
||||
@ -108,6 +180,30 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
}
|
||||
};
|
||||
|
||||
insertItem = (node: EditorNodeType, direction: string) => {
|
||||
if (node.info?.plugin !== this) {
|
||||
return;
|
||||
}
|
||||
const store = this.manager.store;
|
||||
const newSchema = JSONPipeIn(JSONPipeOut(node.schema));
|
||||
|
||||
const parent = node.parent;
|
||||
const nextId =
|
||||
direction === 'upper' ? node.id : parent?.children?.[node.index + 1]?.id;
|
||||
const child = this.manager.addChild(
|
||||
parent.id,
|
||||
parent.region,
|
||||
newSchema,
|
||||
nextId
|
||||
);
|
||||
if (child) {
|
||||
// mobx 修改数据是异步的
|
||||
setTimeout(() => {
|
||||
store.setActiveId(child.$$id);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
const curRendererSchema = context?.schema || {};
|
||||
const isRowContent =
|
||||
@ -137,15 +233,82 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
body: [
|
||||
getSchemaTpl('collapseGroup', [
|
||||
{
|
||||
title: '布局',
|
||||
title: '基础',
|
||||
body: [
|
||||
isSorptionContainer ? getSchemaTpl('layout:sorption') : null,
|
||||
context.node &&
|
||||
getSchemaTpl('layout:flex-layout', {
|
||||
name: 'layout',
|
||||
label: '快捷版式设置',
|
||||
pipeIn: () => {
|
||||
if (isAlive(context.node)) {
|
||||
let children = context.node?.children || [];
|
||||
if (
|
||||
children.every(
|
||||
item => item.schema?.style?.flex === '1 1 auto'
|
||||
)
|
||||
) {
|
||||
return children
|
||||
.map(item => item.schema?.style?.flexGrow || 1)
|
||||
.join(':');
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
pipeOut: (value: string) =>
|
||||
this.setFlexLayout(context.node, value)
|
||||
}),
|
||||
|
||||
// 吸附容器不显示定位相关配置项
|
||||
...(isSorptionContainer ? [] : positionTpl),
|
||||
{
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
className: 'grid grid-cols-2 gap-4 mb-4',
|
||||
body: [
|
||||
{
|
||||
children: (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
this.insertItem(context.node, 'under')
|
||||
}
|
||||
>
|
||||
<Icon className="icon" icon="arrow-to-bottom" />
|
||||
<span>下方插入新行</span>
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
this.insertItem(context.node, 'upper')
|
||||
}
|
||||
>
|
||||
<Icon className="icon" icon="top-arrow-to-top" />
|
||||
<span>上方插入新行</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: 'themeCss.baseControlClassName.padding-and-margin:default'
|
||||
}),
|
||||
getSchemaTpl('theme:border', {
|
||||
name: `themeCss.baseControlClassName.border:default`
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
name: 'themeCss.baseControlClassName.background:default',
|
||||
label: '背景',
|
||||
needCustom: true,
|
||||
needGradient: true,
|
||||
needImage: true,
|
||||
labelMode: 'input'
|
||||
}),
|
||||
|
||||
getSchemaTpl('layout:flex-setting', {
|
||||
label: '弹性布局设置',
|
||||
label: '内部对齐设置',
|
||||
direction: curRendererSchema.direction,
|
||||
justify: curRendererSchema.justify || 'center',
|
||||
alignItems: curRendererSchema.alignItems,
|
||||
@ -258,7 +421,15 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
getSchemaTpl('layout:stickyPosition')
|
||||
]
|
||||
},
|
||||
getSchemaTpl('status')
|
||||
getSchemaTpl('status'),
|
||||
{
|
||||
title: '高级',
|
||||
body: [
|
||||
isSorptionContainer ? getSchemaTpl('layout:sorption') : null,
|
||||
// 吸附容器不显示定位相关配置项
|
||||
...(isSorptionContainer ? [] : positionTpl)
|
||||
]
|
||||
}
|
||||
])
|
||||
]
|
||||
},
|
||||
@ -289,7 +460,9 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
const draggableContainer = this.manager.draggableContainer(id);
|
||||
const isFlexItem = this.manager?.isFlexItem(id);
|
||||
const isFlexColumnItem = this.manager?.isFlexColumnItem(id);
|
||||
const newColumnSchema = defaultFlexColumnSchema('新的一列');
|
||||
const newColumnSchema = isFlexColumnItem
|
||||
? defaultFlexColumnSchema('', false)
|
||||
: defaultFlexColumnSchema();
|
||||
const canAppendSiblings = this.manager?.canAppendSiblings();
|
||||
const toolbarsTooltips: any = {};
|
||||
toolbars.forEach(toolbar => {
|
||||
@ -350,7 +523,8 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
level: 'special',
|
||||
placement: 'bottom',
|
||||
className: 'ae-AppendChild',
|
||||
onClick: () => this.manager.addElem(newColumnSchema)
|
||||
onClick: () =>
|
||||
this.manager.addElem(defaultFlexColumnSchema('', true))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import {defaultValue, getSchemaTpl} from 'amis-editor-core';
|
||||
import {repeatArray} from 'amis-editor-core';
|
||||
import set from 'lodash/set';
|
||||
import {escapeFormula, resolveArrayDatasource} from '../util';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
export class List2Plugin extends BasePlugin {
|
||||
static id = 'List2Plugin';
|
||||
@ -450,7 +451,12 @@ export class List2Plugin extends BasePlugin {
|
||||
props.className = `${props.className || ''} ae-Editor-list`;
|
||||
props.itemsClassName = `${props.itemsClassName || ''} cards-items`;
|
||||
if (props.card && !props.card.className?.includes('listItem')) {
|
||||
props.card.className = `${props.card.className || ''} ae-Editor-listItem`;
|
||||
props.card = merge(
|
||||
{
|
||||
className: `${props.card.className || ''} ae-Editor-listItem`
|
||||
},
|
||||
props.card
|
||||
);
|
||||
}
|
||||
|
||||
// 列表类型内的文本元素显示原始公式
|
||||
|
@ -158,52 +158,51 @@ export class ActionPlugin extends BasePlugin {
|
||||
showLoading: true
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({value, onChange, data}: any) =>
|
||||
data.actionType === 'dialog' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置弹框内容',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置弹框内容
|
||||
</Button>
|
||||
) : null
|
||||
visibleOn: '${actionType == "dialog"}',
|
||||
children: ({value, onChange, data}: any) => (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置弹框内容',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置弹框内容
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
visibleOn: 'data.actionType == "drawer"',
|
||||
name: 'drawer',
|
||||
pipeIn: defaultValue({
|
||||
title: '弹框标题',
|
||||
body: '对,你刚刚点击了'
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({value, onChange, data}: any) =>
|
||||
data.actionType == 'drawer' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置抽出式弹框内容',
|
||||
value: {type: 'drawer', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置抽出式弹框内容
|
||||
</Button>
|
||||
) : null
|
||||
visibleOn: '${actionType == "drawer"}',
|
||||
children: ({value, onChange, data}: any) => (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置抽出式弹框内容',
|
||||
value: {type: 'drawer', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置抽出式弹框内容
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
|
||||
getSchemaTpl('api', {
|
||||
@ -218,35 +217,35 @@ export class ActionPlugin extends BasePlugin {
|
||||
body: '内容'
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({onChange, value, data}: any) =>
|
||||
data.actionType == 'ajax' ? (
|
||||
<div className="m-b">
|
||||
visibleOn: '${actionType == "ajax"}',
|
||||
children: ({onChange, value, data}: any) => (
|
||||
<div className="m-b">
|
||||
<Button
|
||||
size="sm"
|
||||
level={value ? 'danger' : 'info'}
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置反馈弹框详情',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
>
|
||||
配置反馈弹框内容
|
||||
</Button>
|
||||
|
||||
{value ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level={value ? 'danger' : 'info'}
|
||||
onClick={() =>
|
||||
this.manager.openSubEditor({
|
||||
title: '配置反馈弹框详情',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
level="link"
|
||||
className="m-l"
|
||||
onClick={() => onChange('')}
|
||||
>
|
||||
配置反馈弹框内容
|
||||
清空设置
|
||||
</Button>
|
||||
|
||||
{value ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="link"
|
||||
className="m-l"
|
||||
onClick={() => onChange('')}
|
||||
>
|
||||
清空设置
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -63,6 +63,7 @@ export * from './Form/UUID'; // UUID
|
||||
export * from './Form/LocationPicker'; // 地理位置
|
||||
export * from './Form/InputSubForm'; // 子表单项
|
||||
export * from './Form/Hidden'; // 隐藏域
|
||||
export * from './Form/InputSignature'; // 签名面板
|
||||
export * from './Form/Static'; // 静态展示框
|
||||
|
||||
// 功能
|
||||
|
@ -177,7 +177,7 @@ export default class FlexSettingControl extends React.Component<FlexSettingContr
|
||||
|
||||
return (
|
||||
<div className="ap-Flex">
|
||||
{!label && <div className="ap-Flex-label">弹性布局设置</div>}
|
||||
{!label && <div className="ap-Flex-label">内部对齐设置</div>}
|
||||
{flexItems.map(item => (
|
||||
<div
|
||||
className={`ap-Flex-item ap-Flex-${item.field}`}
|
||||
|
@ -324,7 +324,7 @@ export default class TransferTableOption extends React.Component<
|
||||
return {
|
||||
type: 'action',
|
||||
actionType: 'dialog',
|
||||
label: '添加表格列',
|
||||
label: '设置表格列',
|
||||
level: 'enhance',
|
||||
dialog: {
|
||||
title: '设置表格列选项',
|
||||
@ -348,12 +348,14 @@ export default class TransferTableOption extends React.Component<
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'label',
|
||||
placeholder: '标题'
|
||||
placeholder: '标题',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name',
|
||||
placeholder: '绑定字段名'
|
||||
placeholder: '绑定字段名',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
@ -417,7 +419,7 @@ export default class TransferTableOption extends React.Component<
|
||||
{
|
||||
type: 'action',
|
||||
actionType: 'dialog',
|
||||
label: '添加表格行',
|
||||
label: '设置表格行',
|
||||
level: 'enhance',
|
||||
disabled: columns && columns.length === 0,
|
||||
block: true,
|
||||
|
@ -0,0 +1,661 @@
|
||||
import {
|
||||
EditorManager,
|
||||
JSONGetById,
|
||||
JSONGetParentById,
|
||||
JSONPipeIn,
|
||||
JSONPipeOut,
|
||||
JSONUpdate,
|
||||
addModal,
|
||||
modalsToDefinitions
|
||||
} from 'amis-editor-core';
|
||||
import React from 'react';
|
||||
import {observer} from 'mobx-react';
|
||||
import {JSONTraverse, JSONValueMap, RendererProps} from 'amis-core';
|
||||
import {Button, FormField, InputJSONSchema, Select, Switch} from 'amis-ui';
|
||||
import type {EditorModalBody} from '../../../../amis-editor-core/src/store/editor';
|
||||
|
||||
export interface DialogActionPanelProps extends RendererProps {
|
||||
manager: EditorManager;
|
||||
subscribeSchemaSubmit: (
|
||||
fn: (schema: any, value: any, id: string, diff?: any) => any,
|
||||
once?: boolean
|
||||
) => () => void;
|
||||
subscribeActionSubmit: (fn: (value: any) => any) => () => void;
|
||||
addHook: (fn: Function, type?: 'validate' | 'init' | 'flush') => () => void;
|
||||
}
|
||||
|
||||
export interface LocalModal {
|
||||
label: string;
|
||||
value: any;
|
||||
tip: string;
|
||||
modal: EditorModalBody;
|
||||
isNew?: boolean;
|
||||
isModified?: boolean;
|
||||
isActive?: boolean;
|
||||
// 是否为当前动作内嵌的弹窗
|
||||
isCurrentActionModal?: boolean;
|
||||
|
||||
/**
|
||||
* 传参配置
|
||||
*/
|
||||
data?: any;
|
||||
}
|
||||
|
||||
function DialogActionPanel({
|
||||
classnames: cx,
|
||||
render,
|
||||
data,
|
||||
manager,
|
||||
onChange,
|
||||
onBulkChange,
|
||||
node,
|
||||
addHook,
|
||||
subscribeSchemaSubmit
|
||||
}: DialogActionPanelProps) {
|
||||
const eventKey = data.eventKey;
|
||||
|
||||
if (!eventKey) {
|
||||
return <div>上下文数据错误</div>;
|
||||
}
|
||||
const actionIndex = data.actionIndex;
|
||||
|
||||
const store = manager.store;
|
||||
const [modals, setModals] = React.useState<Array<LocalModal>>([]);
|
||||
const currentModal = modals.find(item => item.isActive);
|
||||
|
||||
// 订阅由面板触发的 schema 变跟事件
|
||||
// 写入 store 之前执行,可以对 schema 进行修改
|
||||
React.useEffect(() => {
|
||||
subscribeSchemaSubmit((schema: any, nodeSchema: any, id: string) => {
|
||||
const rawActions = JSONGetById(schema, id)?.onEvent[eventKey]?.actions;
|
||||
if (!rawActions || !Array.isArray(rawActions)) {
|
||||
throw new Error('动作配置错误');
|
||||
}
|
||||
|
||||
const actionSchema =
|
||||
rawActions[
|
||||
typeof actionIndex === 'undefined'
|
||||
? rawActions.length - 1
|
||||
: actionIndex
|
||||
];
|
||||
const modals: Array<LocalModal> = actionSchema.__actionModals;
|
||||
const currentModal = modals.find(item => item.isActive)!;
|
||||
|
||||
schema = {...schema, definitions: {...schema.definitions}};
|
||||
// 可能编辑了其他弹窗,同时所选弹窗里面如果公用了弹窗
|
||||
// 会标记为 isModified
|
||||
modals
|
||||
.filter(
|
||||
item => item.isModified && item !== currentModal && item.modal.$$ref
|
||||
)
|
||||
.forEach(({modal}) => {
|
||||
const {$$originId: originId, ...def} = modal as any;
|
||||
if (originId) {
|
||||
const parent = JSONGetParentById(schema, originId);
|
||||
if (!parent) {
|
||||
// 找不到就丢回去,上层去处理
|
||||
def.$$originId = originId;
|
||||
} else {
|
||||
// TODO 这里要不要再加个判断?
|
||||
// 只更新当前动作中关联的弹窗?
|
||||
const modalType = def.type === 'drawer' ? 'drawer' : 'dialog';
|
||||
schema = JSONUpdate(schema, parent.$$id, {
|
||||
...parent,
|
||||
__actionModals: undefined,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
actionType: def.actionType ?? modalType,
|
||||
[modalType]: JSONPipeIn({
|
||||
$ref: modal.$$ref!
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
schema.definitions[modal.$$ref!] = JSONPipeIn(def);
|
||||
});
|
||||
|
||||
// 处理当前选中的弹窗
|
||||
let newActionSchema: any = null;
|
||||
const modalType =
|
||||
currentModal.modal.type === 'drawer' ? 'drawer' : 'dialog';
|
||||
let originActionId = null;
|
||||
let newRefName = '';
|
||||
|
||||
if (currentModal.isCurrentActionModal) {
|
||||
// 选中的是当前动作内嵌的弹窗
|
||||
// 直接更新当前动作即可
|
||||
newActionSchema = {
|
||||
...actionSchema,
|
||||
__actionModals: undefined,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
actionType: currentModal.modal.actionType ?? modalType,
|
||||
data: currentModal.data,
|
||||
[modalType]: {
|
||||
...currentModal.modal,
|
||||
data: undefined
|
||||
}
|
||||
};
|
||||
} else if (currentModal.modal.$$ref) {
|
||||
// 选中的是引用的弹窗
|
||||
newActionSchema = {
|
||||
...actionSchema,
|
||||
__actionModals: undefined,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
actionType: currentModal.modal.actionType ?? modalType,
|
||||
data: currentModal.data,
|
||||
[modalType]: {
|
||||
$ref: currentModal.modal.$$ref
|
||||
}
|
||||
};
|
||||
|
||||
const originInd = (currentModal.modal as any).$$originId;
|
||||
// 可能弹窗内容更新了
|
||||
|
||||
schema.definitions[currentModal.modal.$$ref] = JSONPipeIn({
|
||||
...currentModal.modal,
|
||||
$$originId: undefined,
|
||||
$$ref: undefined
|
||||
});
|
||||
|
||||
if (originInd) {
|
||||
const parent = JSONGetParentById(schema, originInd);
|
||||
if (parent && parent.actionType) {
|
||||
originActionId = parent.$$id;
|
||||
newRefName = currentModal.modal.$$ref;
|
||||
} else {
|
||||
// 没找到很可能是在主页面里面的弹窗
|
||||
// 还得继续把 originId 给到上一层去处理
|
||||
schema.definitions[currentModal.modal.$$ref].$$originId = originInd;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 选的是别的工作内嵌的弹窗
|
||||
// 需要把目标弹窗转成 definition
|
||||
// 然后都引用这个 definition
|
||||
let refKey: string = '';
|
||||
[schema, refKey] = addModal(schema, currentModal.modal);
|
||||
newActionSchema = {
|
||||
...actionSchema,
|
||||
__actionModals: undefined,
|
||||
args: undefined,
|
||||
dialog: undefined,
|
||||
drawer: undefined,
|
||||
actionType: currentModal.modal.actionType ?? modalType,
|
||||
data: currentModal.data,
|
||||
[modalType]: JSONPipeIn({
|
||||
$ref: refKey
|
||||
})
|
||||
};
|
||||
|
||||
originActionId = currentModal.value;
|
||||
newRefName = refKey;
|
||||
}
|
||||
|
||||
schema = JSONUpdate(
|
||||
schema,
|
||||
actionSchema.$$id,
|
||||
JSONPipeIn(newActionSchema),
|
||||
true
|
||||
);
|
||||
|
||||
// 原来的动作也要更新
|
||||
if (originActionId && newRefName) {
|
||||
schema = JSONUpdate(
|
||||
schema,
|
||||
currentModal.value,
|
||||
JSONPipeIn({
|
||||
$ref: newRefName
|
||||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
return schema;
|
||||
}, true);
|
||||
}, []);
|
||||
|
||||
const [errors, setErrors] = React.useState<{
|
||||
dialog?: string;
|
||||
data?: string;
|
||||
}>({});
|
||||
React.useEffect(() => {
|
||||
const unHook = addHook((data: any): any => {
|
||||
const modals = data.__actionModals;
|
||||
if (!modals || !Array.isArray(modals)) {
|
||||
throw new Error('程序异常');
|
||||
}
|
||||
|
||||
const currentModal = modals.find((item: any) => item.isActive);
|
||||
if (!currentModal) {
|
||||
setErrors({
|
||||
...errors,
|
||||
dialog: '请选择一个弹窗'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const required = currentModal.modal.inputParams?.required;
|
||||
if (Array.isArray(required) && required.length) {
|
||||
if (!currentModal.data) {
|
||||
setErrors({
|
||||
...errors,
|
||||
data: '参数不能为空'
|
||||
});
|
||||
|
||||
return false;
|
||||
} else if (required.some(key => !currentModal.data[key])) {
|
||||
setErrors({
|
||||
...errors,
|
||||
data: '参数中存在必填参数未赋值'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 校验参数赋值是否满足了弹窗的参数要求
|
||||
}, 'validate');
|
||||
return () => unHook();
|
||||
}, []);
|
||||
|
||||
// 初始化弹窗列表
|
||||
React.useEffect(() => {
|
||||
const actionSchema =
|
||||
typeof actionIndex === 'undefined'
|
||||
? {}
|
||||
: node.schema?.onEvent[eventKey]?.actions?.[actionIndex];
|
||||
const dialogBody =
|
||||
actionSchema[
|
||||
actionSchema.actionType === 'drawer' ? 'drawer' : 'dialog'
|
||||
] || actionSchema.args;
|
||||
|
||||
const modals: Array<LocalModal> = store.modals.map(modal => {
|
||||
const isCurrentActionModal = modal.$$id === dialogBody?.$$id;
|
||||
|
||||
return {
|
||||
label: `${
|
||||
modal.editorSetting?.displayName || modal.title || '未命名弹窗'
|
||||
}${
|
||||
isCurrentActionModal
|
||||
? '<当前动作内嵌弹窗>'
|
||||
: modal.$$ref
|
||||
? ''
|
||||
: '<内嵌弹窗>'
|
||||
}`,
|
||||
tip:
|
||||
(modal as any).actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗',
|
||||
value: modal.$$id,
|
||||
modal: modal,
|
||||
isCurrentActionModal,
|
||||
data: modal.data
|
||||
};
|
||||
});
|
||||
|
||||
let dialogId = dialogBody?.$$id || '';
|
||||
const ref = dialogBody?.$ref;
|
||||
if (ref) {
|
||||
dialogId = modals.find(item => item.modal.$$ref === ref)?.value || '';
|
||||
}
|
||||
|
||||
// 初始化有问题的情况
|
||||
const newData: any = {};
|
||||
// if (!dialogId) {
|
||||
// dialogId = guid();
|
||||
// const placeholder = {
|
||||
// $$id: dialogId,
|
||||
// type: 'dialog',
|
||||
// title: '未命名弹窗',
|
||||
// body: [
|
||||
// {
|
||||
// type: 'tpl',
|
||||
// tpl: '弹窗内容'
|
||||
// }
|
||||
// ]
|
||||
// };
|
||||
// modals.push({
|
||||
// label: '未命名弹窗<当前动作内嵌弹窗>',
|
||||
// tip: '弹窗',
|
||||
// value: dialogId,
|
||||
// isCurrentActionModal: true,
|
||||
// modal: placeholder
|
||||
// });
|
||||
// newData['dialog'] = placeholder;
|
||||
// }
|
||||
|
||||
const arr = modals.map(item => ({
|
||||
...item,
|
||||
isActive: dialogId === item.value,
|
||||
data:
|
||||
dialogId === item.value
|
||||
? JSONPipeOut(actionSchema.data ?? item.data)
|
||||
: JSONPipeOut(item.data)
|
||||
}));
|
||||
setModals(arr);
|
||||
newData.__actionModals = arr;
|
||||
onBulkChange(newData);
|
||||
}, []);
|
||||
|
||||
// 处理弹窗切换
|
||||
const handleDialogChange = React.useCallback(
|
||||
(option: any) => {
|
||||
const arr = modals.map(item => ({
|
||||
...item,
|
||||
isActive: item.value === option.value
|
||||
}));
|
||||
onBulkChange({
|
||||
__actionModals: arr
|
||||
});
|
||||
setModals(arr);
|
||||
setErrors({
|
||||
...errors,
|
||||
dialog: '',
|
||||
data: ''
|
||||
});
|
||||
},
|
||||
[modals]
|
||||
);
|
||||
|
||||
// 打开子弹窗后,因为子弹窗里面可能会创建新弹窗,会在 defintions 里面
|
||||
// 所以需要合并一下
|
||||
const mergeDefinitions = React.useCallback(
|
||||
(members: Array<LocalModal>, definitions: any, modal: any) => {
|
||||
const refs: Array<string> = [];
|
||||
JSONTraverse(modal, (value, key) => {
|
||||
if (key === '$ref') {
|
||||
refs.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
let arr = members;
|
||||
Object.keys(definitions).forEach(key => {
|
||||
// 弹窗里面用到了才更新
|
||||
if (!refs.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 要修改就复制一份,避免污染原始数据
|
||||
if (arr === members) {
|
||||
arr = members.concat();
|
||||
}
|
||||
|
||||
const {$$originId, ...modal} = definitions[key];
|
||||
const idx = arr.findIndex(item =>
|
||||
$$originId ? item.value === $$originId : item.modal.$$ref === key
|
||||
);
|
||||
const label = `${
|
||||
modal.editorSetting?.displayName || modal.title || '未命名弹窗'
|
||||
}`;
|
||||
const tip =
|
||||
(modal as any).actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗';
|
||||
|
||||
if (~idx) {
|
||||
arr.splice(idx, 1, {
|
||||
...arr[idx],
|
||||
label: label,
|
||||
tip: tip,
|
||||
modal: {...modal, $$ref: key, $$originId},
|
||||
isModified: true
|
||||
});
|
||||
} else {
|
||||
if ($$originId) {
|
||||
throw new Error('Definition merge exception');
|
||||
}
|
||||
arr.push({
|
||||
label,
|
||||
tip,
|
||||
value: modal.$$id,
|
||||
modal: JSONPipeIn({
|
||||
...modal,
|
||||
$$ref: key
|
||||
}),
|
||||
isModified: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return arr;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// 处理新建弹窗
|
||||
const handleDialogAdd = React.useCallback(
|
||||
(
|
||||
idx?: number | Array<number>,
|
||||
value?: any,
|
||||
skipForm?: boolean,
|
||||
closePopOver?: () => void
|
||||
) => {
|
||||
store.openSubEditor({
|
||||
title: '新建弹窗',
|
||||
value: {
|
||||
type: 'dialog',
|
||||
title: '未命名弹窗',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '弹窗内容'
|
||||
}
|
||||
],
|
||||
definitions: modalsToDefinitions(modals.map(item => item.modal))
|
||||
},
|
||||
onChange: ({definitions, ...modal}: any, diff: any) => {
|
||||
modal = JSONPipeIn(modal);
|
||||
let arr = modals.concat();
|
||||
if (!arr.some(item => item.isNew)) {
|
||||
arr.push({
|
||||
label: `${
|
||||
modal.editorSetting?.displayName || modal.title || '未命名弹窗'
|
||||
}`,
|
||||
tip:
|
||||
(modal as any).actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗',
|
||||
isNew: true,
|
||||
isCurrentActionModal: true,
|
||||
value: modal.$$id,
|
||||
modal: modal
|
||||
});
|
||||
|
||||
arr = mergeDefinitions(arr, definitions, modal);
|
||||
|
||||
arr = arr.map(item => ({
|
||||
...item,
|
||||
isActive: item.value === modal.$$id
|
||||
}));
|
||||
}
|
||||
setModals(arr);
|
||||
onBulkChange({__actionModals: arr});
|
||||
}
|
||||
});
|
||||
closePopOver?.();
|
||||
},
|
||||
[modals]
|
||||
);
|
||||
|
||||
// 处理编辑弹窗
|
||||
const handleDialogEdit = React.useCallback(() => {
|
||||
const currentModal = modals.find(item => item.isActive);
|
||||
if (!currentModal) {
|
||||
return;
|
||||
}
|
||||
store.openSubEditor({
|
||||
title: '编辑弹窗',
|
||||
value: {
|
||||
type: 'dialog',
|
||||
title: '弹窗标题',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '弹窗内容'
|
||||
}
|
||||
],
|
||||
...(currentModal.modal as any),
|
||||
definitions: modalsToDefinitions(modals.map(item => item.modal))
|
||||
},
|
||||
onChange: ({definitions, ...modal}: any, diff: any) => {
|
||||
// 编辑的时候不要修改 $$id
|
||||
modal = JSONPipeIn({...modal, $$id: currentModal.modal.$$id});
|
||||
let arr = modals.map(item =>
|
||||
item.value === currentModal.value
|
||||
? {
|
||||
...item,
|
||||
modal: modal,
|
||||
isModified: true,
|
||||
label: `${
|
||||
modal.editorSetting?.displayName ||
|
||||
modal.title ||
|
||||
'未命名弹窗'
|
||||
}${item.isCurrentActionModal ? '<当前动作内嵌弹窗>' : ''}`,
|
||||
tip:
|
||||
(modal as any).actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗'
|
||||
}
|
||||
: item
|
||||
);
|
||||
arr = mergeDefinitions(arr, definitions, modal);
|
||||
setModals(arr);
|
||||
onBulkChange({__actionModals: arr});
|
||||
}
|
||||
});
|
||||
}, [modals]);
|
||||
|
||||
const handleDataSwitchChange = React.useCallback(
|
||||
(value: any) => {
|
||||
handleDataChange(value ? {} : undefined);
|
||||
},
|
||||
[modals]
|
||||
);
|
||||
|
||||
const handleDataChange = React.useCallback(
|
||||
(value: any) => {
|
||||
let arr = modals.map(modal =>
|
||||
modal.isActive
|
||||
? {
|
||||
...modal,
|
||||
data: value
|
||||
}
|
||||
: modal
|
||||
);
|
||||
setModals(arr);
|
||||
onBulkChange({__actionModals: arr});
|
||||
setErrors({
|
||||
...errors,
|
||||
data: ''
|
||||
});
|
||||
},
|
||||
[modals]
|
||||
);
|
||||
|
||||
const hasRequired =
|
||||
Array.isArray(currentModal?.modal.inputParams?.required) &&
|
||||
currentModal!.modal.inputParams.required.length;
|
||||
React.useEffect(() => {
|
||||
if (hasRequired && !currentModal?.data) {
|
||||
handleDataChange({});
|
||||
}
|
||||
}, [hasRequired]);
|
||||
|
||||
// 渲染弹窗下拉选项
|
||||
const renderMenu = React.useCallback((option: any, stats: any) => {
|
||||
return (
|
||||
<div className="flex w-full justify-between">
|
||||
<span>{option.label}</span>
|
||||
<span className="text-muted">{option.tip}</span>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cx('ae-DialogActionPanel')}>
|
||||
<FormField
|
||||
label="选择弹窗"
|
||||
mode="horizontal"
|
||||
isRequired
|
||||
hasError={!!errors.dialog}
|
||||
errors={errors.dialog}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'Form-control Form-control--withSize Form-control--sizeLg'
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
createBtnLabel="新建弹窗"
|
||||
value={currentModal?.value || ''}
|
||||
onChange={handleDialogChange}
|
||||
options={modals}
|
||||
creatable={!modals.some(item => item.isNew)}
|
||||
clearable={false}
|
||||
onAdd={handleDialogAdd}
|
||||
renderMenu={renderMenu}
|
||||
/>
|
||||
|
||||
{currentModal ? (
|
||||
<div className="m-t-sm">
|
||||
<Button size="sm" level="enhance" onClick={handleDialogEdit}>
|
||||
编辑选中弹窗
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</FormField>
|
||||
{currentModal ? (
|
||||
<FormField
|
||||
label="参数赋值"
|
||||
mode="horizontal"
|
||||
hasError={!!errors.data}
|
||||
errors={errors.data}
|
||||
description={
|
||||
!currentModal.data
|
||||
? '不设置参数,打开弹窗将自动传递所有上下文数据'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'Form-control Form-control--withSize Form-control--sizeLg'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
className="mt-2 m-b-xs"
|
||||
value={!!currentModal.data}
|
||||
onChange={handleDataSwitchChange}
|
||||
disabled={hasRequired}
|
||||
/>
|
||||
|
||||
{currentModal.data ? (
|
||||
<InputJSONSchema
|
||||
className="m-t-sm"
|
||||
value={currentModal.data}
|
||||
onChange={handleDataChange}
|
||||
schema={JSONPipeOut(currentModal.modal.inputParams)}
|
||||
addButtonText="添加参数"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</FormField>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(DialogActionPanel);
|
@ -38,6 +38,11 @@ interface ActionDialogProp {
|
||||
node: SchemaNode,
|
||||
props?: PlainObject
|
||||
) => JSX.Element;
|
||||
|
||||
subscribeSchemaSubmit: (
|
||||
fn: (schema: any, value: any, id: string, diff?: any) => any
|
||||
) => () => void;
|
||||
subscribeActionSubmit: (fn: (value: any) => any) => () => void;
|
||||
}
|
||||
|
||||
export default class ActionDialog extends React.Component<ActionDialogProp> {
|
||||
@ -209,6 +214,8 @@ export default class ActionDialog extends React.Component<ActionDialogProp> {
|
||||
render() {
|
||||
const {
|
||||
data,
|
||||
subscribeSchemaSubmit,
|
||||
subscribeActionSubmit,
|
||||
show,
|
||||
type,
|
||||
actionTree,
|
||||
@ -426,7 +433,10 @@ export default class ActionDialog extends React.Component<ActionDialogProp> {
|
||||
onClose
|
||||
},
|
||||
{
|
||||
data // 必须这样,不然变量会被当作数据映射处理掉
|
||||
data, // 必须这样,不然变量会被当作数据映射处理掉
|
||||
|
||||
subscribeActionSubmit,
|
||||
subscribeSchemaSubmit
|
||||
}
|
||||
);
|
||||
// : null;
|
||||
|
@ -6,10 +6,10 @@ import {
|
||||
BaseEventContext,
|
||||
defaultValue,
|
||||
EditorManager,
|
||||
getFixDialogType,
|
||||
getSchemaTpl,
|
||||
JsonGenerateID,
|
||||
JSONGetById,
|
||||
modalsToDefinitions,
|
||||
persistGet,
|
||||
persistSet,
|
||||
PluginActions,
|
||||
@ -35,6 +35,7 @@ import without from 'lodash/without';
|
||||
import {ActionConfig, ComponentInfo, ContextVariables} from './types';
|
||||
import CmptActionSelect from './comp-action-select';
|
||||
import {ActionData} from '.';
|
||||
import DialogActionPanel from './DialogActionPanel';
|
||||
|
||||
export const getArgsWrapper = (
|
||||
items: any,
|
||||
@ -281,6 +282,73 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
const variableOptions = variableManager?.getVariableOptions() || [];
|
||||
const pageVariableOptions = variableManager?.getPageVariablesOptions() || [];
|
||||
|
||||
const modalDescDetail: (info: any, context: any, props: any) => any = (
|
||||
info,
|
||||
{eventKey, actionIndex},
|
||||
props: any
|
||||
) => {
|
||||
const {
|
||||
actionTree,
|
||||
actions: pluginActions,
|
||||
commonActions,
|
||||
allComponents,
|
||||
node,
|
||||
manager
|
||||
} = props;
|
||||
const store = manager.store;
|
||||
const modals = store.modals;
|
||||
const onEvent = node.schema?.onEvent;
|
||||
const action = onEvent?.[eventKey].actions?.[actionIndex];
|
||||
const actionBody =
|
||||
action?.[action?.actionType === 'drawer' ? 'drawer' : 'dialog'];
|
||||
let modalId = actionBody?.$$id;
|
||||
if (actionBody?.$ref) {
|
||||
modalId =
|
||||
modals.find((item: any) => item.$$ref === actionBody.$ref)?.$$id || '';
|
||||
}
|
||||
const modal = modalId
|
||||
? manager.store.modals.find((item: any) => item.$$id === modalId)
|
||||
: '';
|
||||
if (modal) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
打开
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e: React.UIEvent<any>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
store.openSubEditor({
|
||||
title: '编辑弹窗',
|
||||
value: {
|
||||
type: 'dialog',
|
||||
...modal,
|
||||
definitions: modalsToDefinitions(store.modals)
|
||||
},
|
||||
onChange: ({definitions, ...modal}: any, diff: any) => {
|
||||
store.updateModal(modal.$$id!, modal, definitions);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{modal.editorSetting?.displayName || modal.title || '未命名弹窗'}
|
||||
</a>
|
||||
|
||||
{(modal as any).actionType === 'confirmDialog'
|
||||
? '确认框'
|
||||
: modal.type === 'drawer'
|
||||
? '抽屉弹窗'
|
||||
: '弹窗'}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
actionLabel: '页面',
|
||||
@ -369,84 +437,30 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
description: '打开弹窗,弹窗内支持复杂的交互设计',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog'
|
||||
actionType: 'dialog',
|
||||
descDetail: modalDescDetail
|
||||
},
|
||||
{
|
||||
actionType: 'drawer'
|
||||
actionType: 'drawer',
|
||||
descDetail: modalDescDetail
|
||||
},
|
||||
{
|
||||
actionType: 'confirmDialog'
|
||||
actionType: 'confirmDialog',
|
||||
descDetail: modalDescDetail
|
||||
}
|
||||
],
|
||||
schema: [
|
||||
{
|
||||
type: 'radios',
|
||||
label: '弹窗来源',
|
||||
name: '__dialogSource',
|
||||
required: true,
|
||||
mode: 'horizontal',
|
||||
inputClassName: 'event-action-radio',
|
||||
value: 'new',
|
||||
options: [
|
||||
{
|
||||
label: '选择页面内已有弹窗',
|
||||
value: 'current'
|
||||
},
|
||||
{
|
||||
label: '新建弹窗',
|
||||
value: 'new'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '__dialogTitle',
|
||||
type: 'input-text',
|
||||
label: '弹窗标题',
|
||||
placeholder: '请输入弹窗标题',
|
||||
mode: 'horizontal',
|
||||
size: 'lg',
|
||||
visibleOn: '__dialogSource === "new"'
|
||||
},
|
||||
{
|
||||
name: '__selectDialog',
|
||||
type: 'select',
|
||||
label: '选择弹窗',
|
||||
source: '${__dialogActions}',
|
||||
mode: 'horizontal',
|
||||
size: 'lg',
|
||||
visibleOn: '__dialogSource === "current"',
|
||||
onChange: (value: any, oldValue: any, model: any, form: any) => {
|
||||
form.setValueByName('args', {
|
||||
fromCurrentDialog: true
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'radios',
|
||||
label: '弹窗类型',
|
||||
name: 'groupType',
|
||||
mode: 'horizontal',
|
||||
value: 'dialog',
|
||||
required: true,
|
||||
pipeIn: defaultValue('dialog'),
|
||||
inputClassName: 'event-action-radio',
|
||||
options: [
|
||||
{
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
},
|
||||
{
|
||||
label: '抽屉',
|
||||
value: 'drawer'
|
||||
},
|
||||
{
|
||||
label: '确认对话框',
|
||||
value: 'confirmDialog'
|
||||
}
|
||||
],
|
||||
visibleOn:
|
||||
'data.actionType === "openDialog" && __dialogSource === "new"'
|
||||
component: DialogActionPanel
|
||||
}
|
||||
// {
|
||||
// name: '__selectDialog',
|
||||
// type: 'select',
|
||||
// label: '选择弹窗',
|
||||
// source: '${__dialogActions}',
|
||||
// mode: 'horizontal',
|
||||
// size: 'lg'
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1236,19 +1250,19 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
{/* 只要path字段存在就认为是应用变量赋值,无论是否有值 */}
|
||||
{typeof info?.args?.path === 'string' && !info?.componentId ? (
|
||||
<>
|
||||
设置变量「
|
||||
设置变量
|
||||
<span className="variable-left variable-right">
|
||||
{variableManager.getNameByPath(info.args.path)}
|
||||
</span>
|
||||
」的数据
|
||||
的数据
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
设置组件「
|
||||
设置组件
|
||||
<span className="variable-left variable-right">
|
||||
{info?.rendererLabel || info.componentId || '-'}
|
||||
</span>
|
||||
」的数据
|
||||
的数据
|
||||
</>
|
||||
)}
|
||||
{/* 值为
|
||||
@ -2645,52 +2659,51 @@ export const getOldActionSchema = (
|
||||
showLoading: true
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({value, onChange, data}: any) =>
|
||||
data.actionType === 'dialog' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置弹框内容',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置弹框内容
|
||||
</Button>
|
||||
) : null
|
||||
visibleOn: '${actionType === "dialog"}',
|
||||
children: ({value, onChange, data}: any) => (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置弹框内容',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置弹框内容
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
visibleOn: 'data.actionType == "drawer"',
|
||||
name: 'drawer',
|
||||
pipeIn: defaultValue({
|
||||
title: '抽屉标题',
|
||||
body: '对,你刚刚点击了'
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({value, onChange, data}: any) =>
|
||||
data.actionType == 'drawer' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置抽出式弹框内容',
|
||||
value: {type: 'drawer', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置抽出式弹框内容
|
||||
</Button>
|
||||
) : null
|
||||
visibleOn: '${actionType == "drawer"}',
|
||||
children: ({value, onChange, data}: any) => (
|
||||
<Button
|
||||
size="sm"
|
||||
level="danger"
|
||||
className="m-b"
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置抽出式弹框内容',
|
||||
value: {type: 'drawer', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
block
|
||||
>
|
||||
配置抽出式弹框内容
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
|
||||
getSchemaTpl('api', {
|
||||
@ -2705,35 +2718,35 @@ export const getOldActionSchema = (
|
||||
body: '内容'
|
||||
}),
|
||||
asFormItem: true,
|
||||
children: ({onChange, value, data}: any) =>
|
||||
data.actionType == 'ajax' ? (
|
||||
<div className="m-b">
|
||||
visibleOn: '${actionType == "ajax"}',
|
||||
children: ({onChange, value, data}: any) => (
|
||||
<div className="m-b">
|
||||
<Button
|
||||
size="sm"
|
||||
level={value ? 'danger' : 'info'}
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置反馈弹框详情',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
>
|
||||
配置反馈弹框内容
|
||||
</Button>
|
||||
|
||||
{value ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level={value ? 'danger' : 'info'}
|
||||
onClick={() =>
|
||||
manager.openSubEditor({
|
||||
title: '配置反馈弹框详情',
|
||||
value: {type: 'dialog', ...value},
|
||||
onChange: value => onChange(value)
|
||||
})
|
||||
}
|
||||
level="link"
|
||||
className="m-l"
|
||||
onClick={() => onChange('')}
|
||||
>
|
||||
配置反馈弹框内容
|
||||
清空设置
|
||||
</Button>
|
||||
|
||||
{value ? (
|
||||
<Button
|
||||
size="sm"
|
||||
level="link"
|
||||
className="m-l"
|
||||
onClick={() => onChange('')}
|
||||
>
|
||||
清空设置
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
@ -3228,137 +3241,10 @@ export const getEventControlConfig = (
|
||||
delete action.addOnArgs;
|
||||
}
|
||||
|
||||
if (config.actionType === 'openDialog') {
|
||||
// 初始化弹窗schema
|
||||
const dialogInitSchema = {
|
||||
type: 'dialog',
|
||||
title: action.__dialogTitle,
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '对,你刚刚点击了',
|
||||
wrapperComponent: '',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
showCloseButton: true,
|
||||
showErrorMsg: true,
|
||||
showLoading: true,
|
||||
className: 'app-popover :AMISCSSWrapper',
|
||||
actions: [
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'cancel',
|
||||
label: '取消'
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'confirm',
|
||||
label: '确认',
|
||||
primary: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const drawerInitSchema = {
|
||||
type: 'drawer',
|
||||
title: action.__dialogTitle,
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '对,你刚刚点击了',
|
||||
wrapperComponent: '',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
className: 'app-popover :AMISCSSWrapper',
|
||||
actions: [
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'cancel',
|
||||
label: '取消'
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'confirm',
|
||||
label: '确认',
|
||||
primary: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const confirmDialogInitSchema = {
|
||||
type: 'dialog',
|
||||
title: action.__dialogTitle,
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '对,你刚刚点击了',
|
||||
wrapperComponent: '',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
dialogType: 'confirm',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
confirmBtnLevel: 'primary'
|
||||
};
|
||||
|
||||
const setInitSchema = (groupType: string, action: ActionConfig) => {
|
||||
if (groupType === 'dialog') {
|
||||
JsonGenerateID(dialogInitSchema);
|
||||
action.dialog = dialogInitSchema;
|
||||
} else if (groupType === 'drawer') {
|
||||
JsonGenerateID(drawerInitSchema);
|
||||
action.drawer = drawerInitSchema;
|
||||
} else if (groupType === 'confirmDialog') {
|
||||
JsonGenerateID(confirmDialogInitSchema);
|
||||
action.dialog = confirmDialogInitSchema;
|
||||
}
|
||||
};
|
||||
|
||||
const chooseCurrentDialog = (action: ActionConfig, schema: Schema) => {
|
||||
const selectDialog = action.__selectDialog;
|
||||
let dialogType = getFixDialogType(schema, selectDialog);
|
||||
// 选择现有弹窗后为了使之前的弹窗和现有弹窗$$id唯一,这里重新生成一下
|
||||
let newDialogId = guid();
|
||||
action.actionType = dialogType;
|
||||
action[dialogType] = {
|
||||
$$id: newDialogId,
|
||||
type: dialogType
|
||||
};
|
||||
// 在这里记录一下新生成的弹窗id
|
||||
action.__relatedDialogId = newDialogId;
|
||||
};
|
||||
|
||||
if (type === 'add') {
|
||||
if (config.__dialogSource === 'new') {
|
||||
setInitSchema(config.groupType, action);
|
||||
} else if (config.__dialogSource === 'current') {
|
||||
chooseCurrentDialog(action, shcema!);
|
||||
}
|
||||
}
|
||||
// 编辑
|
||||
else if (type === 'update') {
|
||||
if (config.__dialogSource === 'new') {
|
||||
// 如果切换了弹窗类型或切换了弹窗来源,则初始化schema
|
||||
if (
|
||||
config.groupType !== actionData?.groupType ||
|
||||
(config.__dialogSource === 'new' &&
|
||||
actionData?.__dialogSource === 'current')
|
||||
) {
|
||||
setInitSchema(config.groupType, action);
|
||||
} else {
|
||||
action[config.groupType] = {
|
||||
...actionData![config.groupType],
|
||||
title: config.__dialogTitle
|
||||
};
|
||||
}
|
||||
} else if (config.__dialogSource === 'current') {
|
||||
chooseCurrentDialog(action, shcema!);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 不加回来可能数据会丢失
|
||||
['drawer', 'dialog', 'args'].forEach(key => {
|
||||
action[key] = action[key] ?? actionData?.[key];
|
||||
});
|
||||
|
||||
// 刷新组件时,处理是否追加事件变量
|
||||
if (config.actionType === 'reload') {
|
||||
|
@ -43,9 +43,7 @@ import {
|
||||
PluginEvents,
|
||||
RendererPluginAction,
|
||||
RendererPluginEvent,
|
||||
SubRendererPluginAction,
|
||||
getDialogActions,
|
||||
getFixDialogType
|
||||
SubRendererPluginAction
|
||||
} from 'amis-editor-core';
|
||||
export * from './helper';
|
||||
import {i18n as _i18n} from 'i18n-runtime';
|
||||
@ -76,6 +74,12 @@ interface EventControlProps extends FormControlProps {
|
||||
schema?: Schema
|
||||
) => ActionConfig; // 动作配置提交时格式化
|
||||
owner?: string; // 组件标识
|
||||
|
||||
// 监听面板提交事件
|
||||
// 更改后写入 store 前触发
|
||||
subscribeSchemaSubmit: (
|
||||
fn: (schema: any, value: any, id: string, diff?: any) => any
|
||||
) => () => void;
|
||||
}
|
||||
|
||||
interface EventDialogData {
|
||||
@ -141,6 +145,7 @@ export class EventControl extends React.Component<
|
||||
} = {};
|
||||
drag?: HTMLElement | null;
|
||||
unReaction: any;
|
||||
submitSubscribers: Array<(value: any) => any> = [];
|
||||
|
||||
constructor(props: EventControlProps) {
|
||||
super(props);
|
||||
@ -187,6 +192,7 @@ export class EventControl extends React.Component<
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unReaction?.();
|
||||
this.submitSubscribers = [];
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
@ -224,6 +230,16 @@ export class EventControl extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
subscribeSubmit(subscriber: (value: any) => any) {
|
||||
const fn = (value: any) => subscriber?.(value) || value;
|
||||
this.submitSubscribers.push(fn);
|
||||
return () => {
|
||||
const idx = this.submitSubscribers.indexOf(fn);
|
||||
this.submitSubscribers.splice(idx, 1);
|
||||
};
|
||||
}
|
||||
|
||||
generateEmptyDefault(events: RendererPluginEvent[]) {
|
||||
const onEvent: ActionEventConfig = {};
|
||||
events.forEach((event: RendererPluginEvent) => {
|
||||
@ -782,26 +798,6 @@ export class EventControl extends React.Component<
|
||||
return updateComponentContext(variables);
|
||||
}
|
||||
|
||||
// 获取现有弹窗列表
|
||||
getDialogList(
|
||||
manager: EditorManager,
|
||||
action?: ActionConfig,
|
||||
actionType?: keyof typeof dialogObjMap
|
||||
) {
|
||||
if (
|
||||
action &&
|
||||
actionType &&
|
||||
dialogObjMap[actionType] &&
|
||||
!action?.args?.fromCurrentDialog
|
||||
) {
|
||||
let dialogBodyContent = dialogObjMap[actionType];
|
||||
let filterId = Array.isArray(dialogBodyContent)
|
||||
? action[dialogBodyContent[0]].id || action[dialogBodyContent[1]].id
|
||||
: action[dialogBodyContent].id;
|
||||
return getDialogActions(manager.store.schema, 'source', filterId);
|
||||
} else return getDialogActions(manager.store.schema, 'source');
|
||||
}
|
||||
|
||||
// 唤起动作配置弹窗
|
||||
async activeActionDialog(
|
||||
data: Pick<EventControlState, 'showAcionDialog' | 'type' | 'actionData'>
|
||||
@ -866,38 +862,13 @@ export class EventControl extends React.Component<
|
||||
__actionSchema: actionNode!.schema, // 树节点schema
|
||||
__subActions: hasSubActionNode?.actions, // 树节点子动作
|
||||
__cmptTreeSource: supportComponents ?? [],
|
||||
__dialogActions: this.getDialogList(manager, action, actionGroupType),
|
||||
// __dialogActions: manager.store.modalOptions,
|
||||
__superCmptTreeSource: allComponents,
|
||||
// __supersCmptTreeSource: '',
|
||||
__setValueDs: setValueDs
|
||||
// broadcastId: action.actionType === 'broadcast' ? action.eventName : ''
|
||||
};
|
||||
|
||||
// 编辑时准备已选的弹窗来源和标题
|
||||
if (actionConfig?.actionType == 'openDialog') {
|
||||
const definitions = manager.store.schema.definitions;
|
||||
let dialogBody =
|
||||
dialogObjMap[actionGroupType as keyof typeof dialogObjMap];
|
||||
const dialogObj = Array.isArray(dialogBody)
|
||||
? dialogBody[0] || dialogBody[1]
|
||||
: dialogBody;
|
||||
|
||||
const dialogRef = actionConfig?.[dialogObj!]?.$ref;
|
||||
|
||||
if (dialogRef) {
|
||||
data.actionData!.__dialogTitle = definitions[dialogRef].title;
|
||||
} else {
|
||||
data.actionData!.__dialogTitle = actionConfig?.[dialogObj!]?.title;
|
||||
}
|
||||
|
||||
if (actionConfig.args?.fromCurrentDialog) {
|
||||
data.actionData!.__dialogSource = 'current';
|
||||
data.actionData!.__selectDialog = definitions[dialogRef].$$id;
|
||||
} else {
|
||||
data.actionData!.__dialogSource = 'new';
|
||||
}
|
||||
}
|
||||
|
||||
// 选中项自动滚动至可见位置
|
||||
setTimeout(
|
||||
() =>
|
||||
@ -913,14 +884,14 @@ export class EventControl extends React.Component<
|
||||
pluginActions,
|
||||
getContextSchemas,
|
||||
__superCmptTreeSource: allComponents,
|
||||
__dialogActions: this.getDialogList(manager)
|
||||
__dialogActions: manager.store.modalOptions
|
||||
};
|
||||
}
|
||||
this.setState(data);
|
||||
}
|
||||
|
||||
// 渲染描述信息
|
||||
renderDesc(action: ActionConfig) {
|
||||
renderDesc(action: ActionConfig, actionIndex: number, eventKey: string) {
|
||||
const {
|
||||
actions: pluginActions,
|
||||
actionTree,
|
||||
@ -957,91 +928,39 @@ export class EventControl extends React.Component<
|
||||
}
|
||||
|
||||
return typeof desc === 'function' ? (
|
||||
<div className="action-control-content">{desc?.(info) || '-'}</div>
|
||||
<div className="action-control-content">
|
||||
{desc?.(
|
||||
info,
|
||||
{
|
||||
actionIndex,
|
||||
eventKey
|
||||
},
|
||||
this.props
|
||||
) || '-'}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
getRefsFromCurrentDialog(store: any, action: any) {
|
||||
let definitions = store.schema.definitions;
|
||||
let dialogMaxIndex: number = 0;
|
||||
let dialogRefsName = '';
|
||||
if (definitions) {
|
||||
Object.keys(definitions).forEach(k => {
|
||||
const dialog = definitions[k];
|
||||
if (dialog.$$id === action.__selectDialog) {
|
||||
dialogRefsName = k;
|
||||
}
|
||||
if (k.includes('ref-')) {
|
||||
let index = Number(k.split('-')[2]);
|
||||
dialogMaxIndex = Math.max(dialogMaxIndex, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
let dialogType = getFixDialogType(store.schema, action.__selectDialog);
|
||||
if (!dialogRefsName) {
|
||||
dialogRefsName = dialogMaxIndex
|
||||
? `${dialogType}-ref-${dialogMaxIndex + 1}`
|
||||
: `${dialogType}-ref-1`;
|
||||
}
|
||||
return dialogRefsName;
|
||||
}
|
||||
|
||||
@autobind
|
||||
onSubmit(type: string, config: any) {
|
||||
const {actionConfigSubmitFormatter, manager} = this.props;
|
||||
const {actionData} = this.state;
|
||||
const store = manager.store;
|
||||
const action =
|
||||
|
||||
let action =
|
||||
actionConfigSubmitFormatter?.(config, type, actionData, store.schema) ??
|
||||
config;
|
||||
|
||||
action = this.submitSubscribers.reduce(
|
||||
(action, fn) => fn(action) || action,
|
||||
action
|
||||
);
|
||||
|
||||
delete action.__actionSchema;
|
||||
if (type === 'add') {
|
||||
if (['dialog', 'drawer', 'confirmDialog'].includes(action.actionType)) {
|
||||
let args =
|
||||
action.actionType === 'dialog'
|
||||
? 'dialog'
|
||||
: action.actionType === 'drawer'
|
||||
? 'drawer'
|
||||
: 'dialog';
|
||||
|
||||
if (!config?.__dialogSource || config?.__dialogSource === 'new') {
|
||||
let actionLength = this.state.onEvent[config.eventKey].actions.length;
|
||||
let path = `${store.getSchemaPath(store.activeId)}/onEvent/${
|
||||
config.eventKey
|
||||
}/actions/${actionLength}/${args}`;
|
||||
store.setActiveDialogPath(path);
|
||||
} else if (config?.__dialogSource === 'current') {
|
||||
let dialogRefsName = this.getRefsFromCurrentDialog(store, action);
|
||||
let path = `definitions/${dialogRefsName}`;
|
||||
store.setActiveDialogPath(path);
|
||||
}
|
||||
this.addAction?.(config.eventKey, action);
|
||||
} else {
|
||||
this.addAction?.(config.eventKey, action);
|
||||
}
|
||||
this.addAction?.(config.eventKey, action);
|
||||
} else if (type === 'update') {
|
||||
if (['dialog', 'drawer', 'confirmDialog'].includes(action.actionType)) {
|
||||
let args =
|
||||
action.actionType === 'dialog'
|
||||
? 'dialog'
|
||||
: action.actionType === 'drawer'
|
||||
? 'drawer'
|
||||
: 'dialog';
|
||||
|
||||
if (config.__dialogSource === 'new') {
|
||||
let path = `${store.getSchemaPath(store.activeId)}/onEvent/${
|
||||
config.eventKey
|
||||
}/actions/${config.actionIndex}/${args}`;
|
||||
store.setActiveDialogPath(path);
|
||||
} else if (config.__dialogSource === 'current') {
|
||||
let dialogRefsName = this.getRefsFromCurrentDialog(store, action);
|
||||
let path = `definitions/${dialogRefsName}`;
|
||||
store.setActiveDialogPath(path);
|
||||
}
|
||||
this.updateAction?.(config.eventKey, config.actionIndex, action);
|
||||
} else {
|
||||
this.updateAction?.(config.eventKey, config.actionIndex, action);
|
||||
}
|
||||
this.updateAction?.(config.eventKey, config.actionIndex, action);
|
||||
}
|
||||
|
||||
updateCommonUseActions({
|
||||
@ -1079,6 +998,30 @@ export class EventControl extends React.Component<
|
||||
// });
|
||||
}
|
||||
|
||||
renderActionType(action: any, actionIndex: number, eventKey: string) {
|
||||
const {
|
||||
actionTree,
|
||||
actions: pluginActions,
|
||||
commonActions,
|
||||
allComponents,
|
||||
node,
|
||||
manager
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
{getPropOfAcion(
|
||||
action,
|
||||
'actionLabel',
|
||||
actionTree,
|
||||
pluginActions,
|
||||
commonActions,
|
||||
allComponents
|
||||
) || action.actionType}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
actionTree,
|
||||
@ -1086,7 +1029,8 @@ export class EventControl extends React.Component<
|
||||
commonActions,
|
||||
getComponents,
|
||||
allComponents,
|
||||
render
|
||||
render,
|
||||
subscribeSchemaSubmit
|
||||
} = this.props;
|
||||
const {
|
||||
onEvent,
|
||||
@ -1282,14 +1226,11 @@ export class EventControl extends React.Component<
|
||||
/>
|
||||
</div>
|
||||
<div className="action-item-actiontype">
|
||||
{getPropOfAcion(
|
||||
{this.renderActionType(
|
||||
action,
|
||||
'actionLabel',
|
||||
actionTree,
|
||||
pluginActions,
|
||||
commonActions,
|
||||
allComponents
|
||||
) || action.actionType}
|
||||
actionIndex,
|
||||
eventKey
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="action-control-header-right">
|
||||
@ -1327,7 +1268,7 @@ export class EventControl extends React.Component<
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderDesc(action)}
|
||||
{this.renderDesc(action, actionIndex, eventKey)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@ -1447,6 +1388,8 @@ export class EventControl extends React.Component<
|
||||
onSubmit={this.onSubmit}
|
||||
onClose={this.onClose}
|
||||
render={this.props.render}
|
||||
subscribeSchemaSubmit={subscribeSchemaSubmit}
|
||||
subscribeActionSubmit={this.subscribeSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
101
packages/amis-editor/src/renderer/style-control/FlexLayout.tsx
Normal file
101
packages/amis-editor/src/renderer/style-control/FlexLayout.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file flex 快捷分栏布局设置
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {InputBox, TooltipWrapper} from 'amis-ui';
|
||||
import {FormControlProps, FormItem} from 'amis-core';
|
||||
import cx from 'classnames';
|
||||
|
||||
function LayoutItem({
|
||||
value,
|
||||
onSelect,
|
||||
active,
|
||||
tip
|
||||
}: {
|
||||
value: string;
|
||||
onSelect: () => void;
|
||||
active?: boolean;
|
||||
tip?: string;
|
||||
}) {
|
||||
const items = String(value).split(':');
|
||||
return (
|
||||
<TooltipWrapper key="TooltipWrapper" tooltip={tip}>
|
||||
<div
|
||||
className={cx('ae-FlexLayout-item', {
|
||||
active
|
||||
})}
|
||||
style={{flex: value}}
|
||||
onClick={onSelect}
|
||||
>
|
||||
{items.map(val => (
|
||||
<div className="ae-FlexLayout-itemColumn" style={{flex: val}}></div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function FlexLayouts({
|
||||
onChange,
|
||||
value
|
||||
}: {
|
||||
onChange: (value: string) => void;
|
||||
value?: string;
|
||||
}) {
|
||||
const presetLayouts = [
|
||||
'1',
|
||||
'1:1',
|
||||
'1:2',
|
||||
'2:1',
|
||||
'1:3',
|
||||
'1:1:1',
|
||||
'1:2:1',
|
||||
'1:1:1:1'
|
||||
];
|
||||
let currentLayout = value;
|
||||
if (value) {
|
||||
// 转换成1:x格式
|
||||
let items = String(value).split(':');
|
||||
const min = Math.min.apply(null, items);
|
||||
if (items.every(item => +item % min === 0)) {
|
||||
items = items.map(item => String(+item / min));
|
||||
currentLayout = items.join(':');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ae-FlexLayout">
|
||||
<div className="ae-FlexLayout-wrap">
|
||||
{presetLayouts.map(item => (
|
||||
<LayoutItem
|
||||
key={item}
|
||||
value={item}
|
||||
tip={`排列${item}`}
|
||||
onSelect={() => onChange(item)}
|
||||
active={item === currentLayout}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-gray-500">自定义分隔比例</span>
|
||||
<InputBox
|
||||
className="ae-FlexLayout-input"
|
||||
clearable={false}
|
||||
value={value}
|
||||
placeholder="例如 1:3:2"
|
||||
onChange={val => (currentLayout = val)}
|
||||
onBlur={() => currentLayout && onChange(currentLayout)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@FormItem({type: 'flex-layout'})
|
||||
export class FlexLayoutRenderer extends React.Component<FormControlProps> {
|
||||
render() {
|
||||
return <FlexLayouts {...this.props} />;
|
||||
}
|
||||
}
|
@ -77,15 +77,15 @@ setSchemaTpl(
|
||||
value: 'static'
|
||||
},
|
||||
{
|
||||
label: '相对(relative)',
|
||||
label: '相对原位置定位(relative)',
|
||||
value: 'relative'
|
||||
},
|
||||
{
|
||||
label: '固定(fixed)',
|
||||
label: '视窗中悬浮(fixed)',
|
||||
value: 'fixed'
|
||||
},
|
||||
{
|
||||
label: '绝对(absolute)',
|
||||
label: '绝对定位(absolute)',
|
||||
value: 'absolute'
|
||||
}
|
||||
]
|
||||
@ -238,6 +238,11 @@ setSchemaTpl(
|
||||
flexHide?: boolean;
|
||||
}) => {
|
||||
const configOptions = compact([
|
||||
!config?.flexHide && {
|
||||
label: '弹性布局(flex)',
|
||||
icon: 'flex-display',
|
||||
value: 'flex'
|
||||
},
|
||||
{
|
||||
label: '块级(block)',
|
||||
icon: 'block-display',
|
||||
@ -252,11 +257,6 @@ setSchemaTpl(
|
||||
label: '行内元素(inline)',
|
||||
icon: 'inline-display',
|
||||
value: 'inline'
|
||||
},
|
||||
!config?.flexHide && {
|
||||
label: '弹性布局(flex)',
|
||||
icon: 'flex-display',
|
||||
value: 'flex'
|
||||
}
|
||||
]);
|
||||
const configSchema = {
|
||||
@ -898,7 +898,7 @@ setSchemaTpl(
|
||||
type: 'select',
|
||||
label:
|
||||
config?.label ||
|
||||
tipedLabel(' x轴滚动模式', '用于设置水平方向的滚动模式'),
|
||||
tipedLabel('水平内容超出', '用于设置水平方向的滚动模式'),
|
||||
name: config?.name || 'style.overflowX',
|
||||
value: config?.value || 'visible',
|
||||
visibleOn: config?.visibleOn,
|
||||
@ -914,7 +914,7 @@ setSchemaTpl(
|
||||
value: 'hidden'
|
||||
},
|
||||
{
|
||||
label: '滚动显示',
|
||||
label: '水平滚动',
|
||||
value: 'scroll'
|
||||
},
|
||||
{
|
||||
@ -1104,7 +1104,7 @@ setSchemaTpl(
|
||||
type: 'select',
|
||||
label:
|
||||
config?.label ||
|
||||
tipedLabel(' y轴滚动模式', '用于设置垂直方向的滚动模式'),
|
||||
tipedLabel('垂直内容超出', '用于设置垂直方向的滚动模式'),
|
||||
name: config?.name || 'style.overflowY',
|
||||
value: config?.value || 'visible',
|
||||
visibleOn: config?.visibleOn,
|
||||
@ -1120,7 +1120,7 @@ setSchemaTpl(
|
||||
value: 'hidden'
|
||||
},
|
||||
{
|
||||
label: '滚动显示',
|
||||
label: '垂直滚动',
|
||||
value: 'scroll'
|
||||
},
|
||||
{
|
||||
@ -1349,8 +1349,12 @@ setSchemaTpl('layout:sticky', {
|
||||
inputClassName: 'inline-flex justify-between',
|
||||
onChange: (value: boolean, oldValue: boolean, model: any, form: any) => {
|
||||
if (value) {
|
||||
const inset = form.getValueByName('style.inset');
|
||||
if (!inset || inset === 'auto') {
|
||||
form.setValueByName('stickyPosition', 'auto');
|
||||
form.setValueByName('style.inset', '0px auto 0px auto');
|
||||
}
|
||||
form.setValueByName('style.position', 'sticky');
|
||||
form.setValueByName('style.inset', '0px auto auto auto');
|
||||
form.setValueByName('style.zIndex', 10);
|
||||
} else {
|
||||
form.setValueByName('style.position', 'static');
|
||||
@ -1488,6 +1492,26 @@ setSchemaTpl(
|
||||
}
|
||||
);
|
||||
|
||||
setSchemaTpl(
|
||||
'layout:flex-layout',
|
||||
(config?: {
|
||||
name?: string;
|
||||
label?: string;
|
||||
visibleOn?: string;
|
||||
pipeIn?: (value: any, data: any) => void;
|
||||
pipeOut?: (value: any, data: any) => void;
|
||||
}) => {
|
||||
return {
|
||||
type: 'flex-layout',
|
||||
mode: 'default',
|
||||
name: config?.name || 'layout',
|
||||
label: config?.label ?? false,
|
||||
visibleOn: config?.visibleOn,
|
||||
pipeIn: config?.pipeIn,
|
||||
pipeOut: config?.pipeOut
|
||||
};
|
||||
}
|
||||
);
|
||||
// flex相关配置项(整合版)
|
||||
setSchemaTpl(
|
||||
'layout:flex-setting',
|
||||
|
@ -592,20 +592,16 @@ setSchemaTpl(
|
||||
const curHidePaddingAndMargin = hidePaddingAndMargin ?? false;
|
||||
const styleStateFunc = (visibleOn: string, state: string) => {
|
||||
return [
|
||||
getSchemaTpl('theme:border', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.border:${state}`
|
||||
}),
|
||||
getSchemaTpl('theme:radius', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.radius:${state}`
|
||||
}),
|
||||
!curHidePaddingAndMargin
|
||||
? getSchemaTpl('theme:paddingAndMargin', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.padding-and-margin:${state}`
|
||||
})
|
||||
: null,
|
||||
getSchemaTpl('theme:border', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.border:${state}`
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.background:${state}`,
|
||||
@ -615,6 +611,10 @@ setSchemaTpl(
|
||||
needImage: true,
|
||||
labelMode: 'input'
|
||||
}),
|
||||
getSchemaTpl('theme:radius', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.radius:${state}`
|
||||
}),
|
||||
getSchemaTpl('theme:shadow', {
|
||||
visibleOn: visibleOn,
|
||||
name: `themeCss.${classname}.boxShadow:${state}`
|
||||
|
@ -341,7 +341,8 @@ export const TREE_BASE_EVENTS = (schema: any) => {
|
||||
eventLabel: '值变化',
|
||||
description: '选中值变化时触发',
|
||||
dataSchema: (manager: EditorManager) => {
|
||||
const {value, items} = resolveOptionEventDataSchame(manager);
|
||||
const {value, items, itemSchema} =
|
||||
resolveOptionEventDataSchame(manager);
|
||||
|
||||
return [
|
||||
{
|
||||
@ -352,6 +353,11 @@ export const TREE_BASE_EVENTS = (schema: any) => {
|
||||
title: '数据',
|
||||
properties: {
|
||||
value,
|
||||
item: {
|
||||
type: 'object',
|
||||
title: '选中的项',
|
||||
properties: itemSchema
|
||||
},
|
||||
items
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-formula",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2.1",
|
||||
"description": "负责 amis 里面的表达式实现,内置公式,编辑器等",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -114,4 +114,4 @@
|
||||
}
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
}
|
||||
|
@ -270,16 +270,18 @@ function BoxBorder(props: BorderProps & FormControlProps) {
|
||||
}-border-width`}
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.[getKey('width')]}
|
||||
placeholder={editorDefaultValue?.[getKey('width')] || '边框粗细'}
|
||||
/>
|
||||
<div className="Theme-Border-settings-style-color">
|
||||
<Select
|
||||
options={borderStyleOptions}
|
||||
value={borderData[getKey('style')]}
|
||||
placeholder={getLabel(
|
||||
editorDefaultValue?.[getKey('style')],
|
||||
borderStyleOptions
|
||||
)}
|
||||
placeholder={
|
||||
getLabel(
|
||||
editorDefaultValue?.[getKey('style')],
|
||||
borderStyleOptions
|
||||
) || '边框样式'
|
||||
}
|
||||
onChange={(item: any) => changeItem('style')(item.value)}
|
||||
clearable={!!editorDefaultValue}
|
||||
renderMenu={(item: Options) => {
|
||||
@ -322,7 +324,7 @@ function BoxBorder(props: BorderProps & FormControlProps) {
|
||||
itemName={`${
|
||||
borderType === 'all' ? 'top' : borderType
|
||||
}-border-color`}
|
||||
placeholder={editorDefaultValue?.[getKey('color')]}
|
||||
placeholder={editorDefaultValue?.[getKey('color')] || '边框颜色'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1001,7 +1001,7 @@ function FontEditor(props: FontEditorProps) {
|
||||
}}
|
||||
itemName="color"
|
||||
state={state}
|
||||
placeholder={editorDefaultValue?.color}
|
||||
placeholder={editorDefaultValue?.color || '字体颜色'}
|
||||
editorInheritValue={editorInheritValue?.color}
|
||||
/>
|
||||
</div>
|
||||
@ -1019,7 +1019,7 @@ function FontEditor(props: FontEditorProps) {
|
||||
menuTpl="label"
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.fontSize}
|
||||
placeholder={editorDefaultValue?.fontSize || '字体大小'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -1038,7 +1038,7 @@ function FontEditor(props: FontEditorProps) {
|
||||
menuTpl="label"
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.fontWeight}
|
||||
placeholder={editorDefaultValue?.fontWeight || '字体字重'}
|
||||
/>
|
||||
{(!hideLineHeight || !hideFontFamily) && (
|
||||
<div className="Theme-FontEditor-item-label">字重</div>
|
||||
@ -1058,7 +1058,7 @@ function FontEditor(props: FontEditorProps) {
|
||||
menuTpl="label"
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.lineHeight}
|
||||
placeholder={editorDefaultValue?.lineHeight || '字体行高'}
|
||||
/>
|
||||
<div className="Theme-FontEditor-item-label">行高</div>
|
||||
</div>
|
||||
|
@ -231,7 +231,7 @@ function PaddingAndMarginDialog(props: PaddingAndMarginProps) {
|
||||
itemName="margin-all"
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.margin}
|
||||
placeholder={editorDefaultValue?.margin || '外边距'}
|
||||
/>
|
||||
<div className="Theme-PaddingAndMargin-input-label">外边距</div>
|
||||
</div>
|
||||
@ -250,7 +250,7 @@ function PaddingAndMarginDialog(props: PaddingAndMarginProps) {
|
||||
itemName="padding-all"
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.padding}
|
||||
placeholder={editorDefaultValue?.padding || '内边距'}
|
||||
/>
|
||||
<div className="Theme-PaddingAndMargin-input-label">内边距</div>
|
||||
</div>
|
||||
|
@ -196,7 +196,7 @@ function BoxRadius(props: RadiusProps & RendererProps) {
|
||||
itemName={'all-border-radius'}
|
||||
state={state}
|
||||
inheritValue={editorThemePath ? 'inherit' : ''}
|
||||
placeholder={editorDefaultValue?.[getKey('all')]}
|
||||
placeholder={editorDefaultValue?.[getKey('all')] || '圆角'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2.1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "npm run clean-dist && NODE_ENV=production rollup -c ",
|
||||
@ -36,8 +36,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/mini-decimal": "^1.0.1",
|
||||
"amis-core": "^6.1.0",
|
||||
"amis-formula": "^6.1.0",
|
||||
"amis-core": "^6.2.1",
|
||||
"amis-formula": "^6.2.1",
|
||||
"classnames": "2.3.2",
|
||||
"codemirror": "^5.63.0",
|
||||
"downshift": "6.1.12",
|
||||
@ -65,6 +65,7 @@
|
||||
"react-intersection-observer": "9.5.2",
|
||||
"react-json-view": "1.21.3",
|
||||
"react-overlays": "5.1.1",
|
||||
"react-pdf": "^7.7.1",
|
||||
"react-textarea-autosize": "8.3.3",
|
||||
"react-transition-group": "4.4.2",
|
||||
"react-visibility-sensor": "5.1.1",
|
||||
@ -143,4 +144,4 @@
|
||||
]
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user