Merge branch 'baidu:master' into master

This commit is contained in:
ls 2023-07-31 11:51:36 +08:00 committed by GitHub
commit c8fb3f936e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 4935 additions and 1317 deletions

View File

@ -207,6 +207,7 @@ order: 27
| 属性名 | 类型 | 默认值 | 说明 |
| -------------------- | ----------------------------------------- | --------- | -------------------------------------------------------- |
| type | `string` | `"alert"` | 指定为 alert 渲染器 |
| title | `string` | | alert标题 |
| className | `string` | | 外层 Dom 的类名 |
| level | `string` | `info` | 级别,可以是:`info`、`success`、`warning` 或者 `danger` |
| body | [SchemaNode](../../docs/types/schemanode) | | 显示内容 |

View File

@ -15,7 +15,7 @@ order: 27
```schema: scope="body"
{
"type": "avatar",
"src": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg"
"src": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
```
@ -44,6 +44,7 @@ order: 27
## 动态图片或文字
src、text 都支持变量,可以从上下文中动态获取图片或文字,下面的例子中:
- 第一个获取到了,显示正常
- 第二个没获取到,因此降级为显示 icon
- 第三个图片没获取到,由于 text 优先级比 icon 高,所以显示 text
@ -51,7 +52,7 @@ src、text 都支持变量,可以从上下文中动态获取图片或文字,
```schema
{
"data": {
"myAvatar": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg"
"myAvatar": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
"type": "page",
"body": [
@ -122,16 +123,16 @@ src、text 都支持变量,可以从上下文中动态获取图片或文字,
{
"type": "avatar",
"size": 60,
"src": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg"
"src": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"type": "avatar",
"src": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg"
"src": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"type": "avatar",
"size": 20,
"src": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg"
"src": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
]
@ -213,6 +214,7 @@ src、text 都支持变量,可以从上下文中动态获取图片或文字,
```
## 图片加载失败后,通过 onError 控制是否进行 text、icon 置换
> 如果同时存在 text 和 icon会优先用 text、接着 icon
```schema: scope="body"
@ -241,18 +243,18 @@ src、text 都支持变量,可以从上下文中动态获取图片或文字,
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | ----------- | ------ | --------------------- |
| className | `string` | | 外层 dom 的类名 |
| style | `object` | | 外层 dom 的样式 |
| fit |`'contain'` \| `'cover'` \| `'fill'` \| `'none'` \| `'scale-down'` | `'cover'` | 具体细节可以参考 MDN [文档](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit) |
| src | `string` | | 图片地址 |
| text | `string` | | 文字 |
| icon | `string` | `'fa fa-user'` | 图标 |
| shape | `'circle'` \| `'square'` \| `'rounded'` | `'circle'` | 形状,有三种 `'circle'` (圆形)、`'square'`(正方形)、`'rounded'`(圆角) |
| size | `number` \| `'default'` \| `'normal'` \| `'small'` | `'default'` | `'default' \| 'normal' \| 'small'`三种字符串类型代表不同大小分别是48、40、32也可以直接数字表示 |
| gap | `number` | 4 | 控制字符类型距离左右两侧边界单位像素 |
| alt | `number` | | 图像无法显示时的替代文本 |
| draggable | `boolean` | | 图片是否允许拖动 |
| crossOrigin | `'anonymous'` \| `'use-credentials'` \| `''` | | 图片的 `CORS` 属性设置 |
| onError | `string` | | 图片加载失败的字符串这个字符串是一个New Function内部执行的字符串参数是event使用event.nativeEvent获取原生dom事件这个字符串需要返回boolean值。设置 `"return ture;"` 会在图片加载失败后,使用 `text` 或者 `icon` 代表的信息来进行替换。目前图片加载失败默认是不进行置换。注意:图片加载失败,不包括$获取数据为空情况 |
| 属性名 | 类型 | 默认值 | 说明 |
| ----------- | ------------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| className | `string` | | 外层 dom 的类名 |
| style | `object` | | 外层 dom 的样式 |
| fit | `'contain'` \| `'cover'` \| `'fill'` \| `'none'` \| `'scale-down'` | `'cover'` | 具体细节可以参考 MDN [文档](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit) |
| src | `string` | | 图片地址 |
| text | `string` | | 文字 |
| icon | `string` | `'fa fa-user'` | 图标 |
| shape | `'circle'` \| `'square'` \| `'rounded'` | `'circle'` | 形状,有三种 `'circle'` (圆形)、`'square'`(正方形)、`'rounded'`(圆角) |
| size | `number` \| `'default'` \| `'normal'` \| `'small'` | `'default'` | `'default' \| 'normal' \| 'small'`三种字符串类型代表不同大小(分别是 48、40、32也可以直接数字表示 |
| gap | `number` | 4 | 控制字符类型距离左右两侧边界单位像素 |
| alt | `number` | | 图像无法显示时的替代文本 |
| draggable | `boolean` | | 图片是否允许拖动 |
| crossOrigin | `'anonymous'` \| `'use-credentials'` \| `''` | | 图片的 `CORS` 属性设置 |
| onError | `string` | | 图片加载失败的字符串,这个字符串是一个 New Function 内部执行的字符串,参数是 event使用 event.nativeEvent 获取原生 dom 事件),这个字符串需要返回 boolean 值。设置 `"return ture;"` 会在图片加载失败后,使用 `text` 或者 `icon` 代表的信息来进行替换。目前图片加载失败默认是不进行置换。注意:图片加载失败,不包括$获取数据为空情况 |

View File

@ -26,7 +26,7 @@ order: 33
},
{
"thumbMode": "contain",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
]
}
@ -187,7 +187,7 @@ itemSchema: {
},
{
"thumbMode": "contain",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
],
"onEvent": {
@ -250,7 +250,7 @@ itemSchema: {
},
{
"thumbMode": "contain",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
]
}
@ -292,7 +292,7 @@ itemSchema: {
},
{
"thumbMode": "contain",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
]
}
@ -337,7 +337,7 @@ itemSchema: {
},
{
"thumbMode": "contain",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
]
}

View File

@ -33,7 +33,7 @@ order: 43
}
}
}
}
}
}
}
```
@ -400,6 +400,30 @@ order: 43
}
```
> 3.3.0 及以上版本
如果是表单,可以在表单上配置 `close: false`
```schema: scope="body"
{
"type": "button",
"label": "弹个框",
"actionType": "drawer",
"drawer": {
"type": "form",
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-text",
"name": "name",
"label": "姓名"
}
],
"close": false
}
}
```
## 配置弹窗的按钮
默认弹窗会自动生成两个按钮,一个取消,一个确认。如果通过 `actions` 来自定义配置,则以配置的为准。
@ -445,8 +469,8 @@ order: 43
| title | [SchemaNode](../../docs/types/schemanode) | | 弹出层标题 |
| body | [SchemaNode](../../docs/types/schemanode) | | 往 Drawer 内容区加内容 |
| size | `string` | | 指定 Drawer 大小,支持: `xs`、`sm`、`md`、`lg`、`xl` |
| position | `string` | | 指定 Drawer 方向,支持: `left`、`right`、`top`、`bottom` |
| className | `string` | `` | Drawer 最外层容器的样式类名 |
| position | `string` | `right` | 指定 Drawer 方向,支持: `left`、`right`、`top`、`bottom` |
| className | `string` | | Drawer 最外层容器的样式类名 |
| headerClassName | `string` | | Drawer 头部 区域的样式类名 |
| bodyClassName | `string` | `modal-body` | Drawer body 区域的样式类名 |
| footerClassName | `string` | | Drawer 页脚 区域的样式类名 |

View File

@ -647,14 +647,12 @@ Form 默认会在底部渲染一个提交按钮,用于执行表单的提交行
"value": true
},
{
"type": 'button-toolbar',
"name": 'button-toolbar',
"type": "button-toolbar",
"buttons": [
{
"type": "button",
"label": "提交",
"label": "${isStatic ? '编辑' : '提交'}",
"level": "primary",
"visibleOn": "${!isStatic}",
"onEvent": {
"click": {
"actions": [
@ -663,45 +661,26 @@ Form 默认会在底部渲染一个提交按钮,用于执行表单的提交行
"componentId": "allFormSwitch",
"args": {
"value": {
"isStatic": true
"isStatic": "${!isStatic}"
}
}
},
{
"actionType": "static",
"componentId": "allFormSwitch"
}
]
}
}
},
{
"type": "button",
"label": "编辑",
"level": "primary",
"visibleOn": "${isStatic}",
"onEvent": {
"click": {
"actions": [
{
"actionType": "setValue",
"componentId": "allFormSwitch",
"args": {
"value": {
"isStatic": false
}
}
"expression": "${!isStatic}"
},
{
"actionType": "nonstatic",
"componentId": "allFormSwitch"
"componentId": "allFormSwitch",
"expression": "${isStatic}"
}
]
}
}
}
]
},
}
],
"actions": []
}

View File

@ -71,6 +71,27 @@ order: 10
}
```
## 配置下拉框样式
可以通过 `itemClassName` 指定下拉框样式,如配置最小宽度
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"name": "city",
"type": "input-city",
"label": "城市",
"itemClassName": "min-w-xs",
"searchable": true
}
]
}
```
## 属性表
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置

View File

@ -210,6 +210,26 @@ order: 15
}
```
## 存成两个字段
默认日期范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-date-range",
"name": "begin",
"extraName": "end",
"label": "日期范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
@ -228,6 +248,7 @@ order: 15
| clearable | `boolean` | `true` | 是否可清除 |
| embed | `boolean` | `false` | 是否内联模式 |
| animation | `boolean` | `true` | 是否启用游标动画 | `2.2.0` |
| extraName | `string` | | 是否存成两个字段 | `3.3.0` |
## 事件表

View File

@ -102,6 +102,26 @@ order: 16
}
```
## 存成两个字段
默认日期范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-datetime-range",
"name": "begin",
"extraName": "end",
"label": "日期范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
@ -117,6 +137,7 @@ order: 16
| utc | `boolean` | `false` | [保存 UTC 值](./input-datetime#utc) |
| clearable | `boolean` | `true` | 是否可清除 |
| animation | `boolean` | `true` | 是否启用游标动画 | `2.2.0` |
| extraName | `string` | | 是否存成两个字段 | `3.3.0` |
## 事件表

View File

@ -43,6 +43,26 @@ order: 15
}
```
## 存成两个字段
默认月份范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-month-range",
"name": "begin",
"extraName": "end",
"label": "月份范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
@ -60,6 +80,7 @@ order: 15
| clearable | `boolean` | `true` | 是否可清除 |
| embed | `boolean` | `false` | 是否内联模式 |
| animation | `boolean` | `true` | 是否启用游标动画 | `2.2.0` |
| extraName | `string` | | 是否存成两个字段 | `3.3.0` |
## 事件表

View File

@ -26,7 +26,7 @@ order: 32
## 设置精度
`precision` 设置数字的显示精度,一般需要配合`step`属性使用,以实现细粒度调整。注意带有单位的输入不支持配置精度属性。若设置了`step`值,则会基于`step` 和`precision`的值,选择更高的精度。若输入的内容不满足精度要求,组件会按照精度自动处理,遵循四舍五入规则。
`precision` 设置数字的显示精度,一般需要配合 `step` 属性使用,以实现细粒度调整。注意带有单位的输入不支持配置精度属性。若设置了 `step` 值,则会基于 `step` `precision` 的值,选择更高的精度。若输入的内容不满足精度要求,组件会按照精度自动处理,遵循四舍五入规则。
```schema: scope="body"
{
@ -65,7 +65,7 @@ order: 32
## 重置值
清空/重置组件输入后,组件绑定的值将被设置为`resetValue`,默认为`""`。若`resetValue`为合法数字时,会根据`min`、`max`和`precision`属性,将组件值设置为满足条件的值。若`resetValue`为非数字,则组件清空/重置后设置为该值。
清空/重置组件输入后,组件绑定的值将被设置为 `resetValue`,默认为 `""`。若 `resetValue` 为合法数字时,会根据 `min`、`max` `precision` 属性,将组件值设置为满足条件的值。若 `resetValue` 为非数字,则组件清空/重置后设置为该值。
```schema: scope="body"
{
@ -93,7 +93,7 @@ order: 32
"type": "input-number",
"name": "number3",
"label": "数字带有max和precision",
"max": 100.50,
"max": 100.5,
"precision": 2,
"resetValue": 1000,
"value": 1234,
@ -174,7 +174,7 @@ order: 32
> 2.3.0 及以上版本
默认情况下使用 JavaScript 原生数字类型,但如果要支持输入超过 JavaScript 支持范围的整数或浮点数,可以通过 `"big": true` 开启大数支持,开启之后输入输出都将是字符串
默认情况下使用 JavaScript 原生数字类型,但如果要支持输入超过 JavaScript 支持范围的整数或浮点数,可以通过 `"big": true` 开启大数支持,开启之后输入输出都将是字符串
```schema: scope="body"
{
@ -186,7 +186,7 @@ order: 32
"type": "input-number",
"name": "number",
"label": "数字",
"big": "true"
"big": true
}
]
}
@ -196,65 +196,65 @@ order: 32
> 2.8.0 及以上版本
如果设置了`"clearValueOnEmpty": true`,当输入框的值清空时,会从数据域中删除该表单项对应的值。比较常见的用法是在`combo``input-array`等组件中避免`input-number`清空后提交空字符串。
如果设置了 `"clearValueOnEmpty": true`,当输入框的值清空时,会从数据域中删除该表单项对应的值。比较常见的用法是在 `combo`、`input-array` 等组件中避免 `input-number` 清空后提交空字符串。
```schema: scope="body"
{
"type": "form",
"debug": true,
"debugConfig": {
"levelExpand": 2
},
"body": [
{
"type": "group",
"body": [
{
"name": "numberClear",
"type": "input-number",
"label": "清空",
"value": 123,
"clearValueOnEmpty": true
},
{
"name": "numberNotClear",
"type": "input-number",
"label": "不清空",
"value": 456
}
]
"type": "form",
"debug": true,
"debugConfig": {
"levelExpand": 2
},
{
"type": "combo",
"name": "user",
"label": "用户",
"items": [
"body": [
{
"name": "text",
"label": "名字",
"type": "input-text"
"type": "group",
"body": [
{
"name": "numberClear",
"type": "input-number",
"label": "清空",
"value": 123,
"clearValueOnEmpty": true
},
{
"name": "numberNotClear",
"type": "input-number",
"label": "不清空",
"value": 456
}
]
},
{
"name": "gender",
"label": "性别",
"type": "select",
"options": ["男", "女"]
},
{
"name": "age",
"label": "年龄",
"type": "input-number",
"clearValueOnEmpty": true
"type": "combo",
"name": "user",
"label": "用户",
"items": [
{
"name": "text",
"label": "名字",
"type": "input-text"
},
{
"name": "gender",
"label": "性别",
"type": "select",
"options": ["男", "女"]
},
{
"name": "age",
"label": "年龄",
"type": "input-number",
"clearValueOnEmpty": true
}
]
}
]
}
]
]
}
```
## 原生数字组件
原生数字组件将直接使用浏览器的实现,最终展现效果和浏览器有关,而且只支持 `min`、`max`、`step` 这几个属性设置。
原生数字组件将直接使用浏览器的实现,最终展现效果和浏览器有关,并且只支持 `min`、`max` 和 `step` 这几个属性设置。
```schema: scope="body"
{
@ -274,27 +274,30 @@ order: 32
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ----------------- | --------------------------------------- | ------- | ------------------------------------------ | ------- |
| min | [模板](../../../docs/concepts/template) | | 最小值 |
| max | [模板](../../../docs/concepts/template) | | 最大值 |
| step | `number` | | 步长 |
| precision | `number` | | 精度,即小数点后几位,支持 0 和正整数 |
| showSteps | `boolean` | | 是否显示上下点击按钮 |
| prefix | `string` | | 前缀 |
| suffix | `string` | | 后缀 |
| kilobitSeparator | `boolean` | | 千分分隔 |
| keyboard | `boolean` | | 键盘事件(方向上下) |
| big | `boolean` | | 是否使用大数 |
| displayMode | `string` | | 样式类型 |
| resetValue | `number \| string` | `""` | 清空输入内容时,组件值将设置为`resetValue` |
| clearValueOnEmpty | `boolean` | `false` | 内容为空时从数据域中删除该表单项对应的值 | `2.8.0` |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ----------------- | --------------------------------------- | -------- | ------------------------------------------- | ------- |
| min | [模板](../../../docs/concepts/template) | | 最小值 |
| max | [模板](../../../docs/concepts/template) | | 最大值 |
| step | `number` | | 步长 |
| precision | `number` | | 精度,即小数点后几位,支持 0 和正整数 |
| showSteps | `boolean` | `true` | 是否显示上下点击按钮 |
| readOnly | `boolean` | `false` | 只读 |
| prefix | `string` | | 前缀 |
| suffix | `string` | | 后缀 |
| unitOptions | `string[]` | | 单位选项 | `1.4.0` |
| kilobitSeparator | `boolean` | `false` | 千分分隔 |
| keyboard | `boolean` | `true` | 键盘事件(方向上下) |
| big | `boolean` | `false` | 是否使用大数 | `2.3.0` |
| displayMode | `"base" \| "enhance"` | `"base"` | 样式类型 |
| borderMode | `"full" \| "half" \| "none"` | `"full"` | 边框模式,全边框,还是半边框,或者没边框 |
| resetValue | `number \| string` | `""` | 清空输入内容时,组件值将设置为 `resetValue` |
| clearValueOnEmpty | `boolean` | `false` | 内容为空时从数据域中删除该表单项对应的值 | `2.8.0` |
## 事件表
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。
当前组件会对外派发以下事件,可以通过 `onEvent` 来监听这些事件,并通过 `actions` 来配置执行的动作,在 `actions` 中可以通过 `${事件参数名}` `${event.data.[事件参数名]}` 来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
> `[name]` 表示当前组件绑定的名称,即 `name` 属性,如果没有配置 `name` 属性,则通过 `value` 取值。
| 事件名称 | 事件参数 | 说明 |
| -------- | ------------------------- | ---------------- |
@ -304,10 +307,10 @@ order: 32
## 动作表
当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
当前组件对外暴露以下特性动作,其他组件可以通过指定 `actionType: 动作名称`、`componentId: 该组件id` 来触发这些动作,动作配置可以通过 `args: {动作配置项名称: xxx}` 来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
| 动作名称 | 动作配置 | 说明 |
| -------- | -------------------------- | ------------------------------------------------------ |
| clear | - | 清空 |
| reset | - | 将值重置为`resetValue`,若没有配置`resetValue`,则清空 |
| setValue | `value: number` 更新的数值 | 更新数据 |
| 动作名称 | 动作配置 | 说明 |
| -------- | -------------------------- | -------------------------------------------------------- |
| clear | - | 清空 |
| reset | - | 将值重置为 `resetValue`,若没有配置 `resetValue`,则清空 |
| setValue | `value: number` 更新的数值 | 更新数据 |

View File

@ -42,6 +42,26 @@ order: 15
}
```
## 存成两个字段
默认季度范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-quarter-range",
"name": "begin",
"extraName": "end",
"label": "季度范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
@ -59,6 +79,7 @@ order: 15
| clearable | `boolean` | `true` | 是否可清除 |
| embed | `boolean` | `false` | 是否内联模式 |
| animation | `boolean` | `true` | 是否启用游标动画 | `2.2.0` |
| extraName | `string` | | 是否存成两个字段 | `3.3.0` |
## 事件表

View File

@ -251,6 +251,27 @@ order: 38
}
```
## 存成两个字段
默认滑块多选存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-range",
"multiple": true,
"name": "begin",
"extraName": "end",
"label": "range"
}
]
}
```
## 属性表
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置

View File

@ -65,6 +65,26 @@ order: 15
}
```
## 存成两个字段
默认季度范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-time-range",
"name": "begin",
"extraName": "end",
"label": "时间范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
@ -78,6 +98,7 @@ order: 15
| clearable | `boolean` | `true` | 是否可清除 |
| embed | `boolean` | `false` | 是否内联模式 |
| animation | `boolean` | `true` | 是否启用游标动画 | `2.2.0` |
| extraName | `string` | | 是否存成两个字段 | `3.3.0` |
## 事件表

View File

@ -43,6 +43,26 @@ order: 15
}
```
## 存成两个字段
默认年份范围存储一个字段,用 `delemiter` 分割,如果配置 `extraName` 则会存两个字段。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-year-range",
"name": "begin",
"extraName": "end",
"label": "年份范围"
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置

View File

@ -52,22 +52,22 @@ ListSelect 一般用来实现选择,可以单选也可以多选,和 Radio/Ch
{
"label": "OptionA",
"value": "a",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"label": "OptionB",
"value": "b",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"label": "OptionC",
"value": "c",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"label": "OptionD",
"value": "d",
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}
]
}

View File

@ -94,7 +94,7 @@ order: 52
"type": "form",
"data": {
"id": 1,
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg",
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg",
"images": [
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",

View File

@ -609,10 +609,71 @@ icon:
设置这个 api可以实现左侧选项搜索结果的检索。
##### 发送
默认 GET携带 term 变量,值为搜索框输入的文字,可从上下文中取数据设置进去。
```schema: scope="body"
{
"type": "page",
"body": {
"type": "form",
"api": "/api/mock2/form/saveForm",
"body": [
{
"label": "searchApi",
"type": "transfer",
"name": "transfer",
"searchable": true,
"selectMode": "tree",
"searchApi": "/api/transfer/search?name=${term}",
"options": [
{
"label": "法师",
"children": [
{
"label": "诸葛亮",
"value": "zhugeliang"
}
]
},
{
"label": "战士",
"value": "zhanshi",
"children": [
{
"label": "曹操",
"value": "caocao"
},
{
"label": "钟无艳",
"value": "zhongwuyan"
}
]
},
{
"label": "打野",
"children": [
{
"label": "李白",
"value": "libai"
},
{
"label": "韩信",
"value": "hanxin"
},
{
"label": "云中君",
"value": "yunzhongjun"
}
]
}
]
}
]
}
}
```
##### 响应
格式要求如下:
@ -625,7 +686,7 @@ icon:
"options": [
{
"label": "描述",
"value": "值" // ,
"value": "值"
// "children": [] // 可以嵌套
},
@ -634,14 +695,13 @@ icon:
"value": "值2"
}
],
"value": "值" // 默认值,可以获取列表的同时设置默认值。
}
}
```
适用于需选择的数据/信息源较多时,用户可直观的知道自己所选择的数据/信息的场景,一般左侧框为数据/信息源,右侧为已选数据/信息,被选中信息同时存在于 2 个框内。
### 结果面板跟随模式
`resultListModeFollowSelect` 开启结果面板跟随模式。

View File

@ -259,7 +259,7 @@ Word 渲染支持以下功能:
{
"type": "input-text",
"name": "img",
"value": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg",
"value": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg",
"label": "图片地址"
},
{

View File

@ -118,7 +118,7 @@ order: 67
"audio": "https://news-bos.cdn.bcebos.com/mvideo/%E7%9A%87%E5%90%8E%E5%A4%A7%E9%81%93%E4%B8%9C.aac",
"carousel": [
{
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"html": "<div style=\"width: 100%; height: 200px; background: #e3e3e3; text-align: center; line-height: 200px;\">carousel data in crud</div>"
@ -128,7 +128,7 @@ order: 67
}
],
"date": 1591270438,
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg",
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg",
"json": {
"id": 1,
"text": "text"
@ -185,7 +185,7 @@ order: 67
"audio": "https://news-bos.cdn.bcebos.com/mvideo/%E7%9A%87%E5%90%8E%E5%A4%A7%E9%81%93%E4%B8%9C.aac",
"carousel": [
{
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"html": "<div style=\"width: 100%; height: 200px; background: #e3e3e3; text-align: center; line-height: 200px;\">carousel data in crud</div>"
@ -195,7 +195,7 @@ order: 67
}
],
"date": 1591270438,
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg",
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg",
"json": {
"id": 1,
"text": "text"
@ -244,7 +244,7 @@ order: 67
"audio": "https://news-bos.cdn.bcebos.com/mvideo/%E7%9A%87%E5%90%8E%E5%A4%A7%E9%81%93%E4%B8%9C.aac",
"carousel": [
{
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
},
{
"html": "<div style=\"width: 100%; height: 200px; background: #e3e3e3; text-align: center; line-height: 200px;\">carousel data in crud</div>"
@ -254,7 +254,7 @@ order: 67
}
],
"date": 1591270438,
"image": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg",
"image": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg",
"json": {
"id": 1,
"text": "text"

View File

@ -2,11 +2,11 @@
title: 如何贡献代码
---
如果发现 amis 有不满足的功能,除了发 issue 等官方升级之外,最快的方法就是自己实现它,本文将介绍 amis 代码的基本结构,一步步教你如何新增功能。
如果发现 amis 有不满足的功能,除了发 issue 等官方升级之外,最快的方法就是自己实现它,本文将介绍 amis 代码的基本结构,一步步教你如何新增功能。
## 准备开始
1. 首先,你需要对 React 有基本了解,快速看一遍[官方文档](https://zh-hans.reactjs.org/docs/getting-started.html)就行
1. 首先,你需要对 React 有基本了解,快速看一遍[官方文档](https://zh-hans.react.dev/learn)即可
2. 在 github 上 fork amis 项目到自己的账号下。
3. 创建分支 `git checkout -b feat-xxx`

View File

@ -155,6 +155,8 @@ let amisScoped = amis.embed(
// 另外在 amis 配置项中的 api 也可以配置适配器,针对某个特定接口单独处理。
//
// requestAdaptor(api) {
// // 支持异步,可以通过 api.mockResponse 来设置返回结果,跳过真正的请求发送
// // 此功能自定义 fetcher 的话会失效
// return api;
// }
//

View File

@ -659,6 +659,7 @@ const schema = {
url: '/api/mock2/form/saveForm',
requestAdaptor: function (api, context) {
console.log(context); // 打印上下文数据
return {
...api,
data: {
@ -687,6 +688,47 @@ const schema = {
你也可以使用`debugger`自行进行调试。
#### 拦截请求
如果 api 发送适配器中,修改 api 对象,在 api 对象里面放入 `mockReponse` 属性则会拦截请求发送amis 内部会直接使用 `mockReponse` 的结果返回。
```js
const schema = {
type: 'form',
api: {
method: 'post',
url: '/api/mock2/form/saveForm',
requestAdaptor: function (api, context) {
return {
// 模拟 http 请求返回
mockResponse: {
status: 200, // http 返回状态
data: {
// http 返回结果
status: 0, // amis 返回数据的状态
data: {
name: '模拟返回的值'
}
}
}
};
}
},
body: [
{
type: 'input-text',
name: 'name',
label: '姓名:'
},
{
name: 'text',
type: 'input-email',
label: '邮箱:'
}
]
};
```
### 配置接收适配器
同样的,如果后端返回的响应结构不符合 amis 的[接口格式要求](#%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F-%E9%87%8D%E8%A6%81-),而后端不方便调整时,可以配置`adaptor`实现接收适配器

View File

@ -74,8 +74,7 @@ export default {
},
{
thumbMode: 'contain',
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
}
],
onEvent: {

View File

@ -51,12 +51,12 @@ export function embed(
container.classList.add('amis-scope');
let scoped = {};
const requestAdaptor = (config: any) => {
const requestAdaptor = async (config: any) => {
const fn =
env && typeof env.requestAdaptor === 'function'
? env.requestAdaptor.bind()
: (config: any) => config;
const request = fn(config) || config;
: async (config: any) => config;
const request = (await fn(config)) || config;
return request;
};
@ -189,7 +189,7 @@ export function embed(
config.method = method;
config.data = data;
config = requestAdaptor(config);
config = await requestAdaptor(config);
if (method === 'get' && data) {
config.params = data;
@ -210,7 +210,9 @@ export function embed(
return true;
};
let response = await axios(config);
let response = config.mockResponse
? config.mockResponse
: await axios(config);
response = await attachmentAdpator(response, __);
response = responseAdaptor(api)(response);

View File

@ -33,8 +33,7 @@ module.exports = function (req, res) {
'https://news-bos.cdn.bcebos.com/mvideo/%E7%9A%87%E5%90%8E%E5%A4%A7%E9%81%93%E4%B8%9C.aac',
carousel: [
{
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
},
{
html: '<div style="width: 100%; height: 200px; background: #e3e3e3; text-align: center; line-height: 200px;">carousel data in crud</div>'
@ -45,8 +44,7 @@ module.exports = function (req, res) {
}
],
date: Math.round(Date.now() / 1000),
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
}),
parseInt(req.query.perPage, 10) || 10
)

View File

@ -6,17 +6,15 @@ module.exports = function (req, res) {
imageList: [
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2019-12/1577157239810/da6376bf988c.png',
'https://internal-amis-res.cdn.bcebos.com/images/2019-12/1577157239810/da6376bf988c.png'
},
{
html:
'<div style="width: 100%; height: 300px; background: #e3e3e3; text-align: center; line-height: 300px;">carousel data</div>',
html: '<div style="width: 100%; height: 300px; background: #e3e3e3; text-align: center; line-height: 300px;">carousel data</div>'
},
{
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg',
},
],
},
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
}
]
}
});
};

View File

@ -32,8 +32,7 @@ module.exports = function (req, res) {
Math.round(Math.random() * 10)
),
date: Math.round(Date.now() / 1000),
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
})),
table2: repeat(() => ({
@ -50,8 +49,7 @@ module.exports = function (req, res) {
Math.round(Math.random() * 10)
),
date: Math.round(Date.now() / 1000),
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
image: 'https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg'
}))
}
});

View File

@ -0,0 +1,27 @@
{
"status": 0,
"msg": "",
"data": {
"options": [
{
"label": "法师",
"children": [
{
"label": "诸葛亮",
"value": "zhugeliang"
}
]
},
{
"label": "战士",
"value": "zhanshi",
"children": [
{
"label": "钟无艳",
"value": "zhongwuyan"
}
]
}
]
}
}

View File

@ -1,5 +1,5 @@
{
"status": 0,
"msg": "ok",
"link": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg"
}
"link": "https://suda.cdn.bcebos.com/amis/images/alice-macaw.jpg"
}

View File

@ -61,7 +61,8 @@ export const RENDERER_TRANSMISSION_OMIT_PROPS = [
'inputOnly',
'label',
'renderLabel',
'trackExpression'
'trackExpression',
'editorSetting'
];
const componentCache: SimpleMap = new SimpleMap();

View File

@ -1,6 +1,6 @@
import omit from 'lodash/omit';
import {RendererProps} from '../factory';
import {ConditionGroupValue} from '../types';
import {ConditionGroupValue, Api, SchemaNode} from '../types';
import {createObject} from '../utils/helper';
import {RendererEvent} from '../utils/renderer-event';
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
@ -32,6 +32,7 @@ export interface ListenerAction {
stopPropagation?: boolean; // 阻止后续的事件处理器执行
expression?: string | ConditionGroupValue; // 执行条件
execOn?: string; // 执行条件1.9.0废弃
[propName: string]: any;
}
export interface ILogicAction extends ListenerAction {
@ -158,12 +159,19 @@ export const runActions = async (
!actionInstrance &&
(actionConfig.componentId || actionConfig.componentName)
) {
actionInstrance = getActionByType('component');
} else if (
actionConfig.actionType === 'url' ||
actionConfig.actionType === 'link' ||
actionConfig.actionType === 'jump'
) {
actionInstrance = [
'static',
'nonstatic',
'show',
'visibility',
'hidden',
'enabled',
'disabled',
'usability'
].includes(actionConfig.actionType)
? getActionByType('status')
: getActionByType('component');
} else if (['url', 'link', 'jump'].includes(actionConfig.actionType)) {
// 打开页面动作
actionInstrance = getActionByType('openlink');
}
@ -175,6 +183,7 @@ export const runActions = async (
// 这些节点的子节点运行逻辑由节点内部实现
await runAction(actionInstrance, actionConfig, renderer, event);
if (event.stoped) {
break;
}
@ -192,6 +201,8 @@ export const runAction = async (
let additional: any = {
event
};
let action: ListenerAction = {...actionConfig};
action.args = {...actionConfig.args};
// __rendererData默认为renderer.props.data兼容表单项值变化时的data读取
if (!event.data.__rendererData) {
@ -214,7 +225,7 @@ export const runAction = async (
event.data
);
// 兼容一下1.9.0之前的版本
const expression = actionConfig.expression ?? actionConfig.execOn;
const expression = action.expression ?? action.execOn;
// 执行条件
let isStop = false;
@ -232,21 +243,38 @@ export const runAction = async (
// 支持表达式 >=1.10.0
let preventDefault = false;
if (actionConfig.preventDefault) {
if (action.preventDefault) {
preventDefault = await evalExpressionWithConditionBuilder(
actionConfig.preventDefault,
action.preventDefault,
mergeData,
false
);
}
let key = {
componentId: dataMapping(actionConfig.componentId, mergeData),
componentName: dataMapping(actionConfig.componentName, mergeData)
componentId: dataMapping(action.componentId, mergeData),
componentName: dataMapping(action.componentName, mergeData)
};
// 兼容args包裹的用法
if (action.actionType === 'dialog') {
action.dialog = {...(action.dialog ?? action.args?.dialog)};
delete action.args?.dialog;
} else if (action.actionType === 'drawer') {
action.drawer = {...(action.drawer ?? action.args?.drawer)};
delete action.args?.drawer;
} else if (action.actionType === 'ajax') {
const api = action.api ?? action.args?.api;
action.api = typeof api === 'string' ? api : {...api};
action.options = {...(action.options ?? action.args?.options)};
action.messages = {...(action.messages ?? action.args?.messages)};
delete action.args?.api;
delete action.args?.options;
delete action.args?.messages;
}
// 动作配置
const args = dataMapping(actionConfig.args, mergeData, key =>
const args = dataMapping(action.args, mergeData, key =>
[
'adaptor',
'responseAdaptor',
@ -255,7 +283,7 @@ export const runAction = async (
'condition'
].includes(key)
);
const afterMappingData = dataMapping(actionConfig.data, mergeData);
const afterMappingData = dataMapping(action.data, mergeData);
// 动作数据
const actionData =
@ -265,27 +293,22 @@ export const runAction = async (
...args, // 兼容历史(动作配置与数据混在一起的情况)
...(afterMappingData ?? {})
},
getOmitActionProp(actionConfig.actionType)
getOmitActionProp(action.actionType)
)
: afterMappingData;
// 默认为事件数据
const data =
args && !Object.keys(args).length && actionConfig.data === undefined // 兼容历史
? {}
: actionData !== undefined
? actionData
: event.data;
// 默认为当前数据域
const data = actionData !== undefined ? actionData : mergeData;
console.group?.(`run action ${actionConfig.actionType}`);
console.debug(`[${actionConfig.actionType}] action args, data`, args, data);
console.group?.(`run action ${action.actionType}`);
console.debug(`[${action.actionType}] action args, data`, args, data);
let stopped = false;
const actionResult = await actionInstrance.run(
{
...actionConfig,
...action,
args,
data: actionConfig.actionType === 'reload' ? actionData : data, // 如果是刷新动作则只传action.data
data: action.actionType === 'reload' ? actionData : data, // 如果是刷新动作则只传action.data
...key
},
renderer,
@ -293,19 +316,19 @@ export const runAction = async (
mergeData
);
// 二次确认弹窗如果取消,则终止后续动作
if (actionConfig?.actionType === 'confirmDialog' && !actionResult) {
if (action?.actionType === 'confirmDialog' && !actionResult) {
stopped = true;
}
let stopPropagation = false;
if (actionConfig.stopPropagation) {
if (action.stopPropagation) {
stopPropagation = await evalExpressionWithConditionBuilder(
actionConfig.stopPropagation,
action.stopPropagation,
mergeData,
false
);
}
console.debug(`[${actionConfig.actionType}] action end event`, event);
console.debug(`[${action.actionType}] action end event`, event);
console.groupEnd?.();
// 阻止原有动作执行

View File

@ -43,25 +43,19 @@ export class AjaxAction implements RendererAction {
throw new Error('env.fetcher is required!');
}
if (this.fetcherType === 'download' && action.actionType === 'download') {
// 兼容老的格式
if ((action as any).args?.api) {
(action as any).args.api.responseType = 'blob';
}
if ((action as any)?.api) {
if ((action as any).api) {
(action as any).api.responseType = 'blob';
}
}
const env = event.context.env;
const silent = action?.options?.silent ?? action.args?.options?.silent;
const messages =
(action?.api as ApiObject)?.messages ??
(action.args?.api as ApiObject)?.messages;
const silent = action?.options?.silent;
const messages = (action?.api as ApiObject)?.messages;
try {
const result = await env.fetcher(
action?.api ?? action.args?.api,
action?.api,
action.data ?? {},
action?.options ?? action.args?.options ?? {}
action?.options ?? {}
);
const responseData =
!isEmpty(result.data) || result.ok
@ -84,13 +78,13 @@ export class AjaxAction implements RendererAction {
if (!silent) {
if (!result.ok) {
throw new ServerError(
messages?.failed ?? action.args?.messages?.failed ?? result.msg,
messages?.failed ?? action.messages?.failed ?? result.msg,
result
);
} else {
const msg =
messages?.success ??
action.args?.messages?.success ??
action.messages?.success ??
result.msg ??
result.defaultMsg;
msg &&

View File

@ -1,5 +1,5 @@
import {RendererEvent} from '../utils/renderer-event';
import {createObject, isEmpty} from '../utils/helper';
import {createObject} from '../utils/helper';
import {
RendererAction,
ListenerAction,
@ -8,22 +8,11 @@ import {
} from './Action';
export interface ICmptAction extends ListenerAction {
actionType:
| 'setValue'
| 'static'
| 'nonstatic'
| 'show'
| 'visibility'
| 'hidden'
| 'enabled'
| 'disabled'
| 'usability'
| 'reload';
actionType: string;
args: {
/** actionType为setValue时目标变量的path */
path?: string;
value?: string | {[key: string]: string};
index?: number; // setValue支持更新指定索引的数据一般用于数组类型
path?: string; // setValue时目标变量的path
value?: string | {[key: string]: string}; // setValue时目标变量的值
index?: number; // setValue时支持更新指定索引的数据一般用于数组类型
};
}
@ -45,34 +34,16 @@ export class CmptAction implements RendererAction {
* id或未指定响应组件componentId使
*/
const key = action.componentId || action.componentName;
const dataMergeMode = action.dataMergeMode || 'merge';
let component = key
? event.context.scoped?.[
action.componentId ? 'getComponentById' : 'getComponentByName'
](key)
: renderer;
: null;
const dataMergeMode = action.dataMergeMode || 'merge';
// 显隐&状态控制
if (['show', 'hidden', 'visibility'].includes(action.actionType)) {
let visibility =
action.actionType === 'visibility'
? action.args?.value
: action.actionType === 'show';
return renderer.props.statusStore.setVisible(key!, visibility as any);
} else if (['static', 'nonstatic'].includes(action.actionType)) {
return renderer.props.statusStore.setStatic(
key!,
action.actionType === 'static'
);
} else if (
['enabled', 'disabled', 'usability'].includes(action.actionType)
) {
let usability =
action.actionType === 'usability'
? !action.args?.value
: action.actionType === 'disabled';
return renderer.props.statusStore.setDisable(key!, usability);
if (key && !component) {
throw Error('目标组件没有找到请检查componentId或componentName是否正确');
}
if (action.actionType === 'setValue') {

View File

@ -1,13 +1,12 @@
import {Schema, SchemaNode} from '../types';
import {RendererEvent} from '../utils/renderer-event';
import {filter} from '../utils/tpl';
import {
RendererAction,
ListenerAction,
ListenerContext,
registerAction
} from './Action';
import {createObject, filter, render} from '../index';
import reject from 'lodash/reject';
export interface IAlertAction extends ListenerAction {
actionType: 'alert';
@ -76,7 +75,7 @@ export class DialogAction implements RendererAction {
event,
{
actionType: 'dialog',
dialog: action.dialog ?? action.args?.dialog,
dialog: action.dialog,
reload: 'none'
},
action.data

View File

@ -33,7 +33,7 @@ export class DrawerAction implements RendererAction {
event,
{
actionType: 'drawer',
drawer: action.drawer ?? action.args?.drawer,
drawer: action.drawer,
reload: 'none'
},
action.data

View File

@ -0,0 +1,64 @@
import {RendererEvent} from '../utils/renderer-event';
import {
RendererAction,
ListenerAction,
ListenerContext,
registerAction
} from './Action';
export interface IStatusAction extends ListenerAction {
actionType:
| 'static'
| 'nonstatic'
| 'show'
| 'visibility'
| 'hidden'
| 'enabled'
| 'disabled'
| 'usability';
}
/**
*
*
* @export
* @class StatusAction
* @implements {Action}
*/
export class StatusAction implements RendererAction {
async run(
action: IStatusAction,
renderer: ListenerContext,
event: RendererEvent<any>
) {
/**
* ID查找指定组件
* id或未指定响应组件componentId使
*/
const key = action.componentId || action.componentName;
// 显隐&状态控制
if (['show', 'hidden', 'visibility'].includes(action.actionType)) {
let visibility =
action.actionType === 'visibility'
? action.args?.value
: action.actionType === 'show';
return renderer.props.statusStore.setVisible(key!, visibility as any);
} else if (['static', 'nonstatic'].includes(action.actionType)) {
return renderer.props.statusStore.setStatic(
key!,
action.actionType === 'static'
);
} else if (
['enabled', 'disabled', 'usability'].includes(action.actionType)
) {
let usability =
action.actionType === 'usability'
? !action.args?.value
: action.actionType === 'disabled';
return renderer.props.statusStore.setDisable(key!, usability);
}
}
}
registerAction('status', new StatusAction());

View File

@ -10,6 +10,7 @@ import './ParallelAction';
import './CustomAction';
import './BroadcastAction';
import './CmptAction';
import './StatusAction';
import './AjaxAction';
import './CopyAction';
import './DialogAction';

View File

@ -98,7 +98,13 @@ import Overlay from './components/Overlay';
import PopOver from './components/PopOver';
import {FormRenderer} from './renderers/Form';
import type {FormHorizontal, FormSchemaBase} from './renderers/Form';
import {enableDebug, promisify, replaceText, wrapFetcher} from './utils/index';
import {
enableDebug,
disableDebug,
promisify,
replaceText,
wrapFetcher
} from './utils/index';
import type {OnEventProps} from './utils/index';
import {valueMap as styleMap} from './utils/style-helper';
import {RENDERER_TRANSMISSION_OMIT_PROPS} from './SchemaRenderer';
@ -195,7 +201,9 @@ export {
OnEventProps,
FormSchemaBase,
filterTarget,
CustomStyle
CustomStyle,
enableDebug,
disableDebug
};
export function render(
@ -257,13 +265,6 @@ function AMISRenderer({
translate
} as any;
if (options.enableAMISDebug) {
// 因为里面还有 render
setTimeout(() => {
enableDebug();
}, 10);
}
store = RendererStore.create({}, options);
stores[options.session || 'global'] = store;
} else {
@ -291,6 +292,11 @@ function AMISRenderer({
}
env.theme = getTheme(theme);
React.useEffect(() => {
env.enableAMISDebug ? enableDebug() : disableDebug();
return () => env.enableAMISDebug || disableDebug();
}, [env.enableAMISDebug]);
if (props.locale !== undefined) {
env.translate = translate;
env.locale = locale;

View File

@ -1000,26 +1000,25 @@ export default class Form extends React.Component<FormProps, object> {
handleBulkChange(values: Object, submit: boolean) {
const {onChange, store, formLazyChange} = this.props;
store.setValues(values);
// store.updateData(values);
store.updateData(values);
store.items.forEach(formItem => {
const updatedValue = getVariable(values, formItem.name, false);
if (updatedValue !== undefined) {
// 更新验证状态但保留错误信息
formItem.reset(true);
// 这里需要更新value否则提交时不会使用新的字段值校验
formItem.changeTmpValue(updatedValue);
formItem.validateOnChange && formItem.validate(values);
}
});
// store.items.forEach(formItem => {
// const updatedValue = getVariable(values, formItem.name, false);
// if (updatedValue !== undefined) {
// // 更新验证状态但保留错误信息
// formItem.reset(true);
// // 这里需要更新value否则提交时不会使用新的字段值校验
// formItem.changeTmpValue(updatedValue);
// formItem.validateOnChange && formItem.validate(values);
// }
// });
(formLazyChange === false ? this.emitChange : this.lazyEmitChange)(submit);
}
handleFormSubmit(e: React.UIEvent<any>) {
const {preventEnterSubmit, onActionSensor} = this.props;
const {preventEnterSubmit, onActionSensor, close} = this.props;
e.preventDefault();
if (preventEnterSubmit) {
@ -1029,7 +1028,8 @@ export default class Form extends React.Component<FormProps, object> {
const sensor: any = this.handleAction(
e,
{
type: 'submit'
type: 'submit',
close
},
this.props.store.data
);
@ -1638,7 +1638,8 @@ export default class Form extends React.Component<FormProps, object> {
dispatchEvent,
labelAlign,
labelWidth,
static: isStatic
static: isStatic,
canAccessSuperData
} = props;
const subProps = {

View File

@ -72,6 +72,11 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
*/
name?: string;
/**
*
*/
extraName?: string;
/**
* ,
*/

View File

@ -18,8 +18,7 @@ import {
isNeedFormula,
isExpression,
FormulaExec,
replaceExpression,
isNowFormula
replaceExpression
} from '../utils/formula';
import {IIRendererStore, IRendererStore} from '../store';
import {ScopedContext, IScopedContext} from '../Scoped';
@ -130,6 +129,7 @@ export function wrapControl<
validationErrors,
unique,
value,
extraName,
multiple,
delimiter,
valueField,
@ -209,7 +209,8 @@ export function wrapControl<
maxLength,
validateOnChange,
label,
inputGroupControl
inputGroupControl,
extraName
});
// issue 这个逻辑应该在 combo 里面自己实现。
@ -225,16 +226,41 @@ export function wrapControl<
// 同步 value: 优先使用 props 中的 value
model.changeTmpValue(propValue, 'controlled');
} else {
// 备注: 此处的 value 是 schema 中的 value和props.defaultValue相同
const isExp = isExpression(value);
const curTmpValue = isExp
? FormulaExec['formula'](value, data) // 对组件默认值进行运算
: store?.getValueByName(model.name) ?? replaceExpression(value); // 优先使用公式表达式
// 同步 value
model.changeTmpValue(
curTmpValue,
isExp ? 'formulaChanged' : 'defaultValue'
);
if (isExp) {
model.changeTmpValue(
FormulaExec['formula'](value, data), // 对组件默认值进行运算
'formulaChanged'
);
} else {
let initialValue = model.extraName
? [
store?.getValueByName(
model.name,
form?.canAccessSuperData
),
store?.getValueByName(
model.extraName,
form?.canAccessSuperData
)
]
: store?.getValueByName(model.name, form?.canAccessSuperData);
if (
model.extraName &&
initialValue.every((item: any) => item === undefined)
) {
initialValue = undefined;
}
model.changeTmpValue(
initialValue ?? replaceExpression(value),
typeof initialValue !== 'undefined'
? 'initialValue'
: 'defaultValue'
);
}
}
if (
@ -243,7 +269,13 @@ export function wrapControl<
model.tmpValue !== undefined
) {
// 组件默认值支持表达式需要: 避免初始化时上下文中丢失组件默认值
onChange(model.tmpValue, model.name, false, true);
if (model.extraName) {
const values = model.splitExtraValue(model.tmpValue);
onChange(values[0], model.name, false, true);
onChange(values[1], model.extraName, false, true);
} else {
onChange(model.tmpValue, model.name, false, true);
}
} else if (
onChange &&
typeof propValue === 'undefined' &&
@ -254,7 +286,13 @@ export function wrapControl<
store?.storeType !== TableStore.name
) {
// 如果没有初始值,通过 onChange 设置过去
onChange(model.tmpValue, model.name, false, true);
if (model.extraName) {
const values = model.splitExtraValue(model.tmpValue);
onChange(values[0], model.name, false, true);
onChange(values[1], model.extraName, false, true);
} else {
onChange(model.tmpValue, model.name, false, true);
}
}
}
@ -319,7 +357,8 @@ export function wrapControl<
'validateApi',
'minLength',
'maxLength',
'label'
'label',
'extraName'
],
prevProps.$schema,
props.$schema
@ -346,7 +385,8 @@ export function wrapControl<
minLength: props.$schema.minLength,
maxLength: props.$schema.maxLength,
label: props.$schema.label,
inputGroupControl: props?.inputGroupControl
inputGroupControl: props?.inputGroupControl,
extraName: props.$schema.extraName
});
}
@ -363,45 +403,39 @@ export function wrapControl<
} else if (
model &&
typeof props.defaultValue !== 'undefined' &&
isExpression(props.defaultValue)
) {
let nowFormulaChecked = false;
// 渲染器中的 defaultValue 优先(备注: SchemaRenderer中会将 value 改成 defaultValue
if (
!isEqual(props.defaultValue, prevProps.defaultValue) ||
isExpression(props.defaultValue) &&
(!isEqual(props.defaultValue, prevProps.defaultValue) ||
(props.data !== prevProps.data &&
(isNeedFormula(
isNeedFormula(
props.defaultValue,
props.data,
prevProps.data
) ||
(nowFormulaChecked = isNowFormula(props.defaultValue))))
)))
) {
const curResult = FormulaExec['formula'](
props.defaultValue,
props.data
);
const prevResult = FormulaExec['formula'](
prevProps.defaultValue,
prevProps.data
);
if (
!isEqual(curResult, prevResult) &&
!isEqual(curResult, model.tmpValue)
) {
const curResult = FormulaExec['formula'](
props.defaultValue,
props.data
);
const prevResult = FormulaExec['formula'](
prevProps.defaultValue,
prevProps.data
);
if (
!isEqual(curResult, prevResult) &&
!isEqual(curResult, model.tmpValue)
) {
// 识别上下文变动、自身数值变动、公式运算结果变动
model.changeTmpValue(curResult, 'formulaChanged');
// 识别上下文变动、自身数值变动、公式运算结果变动
model.changeTmpValue(curResult, 'formulaChanged');
if (model.extraName) {
const values = model.splitExtraValue(curResult);
props.onChange?.(values[0], model.name, false);
props.onChange?.(values[1], model.extraName, false);
} else {
props.onChange?.(curResult, model.name, false);
} else if (nowFormulaChecked) {
const nowData = props.data[model.name];
// now 表达式,计算后的值永远相同
model.changeTmpValue(nowData, 'formulaChanged');
props.onChange?.(nowData, model.name, false);
}
}
} else if (model) {
const valueByName = getVariable(props.data, model.name);
// value 非公式表达式时name 值优先,若 defaultValue 主动变动时,则使用 defaultValue
if (
// 然后才是查看关联的 name 属性值是否变化
@ -410,12 +444,30 @@ export function wrapControl<
isEqual(model.emitedValue, model.tmpValue))
) {
model.changeEmitedValue(undefined);
const prevValueByName = getVariable(props.data, model.name);
const valueByName = model.extraName
? [
getVariable(props.data, model.name, false),
getVariable(props.data, model.extraName, false)
]
: getVariable(props.data, model.name, false);
if (
(!isEqual(valueByName, prevValueByName) ||
getVariable(props.data, model.name, false) !==
getVariable(prevProps.data, model.name, false)) &&
!isEqual(valueByName, model.tmpValue)
!isEqual(
valueByName,
model.extraName
? model.splitExtraValue(model.tmpValue)
: model.tmpValue
) &&
(!isEqual(
model.extraName ? valueByName[0] : valueByName,
getVariable(prevProps.data, model.name, false)
) ||
// extraName
(model.extraName &&
!isEqual(
valueByName[1],
getVariable(prevProps.data, model.extraName, false)
)))
) {
model.changeTmpValue(
valueByName,
@ -633,10 +685,18 @@ export function wrapControl<
if (!this.model) {
return;
}
const model = this.model;
const value = this.model.tmpValue;
const oldValue = getVariable(data, this.model.name, false);
const oldValue = model.extraName
? [
getVariable(data, model.name, false),
getVariable(data, model.extraName, false)
]
: getVariable(data, model.name, false);
if (oldValue === value) {
if (
model.extraName ? isEqual(oldValue, value) : oldValue === value
) {
return;
}
@ -668,7 +728,13 @@ export function wrapControl<
return;
}
onChange?.(value, name!, submitOnChange === true);
if (model.extraName) {
const values = model.splitExtraValue(value);
onChange?.(values[0], name!);
onChange?.(values[1], model.extraName, submitOnChange === true);
} else {
onChange?.(value, name!, submitOnChange === true);
}
this.checkValidate();
}
@ -690,6 +756,7 @@ export function wrapControl<
return;
}
const model = this.model;
const {
formStore: form,
name,
@ -703,7 +770,13 @@ export function wrapControl<
value = pipeOut(value, oldValue, data);
}
onChange?.(value, name!, false, true);
if (model.extraName) {
const values = model.splitExtraValue(value);
onChange?.(values[0], name!, false, true);
onChange?.(values[1], model.extraName!, false, true);
} else {
onChange?.(value, name!, false, true);
}
}
getValue() {

View File

@ -51,7 +51,9 @@ export const FormStore = ServiceStore.named('FormStore')
if (current.storeType === 'FormItemStore') {
formItems.push(current);
} else {
} else if (
!['ComboStore', 'TableStore', 'FormStore'].includes(current.storeType)
) {
pool.push(...current.children);
}
}
@ -68,33 +70,10 @@ export const FormStore = ServiceStore.named('FormStore')
return getItems();
},
/**
* items(), formItem
* form
*/
get directItems() {
const formItems: Array<IFormItemStore> = [];
// 查找孩子节点中是 formItem 的表单项
const pool = self.children.concat();
while (pool.length) {
const current = pool.shift()!;
if (current.storeType === 'FormItemStore') {
formItems.push(current);
} else if (
!['ComboStore', 'TableStore'].includes(current.storeType)
) {
pool.push(...current.children);
}
}
return formItems;
},
/** 获取InputGroup的子元素 */
get inputGroupItems() {
const formItems: Record<string, IFormItemStore[]> = {};
const children: Array<any> = this.directItems.concat();
const children: Array<any> = this.items.concat();
while (children.length) {
const current = children.shift();
@ -190,11 +169,31 @@ export const FormStore = ServiceStore.named('FormStore')
// 如果数据域中有数据变化就都reset一下去掉之前残留的验证消息
self.items.forEach(item => {
const value = getVariable(values, item.name);
if (value !== undefined && value !== item.tmpValue) {
item.changeTmpValue(value);
if (item.extraName) {
const value = [
getVariable(values, item.name, false),
getVariable(values, item.extraName, false)
];
if (
value.some(item => item !== undefined) &&
!isEqual(value, item.tmpValue)
) {
const origin = item.splitExtraValue(item.tmpValue);
item.changeTmpValue(
value.map((item, idx) => item ?? origin[idx]),
'dataChanged'
);
item.changeEmitedValue(undefined);
}
} else {
const value = getVariable(values, item.name, false);
if (value !== undefined && value !== item.tmpValue) {
item.changeTmpValue(value, 'dataChanged');
item.changeEmitedValue(undefined);
}
}
item.reset();
item.validateOnChange && item.validate(self.data);
});
// 同步 options
@ -578,7 +577,7 @@ export const FormStore = ServiceStore.named('FormStore')
validateErrCb?: () => void
) {
self.validated = true;
const items = self.directItems.concat();
const items = self.items.concat();
for (let i = 0, len = items.length; i < len; i++) {
let item = items[i] as IFormItemStore;

View File

@ -66,6 +66,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
messages: types.optional(types.frozen(), {}),
errorData: types.optional(types.array(ErrorDetail), []),
name: types.string,
extraName: '',
itemId: '', // 因为 name 可能会重名,所以加个 id 进来,如果有需要用来定位具体某一个
unsetValueOnInvisible: false,
itemsRef: types.optional(types.array(types.string), []),
@ -210,6 +211,15 @@ export const FormItemStore = StoreNode.named('FormItemStore')
});
return selectedOptions;
},
splitExtraValue(value: any) {
const delimiter = self.delimiter || ',';
const values = Array.isArray(value)
? value
: typeof value === 'string'
? value.split(delimiter || ',')
: [];
return values;
}
};
})
@ -220,6 +230,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
let loadAutoUpdateCancel: Function | null = null;
function config({
extraName,
required,
unique,
value,
@ -244,6 +255,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
label,
inputGroupControl
}: {
extraName?: string;
required?: boolean;
unique?: boolean;
value?: any;
@ -276,6 +288,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
rules = str2rules(rules);
}
typeof extraName !== 'undefined' && (self.extraName = extraName);
typeof type !== 'undefined' && (self.type = type);
typeof id !== 'undefined' && (self.itemId = id);
typeof messages !== 'undefined' && (self.messages = messages);
@ -1236,6 +1249,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
function changeTmpValue(
value: any,
changeReason?:
| 'initialValue' // 初始值,读取与当前数据域,或者上层数据域
| 'formInited' // 表单初始化
| 'dataChanged' // 表单数据变化
| 'formulaChanged' // 公式运算结果变化

View File

@ -217,13 +217,17 @@ export interface ApiObject extends BaseApiObject {
operationName?: string;
body?: PlainObject;
query?: PlainObject;
mockResponse?: PlainObject;
adaptor?: (
payload: object,
response: fetcherResult,
api: ApiObject,
context: any
) => any;
requestAdaptor?: (api: ApiObject, context: any) => ApiObject;
requestAdaptor?: (
api: ApiObject,
context: any
) => ApiObject | Promise<ApiObject>;
/** 是否过滤为空字符串的 query 参数 */
filterEmptyQuery?: boolean;
}
@ -641,6 +645,21 @@ export interface BaseSchemaWithoutType {
style?: {
[propName: string]: any;
};
/**
*
*/
editorSetting?: {
/**
* 便
*/
displayName?: string;
/**
* 便
*/
mock?: any;
};
}
export type OperatorType =

View File

@ -76,7 +76,7 @@ export function buildApi(
}
if (api.requestAdaptor && typeof api.requestAdaptor === 'string') {
api.requestAdaptor = str2function(
api.requestAdaptor = str2AsyncFunction(
api.requestAdaptor,
'api',
'context'
@ -84,7 +84,7 @@ export function buildApi(
}
if (api.adaptor && typeof api.adaptor === 'string') {
api.adaptor = str2function(
api.adaptor = str2AsyncFunction(
api.adaptor,
'payload',
'response',
@ -464,12 +464,16 @@ export function wrapFetcher(
return fn as any;
}
const wrappedFetcher = function (api: Api, data: object, options?: object) {
const wrappedFetcher = async function (
api: Api,
data: object,
options?: object
) {
api = buildApi(api, data, options) as ApiObject;
if (api.requestAdaptor) {
debug('api', 'before requestAdaptor', api);
api = api.requestAdaptor(api, data) || api;
api = (await api.requestAdaptor(api, data)) || api;
debug('api', 'after requestAdaptor', api);
}
@ -501,6 +505,12 @@ export function wrapFetcher(
api.headers['Content-Type'] = 'application/json';
}
// 如果发送适配器中设置了 mockResponse
// 则直接跳过请求发送
if (api.mockResponse) {
return wrapAdaptor(Promise.resolve(api.mockResponse) as any, api, data);
}
if (!isValidApi(api.url)) {
throw new Error(`invalid api url:${api.url}`);
}

View File

@ -2,9 +2,10 @@
* amis amis
*/
import React, {Component, useEffect, useRef, useState} from 'react';
import React, {Component, useEffect, useRef, useState, version} from 'react';
import cx from 'classnames';
import {findDOMNode, render} from 'react-dom';
import {findDOMNode, render, unmountComponentAtNode} from 'react-dom';
// import {createRoot} from 'react-dom/client';
import {autorun, observable} from 'mobx';
import {observer} from 'mobx-react';
import {uuidv4} from './helper';
@ -84,16 +85,18 @@ const LogView = observer(({store}: {store: AMISDebugStore}) => {
[{log.cat}] {log.msg}
</div>
{log.ext ? (
<JsonView
name={null}
theme="monokai"
src={JSON.parse(log.ext)}
collapsed={true}
enableClipboard={false}
displayDataTypes={false}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle="square"
/>
<React.Suspense fallback={<div>Loading...</div>}>
<JsonView
name={null}
theme="monokai"
src={JSON.parse(log.ext)}
collapsed={true}
enableClipboard={false}
displayDataTypes={false}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle="square"
/>
</React.Suspense>
) : null}
</div>
);
@ -126,16 +129,18 @@ const AMISDebug = observer(({store}: {store: AMISDebugStore}) => {
stackDataView.push(
<div key={`data-${level}`}>
<h3>Data Level-{level}</h3>
<JsonView
key={`dataview-${stack}`}
name={null}
theme="monokai"
src={stack}
collapsed={level === 0 ? false : true}
enableClipboard={false}
displayDataTypes={false}
iconStyle="square"
/>
<React.Suspense fallback={<div>Loading...</div>}>
<JsonView
key={`dataview-${stack}`}
name={null}
theme="monokai"
src={stack}
collapsed={level === 0 ? false : true}
enableClipboard={false}
displayDataTypes={false}
iconStyle="square"
/>
</React.Suspense>
</div>
);
level += 1;
@ -319,7 +324,7 @@ function handleMouseclick(e: MouseEvent) {
}
const dom = e.target as HTMLElement;
const target = dom.closest(`[data-debug-id]`);
if (target) {
if (target && !target.closest('.AMISDebug')) {
store.activeId = target.getAttribute('data-debug-id')!;
store.tab = 'inspect';
}
@ -366,6 +371,7 @@ autorun(() => {
// 页面中只能有一个实例
let isEnabled = false;
let unmount: () => void;
export function enableDebug() {
if (isEnabled) {
@ -376,7 +382,21 @@ export function enableDebug() {
const amisDebugElement = document.createElement('div');
document.body.appendChild(amisDebugElement);
const element = <AMISDebug store={store} />;
// if (parseInt(version.split('.')[0], 10) >= 18) {
// const root = createRoot(amisDebugElement);
// root.render(element);
// unmount = () => {
// root.unmount();
// document.body.removeChild(amisDebugElement);
// };
// } else {
render(element, amisDebugElement);
unmount = () => {
unmountComponentAtNode(amisDebugElement);
document.body.removeChild(amisDebugElement);
};
// }
document.body.appendChild(amisHoverBox);
document.body.appendChild(amisActiveBox);
@ -384,6 +404,18 @@ export function enableDebug() {
document.addEventListener('click', handleMouseclick);
}
export function disableDebug() {
if (!isEnabled) {
return;
}
isEnabled = false;
unmount?.();
document.body.removeChild(amisHoverBox);
document.body.removeChild(amisActiveBox);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('click', handleMouseclick);
}
interface DebugWrapperProps {
renderer: any;
children?: React.ReactNode;

View File

@ -225,7 +225,8 @@ export function isNeedFormula(
const variables = FormulaExec.collect(expression);
return variables.some(
(variable: string) =>
FormulaExec.var(variable, prevData) !== FormulaExec.var(variable, curData)
FormulaExec.var(variable, prevData) !==
FormulaExec.var(variable, curData)
);
} catch (e) {
console.warn(
@ -238,11 +239,6 @@ export function isNeedFormula(
}
}
export function isNowFormula(expression: string): boolean {
const block = expression.split(/\${|\||}/).filter(item => item);
return block[1] === 'now';
}
// 将 \${xx} 替换成 ${xx}
export function replaceExpression(expression: any): any {
if (

View File

@ -31,7 +31,7 @@
align-items: center;
flex: 1;
margin-right: #{px2rem(10px)};
max-width: calc(100% - 52px);
// max-width: calc(100% - 52px);
height: var(--Button--sm-height);
& > input {

View File

@ -9,6 +9,7 @@ import {
import cx from 'classnames';
import {prompt, toast} from 'amis';
import debounce from 'lodash/debounce';
import isArray from 'lodash/isArray';
import findIndex from 'lodash/findIndex';
import {parse, stringify} from 'json-ast-comments';
import isPlainObject from 'lodash/isPlainObject';
@ -115,12 +116,17 @@ export default class AMisCodeEditor extends React.Component<AMisCodeEditorProps>
obj2str(value: any, props: AMisCodeEditorProps) {
// 隐藏公共配置
value = filterSchemaForConfig(value);
value = {
type: value?.type,
...value
};
if (!value.type && props.$schema?.match(/PageSchema/i)) {
if (!isArray(value)) {
value = {
type: value?.type,
...value
};
}
if (isArray(value)) {
return stringify(value);
} else if (!value.type && props.$schema?.match(/PageSchema/i)) {
value.type = 'page';
} else if (!value.type) {
delete value.type;
@ -153,7 +159,7 @@ export default class AMisCodeEditor extends React.Component<AMisCodeEditorProps>
const {onChange, value} = this.props;
let ret: any = this.str2obj(this.state.contents);
if (!ret || !isPlainObject(ret)) {
if (!ret || (!isPlainObject(ret) && !isArray(ret))) {
this.setState({
wrongSchema: this.state.contents
});

View File

@ -107,7 +107,9 @@ export function makeWrapper(
if (info.name) {
const nodeSchema = manager.store.getNodeById(info.id)?.schema;
const tag = nodeSchema?.title ?? nodeSchema?.name;
manager.dataSchema.current.tag = `${tag ?? ''}${info.name}`;
manager.dataSchema.current.tag = `${info.name}${
tag ? ` : ${tag}` : ''
}`;
manager.dataSchema.current.group = '组件上下文';
}
}

View File

@ -99,7 +99,8 @@ export function autoPreRegisterEditorCustomPlugins() {
}
/**
* Editor插件
* Editor插件
* 备注: 支持覆盖原有组件 priority
* @param editor
*/
export function registerEditorPlugin(klass: PluginClass) {
@ -1939,6 +1940,13 @@ export class EditorManager {
let region = node;
const trigger = node;
// 删掉当前行记录scope保持原始scope
for (const key in this.dataSchema.idMap) {
if (/\-currentRow$/.test(key)) {
this.dataSchema.removeScope(key);
}
}
// 查找最近一层的数据域
while (!scope && from) {
const nodeId = from.info?.id;
@ -1963,12 +1971,13 @@ export class EditorManager {
if (!nearestScope && node && !node.isSecondFactor) {
nearestScope = scope;
}
const jsonschema = await node?.info?.plugin?.buildDataSchemas?.(
node,
region,
trigger
trigger,
node
);
if (jsonschema) {
scope.removeSchema(jsonschema.$id);
scope.addSchema(jsonschema);
@ -1977,7 +1986,16 @@ export class EditorManager {
scope = withoutSuper ? undefined : scope.parent;
}
if (nearestScope?.id) {
// 存在当前行时找到最底层todo暂不考虑table套service+table的场景
const nearestScopeId = Object.keys(this.dataSchema.idMap).find(
key =>
/\-currentRow$/.test(key) &&
!this.dataSchema.idMap[key].children?.length
);
if (nearestScopeId) {
this.dataSchema.switchTo(nearestScopeId);
} else if (nearestScope?.id) {
this.dataSchema.switchTo(nearestScope.id);
}

View File

@ -13,7 +13,7 @@ import {EditorDNDManager} from './dnd';
import React from 'react';
import {DiffChange} from './util';
import find from 'lodash/find';
import type {RendererConfig} from 'amis-core';
import type {RendererConfig, Schema} from 'amis-core';
import type {MenuDivider, MenuItem} from 'amis-ui/lib/components/ContextMenu';
import type {BaseSchema, SchemaCollection} from 'amis';
import type {AsyncLayerOptions} from './component/AsyncLayer';
@ -916,7 +916,8 @@ export interface PluginInterface
buildDataSchemas?: (
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
trigger?: EditorNodeType,
parent?: EditorNodeType
) => any | Promise<any>;
rendererBeforeDispatchEvent?: (
@ -925,6 +926,19 @@ export interface PluginInterface
data: any
) => void;
/**
* schema schema
* @param schema
* @param renderer
* @param props
* @returns
*/
patchSchema?: (
schema: Schema,
renderer: RendererConfig,
props?: any
) => Schema | void;
dispose?: () => void;
}
@ -1208,6 +1222,22 @@ export abstract class BasePlugin implements PluginInterface {
: plugin instanceof rendererNameOrKlass
);
}
buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType,
parent?: EditorNodeType
) {
return {
type: 'string',
title:
typeof node.schema.label === 'string'
? node.schema.label
: node.schema.name,
originalValue: node.schema.value // 记录原始值,循环引用检测需要
} as any;
}
}
/**

View File

@ -649,9 +649,19 @@ export const EditorNode = types
);
}
// 调用 amis 纠错补丁
patched = filterSchema(patched, {
component: info.renderer.component
} as any);
// 调用插件上的补丁
patched =
info.plugin?.patchSchema?.(
patched,
{
component: info.renderer.component
},
component?.props
) || patched;
patched = JSONPipeIn(patched);
if (patched !== schema) {
root.changeValueById(info.id, patched, undefined, true, true);

View File

@ -2023,7 +2023,8 @@ export class CRUDPlugin extends BasePlugin {
async buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
trigger?: EditorNodeType,
parent?: EditorNodeType
) {
const child: EditorNodeType = node.children.find(
item => !!~['table', 'table2', 'cards', 'list'].indexOf(item.type)
@ -2036,12 +2037,12 @@ export class CRUDPlugin extends BasePlugin {
let childSchame = await child.info.plugin.buildDataSchemas(
child,
undefined,
trigger
trigger,
node
);
// 兼容table的rows并自行merged异步数据
if (child.type === 'table') {
let cellProperties: any = {}; // 收集当前行记录中的列
let itemsSchema: any = {}; // 收集选择记录中的列
const columns: EditorNodeType = child.children.find(
item => item.isRegion && item.region === 'columns'
@ -2068,25 +2069,34 @@ export class CRUDPlugin extends BasePlugin {
...rowsSchema?.properties
};
Object.keys(tmpProperties).map(key => {
if (isColumnChild) {
// 给列补充group
cellProperties[key] = {
...cellProperties[key],
group: '当前行记录'
if (isColumnChild) {
Object.keys(tmpProperties).map(key => {
itemsSchema[key] = {
...tmpProperties[key]
};
});
const childScope = this.manager.dataSchema.getScope(
`${child.id}-${child.type}-currentRow`
);
if (childScope) {
childScope?.setSchemas([
{
$id: `${child.id}-${child.type}-currentRow`,
type: 'object',
properties: itemsSchema
}
]);
childScope.tag = `当前行记录 : ${node.type}`;
}
itemsSchema[key] = {
...tmpProperties[key]
};
});
}
}
childSchame = {
$id: childSchame.$id,
type: childSchame.type,
properties: {
...cellProperties,
items: childSchame.properties.rows,
selectedItems: {
...childSchame.properties.selectedItems,

View File

@ -9,7 +9,8 @@ import {
defaultValue,
getSchemaTpl,
CodeEditor as AmisCodeEditor,
RendererPluginEvent
RendererPluginEvent,
tipedLabel
} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
@ -75,6 +76,23 @@ const DEFAULT_EVENT_PARAMS = [
}
];
const chartDefaultConfig = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}
],
backgroundColor: 'transparent'
};
export class ChartPlugin extends BasePlugin {
static id = 'ChartPlugin';
// 关联渲染器名字
@ -92,21 +110,7 @@ export class ChartPlugin extends BasePlugin {
pluginIcon = 'chart-plugin';
scaffold = {
type: 'chart',
config: {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}
]
},
config: chartDefaultConfig,
replaceChartOption: true
};
previewSchema = {
@ -192,116 +196,157 @@ export class ChartPlugin extends BasePlugin {
return [
getSchemaTpl('tabs', [
{
title: '常规',
title: '属性',
body: [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
getSchemaTpl('api', {
label: '接口拉取',
description:
'接口可以返回配置,或者数据,建议返回数据可映射到 Echarts 配置中'
}),
getSchemaTpl('collapseGroup', [
{
title: '基本',
body: [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
getSchemaTpl('name')
]
},
{
title: '数据设置',
body: [
/*
{
type: 'select',
name: 'chartDataType',
label: '数据获取方式',
value: 'json',
onChange(value: any, oldValue: any, model: any, form: any) {
if (value === 'json') {
form.setValueByName('api', undefined);
form.setValueByName('config', chartDefaultConfig);
} else {
form.setValueByName('config', undefined);
}
},
options: [
{
label: '接口数据',
value: 'dataApi'
},
{
label: '静态JSON数据',
value: 'json'
}
]
},
*/
getSchemaTpl('apiControl', {
label: '数据接口',
// visibleOn: 'chartDataType === "dataApi"',
description:
'接口可以返回echart图表完整配置或者图表数据建议返回图表数据映射到 Echarts 配置中'
}),
getSchemaTpl('switch', {
label: '初始是否拉取',
name: 'initFetch',
visibleOn: 'data.api',
pipeIn: defaultValue(true)
}),
getSchemaTpl('switch', {
label: '初始是否拉取',
name: 'initFetch',
// visibleOn: 'chartDataType === "dataApi" && data.api',
visibleOn: 'data.api',
pipeIn: defaultValue(true)
}),
{
name: 'interval',
label: '定时刷新间隔',
type: 'input-number',
step: 500,
visibleOn: 'data.api',
description: '设置后将自动定时刷新最小3000, 单位 ms'
},
{
name: 'config',
asFormItem: true,
component: ChartConfigEditor,
// type: 'json-editor',
label: 'Echarts 配置',
description: '支持数据映射,可将接口返回的数据填充进来'
// size: 'lg'
// pipeOut: (value: any) => {
// try {
// return value ? JSON.parse(value) : null;
// } catch (e) {}
// return null;
// }
},
{
name: 'clickAction',
asFormItem: true,
children: ({onChange, value}: any) => (
<div className="m-b">
<Button
size="sm"
level={value ? 'danger' : 'info'}
onClick={this.editDrillDown.bind(this, context.id)}
>
DrillDown
</Button>
{
name: 'interval',
label: '定时刷新间隔',
type: 'input-number',
step: 500,
// visibleOn: 'chartDataType === "dataApi" && data.api',
visibleOn: 'data.api',
description: '设置后将自动定时刷新最小3000, 单位 ms'
},
{
name: 'config',
asFormItem: true,
// visibleOn: 'chartDataType === "json"',
component: ChartConfigEditor,
// type: 'json-editor',
label: tipedLabel(
'Echarts 配置',
'支持数据映射,可将接口返回的数据填充进来'
)
},
{
name: 'dataFilter',
type: 'js-editor',
allowFullscreen: true,
label: '数据映射dataFilter',
size: 'lg',
description: `
Echart
<p>(config, echarts, data) => config</p>
<p></p>
<ul>
<li><code>config</code> </li>
<li><code>echarts</code> echarts </li>
<li><code>data</code> </li>
</ul>
<p></p>
<pre>debugger; // 可以浏览器中断点调试\n\n// 查看原始数据\nconsole.log(config)\n\n// 返回新的结果 \nreturn {}</pre>
`
},
getSchemaTpl('switch', {
label: 'Chart 配置完全替换',
name: 'replaceChartOption',
labelRemark: {
trigger: 'click',
className: 'm-l-xs',
rootClose: true,
content:
'默认为追加模式,新的配置会跟旧的配置合并,如果勾选将直接完全覆盖。',
placement: 'left'
}
})
]
},
{
title: '图表下钻',
body: [
{
name: 'clickAction',
asFormItem: true,
children: ({onChange, value}: any) => (
<div className="m-b">
<Button
size="sm"
level={value ? 'danger' : 'info'}
onClick={this.editDrillDown.bind(this, context.id)}
>
DrillDown
</Button>
{value ? (
<Button
size="sm"
level="link"
className="m-l"
onClick={() => onChange('')}
>
DrillDown
</Button>
) : null}
</div>
)
},
{
name: 'dataFilter',
type: 'js-editor',
allowFullscreen: true,
label: '数据加工',
size: 'lg',
description: `
Echart
<p>(config, echarts, data) => config</p>
<p></p>
<ul>
<li><code>config</code> </li>
<li><code>echarts</code> echarts </li>
<li><code>data</code> </li>
</ul>
<p></p>
<pre>debugger; // 可以浏览器中断点调试\n\n// 查看原始数据\nconsole.log(config)\n\n// 返回新的结果 \nreturn {}</pre>
`
},
getSchemaTpl('switch', {
label: 'Chart 配置完全替换',
name: 'replaceChartOption',
labelRemark: {
trigger: 'click',
className: 'm-l-xs',
rootClose: true,
content:
'默认为追加模式,新的配置会跟旧的配置合并,如果勾选将直接完全覆盖。',
placement: 'left'
}
})
{value ? (
<Button
size="sm"
level="link"
className="m-l"
onClick={() => onChange('')}
>
DrillDown
</Button>
) : null}
</div>
)
}
]
},
getSchemaTpl('status')
])
]
},
{
title: '外观',
body: [getSchemaTpl('className')]
},
{
title: '显隐',
body: [getSchemaTpl('visible')]
},
{
title: '其他',
body: [getSchemaTpl('name')]
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {exclude: ['layout']}),
{
title: '自定义 CSS 类名',
body: [getSchemaTpl('className')]
}
])
},
{
title: '事件',

View File

@ -204,34 +204,30 @@ export class ContainerPlugin extends LayoutBasePlugin {
// 自由容器不需要 display 相关配置项
...(!isFreeContainer ? displayTpl : []),
isFlexItem
? getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
: null,
...(isFlexItem
? [
getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
]
: []),
getSchemaTpl('layout:overflow-x', {
visibleOn: `${
@ -308,7 +304,11 @@ export class ContainerPlugin extends LayoutBasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {exclude: ['layout']})
...getSchemaTpl('theme:common', {exclude: ['layout']}),
{
title: '自定义 CSS 类名',
body: [getSchemaTpl('className')]
}
])
},
{

View File

@ -14,6 +14,7 @@ import {
} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
import omit from 'lodash/omit';
import type {RendererConfig, Schema} from 'amis-core';
export class DialogPlugin extends BasePlugin {
static id = 'DialogPlugin';
@ -349,6 +350,35 @@ export class DialogPlugin extends BasePlugin {
properties: dataSchema
};
}
/**
* dialog
*/
patchSchema(schema: Schema, info: RendererConfig, props?: any) {
if (Array.isArray(schema.actions)) {
return;
}
return {
...schema,
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消'
},
props?.confirm
? {
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true
}
: null
].filter((item: any) => item)
};
}
}
registerEditorPlugin(DialogPlugin);

View File

@ -664,16 +664,41 @@ export class ComboControlPlugin extends BasePlugin {
async buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
trigger?: EditorNodeType,
parent?: EditorNodeType
) {
const itemsSchema: any = {
$id: 'comboItems',
$id: `${node.id}-${node.type}-tableRows`,
type: 'object',
properties: {}
};
const items = node.children?.find(
child => child.isRegion && child.region === 'items'
);
const parentScopeId = `${parent?.id}-${parent?.type}${
node.parent?.type === 'cell' ? '-currentRow' : ''
}`;
let isColumnChild = false;
if (trigger) {
isColumnChild = someTree(items.children, item => item.id === trigger?.id);
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
if (this.manager.dataSchema.getScope(scopeId)) {
this.manager.dataSchema.removeScope(scopeId);
}
if (this.manager.dataSchema.getScope(parentScopeId)) {
this.manager.dataSchema.switchTo(parentScopeId);
}
this.manager.dataSchema.addScope([], scopeId);
this.manager.dataSchema.current.tag = '当前行记录';
this.manager.dataSchema.current.group = '组件上下文';
}
}
const pool = items.children.concat();
while (pool.length) {
@ -681,16 +706,22 @@ export class ComboControlPlugin extends BasePlugin {
const schema = current.schema;
if (schema.name) {
itemsSchema.properties[schema.name] = current.info?.plugin
?.buildDataSchemas
? await current.info.plugin.buildDataSchemas(current, region, trigger)
: {
type: 'string',
title: schema.label || schema.name
};
itemsSchema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
region,
trigger,
node
);
}
}
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
const scope = this.manager.dataSchema.getScope(scopeId);
scope?.addSchema(itemsSchema);
}
if (node.schema?.multiple) {
return {
$id: 'combo',
@ -700,7 +731,10 @@ export class ComboControlPlugin extends BasePlugin {
};
}
return {...itemsSchema, title: node.schema?.label || node.schema?.name};
return {
...itemsSchema,
title: node.schema?.label || node.schema?.name
};
}
async getAvailableContextFields(

View File

@ -1,4 +1,8 @@
import {getI18nEnabled, registerEditorPlugin} from 'amis-editor-core';
import {
JSONPipeOut,
getI18nEnabled,
registerEditorPlugin
} from 'amis-editor-core';
import {
BasePlugin,
ChangeEventContext,
@ -11,7 +15,7 @@ import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {jsonToJsonSchema} from 'amis-editor-core';
import {EditorNodeType} from 'amis-editor-core';
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
import {setVariable, someTree} from 'amis-core';
import {RendererConfig, Schema, setVariable, someTree} from 'amis-core';
import {getEventControlConfig} from '../../renderer/event-control/helper';
// 用于脚手架的常用表单控件
@ -939,102 +943,22 @@ export class FormPlugin extends BasePlugin {
trigger?: EditorNodeType
) {
const jsonschema: any = {
$id: 'formItems',
type: 'object',
properties: {}
...jsonToJsonSchema(JSONPipeOut(node.schema.data))
};
const pool = node.children.concat();
while (pool.length) {
const current = pool.shift() as EditorNodeType;
const schema = current.schema;
if (current.rendererConfig?.type === 'combo') {
if (trigger) {
const items = current.children?.find(
child => child.isRegion && child.region === 'items'
if (current.rendererConfig?.isFormItem && schema.name) {
jsonschema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
region,
trigger,
node
);
const isItemsChild = someTree(
items.children,
item => item.id === trigger?.id
);
if (isItemsChild) {
const itemsChilds = items.children.concat();
while (itemsChilds.length) {
const currentItem = itemsChilds.shift() as EditorNodeType;
const itemSchema = currentItem.schema;
if (itemSchema.name) {
jsonschema.properties[itemSchema.name] = {
...(currentItem.info?.plugin?.buildDataSchemas
? await currentItem.info.plugin.buildDataSchemas(
currentItem,
region,
trigger
)
: {
type: 'string',
title: itemSchema.label || itemSchema.name
}),
group: `当前行记录(${schema.label || schema.name})`
};
}
}
}
}
if (schema.name) {
jsonschema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(current, region);
}
} else if (current.rendererConfig?.type === 'input-table') {
if (trigger) {
const columns: EditorNodeType = current.children.find(
item => item.isRegion && item.region === 'columns'
);
const isColumnChild = someTree(
columns?.children,
item => item.id === trigger.id
);
if (isColumnChild) {
for (let col of schema?.columns) {
if (col.name) {
jsonschema.properties[col.name] = {
type: 'string',
title: col.label || col.name,
group: `当前行记录(${schema.label || schema.name})`
};
}
}
}
}
if (schema.name) {
jsonschema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
region,
trigger
);
}
} else if (current.rendererConfig?.isFormItem) {
if (schema.name) {
jsonschema.properties[schema.name] = current.info?.plugin
?.buildDataSchemas
? await current.info.plugin.buildDataSchemas(
current,
region,
trigger
)
: {
type: 'string',
title:
typeof schema.label === 'string' ? schema.label : schema.name,
originalValue: schema.value // 记录原始值,循环引用检测需要
};
}
} else {
pool.push(...current.children);
}
@ -1056,6 +980,40 @@ export class FormPlugin extends BasePlugin {
scope?.addSchema(jsonschema);
}
}
/**
* form
*/
patchSchema(schema: Schema, info: RendererConfig, props: any) {
if (
Array.isArray(schema.actions) ||
schema.wrapWithPanel === false ||
(Array.isArray(schema.body) &&
schema.body.some(
(item: any) =>
item &&
!!~['submit', 'button', 'button-group', 'reset'].indexOf(
(item as any)?.body?.[0]?.type ||
(item as any)?.body?.type ||
(item as any).type
)
))
) {
return;
}
return {
...schema,
actions: [
{
type: 'submit',
label:
props?.translate(props?.submitText) || schema.submitText || '提交',
primary: true
}
]
};
}
}
registerEditorPlugin(FormPlugin);

View File

@ -325,6 +325,9 @@ export class DateRangeControlPlugin extends BasePlugin {
getSchemaTpl('formItemName', {
required: true
}),
getSchemaTpl('formItemExtraName'),
getSchemaTpl('label'),
getSchemaTpl('selectDateRangeType', {
value: this.scaffold.type,

View File

@ -18,7 +18,7 @@ import {
EditorManager,
DSBuilderManager
} from 'amis-editor-core';
import {setVariable, someTree} from 'amis-core';
import {getTreeAncestors, setVariable, someTree} from 'amis-core';
import {ValidatorTag} from '../../validator';
import {
getEventControlConfig,
@ -1105,20 +1105,46 @@ export class TableControlPlugin extends BasePlugin {
async buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
trigger?: EditorNodeType,
parent?: EditorNodeType
) {
const itemsSchema: any = {
$id: 'inputTableRow',
$id: `${node.id}-${node.type}-tableRows`,
type: 'object',
properties: {}
};
const columns: EditorNodeType = node.children.find(
item => item.isRegion && item.region === 'columns'
);
const parentScopeId = `${parent?.id}-${parent?.type}${
node.parent?.type === 'cell' ? '-currentRow' : ''
}`;
let isColumnChild = false;
// 追加当前行scope
if (trigger) {
isColumnChild = someTree(
columns?.children,
item => item.id === trigger.id
);
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
if (this.manager.dataSchema.getScope(scopeId)) {
this.manager.dataSchema.removeScope(scopeId);
}
if (this.manager.dataSchema.getScope(parentScopeId)) {
this.manager.dataSchema.switchTo(parentScopeId);
}
this.manager.dataSchema.addScope([], scopeId);
this.manager.dataSchema.current.tag = '当前行记录';
this.manager.dataSchema.current.group = '组件上下文';
}
}
const cells: any = columns.children.concat();
while (cells.length > 0) {
const cell = cells.shift() as EditorNodeType;
// cell的孩子貌似只会有一个
@ -1128,17 +1154,13 @@ export class TableControlPlugin extends BasePlugin {
const schema = current.schema;
if (schema.name) {
itemsSchema.properties[schema.name] = current.info?.plugin
?.buildDataSchemas
? await current.info.plugin.buildDataSchemas(
current,
region,
trigger
)
: {
type: 'string',
title: schema.label || schema.name
};
itemsSchema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
region,
trigger,
node
);
}
}
}
@ -1147,8 +1169,15 @@ export class TableControlPlugin extends BasePlugin {
return itemsSchema;
}
// 追加当前行数据
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
const scope = this.manager.dataSchema.getScope(scopeId);
scope?.addSchema(itemsSchema);
}
return {
$id: 'inputTable',
$id: `${node.id}-${node.type}-tableData`,
type: 'array',
title: node.schema?.label || node.schema?.name,
items: itemsSchema

View File

@ -1,16 +1,15 @@
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {registerEditorPlugin} from 'amis-editor-core';
import {
registerEditorPlugin,
BasePlugin,
BasicSubRenderInfo,
RendererEventContext,
SubRendererInfo,
BaseEventContext,
tipedLabel
RendererPluginAction,
RendererPluginEvent,
tipedLabel,
defaultValue,
getSchemaTpl
} from 'amis-editor-core';
import {ValidatorTag} from '../../validator';
import {getEventControlConfig} from '../../renderer/event-control/helper';
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
export class MatrixControlPlugin extends BasePlugin {
static id = 'MatrixControlPlugin';
@ -127,8 +126,9 @@ export class MatrixControlPlugin extends BasePlugin {
required: true
}),
getSchemaTpl('label'),
getSchemaTpl('multiple', {
value: true
getSchemaTpl('switch', {
name: 'multiple',
label: '可多选'
}),
{
label: tipedLabel('模式', '行级、列级或者单个单元单选'),
@ -140,7 +140,7 @@ export class MatrixControlPlugin extends BasePlugin {
left: 2,
justify: true
},
visibleOn: '!this.multiple',
visibleOn: '!data.multiple',
options: [
{
label: '行级',
@ -157,37 +157,15 @@ export class MatrixControlPlugin extends BasePlugin {
],
pipeIn: defaultValue('column')
},
getSchemaTpl('autoFillApi'),
{
label: tipedLabel('列全选', '列级全选功能'),
getSchemaTpl('switch', {
name: 'yCheckAll',
type: 'select',
options: [
{
label: '是',
value: true
},
{
label: '否',
value: false
}
]
},
{
label: tipedLabel('行全选', '行级全选功能'),
label: tipedLabel('列全选', '列级全选功能')
}),
getSchemaTpl('switch', {
name: 'xCheckAll',
type: 'select',
options: [
{
label: '是',
value: true
},
{
label: '否',
value: false
}
]
}
label: tipedLabel('行全选', '行级全选功能')
}),
getSchemaTpl('autoFillApi')
]
},
{

View File

@ -113,34 +113,30 @@ export class FlexPluginBase extends LayoutBasePlugin {
getSchemaTpl('layout:flex-wrap'),
isFlexItem
? getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
: null,
...(isFlexItem
? [
getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
]
: []),
getSchemaTpl('layout:overflow-x', {
visibleOn: `${
@ -212,7 +208,11 @@ export class FlexPluginBase extends LayoutBasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {exclude: ['layout']})
...getSchemaTpl('theme:common', {exclude: ['layout']}),
{
title: '自定义 CSS 类名',
body: [getSchemaTpl('className')]
}
])
}
])

View File

@ -1,4 +1,4 @@
import {ContainerWrapper} from 'amis-editor-core';
import {ContainerWrapper, JSONPipeOut} from 'amis-editor-core';
import {registerEditorPlugin} from 'amis-editor-core';
import {
BaseEventContext,
@ -369,14 +369,30 @@ export class PagePlugin extends BasePlugin {
region?: EditorNodeType,
trigger?: EditorNodeType
) {
const scope = this.manager.dataSchema.getScope(`${node.id}-${node.type}`);
let jsonschema = {
$id: 'pageStaticData',
...jsonToJsonSchema(omit(node.schema.data, '$$id'))
...jsonToJsonSchema(JSONPipeOut(node.schema.data))
};
scope?.removeSchema(jsonschema.$id);
scope?.addSchema(jsonschema);
const pool = node.children.concat();
while (pool.length) {
const current = pool.shift() as EditorNodeType;
const schema = current.schema;
if (current.rendererConfig?.isFormItem && schema?.name) {
jsonschema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
undefined,
trigger,
node
);
} else if (!current.rendererConfig?.storeType) {
pool.push(...current.children);
}
}
return jsonschema;
}
rendererBeforeDispatchEvent(node: EditorNodeType, e: any, data: any) {

View File

@ -2,6 +2,7 @@ import {Button} from 'amis';
import React from 'react';
import {
EditorNodeType,
JSONPipeOut,
jsonToJsonSchema,
registerEditorPlugin
} from 'amis-editor-core';
@ -151,9 +152,6 @@ export class ServicePlugin extends BasePlugin {
panelTitle = '服务';
panelBodyCreator = (context: BaseEventContext) => {
console.log(context);
console.log(context.node.parent);
console.log(context.node.parent.getComponent());
return getSchemaTpl('tabs', [
{
title: '属性',
@ -291,6 +289,36 @@ export class ServicePlugin extends BasePlugin {
]);
};
async buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
) {
let jsonschema: any = {
...jsonToJsonSchema(JSONPipeOut(node.schema.data ?? {}))
};
const pool = node.children.concat();
while (pool.length) {
const current = pool.shift() as EditorNodeType;
const schema = current.schema;
if (current.rendererConfig?.isFormItem && schema?.name) {
jsonschema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
undefined,
trigger,
node
);
} else if (!current.rendererConfig?.storeType) {
pool.push(...current.children);
}
}
return jsonschema;
}
rendererBeforeDispatchEvent(node: EditorNodeType, e: any, data: any) {
if (e === 'fetchInited') {
const scope = this.manager.dataSchema.getScope(`${node.id}-${node.type}`);

View File

@ -3,7 +3,7 @@
*/
import {registerEditorPlugin} from 'amis-editor-core';
import {BasePlugin} from 'amis-editor-core';
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
export class SparklinePlugin extends BasePlugin {
@ -30,14 +30,54 @@ export class SparklinePlugin extends BasePlugin {
};
panelTitle = '走势图';
panelBody = [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
{
name: 'height',
type: 'input-number',
label: '高度'
}
];
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
return [
getSchemaTpl('tabs', [
{
title: '属性',
body: [
getSchemaTpl('collapseGroup', [
{
title: '基本',
body: [
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
getSchemaTpl('name')
]
},
{
title: '宽高设置',
body: [
{
name: 'width',
type: 'input-number',
label: '宽度'
},
{
name: 'height',
type: 'input-number',
label: '高度'
}
]
},
getSchemaTpl('status')
])
]
},
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {exclude: ['layout']}),
{
title: '自定义 CSS 类名',
body: [getSchemaTpl('className')]
}
])
}
])
];
};
}
registerEditorPlugin(SparklinePlugin);

View File

@ -760,40 +760,66 @@ export class TablePlugin extends BasePlugin {
async buildDataSchemas(
node: EditorNodeType,
region?: EditorNodeType,
trigger?: EditorNodeType
trigger?: EditorNodeType,
parent?: EditorNodeType
) {
let itemsSchema: any = {
$id: 'tableRow',
$id: `${node.id}-${node.type}-tableRows`,
type: 'object',
properties: {}
};
const columns: EditorNodeType = node.children.find(
item => item.isRegion && item.region === 'columns'
);
const cells: any = columns.children.concat();
const parentScopeId = `${parent?.id}-${parent?.type}${
node.parent?.type === 'cell' ? '-currentRow' : ''
}`;
let isColumnChild = false;
while (cells.length > 0 && cells.length <= columns.children?.length) {
// 追加当前行scope
if (trigger) {
isColumnChild = someTree(
columns?.children,
item => item.id === trigger.id
);
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
if (this.manager.dataSchema.getScope(scopeId)) {
this.manager.dataSchema.removeScope(scopeId);
}
if (this.manager.dataSchema.getScope(parentScopeId)) {
this.manager.dataSchema.switchTo(parentScopeId);
}
this.manager.dataSchema.addScope([], scopeId);
this.manager.dataSchema.current.tag = '当前行记录';
this.manager.dataSchema.current.group = '组件上下文';
}
}
let index = 0;
const cells: any = columns.children.concat();
// 存在预览节点,限制下遍历数
while (cells.length > 0 && index < node.schema.columns.length) {
const cell = cells.shift() as EditorNodeType;
// cell的孩子貌似只会有一个
const items = cell.children.concat();
while (items.length) {
const current = items.shift() as EditorNodeType;
const schema = current.schema;
if (schema.name) {
itemsSchema.properties[schema.name] = current.info?.plugin
?.buildDataSchemas
? await current.info.plugin.buildDataSchemas(
current,
region,
trigger
)
: {
type: 'string',
title: schema.label || schema.name
};
itemsSchema.properties[schema.name] =
await current.info.plugin.buildDataSchemas?.(
current,
region,
trigger,
node
);
}
}
index++;
}
// 收集source绑定的列表成员
@ -831,28 +857,17 @@ export class TablePlugin extends BasePlugin {
return itemsSchema;
}
let cellProperties: any = {};
if (trigger) {
const isColumnChild = someTree(
columns?.children,
item => item.id === trigger.id
);
if (isColumnChild) {
Object.keys(itemsSchema.properties).map(key => {
cellProperties[key] = {
...itemsSchema.properties[key],
group: '当前行记录'
};
});
}
// 追加当前行数据
if (isColumnChild) {
const scopeId = `${node.id}-${node.type}-currentRow`;
const scope = this.manager.dataSchema.getScope(scopeId);
scope?.addSchema(itemsSchema);
}
return {
$id: 'table',
$id: `${node.id}-${node.type}`,
type: 'object',
properties: {
...cellProperties,
rows: {
type: 'array',
title: '数据列表',

View File

@ -1,8 +1,9 @@
import React from 'react';
import {getEventControlConfig} from '../renderer/event-control/helper';
import {tipedLabel} from 'amis-editor-core';
import {registerEditorPlugin, getSchemaTpl} from 'amis-editor-core';
import {registerEditorPlugin, getSchemaTpl, diff} from 'amis-editor-core';
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
import {schemaArrayFormat, schemaToArray} from '../util';
export class TimelinePlugin extends BasePlugin {
static id = 'TimelinePlugin';
@ -90,7 +91,42 @@ export class TimelinePlugin extends BasePlugin {
getSchemaTpl('timelineItemControl', {
name: 'items',
mode: 'normal'
})
}),
{
type: 'ae-switch-more',
mode: 'normal',
label: '自定义标题显示模板',
bulk: false,
name: 'itemTitleSchema',
formType: 'extend',
form: {
body: [
{
type: 'button',
level: 'primary',
size: 'sm',
block: true,
onClick: this.editDetail.bind(this, context),
label: '配置标题显示模板'
}
]
},
pipeIn: (value: any) => {
if (typeof value === 'undefined') {
return false;
}
return typeof value !== 'string';
},
pipeOut: (value: any) => {
if (value === true) {
return {
type: 'tpl',
tpl: '请编辑标题内容'
};
}
return value ? value : undefined;
}
}
]
},
getSchemaTpl('status')
@ -105,6 +141,33 @@ export class TimelinePlugin extends BasePlugin {
])
}
]);
editDetail(context: BaseEventContext) {
const {id, schema} = context;
const manager = this.manager;
const store = manager.store;
const node = store.getNodeById(id);
const value = store.getValueOf(id);
const defaultItemSchema = {
type: 'tpl',
tpl: '请编辑标题内容'
};
node &&
value &&
this.manager.openSubEditor({
title: '配置标题显示模板',
value: schemaToArray(value.itemTitleSchema ?? defaultItemSchema),
slot: {
type: 'container',
body: '$$'
},
onChange: (newValue: any) => {
newValue = {...value, itemTitleSchema: schemaArrayFormat(newValue)};
manager.panelChangeValue(newValue, diff(value, newValue));
},
data: schema
});
}
}
registerEditorPlugin(TimelinePlugin);

View File

@ -78,34 +78,30 @@ export class WrapperPlugin extends LayoutBasePlugin {
'data.style && (data.style.display === "flex" || data.style.display === "inline-flex")'
}),
isFlexItem
? getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
})
: null,
isFlexItem
? getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
: null,
...(isFlexItem
? [
getSchemaTpl('layout:flex', {
isFlexColumnItem,
label: isFlexColumnItem ? '高度设置' : '宽度设置',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-grow', {
visibleOn:
'data.style && data.style.flex === "1 1 auto" && (data.style.position === "static" || data.style.position === "relative")'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '弹性高度' : '弹性宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "1 1 auto"'
}),
getSchemaTpl('layout:flex-basis', {
label: isFlexColumnItem ? '固定高度' : '固定宽度',
visibleOn:
'data.style && (data.style.position === "static" || data.style.position === "relative") && data.style.flex === "0 0 150px"'
})
]
: []),
getSchemaTpl('layout:overflow-x', {
visibleOn: `${

View File

@ -79,6 +79,28 @@ setSchemaTpl('formItemName', {
// validateOnChange: false
});
setSchemaTpl('formItemExtraName', {
className: 'mb-3',
type: 'fieldset',
body: [
getSchemaTpl('formItemName', {
required: true,
label: '额外字段',
name: 'extraName',
visibleOn: 'typeof this.extraName === "string"'
}),
{
type: 'switch',
label: tipedLabel('存成两个字段', '开启后将选中范围分别存成两个字段'),
name: 'extraName',
pipeIn: (value: any) => typeof value === 'string',
pipeOut: (value: any) => (value ? '' : undefined),
inputClassName: 'is-inline'
}
]
});
setSchemaTpl(
'formItemMode',
(config: {

View File

@ -599,6 +599,9 @@ setSchemaTpl(
// 适配
form.setValueByName('style.flexGrow', undefined);
form.setValueByName('style.flexBasis', undefined);
form.setValueByName('style.overflowX', undefined);
form.setValueByName('style.overflowY', undefined);
form.setValueByName('style.overflow', undefined);
if (config?.isFlexColumnItem) {
form.setValueByName('style.height', undefined);
@ -893,7 +896,7 @@ setSchemaTpl(
config?.label ||
tipedLabel(' x轴滚动模式', '用于设置水平方向的滚动模式'),
name: config?.name || 'style.overflowX',
value: config?.value || 'auto',
value: config?.value || 'visible',
visibleOn: config?.visibleOn,
pipeIn: config?.pipeIn,
pipeOut: config?.pipeOut,
@ -1099,7 +1102,7 @@ setSchemaTpl(
config?.label ||
tipedLabel(' y轴滚动模式', '用于设置垂直方向的滚动模式'),
name: config?.name || 'style.overflowY',
value: config?.value || 'auto',
value: config?.value || 'visible',
visibleOn: config?.visibleOn,
pipeIn: config?.pipeIn,
pipeOut: config?.pipeOut,

View File

@ -64,7 +64,7 @@
"react-hook-form": "7.39.0",
"react-json-view": "1.21.3",
"react-overlays": "5.1.1",
"react-textarea-autosize": "8.5.2",
"react-textarea-autosize": "8.3.3",
"react-transition-group": "4.4.2",
"react-visibility-sensor": "5.1.1",
"sortablejs": "1.15.0",

View File

@ -279,6 +279,26 @@ $widths: map-merge(
)
) !default;
$minWidths: (
none: none,
0: 0rem,
xs: 20rem,
sm: 24rem,
md: 28rem,
lg: 32rem,
xl: 36rem,
2xl: 42rem,
3xl: 48rem,
4xl: 56rem,
5xl: 64rem,
6xl: 72rem,
7xl: 80rem,
full: 100%,
min: min-content,
max: max-content,
prose: 65ch
) !default;
$maxWidths: (
none: none,
0: 0rem,

View File

@ -282,7 +282,7 @@
padding: 10px;
margin: -10px 0px;
background: $white;
z-index: 1;
z-index: 2;
}
> .#{$ns}CBGroupOrItem-dragbar {
left: px2rem(-5px);

View File

@ -40,6 +40,7 @@
&-tab {
overflow: hidden;
border-bottom: 1px solid #3d3d3d;
}
&-tab > button {
@ -90,6 +91,7 @@
&-content {
pointer-events: all;
display: none;
height: 100%;
}
&-resize {
@ -122,7 +124,7 @@
&.is-expanded {
width: 420px;
overflow: auto;
background: #272821;
color: #cccccc;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
@ -168,6 +170,37 @@
padding: var(--gap-sm);
}
&-log,
&-inspect {
height: 100%;
overflow: auto;
// 火狐浏览器
scrollbar-width: thin;
scrollbar-color: #6b6b6b #2b2b2b;
&::-webkit-scrollbar {
position: relative;
z-index: 10;
background-color: #2c2c2c;
width: 16px;
height: 16px;
border-left: 1px solid #3d3d3d;
// border-top: 1px solid #3d3d3d;
}
&::-webkit-scrollbar-thumb {
background: #6b6b6b;
background-clip: content-box;
border: 4px solid transparent;
border-radius: 500px;
&:hover {
background: #939393;
background-clip: content-box;
}
}
}
&-logLine {
overflow-x: hidden;
}

View File

@ -89,11 +89,20 @@
}
&-body {
padding: var(--drawer-content-paddingTop) var(--drawer-content-paddingRight)
padding: 0 var(--drawer-content-paddingRight)
var(--drawer-content-paddingBottom) var(--drawer-content-paddingLeft);
flex-basis: 0;
flex-grow: 1;
overflow: auto;
// 因为如果成员里面有 position:sticky 的内容
// padding 会导致位置不正确
// 所以改成这种写法
&:before {
content: '';
display: block;
height: var(--drawer-content-paddingTop);
}
}
&-footer {

View File

@ -350,6 +350,7 @@
&-submenu-title {
display: flex;
align-items: center;
justify-content: space-between;
.#{$ns}Nav-Menu-item-wrap {

View File

@ -433,7 +433,7 @@
}
&:active {
background: transparent;
background: var(--Table-onHover-bg);
}
&:hover.#{$ns}Table-placeholder {

View File

@ -2,97 +2,108 @@
title: Width
---
| Class | Properties |
| ----------- | ------------------------ |
| w-px | width: 0.0625rem |
| w-0 | width: 0 |
| w-none | width: 0 |
| w-0\.5 | width: 0.125rem |
| w-1 | width: 0.25rem |
| w-1\.5 | width: 0.375rem |
| w-2 | width: 0.5rem |
| w-2\.5 | width: 0.625rem |
| w-3 | width: 0.75rem |
| w-3\.5 | width: 0.875rem |
| w-4 | width: 1rem |
| w-5 | width: 1.25rem |
| w-6 | width: 1.5rem |
| w-7 | width: 1.75rem |
| w-8 | width: 2rem |
| w-9 | width: 2.25rem |
| w-10 | width: 2.5rem |
| w-11 | width: 2.75rem |
| w-12 | width: 3rem |
| w-14 | width: 3.5rem |
| w-16 | width: 4rem |
| w-18 | width: 4.5rem |
| w-20 | width: 5rem |
| w-24 | width: 6rem |
| w-28 | width: 7rem |
| w-32 | width: 8rem |
| w-36 | width: 9rem |
| w-40 | width: 10rem |
| w-44 | width: 11rem |
| w-48 | width: 12rem |
| w-52 | width: 13rem |
| w-56 | width: 14rem |
| w-60 | width: 15rem |
| w-64 | width: 16rem |
| w-72 | width: 18rem |
| w-80 | width: 20rem |
| w-96 | width: 24rem |
| w-auto | width: auto |
| w-1x | width: 1em |
| w-2x | width: 2em |
| w-3x | width: 3em |
| w-1\/2 | width: 50% |
| w-1\/3 | width: 33.333333% |
| w-2\/3 | width: 66.666667% |
| w-1\/4 | width: 25% |
| w-2\/4 | width: 50% |
| w-3\/4 | width: 75% |
| w-1\/5 | width: 20% |
| w-2\/5 | width: 40% |
| w-3\/5 | width: 60% |
| w-4\/5 | width: 80% |
| w-1\/6 | width: 16.666667% |
| w-2\/6 | width: 33.333333% |
| w-3\/6 | width: 50% |
| w-4\/6 | width: 66.666667% |
| w-5\/6 | width: 83.333333% |
| w-1\/12 | width: 8.333333% |
| w-2\/12 | width: 16.666667% |
| w-3\/12 | width: 25% |
| w-4\/12 | width: 33.333333% |
| w-5\/12 | width: 41.666667% |
| w-6\/12 | width: 50% |
| w-7\/12 | width: 58.333333% |
| w-8\/12 | width: 66.666667% |
| w-9\/12 | width: 75% |
| w-10\/12 | width: 83.333333% |
| w-11\/12 | width: 91.666667% |
| w-full | width: 100% |
| w-screen | width: 100vw |
| w-min | width: min-content |
| w-max | width: max-content |
| min-w-0 | min-width: 0px |
| min-w-full | min-width: 100% |
| min-w-min | min-width: min-content |
| min-w-max | min-width: max-content |
| max-w-none | max-width: none |
| max-w-0 | max-width: 0rem |
| max-w-xs | max-width: 20rem |
| max-w-sm | max-width: 24rem |
| max-w-md | max-width: 28rem |
| max-w-lg | max-width: 32rem |
| max-w-xl | max-width: 36rem |
| max-w-2xl | max-width: 42rem |
| max-w-3xl | max-width: 48rem |
| max-w-4xl | max-width: 56rem |
| max-w-5xl | max-width: 64rem |
| max-w-6xl | max-width: 72rem |
| max-w-7xl | max-width: 80rem |
| max-w-full | max-width: 100% |
| max-w-min | max-width: min-content |
| max-w-max | max-width: max-content |
| max-w-prose | max-width: 65ch |
| Class | Properties |
| ----------- | ---------------------- |
| w-px | width: 0.0625rem |
| w-0 | width: 0 |
| w-none | width: 0 |
| w-0\.5 | width: 0.125rem |
| w-1 | width: 0.25rem |
| w-1\.5 | width: 0.375rem |
| w-2 | width: 0.5rem |
| w-2\.5 | width: 0.625rem |
| w-3 | width: 0.75rem |
| w-3\.5 | width: 0.875rem |
| w-4 | width: 1rem |
| w-5 | width: 1.25rem |
| w-6 | width: 1.5rem |
| w-7 | width: 1.75rem |
| w-8 | width: 2rem |
| w-9 | width: 2.25rem |
| w-10 | width: 2.5rem |
| w-11 | width: 2.75rem |
| w-12 | width: 3rem |
| w-14 | width: 3.5rem |
| w-16 | width: 4rem |
| w-18 | width: 4.5rem |
| w-20 | width: 5rem |
| w-24 | width: 6rem |
| w-28 | width: 7rem |
| w-32 | width: 8rem |
| w-36 | width: 9rem |
| w-40 | width: 10rem |
| w-44 | width: 11rem |
| w-48 | width: 12rem |
| w-52 | width: 13rem |
| w-56 | width: 14rem |
| w-60 | width: 15rem |
| w-64 | width: 16rem |
| w-72 | width: 18rem |
| w-80 | width: 20rem |
| w-96 | width: 24rem |
| w-auto | width: auto |
| w-1x | width: 1em |
| w-2x | width: 2em |
| w-3x | width: 3em |
| w-1\/2 | width: 50% |
| w-1\/3 | width: 33.333333% |
| w-2\/3 | width: 66.666667% |
| w-1\/4 | width: 25% |
| w-2\/4 | width: 50% |
| w-3\/4 | width: 75% |
| w-1\/5 | width: 20% |
| w-2\/5 | width: 40% |
| w-3\/5 | width: 60% |
| w-4\/5 | width: 80% |
| w-1\/6 | width: 16.666667% |
| w-2\/6 | width: 33.333333% |
| w-3\/6 | width: 50% |
| w-4\/6 | width: 66.666667% |
| w-5\/6 | width: 83.333333% |
| w-1\/12 | width: 8.333333% |
| w-2\/12 | width: 16.666667% |
| w-3\/12 | width: 25% |
| w-4\/12 | width: 33.333333% |
| w-5\/12 | width: 41.666667% |
| w-6\/12 | width: 50% |
| w-7\/12 | width: 58.333333% |
| w-8\/12 | width: 66.666667% |
| w-9\/12 | width: 75% |
| w-10\/12 | width: 83.333333% |
| w-11\/12 | width: 91.666667% |
| w-full | width: 100% |
| w-screen | width: 100vw |
| min-w-none | min-width: none |
| min-w-0 | min-width: 0rem |
| min-w-xs | min-width: 20rem |
| min-w-sm | min-width: 24rem |
| min-w-md | min-width: 28rem |
| min-w-lg | min-width: 32rem |
| min-w-xl | min-width: 36rem |
| min-w-2xl | min-width: 42rem |
| min-w-3xl | min-width: 48rem |
| min-w-4xl | min-width: 56rem |
| min-w-5xl | min-width: 64rem |
| min-w-6xl | min-width: 72rem |
| min-w-7xl | min-width: 80rem |
| min-w-full | min-width: 100% |
| min-w-min | min-width: min-content |
| min-w-max | min-width: max-content |
| min-w-prose | min-width: 65ch |
| max-w-none | max-width: none |
| max-w-0 | max-width: 0rem |
| max-w-xs | max-width: 20rem |
| max-w-sm | max-width: 24rem |
| max-w-md | max-width: 28rem |
| max-w-lg | max-width: 32rem |
| max-w-xl | max-width: 36rem |
| max-w-2xl | max-width: 42rem |
| max-w-3xl | max-width: 48rem |
| max-w-4xl | max-width: 56rem |
| max-w-5xl | max-width: 64rem |
| max-w-6xl | max-width: 72rem |
| max-w-7xl | max-width: 80rem |
| max-w-full | max-width: 100% |
| max-w-min | max-width: min-content |
| max-w-max | max-width: max-content |
| max-w-prose | max-width: 65ch |

View File

@ -115,6 +115,21 @@ title: Width
*/
@mixin min-width-sizing($map: $minWidths, $prefix: '.') {
@each $name, $value in $map {
@if $name == default {
#{$prefix}min-w {
min-width: $value;
}
} @else {
#{$prefix}min-w-#{'' + selector-escape($name)} {
min-width: $value;
}
}
}
}
@mixin max-width-sizing($map: $maxWidths, $prefix: '.') {
@each $name, $value in $map {
@if $name == default {
@ -148,6 +163,7 @@ title: Width
}
@include max-width-sizing($maxWidths, $prefix);
@include min-width-sizing($minWidths, $prefix);
}
@include make-widths();

View File

@ -3,7 +3,7 @@
* @author fex
*/
import React from 'react';
import React, {version} from 'react';
import {render} from 'react-dom';
import Modal from './Modal';
import Button from './Button';
@ -11,6 +11,7 @@ import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import Html from './Html';
import type {PlainObject} from 'amis-core';
// import {createRoot} from 'react-dom/client';
export interface AlertProps extends ThemeProps, LocaleProps {
container?: any;
confirmText?: string;
@ -52,13 +53,21 @@ export interface AlertState {
export class Alert extends React.Component<AlertProps, AlertState> {
static instance: any = null;
static getInstance() {
static async getInstance() {
if (!Alert.instance) {
console.warn('Alert 组件应该没有被渲染,所以隐性的渲染到 body 了');
const container = document.body;
const div = document.createElement('div');
container.appendChild(div);
// if (parseInt(version.split('.')[0], 10) >= 18) {
// const root = createRoot(div);
// await new Promise<void>(resolve =>
// root.render(<FinnalAlert ref={() => resolve()} />)
// );
// } else {
render(<FinnalAlert />, div);
// }
}
return Alert.instance;
@ -346,23 +355,35 @@ function renderForm(
return renderSchemaFn?.(controls, value, callback, scopeRef, theme);
}
export const alert: (content: string, title?: string) => void = (
export const alert: (content: string, title?: string) => Promise<void> = async (
content,
title
) => Alert.getInstance().alert(content, title);
) => {
const instance = await Alert.getInstance();
return instance.alert(content, title);
};
export const confirm: (
content: string | React.ReactNode,
title?: string,
optionsOrCofnrimText?: string | ConfirmOptions,
cancelText?: string
) => Promise<any> = (content, title, optionsOrCofnrimText, cancelText) =>
Alert.getInstance().confirm(content, title, optionsOrCofnrimText, cancelText);
) => Promise<any> = async (
content,
title,
optionsOrCofnrimText,
cancelText
) => {
const instance = await Alert.getInstance();
return instance.confirm(content, title, optionsOrCofnrimText, cancelText);
};
export const prompt: (
controls: any,
defaultvalue?: any,
title?: string,
confirmText?: string
) => Promise<any> = (controls, defaultvalue, title, confirmText) =>
Alert.getInstance().prompt(controls, defaultvalue, title, confirmText);
) => Promise<any> = async (controls, defaultvalue, title, confirmText) => {
const instance = await Alert.getInstance();
return instance.prompt(controls, defaultvalue, title, confirmText);
};
export const FinnalAlert = themeable(localeable(Alert));
export default FinnalAlert;

View File

@ -1,5 +1,5 @@
import {ClassNamesFn, themeable} from 'amis-core';
import React from 'react';
import React, {version} from 'react';
import {render} from 'react-dom';
import {autobind, calculatePosition} from 'amis-core';
import Transition, {
@ -7,6 +7,7 @@ import Transition, {
ENTERING,
EXITING
} from 'react-transition-group/Transition';
// import {createRoot} from 'react-dom/client';
const fadeStyles: {
[propName: string]: string;
} = {
@ -49,12 +50,20 @@ export class ContextMenu extends React.Component<
ContextMenuState
> {
static instance: any = null;
static getInstance() {
static async getInstance() {
if (!ContextMenu.instance) {
const container = document.body;
const div = document.createElement('div');
container.appendChild(div);
// if (parseInt(version.split('.')[0], 10) >= 18) {
// const root = createRoot(div);
// await new Promise<void>(resolve =>
// root.render(<ThemedContextMenu ref={() => resolve()} />)
// );
// } else {
render(<ThemedContextMenu />, div);
// }
}
return ContextMenu.instance;
@ -309,5 +318,7 @@ export function openContextMenus(
menus: Array<MenuItem | MenuDivider>,
onClose?: () => void
) {
return ContextMenu.getInstance().openContextMenus(info, menus, onClose);
return ContextMenu.getInstance().then(instance =>
instance.openContextMenus(info, menus, onClose)
);
}

View File

@ -126,6 +126,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
onDragStart={onDragStart}
config={config}
fields={fields}
formula={formula}
value={value as ConditionGroupValue}
onChange={this.handleItemChange}
fieldClassName={fieldClassName}

View File

@ -1,6 +1,6 @@
import React, {useEffect} from 'react';
import {themeable, ThemeProps, filterTree, getTreeAncestors} from 'amis-core';
import {themeable, ThemeProps, filterTree} from 'amis-core';
import GroupedSelection from '../GroupedSelection';
import Tabs, {Tab} from '../Tabs';
import TreeSelection from '../TreeSelection';
@ -54,7 +54,7 @@ const memberOpers = [
{
label: '取该成员的平均值',
value: 'AVG(ARRAYMAP(${arr}, item => item.${member}))',
description: '即计算该成员记录的总和,需确认该成员记录均为数字类型'
description: '即计算该成员记录的平均值,需确认该成员记录均为数字类型'
},
{
label: '取该成员的最大值',
@ -140,48 +140,51 @@ function VariableList(props: VariableListProps) {
)}
{/* 控制只对第一层数组成员展示快捷操作入口 */}
{option.memberDepth !== undefined &&
option.memberDepth < 2 &&
option.label &&
(!selfVariableName || option.value !== selfVariableName) ? (
<PopOverContainer
popOverContainer={() =>
document.querySelector(`.${cx('FormulaPicker-Modal')}`)
}
popOverRender={({onClose}) => (
<ul className={cx(`${classPrefix}-item-oper`)}>
{memberOpers.map((item, i) => {
return (
<TooltipWrapper
tooltip={item.description}
tooltipTheme="dark"
>
<li
key={i}
onClick={() =>
handleMemberClick(
{...item, isMember: true},
option,
onClose
)
}
option.memberDepth < 2 ? (
<PopOverContainer
popOverContainer={() =>
document.querySelector(`.${cx('FormulaPicker-Modal')}`)
}
popOverRender={({onClose}) => (
<ul className={cx(`${classPrefix}-item-oper`)}>
{memberOpers.map((item, i) => {
return (
<TooltipWrapper
tooltip={item.description}
tooltipTheme="dark"
>
<span>{item.label}</span>
</li>
</TooltipWrapper>
);
})}
</ul>
)}
>
{({onClick, ref, isOpened}) => (
<TooltipWrapper
tooltip={option.description ?? option.label}
tooltipTheme="dark"
>
<label onClick={onClick}>{option.label}</label>
</TooltipWrapper>
)}
</PopOverContainer>
<li
key={i}
onClick={() =>
handleMemberClick(
{...item, isMember: true},
option,
onClose
)
}
>
<span>{item.label}</span>
</li>
</TooltipWrapper>
);
})}
</ul>
)}
>
{({onClick, ref, isOpened}) => (
<TooltipWrapper
tooltip={option.description ?? option.label}
tooltipTheme="dark"
>
<label onClick={onClick}>{option.label}</label>
</TooltipWrapper>
)}
</PopOverContainer>
) : (
<label>{option.label}</label>
)
) : null}
{option?.tag ? (
<span className={cx(`${classPrefix}-item-tag`)}>

View File

@ -1,58 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EventAction:ajax 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
发送请求
</span>
</button>
<span
class="cxd-TplField"
>
<span>
18岁的天空
</span>
</span>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
发送请求2
</span>
</button>
<span
class="cxd-TplField"
>
<span>
岁的天空status:msg:
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:ajax 2`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
发送请求
</span>
</button>
<span
class="cxd-TplField"
>
<span>
18岁的天空
</span>
</span>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
发送请求2
</span>
</button>
<span
class="cxd-TplField"
>
<span>
18岁的天空status:0msg:ok
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:ajax args 1`] = `
<div>
<div
class="cxd-Page"

View File

@ -297,3 +297,301 @@ exports[`EventAction:dialog 7`] = `
</div>
</div>
`;
exports[`EventAction:dialog args 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Modal cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Modal-overlay in"
/>
<div
class="cxd-Modal-content in"
>
<div
class="cxd-Modal-header"
>
<a
class="cxd-Modal-close"
data-position="left"
data-tooltip="关闭"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Modal-title"
>
模态弹窗
</div>
</div>
<div
class="cxd-Modal-body"
role="dialog-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开子弹窗
</span>
</button>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default ml-2"
type="button"
>
<span>
关闭当前弹窗
</span>
</button>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default ml-2"
type="button"
>
<span>
触发确认
</span>
</button>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default ml-2"
type="button"
>
<span>
触发取消
</span>
</button>
</div>
<div
class="cxd-Modal-footer"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
取消
</span>
</button>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 2`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 3`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 4`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 5`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 6`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:dialog args 7`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开弹窗
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -279,3 +279,283 @@ exports[`EventAction:drawer 7`] = `
</div>
</div>
`;
exports[`EventAction:drawer args 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Drawer cxd-Drawer--right cxd-Drawer--md cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Drawer-overlay in"
/>
<div
class="cxd-Drawer-content in"
>
<a
class="cxd-Drawer-close"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Drawer-header"
>
<div
class="cxd-Drawer-title"
>
<span
class="cxd-TplField"
>
<span>
模态抽屉
</span>
</span>
</div>
</div>
<div
class="cxd-Drawer-body"
>
<div
class="cxd-Spinner-overlay in"
/>
<div
class="cxd-Spinner cxd-Spinner--overlay in"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
/>
</div>
</div>
<div
class="cxd-Drawer-footer"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
取消
</span>
</button>
<button
class="cxd-Button cxd-Button--primary cxd-Button--size-default"
type="button"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 2`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 3`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 4`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 5`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 6`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`EventAction:drawer args 7`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
打开抽屉
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -1,47 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EventAction:hidden 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
按钮2
</span>
</button>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
按钮4
</span>
</button>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
按钮5
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -3,7 +3,7 @@ import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv, wait} from '../helper';
test('EventAction:ajax', async () => {
test('EventAction:ajax args', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
@ -108,7 +108,6 @@ test('EventAction:ajax', async () => {
await waitFor(() => {
expect(getByText('18岁的天空')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('发送请求2'));
await waitFor(() => {
@ -116,3 +115,310 @@ test('EventAction:ajax', async () => {
});
expect(container).toMatchSnapshot();
});
test('EventAction:ajax', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
data: {
age: 18
}
}
})
);
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
id: 'page_001',
data: {
name: 'lll'
},
body: [
{
type: 'button',
label: '发送请求',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'ajax',
api: {
url: '/api/xxx',
method: 'get'
},
messages: {
success: '成功了!欧耶',
failed: '失败了呢。。'
},
outputVar: 'result'
},
{
actionType: 'setValue',
componentId: 'page_001',
args: {
value: '${event.data.result.responseData}'
}
}
]
}
}
},
{
type: 'tpl',
tpl: '${age}岁的天空'
},
{
type: 'button',
label: '发送请求2',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'ajax',
api: {
url: '/api/xxx',
method: 'get'
},
messages: {
success: '成功了!欧耶',
failed: '失败了呢。。'
}
},
{
actionType: 'setValue',
componentId: 'page_001',
args: {
value: '${event.data}'
}
}
]
}
}
},
{
type: 'tpl',
tpl: '${responseResult.responseData.age}岁的天空status:${responseResult.responseStatus}msg:${responseResult.responseMsg}'
}
]
},
{},
makeEnv({
fetcher
})
)
);
fireEvent.click(getByText('发送请求'));
await waitFor(() => {
expect(getByText('18岁的天空')).toBeInTheDocument();
});
fireEvent.click(getByText('发送请求2'));
await waitFor(() => {
expect(getByText('18岁的天空status:0msg:ok')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('EventAction:ajax data1', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
data: {
name: 'amis',
age: 18
}
}
})
);
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'lll'
},
body: [
{
type: 'button',
label: '发送请求',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'ajax',
args: {
api: {
url: '/api/xxx?name=${event.data.name}',
method: 'post',
data: {
myname1: '${name}',
myname2: '\\${name}',
myname3: '${text}',
myname4: '\\${text}'
}
}
},
outputVar: 'result'
},
{
actionType: 'ajax',
args: {
api: {
url: '/api/xxx?q1=${result.responseData.age}',
method: 'post',
data: {
param1: '${event.data.result.responseData.name}',
param2: '${responseData.name}',
param3: '${result.name}',
param4: '${event.data.responseData.name}'
}
}
}
}
]
}
}
}
]
},
{
data: {
text: '${lll}'
}
},
makeEnv({
fetcher
})
)
);
await waitFor(() => {
expect(getByText('发送请求')).toBeInTheDocument();
});
fireEvent.click(getByText(/发送请求/));
await waitFor(() => {
expect(fetcher).toHaveBeenCalledTimes(2);
expect(fetcher.mock.calls[0][0].url).toEqual('/api/xxx?name=lll');
expect(fetcher.mock.calls[0][0].data).toMatchObject({
myname1: 'lll',
myname2: '${name}',
myname3: '${lll}',
myname4: '${text}'
});
expect(fetcher.mock.calls[1][0].url).toEqual('/api/xxx?q1=18');
expect(fetcher.mock.calls[1][0].data).toMatchObject({
param1: 'amis',
param2: 'amis',
param3: 'amis',
param4: 'amis'
});
});
});
test('EventAction:ajax data2', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
data: {
name: 'amis',
age: 18
}
}
})
);
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'lll'
},
body: [
{
type: 'button',
label: '发送请求',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'ajax',
api: {
url: '/api/xxx?name=${event.data.name}',
method: 'post',
data: {
myname1: '${name}',
myname2: '\\${name}',
myname3: '${text}',
myname4: '\\${text}'
}
},
outputVar: 'result'
},
{
actionType: 'ajax',
api: {
url: '/api/xxx?q1=${result.responseData.age}',
method: 'post',
data: {
param1: '${event.data.result.responseData.name}',
param2: '${responseData.name}',
param3: '${result.name}',
param4: '${event.data.responseData.name}'
}
}
}
]
}
}
}
]
},
{
data: {
text: '${lll}'
}
},
makeEnv({
fetcher
})
)
);
await waitFor(() => {
expect(getByText('发送请求')).toBeInTheDocument();
});
fireEvent.click(getByText(/发送请求/));
await waitFor(() => {
expect(fetcher).toHaveBeenCalledTimes(2);
expect(fetcher.mock.calls[0][0].url).toEqual('/api/xxx?name=lll');
expect(fetcher.mock.calls[0][0].data).toMatchObject({
myname1: 'lll',
myname2: '${name}',
myname3: '${lll}',
myname4: '${text}'
});
expect(fetcher.mock.calls[1][0].url).toEqual('/api/xxx?q1=18');
expect(fetcher.mock.calls[1][0].data).toMatchObject({
param1: 'amis',
param2: 'amis',
param3: 'amis',
param4: 'amis'
});
});
});

View File

@ -35,7 +35,7 @@ test('EventAction:custom', async () => {
actionType: 'custom',
args: {
script:
"doAction({actionType: 'ajax', args: {api: '/api/xxx'}, outputVar: 'result'});"
"doAction({actionType: 'ajax', api: '/api/xxx', outputVar: 'result'});"
}
},
{
@ -60,7 +60,7 @@ test('EventAction:custom', async () => {
actionType: 'custom',
args: {
script:
"doAction({actionType: 'ajax', args: {api: '/api/xxx'}, outputVar: 'result'});event.stopPropagation();"
"doAction({actionType: 'ajax', api: '/api/xxx', outputVar: 'result'});event.stopPropagation();"
}
},
{

View File

@ -3,7 +3,7 @@ import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv, wait} from '../helper';
test('EventAction:dialog', async () => {
test('EventAction:dialog args', async () => {
const notify = jest.fn();
const {getByText, container}: any = render(
amisRender(
@ -220,97 +220,422 @@ test('EventAction:dialog', async () => {
expect(container).toMatchSnapshot();
}, 7000);
// test('EventAction:alert', async () => {
// const alert = jest.fn();
// const {getByText, container}: any = render(
// amisRender(
// {
// type: 'page',
// data: {
// msg: '去吃饭了'
// },
// body: [
// {
// type: 'button',
// label: '提示对话框',
// level: 'primary',
// onEvent: {
// click: {
// actions: [
// {
// actionType: 'alert',
// args: {
// msg: '<a href="http://www.baidu.com" target="_blank">${msg}~</a>'
// }
// }
// ]
// }
// }
// }
// ]
// },
// {},
// makeEnv({
// getModalContainer: () => container,
// alert
// })
// )
// );
test('EventAction:dialog', async () => {
const notify = jest.fn();
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
body: [
{
type: 'button',
label: '打开弹窗',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
id: 'dialog_001',
title: '模态弹窗',
body: [
{
type: 'button',
label: '打开子弹窗',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '模态子弹窗',
body: [
{
type: 'button',
label: '关闭父弹窗',
onEvent: {
click: {
actions: [
{
actionType: 'closeDialog',
componentId: 'dialog_001'
}
]
}
}
}
]
}
}
]
}
}
},
{
type: 'button',
label: '关闭当前弹窗',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'closeDialog'
}
]
}
}
},
{
type: 'button',
label: '触发确认',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'confirm',
componentId: 'dialog_001'
}
]
}
}
},
{
type: 'button',
label: '触发取消',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'cancel',
componentId: 'dialog_001'
}
]
}
}
}
],
onEvent: {
confirm: {
actions: [
{
actionType: 'toast',
args: {
msg: 'confirm'
}
}
]
},
cancel: {
actions: [
{
actionType: 'toast',
args: {
msg: 'cancel'
}
}
]
}
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container,
notify
})
)
);
// fireEvent.click(getByText('提示对话框'));
// await waitFor(() => {
// expect(alert).toHaveBeenCalled();
// });
// expect(alert.mock.calls[0][0]).toEqual(
// '<a href="http://www.baidu.com" target="_blank">去吃饭了~</a>'
// );
// });
// events
fireEvent.click(getByText('打开弹窗'));
expect(container).toMatchSnapshot();
// test('EventAction:confirm', async () => {
// const confirm = jest.fn();
// const {getByText, container}: any = render(
// amisRender(
// {
// type: 'page',
// data: {
// title: '操作确认',
// msg: '要删除它吗?'
// },
// body: [
// {
// type: 'button',
// label: '确认对话框',
// level: 'primary',
// onEvent: {
// click: {
// actions: [
// {
// actionType: 'confirmDialog',
// args: {
// title: '${title}',
// msg: '<span style="color:red">${msg}</span>'
// }
// }
// ]
// }
// }
// }
// ]
// },
// {},
// makeEnv({
// getModalContainer: () => container,
// confirm
// })
// )
// );
await waitFor(() => {
expect(getByText('确认')).toBeInTheDocument();
});
fireEvent.click(getByText('确认'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
// fireEvent.click(getByText('确认对话框'));
// await waitFor(() => {
// expect(confirm).toHaveBeenCalled();
// });
// expect(confirm.mock.calls[0][0]).toEqual(
// '<span style="color:red">要删除它吗?</span>'
// );
// expect(confirm.mock.calls[0][1]).toEqual('操作确认');
// });
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(getByText('取消')).toBeInTheDocument();
});
fireEvent.click(getByText('取消'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
// actions
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(getByText('关闭当前弹窗')).toBeInTheDocument();
});
fireEvent.click(getByText('关闭当前弹窗'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(getByText('打开子弹窗')).toBeInTheDocument();
});
fireEvent.click(getByText('打开子弹窗'));
await waitFor(() => {
expect(getByText('关闭父弹窗')).toBeInTheDocument();
});
fireEvent.click(getByText('关闭父弹窗'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(getByText('触发确认')).toBeInTheDocument();
});
fireEvent.click(getByText('触发确认'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(getByText('触发取消')).toBeInTheDocument();
});
fireEvent.click(getByText('触发取消'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
}, 7000);
test('EventAction:dialog data', async () => {
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'amis'
},
body: [
{
type: 'button',
label: '打开弹窗',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
args: {
dialog: {
type: 'dialog',
id: 'dialog_001',
title: '模态弹窗${event.data.name}',
body: [
{
type: 'tpl',
tpl: '你好,我是${name}、${name1}、${name2}'
}
],
data: {
name1: '${name}',
name2: '${event.data.name}'
}
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container
})
)
);
// events
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
expect(getByText('模态弹窗')).toBeInTheDocument();
expect(getByText('你好我是、amis、amis')).toBeInTheDocument(); // 因为事件动作给args提前做了映射
});
}, 7000);
test('EventAction:dialog data2', async () => {
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'amis'
},
body: [
{
type: 'button',
label: '打开弹窗',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
id: 'dialog_001',
title: '模态弹窗${event.data.name}',
body: [
{
type: 'tpl',
tpl: '你好,我是${name}、${name1}、${name2}'
}
],
data: {
name1: '${name}',
name2: '${event.data.name}'
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container
})
)
);
// events
fireEvent.click(getByText('打开弹窗'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
expect(getByText('模态弹窗')).toBeInTheDocument();
expect(getByText('你好我是、amis、amis')).toBeInTheDocument();
});
}, 7000);
// // test('EventAction:alert', async () => {
// // const alert = jest.fn();
// // const {getByText, container}: any = render(
// // amisRender(
// // {
// // type: 'page',
// // data: {
// // msg: '去吃饭了'
// // },
// // body: [
// // {
// // type: 'button',
// // label: '提示对话框',
// // level: 'primary',
// // onEvent: {
// // click: {
// // actions: [
// // {
// // actionType: 'alert',
// // args: {
// // msg: '<a href="http://www.baidu.com" target="_blank">${msg}~</a>'
// // }
// // }
// // ]
// // }
// // }
// // }
// // ]
// // },
// // {},
// // makeEnv({
// // getModalContainer: () => container,
// // alert
// // })
// // )
// // );
// // fireEvent.click(getByText('提示对话框'));
// // await waitFor(() => {
// // expect(alert).toHaveBeenCalled();
// // });
// // expect(alert.mock.calls[0][0]).toEqual(
// // '<a href="http://www.baidu.com" target="_blank">去吃饭了~</a>'
// // );
// // });
// // test('EventAction:confirm', async () => {
// // const confirm = jest.fn();
// // const {getByText, container}: any = render(
// // amisRender(
// // {
// // type: 'page',
// // data: {
// // title: '操作确认',
// // msg: '要删除它吗?'
// // },
// // body: [
// // {
// // type: 'button',
// // label: '确认对话框',
// // level: 'primary',
// // onEvent: {
// // click: {
// // actions: [
// // {
// // actionType: 'confirmDialog',
// // args: {
// // title: '${title}',
// // msg: '<span style="color:red">${msg}</span>'
// // }
// // }
// // ]
// // }
// // }
// // }
// // ]
// // },
// // {},
// // makeEnv({
// // getModalContainer: () => container,
// // confirm
// // })
// // )
// // );
// // fireEvent.click(getByText('确认对话框'));
// // await waitFor(() => {
// // expect(confirm).toHaveBeenCalled();
// // });
// // expect(confirm.mock.calls[0][0]).toEqual(
// // '<span style="color:red">要删除它吗?</span>'
// // );
// // expect(confirm.mock.calls[0][1]).toEqual('操作确认');
// // });

View File

@ -3,6 +3,223 @@ import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv, wait} from '../helper';
test('EventAction:drawer args', async () => {
const notify = jest.fn();
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
body: [
{
type: 'button',
label: '打开抽屉',
onEvent: {
click: {
actions: [
{
actionType: 'drawer',
args: {
drawer: {
type: 'drawer',
id: 'drawer_001',
title: '模态抽屉',
body: [
{
type: 'button',
label: '打开子抽屉',
onEvent: {
click: {
actions: [
{
actionType: 'drawer',
args: {
drawer: {
type: 'drawer',
title: '模态子抽屉',
body: [
{
type: 'button',
label: '关闭父抽屉',
onEvent: {
click: {
actions: [
{
actionType: 'closeDrawer',
componentId: 'drawer_001'
}
]
}
}
}
]
}
}
}
]
}
}
},
{
type: 'button',
label: '关闭当前抽屉',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'closeDrawer'
}
]
}
}
},
{
type: 'button',
label: '触发确认',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'confirm',
componentId: 'drawer_001'
}
]
}
}
},
{
type: 'button',
label: '触发取消',
className: 'ml-2',
onEvent: {
click: {
actions: [
{
actionType: 'cancel',
componentId: 'drawer_001'
}
]
}
}
}
],
onEvent: {
confirm: {
actions: [
{
actionType: 'toast',
args: {
msg: 'confirm'
}
}
]
},
cancel: {
actions: [
{
actionType: 'toast',
args: {
msg: 'cancel'
}
}
]
}
}
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container,
notify
})
)
);
// events
fireEvent.click(getByText('打开抽屉'));
expect(container).toMatchSnapshot();
await waitFor(() => {
expect(getByText('确认')).toBeInTheDocument();
});
fireEvent.click(getByText('确认'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(getByText('取消')).toBeInTheDocument();
});
fireEvent.click(getByText('取消'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
// actions
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(getByText('关闭当前抽屉')).toBeInTheDocument();
});
fireEvent.click(getByText('关闭当前抽屉'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(getByText('打开子抽屉')).toBeInTheDocument();
});
fireEvent.click(getByText('打开子抽屉'));
await waitFor(() => {
expect(getByText('关闭父抽屉')).toBeInTheDocument();
});
fireEvent.click(getByText('关闭父抽屉'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(getByText('触发确认')).toBeInTheDocument();
});
fireEvent.click(getByText('触发确认'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(getByText('触发取消')).toBeInTheDocument();
});
fireEvent.click(getByText('触发取消'));
await wait(300);
expect(notify).toHaveBeenCalled();
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
}, 7000);
test('EventAction:drawer', async () => {
const notify = jest.fn();
const {getByText, container}: any = render(
@ -215,3 +432,115 @@ test('EventAction:drawer', async () => {
});
expect(container).toMatchSnapshot();
}, 7000);
test('EventAction:drawer data', async () => {
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'amis'
},
body: [
{
type: 'button',
label: '打开抽屉',
onEvent: {
click: {
actions: [
{
actionType: 'drawer',
args: {
drawer: {
type: 'drawer',
id: 'drawer_001',
title: '模态弹窗${event.data.name}',
body: [
{
type: 'tpl',
tpl: '你好,我是${name}、${name1}、${name2}'
}
],
data: {
name1: '${name}',
name2: '${event.data.name}'
}
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container
})
)
);
// events
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
expect(getByText('模态弹窗')).toBeInTheDocument();
expect(getByText('你好我是、amis、amis')).toBeInTheDocument(); // 因为事件动作给args提前做了映射
});
}, 7000);
test('EventAction:drawer data2', async () => {
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
data: {
name: 'amis'
},
body: [
{
type: 'button',
label: '打开抽屉',
onEvent: {
click: {
actions: [
{
actionType: 'drawer',
drawer: {
type: 'drawer',
id: 'drawer_001',
title: '模态弹窗${event.data.name}',
body: [
{
type: 'tpl',
tpl: '你好,我是${name}、${name1}、${name2}'
}
],
data: {
name1: '${name}',
name2: '${event.data.name}'
}
}
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container
})
)
);
// events
fireEvent.click(getByText('打开抽屉'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
expect(getByText('模态弹窗')).toBeInTheDocument();
expect(getByText('你好我是、amis、amis')).toBeInTheDocument();
});
}, 7000);

View File

@ -1,7 +1,7 @@
import {fireEvent, render} from '@testing-library/react';
import {fireEvent, render, waitFor} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv} from '../helper';
import {makeEnv, wait} from '../helper';
test('EventAction:hidden', async () => {
const {getByText, container}: any = render(
@ -21,6 +21,7 @@ test('EventAction:hidden', async () => {
{
type: 'action',
label: '按钮2',
className: 'btn_2',
hiddenOn: '${btnNotHidden}',
onEvent: {
click: {
@ -36,12 +37,14 @@ test('EventAction:hidden', async () => {
{
type: 'action',
label: '按钮3',
className: 'btn_3',
hiddenOn: '${btnNotHidden}',
id: 'ui:button_test_3'
},
{
type: 'action',
label: '按钮4',
className: 'btn_4',
hiddenOn: '${btnNotHidden}',
onEvent: {
click: {
@ -57,6 +60,7 @@ test('EventAction:hidden', async () => {
{
type: 'action',
label: '按钮5',
className: 'btn_5',
hidden: true,
id: 'ui:button_test_5'
}
@ -67,8 +71,18 @@ test('EventAction:hidden', async () => {
)
);
fireEvent.click(getByText(/按钮2/));
fireEvent.click(getByText(/按钮4/));
await waitFor(() => {
expect(container.querySelector('.btn_2')).toBeInTheDocument();
expect(container.querySelector('.btn_3')).toBeInTheDocument();
expect(container.querySelector('.btn_4')).toBeInTheDocument();
expect(container.querySelector('.btn_5')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/按钮2/));
await wait(300);
expect(container.querySelector('.btn_3')).not.toBeInTheDocument();
fireEvent.click(getByText(/按钮4/));
await wait(300);
expect(container.querySelector('.btn_5')).toBeInTheDocument();
});

View File

@ -75,7 +75,7 @@ exports[`doAction:service reload 1`] = `
placeholder=""
size="10"
type="text"
value="Amis Renderer"
value="amis"
/>
</div>
</div>
@ -321,7 +321,7 @@ exports[`doAction:service reload 2`] = `
placeholder=""
size="10"
type="text"
value="Amis Renderer"
value="amis"
/>
</div>
</div>

View File

@ -427,7 +427,7 @@ test('doAction:form reload default', async () => {
)
);
await wait(200); // 等待 initApi 加载完
await wait(500); // 等待 initApi 加载完
expect(
(container.querySelector('[name="author"]') as HTMLInputElement).value
).toEqual('fex');
@ -527,7 +527,7 @@ test('doAction:form reload with data', async () => {
)
);
await wait(200);
await wait(500);
const author: HTMLInputElement = container.querySelector('[name="author"]')!;
expect(author).toBeInTheDocument();
fireEvent.change(author, {
@ -698,7 +698,7 @@ test('doAction:form clear', async () => {
)
);
await wait(200);
await wait(500);
await waitFor(() => {
expect(getByText('清空表单')).toBeInTheDocument();
});

View File

@ -96,7 +96,7 @@ test('EventAction:inputRange', async () => {
)
);
await wait(200);
await wait(500);
const inputs = container.querySelector('.cxd-InputRange-input input')!;
// input change

View File

@ -342,7 +342,7 @@ test('Renderers:Action tooltip', async () => {
// });
// 14. confirmText
test('Renderers:Action with confirmText & actionType ajax', () => {
test('Renderers:Action with confirmText & actionType ajax', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
@ -372,7 +372,7 @@ test('Renderers:Action with confirmText & actionType ajax', () => {
)
);
fireEvent.click(container.querySelector('.cxd-Button'));
wait(500);
await wait(500);
expect(baseElement).toMatchSnapshot();
expect(baseElement.querySelector('.cxd-Modal-content')!).toHaveTextContent(
@ -380,14 +380,16 @@ test('Renderers:Action with confirmText & actionType ajax', () => {
);
fireEvent.click(getByText('取消'));
wait(500);
await wait(500);
expect(fetcher).not.toBeCalled();
// fireEvent.click(container.querySelector('.cxd-Button'));
// wait(500);
// fireEvent.click(getByText('确认'));
// fetcher 不生效
// expect(fetcher).toBeCalled();
fireEvent.click(container.querySelector('.cxd-Button'));
await wait(500);
fireEvent.click(getByText('确认'));
await wait(200);
// fetcher 该被执行了
expect(fetcher).toBeCalled();
});
// 15.Action 作为容器组件

View File

@ -2,7 +2,7 @@ import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv} from '../helper';
import {makeEnv, wait} from '../helper';
import moment from 'moment';
import {act} from 'react-test-renderer';
@ -89,3 +89,44 @@ test('Renderer:date reset', async () => {
// 重制后的日期 等于初始化的日期
expect(inputElement?.value === defaultValue).toBeTruthy();
});
test('Renderer:date defaultValue', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
data: {updateTime: 1680255708}
}
})
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: {
url: '/amis/initData',
method: 'GET'
},
title: '编辑',
body: [
{
type: 'input-date',
label: '日期',
name: 'updateTime',
format: 'YYYY-MM-DD',
value: '${NOW()}'
}
]
},
{},
makeEnv({
fetcher
})
)
);
await wait(500);
const inputElement = container.querySelector('input[type="text"]') as any;
expect(inputElement?.value).toBe('2023-03-31'); // 默认值的优先级没有接口返回的高,所以应该是 2023-03-31
});

View File

@ -391,7 +391,7 @@ test('Form:options:autoFill:validation', async () => {
expect(option1.getAttribute('value')).toEqual('OptionB');
expect(option1.getAttribute('value')).toEqual('OptionB');
expect(screen.queryByText(validationMsg1)).not.toBeInTheDocument();
expect(screen.queryByText(validationMsg2)).toBeInTheDocument();
expect(screen.queryByText(validationMsg2)).not.toBeInTheDocument();
// 提交后校验信息全部消除
fireEvent.click(submitBtn);

View File

@ -4,6 +4,7 @@ import '../../../src';
import {render as amisRender} from '../../../src';
import {wait, makeEnv} from '../../helper';
import {clearStoresCache} from '../../../src';
import moment from 'moment';
afterEach(() => {
cleanup();
@ -145,3 +146,101 @@ test('Renderer:FormItem:validateApi:failed', async () => {
expect(onSubmit).not.toHaveBeenCalled();
expect(container).toMatchSnapshot();
});
test('Renderer:FormItem:extraName', async () => {
const onSubmit = jest.fn();
const {container, getByText} = render(
amisRender(
{
type: 'form',
id: 'theform',
body: [
{
type: 'input-date-range',
format: 'YYYY-MM-DD',
name: 'begin',
extraName: 'end',
label: 'Label'
}
],
title: 'The form',
actions: [
{
type: 'button',
label: 'ChangeValue',
onEvent: {
click: {
actions: [
{
actionType: 'setValue',
componentId: 'theform',
args: {
value: {
end: `${moment().format('YYYY-MM')}-16`
}
}
}
]
}
}
},
{
type: 'submit',
label: 'Submit'
}
]
},
{
onSubmit
},
makeEnv({})
)
);
// 打开弹框
fireEvent.click(
container.querySelector('.cxd-DateRangePicker-input') as HTMLElement
);
await wait(200);
// 点击选择
fireEvent.click(
container.querySelector(
'.cxd-DateRangePicker-popover tr td[data-value="15"]'
) as HTMLElement
);
// 点击选择
fireEvent.click(
container.querySelector(
'.cxd-DateRangePicker-popover tr td[data-value="15"]'
) as HTMLElement
);
fireEvent.click(getByText('确认'));
fireEvent.click(getByText('Submit'));
await wait(300);
expect(onSubmit).toBeCalledTimes(1);
expect(onSubmit.mock.calls[0][0]).toMatchObject({
begin: `${moment().format('YYYY-MM')}-15`,
end: `${moment().format('YYYY-MM')}-15`
});
fireEvent.click(getByText('ChangeValue'));
await wait(200);
expect(
(container.querySelector('input[placeholder="结束时间"]') as any).value
).toBe(`${moment().format('YYYY-MM')}-16`);
fireEvent.click(getByText('Submit'));
await wait(300);
expect(onSubmit).toBeCalledTimes(2);
expect(onSubmit.mock.calls[1][0]).toMatchObject({
begin: `${moment().format('YYYY-MM')}-15`,
end: `${moment().format('YYYY-MM')}-16`
});
});

Some files were not shown because too many files have changed in this diff Show More