mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:58:05 +08:00
Merge branch 'baidu:master' into fix-transfer
This commit is contained in:
commit
a33f9e7779
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -7,6 +7,7 @@
|
||||
"files.exclude": {
|
||||
"**/lib": true,
|
||||
"**/esm": true,
|
||||
"**/sdk": true,
|
||||
"**/tsconfig.tsbuildinfo": true,
|
||||
"**/schema.json": true,
|
||||
"**/lerna-debug.log": true,
|
||||
@ -15,6 +16,15 @@
|
||||
"public/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/lib": true,
|
||||
"**/esm": true,
|
||||
"**/sdk": true,
|
||||
"**/tsconfig.tsbuildinfo": true,
|
||||
"**/schema.json": true,
|
||||
"**/lerna-debug.log": true,
|
||||
"**/package-lock.json": true,
|
||||
"**/.rollup.cache": true,
|
||||
"public/**": true,
|
||||
"examples/docs.json": true,
|
||||
"examples/components/EChartsEditor/option-parts/**/*": true
|
||||
}
|
||||
|
@ -882,6 +882,7 @@ combo 还有一个作用是增加层级,比如返回的数据是一个深层
|
||||
| joinValues | `boolean` | `true` | 默认为 `true` 当扁平化开启的时候,是否用分隔符的形式发送给后端,否则采用 array 的方式。 |
|
||||
| delimiter | `string` | `false` | 当扁平化开启并且 joinValues 为 true 时,用什么分隔符。 |
|
||||
| addable | `boolean` | `false` | 是否可新增 |
|
||||
| addattop | `boolean` | `false` | 在顶部添加 |
|
||||
| removable | `boolean` | `false` | 是否可删除 |
|
||||
| deleteApi | [API](../../../docs/types/api) | | 如果配置了,则删除前会发送一个 api,请求成功才完成删除 |
|
||||
| deleteConfirmText | `string` | `"确认要删除?"` | 当配置 `deleteApi` 才生效!删除时用来做用户确认 |
|
||||
|
@ -218,7 +218,33 @@ order: 1
|
||||
"label": "禁用",
|
||||
"name": "text2",
|
||||
"disabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "grid",
|
||||
"columns": [
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"label": "姓名",
|
||||
"name": "name",
|
||||
"value": "amis",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"type": "input-email",
|
||||
"label": "邮箱",
|
||||
"name": "email",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
@ -250,6 +250,8 @@ order: 56
|
||||
|
||||
## 前缀和后缀
|
||||
|
||||
`prefix` 和 `suffix` 属性支持数据映射。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
@ -281,10 +283,10 @@ order: 56
|
||||
}
|
||||
```
|
||||
|
||||
支持数据映射
|
||||
|
||||
## 显示计数器
|
||||
|
||||
配置`"showCounter": true`后输入框将显示计数器,一般会配合`maxLength`属性以限制输入长度,如果不设置`maxLength`,则仅展示计数器,并不会限制用户的输入长度。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
@ -294,14 +296,33 @@ order: 56
|
||||
"type": "input-text",
|
||||
"label": "A",
|
||||
"showCounter": true,
|
||||
"placeholder": "请输入"
|
||||
"placeholder": "请输入",
|
||||
"showCounter": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "aa",
|
||||
"value": "aa"
|
||||
},
|
||||
{
|
||||
"label": "bb",
|
||||
"value": "bb"
|
||||
},
|
||||
{
|
||||
"label": "cc",
|
||||
"value": "cc"
|
||||
},
|
||||
{
|
||||
"label": "dd",
|
||||
"value": "dd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": "input-text",
|
||||
"label": "B",
|
||||
"showCounter": true,
|
||||
"maxLength": 100,
|
||||
"maxLength": 20,
|
||||
"placeholder": "请输入"
|
||||
}
|
||||
]
|
||||
@ -381,12 +402,12 @@ order: 56
|
||||
|
||||
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`event.data.xxx`事件参数变量来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | --------------------------------- | --------------------------------------------- |
|
||||
| click | `event.data.value: string` 输入值 | 点击输入框时触发,只针对选择器模式的输入框有效 |
|
||||
| enter | `event.data.value: string` 输入值 | 回车时触发,只针对选择器模式的输入框有效 |
|
||||
| focus | `event.data.value: string` 输入值 | 输入框获取焦点时触发 |
|
||||
| blur | `event.data.value: string` 输入值 | 输入框失去焦点时触发 |
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | --------------------------------- | ---------------------------------------------- |
|
||||
| click | `event.data.value: string` 输入值 | 点击输入框时触发,只针对选择器模式的输入框有效 |
|
||||
| enter | `event.data.value: string` 输入值 | 回车时触发,只针对选择器模式的输入框有效 |
|
||||
| focus | `event.data.value: string` 输入值 | 输入框获取焦点时触发 |
|
||||
| blur | `event.data.value: string` 输入值 | 输入框失去焦点时触发 |
|
||||
| change | `event.data.value: string` 输入值 | 值变化时触发 |
|
||||
|
||||
## 动作表
|
||||
|
@ -518,3 +518,4 @@ order: 35
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | ------------------------------------------------------------------ | ---------------- |
|
||||
| change | `event.data.value: string`<br/> `event.data.option: Option` 选中值 | 选中值变化时触发 |
|
||||
| itemclick | `event.data.label: string`<br/> `event.data.id: string` | 点击时触发 |
|
@ -37,7 +37,8 @@ order: 57
|
||||
"name": "textarea",
|
||||
"type": "textarea",
|
||||
"label": "多行文本",
|
||||
"clearable": true
|
||||
"clearable": true,
|
||||
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmodtion tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -63,6 +64,8 @@ order: 57
|
||||
|
||||
## 显示计数器
|
||||
|
||||
配置`"showCounter": true`后输入框将显示计数器,一般会配合`maxLength`属性以限制输入长度,如果不设置`maxLength`,则仅展示计数器,并不会限制用户的输入长度。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
@ -80,7 +83,7 @@ order: 57
|
||||
"type": "textarea",
|
||||
"label": "B",
|
||||
"showCounter": true,
|
||||
"maxLength": 100,
|
||||
"maxLength": 30,
|
||||
"placeholder": "请输入"
|
||||
}
|
||||
]
|
||||
|
@ -322,10 +322,28 @@ List 的内容、Card 卡片的内容配置同上
|
||||
}
|
||||
```
|
||||
|
||||
## 工具栏
|
||||
|
||||
> 2.2.0 及以上版本
|
||||
|
||||
配置`"showToolbar": true`使图片在放大模式下开启图片工具栏。配置`"toolbarActions"`属性可以自定义工具栏的展示方式,具体配置参考[ImageAction](./image#imageaction)
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "image",
|
||||
"src": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",
|
||||
"enlargeAble": true,
|
||||
"showToolbar": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| -------------- | ------------------------------------ | --------- | -------------------------------------------------------------------------------------- |
|
||||
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| -------------- | ------------------------------------ | --------- | -------------------------------------------------------------------------------------- | ------- |
|
||||
| type | `string` | | 如果在 Table、Card 和 List 中,为`"image"`;在 Form 中用作静态展示,为`"static-image"` |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| innerClassName | `string` | | 组件内层 CSS 类名 |
|
||||
@ -346,3 +364,22 @@ List 的内容、Card 卡片的内容配置同上
|
||||
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
|
||||
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
|
||||
| imageMode | `string` | `thumb` | 图片展示模式,可选:`'thumb'`, `'original'` 即:缩略图模式 或者 原图模式 |
|
||||
| showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` |
|
||||
| toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` |
|
||||
|
||||
#### ImageAction
|
||||
|
||||
```typescript
|
||||
interface ImageAction {
|
||||
/* 操作key */
|
||||
key: 'rotateRight' | 'rotateLeft' | 'zoomIn' | 'zoomOut' | 'scaleOrigin';
|
||||
/* 动作名称 */
|
||||
label?: string;
|
||||
/* 动作icon */
|
||||
icon?: string;
|
||||
/* 动作自定义CSS类 */
|
||||
iconClassName?: string;
|
||||
/* 动作是否禁用 */
|
||||
disabled?: boolean;
|
||||
}
|
||||
```
|
||||
|
@ -66,12 +66,12 @@ order: 53
|
||||
|
||||
```ts
|
||||
Array<{
|
||||
image: string; // 小图,预览图
|
||||
src?: string; // 原图
|
||||
title?: string; // 标题
|
||||
description?: string; // 描述
|
||||
[propName:string]: any; // 还可以有其他数据
|
||||
}>
|
||||
image: string; // 小图,预览图
|
||||
src?: string; // 原图
|
||||
title?: string; // 标题
|
||||
description?: string; // 描述
|
||||
[propName: string]: any; // 还可以有其他数据
|
||||
}>;
|
||||
```
|
||||
|
||||
### 配置预览图地址
|
||||
@ -454,18 +454,67 @@ List 的内容、Card 卡片的内容配置同上
|
||||
}
|
||||
```
|
||||
|
||||
## 工具栏
|
||||
|
||||
> 2.2.0 及以上版本
|
||||
|
||||
配置`"showToolbar": true`使图片在放大模式下开启图片工具栏。配置`"toolbarActions"`属性可以自定义工具栏的展示方式,具体配置参考[ImageAction](./image#imageaction)
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"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",
|
||||
"a": "aaa1",
|
||||
"b": "bbb1"
|
||||
},
|
||||
{
|
||||
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80",
|
||||
"a": "aaa2",
|
||||
"b": "bbb2"
|
||||
},
|
||||
{
|
||||
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80",
|
||||
"a": "aaa3",
|
||||
"b": "bbb3"
|
||||
},
|
||||
{
|
||||
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80",
|
||||
"a": "aaa4",
|
||||
"b": "bbb4"
|
||||
},
|
||||
{
|
||||
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80",
|
||||
"a": "aaa5",
|
||||
"b": "bbb5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"body": {
|
||||
"type": "images",
|
||||
"source": "${images}",
|
||||
"enlargeAble": true,
|
||||
"showToolbar": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------ | ------------------------------------------ | --------- | ---------------------------------------------------------------------------------------- |
|
||||
| type | `string` | `images` | 如果在 Table、Card 和 List 中,为`"images"`;在 Form 中用作静态展示,为`"static-images"` |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| defaultImage | `string` | | 默认展示图片 |
|
||||
| value | `string`或`Array<string>`或`Array<object>` | | 图片数组 |
|
||||
| source | `string` | | 数据源 |
|
||||
| delimiter | `string` | `,` | 分隔符,当 value 为字符串时,用该值进行分隔拆分 |
|
||||
| src | `string` | | 预览图地址,支持数据映射获取对象中图片变量 |
|
||||
| originalSrc | `string` | | 原图地址,支持数据映射获取对象中图片变量 |
|
||||
| enlargeAble | `boolean` | | 支持放大预览 |
|
||||
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
|
||||
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
|
||||
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| -------------- | ------------------------------------------ | --------- | ---------------------------------------------------------------------------------------- | ------- |
|
||||
| type | `string` | `images` | 如果在 Table、Card 和 List 中,为`"images"`;在 Form 中用作静态展示,为`"static-images"` |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| defaultImage | `string` | | 默认展示图片 |
|
||||
| value | `string`或`Array<string>`或`Array<object>` | | 图片数组 |
|
||||
| source | `string` | | 数据源 |
|
||||
| delimiter | `string` | `,` | 分隔符,当 value 为字符串时,用该值进行分隔拆分 |
|
||||
| src | `string` | | 预览图地址,支持数据映射获取对象中图片变量 |
|
||||
| originalSrc | `string` | | 原图地址,支持数据映射获取对象中图片变量 |
|
||||
| enlargeAble | `boolean` | | 支持放大预览 |
|
||||
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
|
||||
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
|
||||
| showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` |
|
||||
| toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` |
|
||||
|
@ -160,15 +160,14 @@ public class StreamingResponseBodyController {
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------ | --------- | ------ | ----------------------------------------------- |
|
||||
| height | `number` | 500 | 展示区域高度 |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| autoScroll | `boolean` | true | 是否自动滚动 |
|
||||
| placeholder | `string` | | 加载中的文字 |
|
||||
| encoding | `string` | utf-8 | 返回内容的字符编码 |
|
||||
| source | `string` | | 接口 |
|
||||
| rowHeight | `number` | | 设置每行高度,将会开启虚拟渲染 |
|
||||
| maxLength | `number` | | 最大显示行数 |
|
||||
| disableColor | `boolean` | | 关闭 ANSI 颜色支持 |
|
||||
| operation | `Array` | | 可选日志操作:['stop','clear','showLineNumber','filter'] |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | --------- | ------ | -------------------------------------------------------- |
|
||||
| height | `number` | 500 | 展示区域高度 |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| autoScroll | `boolean` | true | 是否自动滚动 |
|
||||
| placeholder | `string` | | 加载中的文字 |
|
||||
| encoding | `string` | utf-8 | 返回内容的字符编码 |
|
||||
| source | `string` | | 接口 |
|
||||
| rowHeight | `number` | | 设置每行高度,将会开启虚拟渲染 |
|
||||
| maxLength | `number` | | 最大显示行数 |
|
||||
| operation | `Array` | | 可选日志操作:['stop','clear','showLineNumber','filter'] |
|
||||
|
@ -125,7 +125,6 @@ order: 57
|
||||
"name": "id",
|
||||
"label": "Id"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "type",
|
||||
"label": "映射",
|
||||
|
@ -638,25 +638,64 @@ ws.on('connection', function connection(ws) {
|
||||
}
|
||||
```
|
||||
|
||||
### 函数触发事件
|
||||
|
||||
> 2.3.0 及以上版本
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "service",
|
||||
"api": "/api/mock2/page/initData",
|
||||
"dataProvider": {
|
||||
"inited": "setData({ addedNumber: data.number + 1 })",
|
||||
"onApiFetched": "setData({ year: new Date(data.date).getFullYear(), })"
|
||||
},
|
||||
"data": {
|
||||
"number": 8887
|
||||
},
|
||||
"body": {
|
||||
"type": "panel",
|
||||
"title": "$title",
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"wrapperComponent": "p",
|
||||
"tpl": "静态数字为:<strong>${addedNumber}</strong>"
|
||||
},
|
||||
{
|
||||
"type": "tpl",
|
||||
"wrapperComponent": "p",
|
||||
"tpl": "接口返回值的日期为:<strong>${date}</strong>"
|
||||
},
|
||||
{
|
||||
"type": "tpl",
|
||||
"wrapperComponent": "p",
|
||||
"tpl": "接口返回值的年份为:<strong>${year}</strong>"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------------- | ----------------------------------------- | -------------- | ----------------------------------------------------------------------------- |
|
||||
| type | `string` | `"service"` | 指定为 service 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| body | [SchemaNode](../../docs/types/schemanode) | | 内容容器 |
|
||||
| api | [api](../../docs/types/api) | | 初始化数据域接口地址 |
|
||||
| ws | `string` | | WebScocket 地址 |
|
||||
| dataProvider | `string` | | 数据获取函数 |
|
||||
| initFetch | `boolean` | | 是否默认拉取 |
|
||||
| schemaApi | [api](../../docs/types/api) | | 用来获取远程 Schema 接口地址 |
|
||||
| initFetchSchema | `boolean` | | 是否默认拉取 Schema |
|
||||
| messages | `Object` | | 消息提示覆写,默认消息读取的是接口返回的 toast 提示文字,但是在此可以覆写它。 |
|
||||
| messages.fetchSuccess | `string` | | 接口请求成功时的 toast 提示文字 |
|
||||
| messages.fetchFailed | `string` | `"初始化失败"` | 接口请求失败时 toast 提示文字 |
|
||||
| interval | `number` | | 轮询时间间隔,单位 ms(最低 1000) |
|
||||
| silentPolling | `boolean` | `false` | 配置轮询时是否显示加载动画 |
|
||||
| stopAutoRefreshWhen | [表达式](../../docs/concepts/expression) | | 配置停止轮询的条件 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| type | `string` | `"service"` | 指定为 service 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| body | [SchemaNode](../../docs/types/schemanode) | | 内容容器 |
|
||||
| api | [API](../../docs/types/api) | | 初始化数据域接口地址 |
|
||||
| ws | `string` | | WebScocket 地址 |
|
||||
| dataProvider | `string \| Record<"inited" \| "onApiFetched" \| "onSchemaApiFetched" \| "onWsFetched", string>` | | 数据获取函数 | <ul><li>`1.4.0`</li><li>`1.8.0`支持`env`参数</li><li>`2.3.0` 支持基于事件触发</li></ul> |
|
||||
| initFetch | `boolean` | | 是否默认拉取 |
|
||||
| schemaApi | [API](../../docs/types/api) | | 用来获取远程 Schema 接口地址 |
|
||||
| initFetchSchema | `boolean` | | 是否默认拉取 Schema |
|
||||
| messages | `Object` | | 消息提示覆写,默认消息读取的是接口返回的 toast 提示文字,但是在此可以覆写它。 |
|
||||
| messages.fetchSuccess | `string` | | 接口请求成功时的 toast 提示文字 |
|
||||
| messages.fetchFailed | `string` | `"初始化失败"` | 接口请求失败时 toast 提示文字 |
|
||||
| interval | `number` | | 轮询时间间隔,单位 ms(最低 1000) |
|
||||
| silentPolling | `boolean` | `false` | 配置轮询时是否显示加载动画 |
|
||||
| stopAutoRefreshWhen | [表达式](../../docs/concepts/expression) | | 配置停止轮询的条件 |
|
||||
|
||||
## 事件表
|
||||
|
||||
|
@ -10,6 +10,8 @@ order: 65
|
||||
|
||||
## 基本用法
|
||||
|
||||
它最适合的用法是放在列表类组件(CRUD,Table,List 等)的列中,用来表示状态。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "status",
|
||||
@ -17,53 +19,89 @@ order: 65
|
||||
}
|
||||
```
|
||||
|
||||
它最适合的用法是放在 crud 的列中,用来表示状态
|
||||
|
||||
## 默认状态列表
|
||||
|
||||
```schema
|
||||
下表是默认支持的几种状态:
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "page",
|
||||
"body": [
|
||||
{
|
||||
"type": "status",
|
||||
"value": 0
|
||||
"type": "table",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"label": "-",
|
||||
"value": "0",
|
||||
"icon": "fail",
|
||||
"status": 0
|
||||
},
|
||||
{
|
||||
"label": "-",
|
||||
"value": "1",
|
||||
"icon": "success",
|
||||
"status": 1
|
||||
},
|
||||
{
|
||||
"label": "成功",
|
||||
"value": "success",
|
||||
"icon": "success",
|
||||
"status": "success"
|
||||
},
|
||||
{
|
||||
"label": "运行中",
|
||||
"value": "pending",
|
||||
"icon": "rolling",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"label": "排队中",
|
||||
"value": "queue",
|
||||
"icon": "warning",
|
||||
"status": "queue"
|
||||
},
|
||||
{
|
||||
"label": "调度中",
|
||||
"value": "schedule",
|
||||
"icon": "schedule",
|
||||
"status": "schedule"
|
||||
},
|
||||
{
|
||||
"label": "失败",
|
||||
"value": "fail",
|
||||
"icon": "fail",
|
||||
"status": "fail"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "success"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "pending"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "fail"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "fail"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "queue"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"value": "schedule"
|
||||
}
|
||||
]
|
||||
"columns": [
|
||||
{
|
||||
"name": "value",
|
||||
"label": "默认value值"
|
||||
},
|
||||
{
|
||||
"name": "label",
|
||||
"label": "默认label"
|
||||
},
|
||||
{
|
||||
"name": "icon",
|
||||
"label": "默认icon值"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"label": "状态",
|
||||
"type": "mapping",
|
||||
"map": {
|
||||
"*": {
|
||||
"type": "status"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义状态图标和文本
|
||||
|
||||
通过 `map` 和 `mapLabel`
|
||||
如果默认提供的状态无法满足业务需求,可以使用`map` 和 `labelMap`属性分别配置状态组件的**图标**和**展示文案**。用户自定义的`map` 和 `labelMap`会和默认属性进行 merge,如果只需要修改某一项配置时,无需全量覆盖。
|
||||
|
||||
```schema
|
||||
{
|
||||
@ -79,7 +117,8 @@ order: 65
|
||||
"0": "正常",
|
||||
"1": "异常"
|
||||
},
|
||||
"value": 0
|
||||
"value": 0,
|
||||
"className": "mr-3"
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
@ -97,12 +136,97 @@ order: 65
|
||||
}
|
||||
```
|
||||
|
||||
## 动态数据
|
||||
|
||||
`map` 和 `labelMap`支持配置变量,通过数据映射获取上下文中的变量。
|
||||
|
||||
> 2.3.0 及以上版本
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"statusLabel": {
|
||||
"success": "任务成功",
|
||||
"warning": "已停机"
|
||||
},
|
||||
"statusIcon": {
|
||||
"waiting": "far fa-clock",
|
||||
"warning": "fas fa-exclamation"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "table",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Task1",
|
||||
"status": "success"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Task2",
|
||||
"status": "processing",
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "Task3",
|
||||
"status": "waiting",
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"name": "Task4",
|
||||
"status": "warning",
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"name": "Task5",
|
||||
"status": "fail",
|
||||
}
|
||||
]
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"label": "状态",
|
||||
"type": "mapping",
|
||||
"map": {
|
||||
"*": {
|
||||
"type": "status",
|
||||
"map": {
|
||||
"processing": "rolling",
|
||||
"waiting": "${statusIcon.waiting}",
|
||||
"warning": "${statusIcon.warning}"
|
||||
},
|
||||
"labelMap": {
|
||||
"success": "${statusLabel.success}",
|
||||
"processing": "处理中",
|
||||
"waiting": "等待中",
|
||||
"warning": "${statusLabel.warning}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | -------- | ------ | ------------------------------- |
|
||||
| type | `string` | | `"status"` 指定为 Status 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| className | `string` | | 外层 Dom 的 CSS 类名 |
|
||||
| placeholder | `string` | `-` | 占位文本 |
|
||||
| map | `object` | | 映射图标 |
|
||||
| labelMap | `object` | | 映射文本 |
|
||||
|
@ -650,44 +650,50 @@ order: 67
|
||||
{
|
||||
"type": "service",
|
||||
"api": "/api/mock2/sample?perPage=5",
|
||||
"className": "w-xxl",
|
||||
"className": "flex justify-center",
|
||||
"body": [
|
||||
{
|
||||
"type": "table",
|
||||
"source": "$rows",
|
||||
"className": "m-b-none",
|
||||
"columnsTogglable": false,
|
||||
"columns": [
|
||||
"type": "wrapper",
|
||||
"className": "w-xxl border-2 border-solid border-indigo-400",
|
||||
"body": [
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Engine",
|
||||
"fixed": "left"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "Grade"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "version",
|
||||
"label": "Version"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform",
|
||||
"fixed": "right"
|
||||
"type": "table",
|
||||
"source": "$rows",
|
||||
"className": "m-b-none",
|
||||
"columnsTogglable": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID",
|
||||
"fixed": "left"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Engine",
|
||||
"groupName": 'Group-1',
|
||||
"fixed": "left"
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "Grade",
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"label": "Version"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser",
|
||||
"groupName": 'Group-2',
|
||||
"fixed": "right"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform",
|
||||
"groupName": 'Group-2',
|
||||
"fixed": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1415,7 +1421,7 @@ popOver 的其它配置请参考 [popover](./popover)
|
||||
{
|
||||
"type": "table",
|
||||
"source": "$rows",
|
||||
"rowClassNameExpr": "<%= data.id % 2 ? 'bg-success' : '' %>",
|
||||
"rowClassNameExpr": "<%= data.id % 2 ? 'bg-success' : 'bg-blue-50' %>",
|
||||
"columns": [
|
||||
{
|
||||
"name": "engine",
|
||||
@ -1871,11 +1877,10 @@ popOver 的其它配置请参考 [popover](./popover)
|
||||
| prefixRow | `Array` | | 顶部总结行 |
|
||||
| affixRow | `Array` | | 底部总结行 |
|
||||
| itemBadge | [`BadgeSchema`](./badge) | | 行角标配置 |
|
||||
| autoFillHeight | `boolean` 丨 `{height: number}` | | 内容区域自适应高度 |
|
||||
| autoFillHeight | `boolean` 丨 `{height: number}` | | 内容区域自适应高度 |
|
||||
| resizable | `boolean` | `true` | 列宽度是否支持调整 |
|
||||
| selectable | `boolean` | `false` | 支持勾选 |
|
||||
| multiple | `boolean` | `false` | 勾选icon是否为多选样式`checkbox`, 默认为`radio` |
|
||||
|
||||
| multiple | `boolean` | `false` | 勾选 icon 是否为多选样式`checkbox`, 默认为`radio` |
|
||||
|
||||
## 列配置属性表
|
||||
|
||||
|
@ -18,10 +18,12 @@ order: 12
|
||||
"data": {
|
||||
"name": "rick"
|
||||
},
|
||||
"body": {
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}" // 输出: my name is rick
|
||||
}
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}" // 输出: my name is rick
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@ -812,7 +814,7 @@ ${xxx | url_encode}
|
||||
|
||||
### url_decode
|
||||
|
||||
效果同 [decodeURIComponent() - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent)
|
||||
效果同 [decodeURIComponent() - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent),注意从`2.3.0`版本开始,不合法的输入会被转化为`undefined`。
|
||||
|
||||
##### 基本用法
|
||||
|
||||
|
@ -8,42 +8,124 @@ icon:
|
||||
order: 13
|
||||
---
|
||||
|
||||
一般来说,属性名类似于`xxxOn` 或者 `className` 的配置项,都可以使用表达式进行配置,表达式具有如下的语法:
|
||||
## 使用场景
|
||||
|
||||
amis 中有很多场景会用到表达式。
|
||||
|
||||
- 模板中变量取值。 如:`my name is ${xxx}`
|
||||
- api 地址参数取值 如 `http://mydomain.com/api/xxx?id=${id}`
|
||||
- api 发送&接收数据映射
|
||||
|
||||
```
|
||||
{
|
||||
"type": "crud",
|
||||
"api": {
|
||||
method: "post"
|
||||
url: "http://mydomain.com/api/xxx",
|
||||
data: {
|
||||
skip: "${(page - 1) * perPage}",
|
||||
take: "${perPage}"
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- 组件显示与隐藏条件
|
||||
|
||||
```
|
||||
{
|
||||
"name": "xxxText",
|
||||
"type": "input-text",
|
||||
"visibleOn": "${ xxxFeature.on }"
|
||||
}
|
||||
```
|
||||
|
||||
- 表单默认值
|
||||
|
||||
```
|
||||
{
|
||||
"name": "xxxText",
|
||||
"type": "input-text",
|
||||
"value": "${ TODAY() }"
|
||||
}
|
||||
```
|
||||
|
||||
- 等等
|
||||
|
||||
amis 中表达式有两种语法:
|
||||
|
||||
- 一种是纯 js 表达式,如 `data.xxx === 1`。
|
||||
- 另一种是用 `${` 和 `}` 包裹的表达式。如:`${ xxx === 1}`。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "当前作用域中变量 show 是 1 的时候才可以看得到我哦~",
|
||||
"visibleOn": "this.show === 1"
|
||||
"visibleOn": "${show === 1}"
|
||||
}
|
||||
```
|
||||
|
||||
其中:`this.show === 1` 就是表达式。
|
||||
第一种是早期的版本,偷懒直接用的 [Javascript](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript),灵活性虽高,但是安全性欠佳。建议使用新版本规则,新规则跟 tpl 模板取值规则完全一样,不用来回切换语法。
|
||||
|
||||
## 表达式语法
|
||||
|
||||
> 表达式语法实际上是 JavaScript 代码,更多 JavaScript 知识查看 [这里](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript)。
|
||||
表达式主要由三部分组成:开始字符、表达式内容和结束字符。其中开始字符固定是 `${` 结束字符固定是 `}`,中间内容才是表达式正文。这里说的语法主要还是表达式正文的语法。
|
||||
|
||||
在 amis 的实现过程中,当正则匹配到某个组件存在`xxxOn`语法的属性名时,会尝试进行下面步骤(以上面配置为例):
|
||||
规则主要包含:
|
||||
|
||||
1. 提取`visibleOn`配置项配置的 JavaScript 语句`this.show === 1`,并以当前组件的数据域为这段代码的数据作用域,执行这段 js 代码;
|
||||
2. 之后将执行结果赋值给`visible`并添加到组件属性中
|
||||
3. 执行渲染。当前示例中:`visible`代表着是否显示当前组件;
|
||||
- 变量名: `xxx变量`、`xxx变量.xxx属性`、`xxx变量[xxx属性]`
|
||||
- 布尔值: `true` 或者 `false`
|
||||
- null:`null`
|
||||
- undefined: `undefined`
|
||||
- 数字: `123` 或者 `123.23`
|
||||
- 字符串: `"string"` 或者 `'string'`
|
||||
- 字符模板
|
||||
|
||||
组件不同的配置项会有不同的效果,请大家在组件文档中多留意。
|
||||
```
|
||||
`my name is ${name}`
|
||||
```
|
||||
|
||||
> 表达式的执行结果预期应该是`boolean`类型值,如果不是,amis 会根据 JavaScript 的规则将结果视作`boolean`类型进行判断
|
||||
- 数组: `[1, 2, 3]`
|
||||
- 对象: `{a: 1, b: 2}`
|
||||
- 组合使用如: `{a: 1: b: [1, 2, 3], [key]: yyy变量}`
|
||||
- 三元表达式: `xx变量 == 1 ? 2 : 3`
|
||||
- 二元表达式: `xx变量 && yy 变量` 、 `xx变量 || yy 变量`、 `xx变量 == 123`
|
||||
|
||||
## 新表达式语法
|
||||
- `+` 相加
|
||||
- `-` 相减
|
||||
- `*` 乘
|
||||
- `/` 除
|
||||
- `**` pow 运算
|
||||
- `||` 或者
|
||||
- `&&` 并且
|
||||
- `|` 或运算
|
||||
- `^` 异或运算
|
||||
- `&` 与运算
|
||||
- `==` 等于比较
|
||||
- `!=` 不等于
|
||||
- `===` 恒等于
|
||||
- `!==` 不恒等于
|
||||
- `<` 小于
|
||||
- `<=` 小于或等于
|
||||
- `>` 大于
|
||||
- `>=` 大于或等于
|
||||
- `<<` 左移
|
||||
- `>>` 右移
|
||||
- `>>>` 带符号位的右移运算符
|
||||
|
||||
> 1.5.0 及以上版本
|
||||
- 一元表达式: `!xx变量`、`~xx变量`
|
||||
|
||||
原来的表达式用的就是原生 js,灵活性虽大,但是安全性不佳,为了与后端公式保持统一,故引入了新的规则,如:`${这里是表达式}`,也就是说如果开始字符是 `${` 且 `}` 结尾则认为是新版本的表达式。这个规则与模板中的语法保持一致。
|
||||
* `+` 一元加法
|
||||
* `-` 一元减法
|
||||
* `~` 否运算符,加 1 取反
|
||||
* `!` 取反
|
||||
|
||||
- `${a == 1}` 变量 a 是否和 1 相等
|
||||
- `${a % 2}` 变量 a 是否为偶数。
|
||||
- 函数调用:`SUM(1, 2, 3)`
|
||||
- 箭头函数:`() => abc` 注意这个箭头函数只支持单表达式,不支持多条语句。主要配置其他函数使用如:`ARRAY_MAP(arr, item => item.abc)`
|
||||
- 括号包裹,修改运算优先级:`(10 - 2) * 3`
|
||||
|
||||
表达式中的语法与默认模板中的语法保持一致,所以以下示例直接用模板来方便呈现结果。
|
||||
示例:
|
||||
|
||||
```schema
|
||||
{
|
||||
|
@ -40,13 +40,15 @@ export default {
|
||||
name: 'id',
|
||||
label: 'ID',
|
||||
remark: 'ID',
|
||||
groupName: 'A'
|
||||
groupName: 'A',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade',
|
||||
remark: 'CSS grade',
|
||||
groupName: 'A'
|
||||
groupName: 'A',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
@ -69,7 +71,8 @@ export default {
|
||||
name: 'version',
|
||||
label: 'Engine version',
|
||||
remark: 'Engine version',
|
||||
groupName: 'B'
|
||||
groupName: 'B',
|
||||
fixed: 'right'
|
||||
}
|
||||
],
|
||||
data: {
|
||||
|
@ -131,7 +131,13 @@ export default {
|
||||
align: 'right'
|
||||
}
|
||||
],
|
||||
footerToolbar: ['statistics', 'switch-per-page', 'pagination'],
|
||||
footerToolbar: [
|
||||
'statistics',
|
||||
{
|
||||
type: 'pagination',
|
||||
layout: 'perPage,pager,go'
|
||||
}
|
||||
],
|
||||
// rowClassNameExpr: '<%= data.id == 1 ? "bg-success" : "" %>',
|
||||
columns: [
|
||||
{
|
||||
|
@ -118,6 +118,7 @@ import DynamicTabSchema from './Tabs/Dynamic';
|
||||
import Tab1Schema from './Tabs/Tab1';
|
||||
import Tab2Schema from './Tabs/Tab2';
|
||||
import Tab3Schema from './Tabs/Tab3';
|
||||
import Loading from './Loading';
|
||||
|
||||
import {Switch} from 'react-router-dom';
|
||||
import {navigations2route} from './App';
|
||||
@ -822,6 +823,13 @@ export const examples = [
|
||||
component: SdkTest
|
||||
},
|
||||
|
||||
{
|
||||
label: '多 loading',
|
||||
icon: 'fa fa-spinner',
|
||||
path: '/examples/loading',
|
||||
component: makeSchemaRenderer(Loading)
|
||||
},
|
||||
|
||||
{
|
||||
label: 'APP 多页应用',
|
||||
icon: 'fa fa-cubes',
|
||||
|
@ -39,7 +39,19 @@ export default {
|
||||
label: '选项4',
|
||||
value: 4
|
||||
}
|
||||
]
|
||||
],
|
||||
"onEvent": {
|
||||
"itemclick": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "alert",
|
||||
"args": {
|
||||
"msg": "${event.data.label}~${event.data.id}</a>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
@ -285,6 +297,18 @@ export default {
|
||||
toggled: true
|
||||
}
|
||||
]
|
||||
},
|
||||
"onEvent": {
|
||||
"itemclick": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "alert",
|
||||
"args": {
|
||||
"msg": "${ecent.data.label}~${event.data.id}</a>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
181
examples/components/Loading.jsx
Normal file
181
examples/components/Loading.jsx
Normal file
@ -0,0 +1,181 @@
|
||||
const loadingBody = {
|
||||
type: 'service',
|
||||
api: '/api/mock2/sample?orderBy=id&orderDir=desc&perPage=10&waitSeconds=10',
|
||||
body: {
|
||||
type: 'page',
|
||||
initApi:
|
||||
'/api/mock2/sample?orderBy=id&orderDir=desc&perPage=10&waitSeconds=10',
|
||||
body: [
|
||||
{
|
||||
loading: false,
|
||||
type: 'nav',
|
||||
stacked: true,
|
||||
className: 'w-md',
|
||||
draggable: true,
|
||||
saveOrderApi: '/api/options/nav',
|
||||
source:
|
||||
'/api/mock2/sample?orderBy=id&orderDir=desc&perPage=10&waitSeconds=30',
|
||||
itemActions: [
|
||||
{
|
||||
type: 'icon',
|
||||
icon: 'cloud',
|
||||
visibleOn: "this.to === '?cat=1'"
|
||||
},
|
||||
{
|
||||
type: 'dropdown-button',
|
||||
level: 'link',
|
||||
icon: 'fa fa-ellipsis-h',
|
||||
hideCaret: true,
|
||||
buttons: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '编辑'
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '删除'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: 'OpenDialog',
|
||||
actionType: 'drawer',
|
||||
reload: 'thepage',
|
||||
drawer: {
|
||||
body: {
|
||||
type: 'form',
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'a',
|
||||
value: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
syncLocation: false,
|
||||
api: '/api/mock2/sample?waitSeconds=30',
|
||||
headerToolbar: ['bulkActions'],
|
||||
bulkActions: [
|
||||
{
|
||||
label: '批量删除',
|
||||
actionType: 'ajax',
|
||||
api: 'delete:/api/mock2/sample/${ids|raw}?waitSeconds=30',
|
||||
confirmText: '确定要批量删除?'
|
||||
},
|
||||
{
|
||||
label: '批量修改',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '批量编辑',
|
||||
body: {
|
||||
type: 'form',
|
||||
api: '/api/mock2/sample/bulkUpdate2?waitSeconds=30',
|
||||
body: [
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'ids'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'engine',
|
||||
label: 'Engine'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine'
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
label: 'Platform(s)'
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample?orderBy=id&orderDir=desc&waitSeconds=30',
|
||||
syncLocation: false,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine'
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
buttons: [
|
||||
{
|
||||
label: '删除',
|
||||
type: 'button',
|
||||
actionType: 'ajax',
|
||||
level: 'danger',
|
||||
confirmText: '确认要删除?',
|
||||
api: 'delete:/api/mock2/sample/${id}?waitSeconds=30'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
type: 'page',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: 'dialog内带 loading',
|
||||
actionType: 'dialog',
|
||||
level: 'primary',
|
||||
dialog: {
|
||||
size: 'lg',
|
||||
title: '提示',
|
||||
body: loadingBody
|
||||
}
|
||||
},
|
||||
loadingBody
|
||||
]
|
||||
};
|
@ -6,7 +6,7 @@ import {toast} from 'amis';
|
||||
import {normalizeLink} from 'amis-core';
|
||||
import {withRouter} from 'react-router';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import {qsparse} from 'amis-core';
|
||||
import {qsparse, parseQuery} from 'amis-core';
|
||||
|
||||
function loadEditor() {
|
||||
return new Promise(resolve =>
|
||||
@ -86,7 +86,7 @@ export default function (schema, showCode, envOverrides) {
|
||||
if (pathname !== location.pathname || !location.search) {
|
||||
return false;
|
||||
}
|
||||
const currentQuery = qsparse(location.search.substring(1));
|
||||
const currentQuery = parseQuery(location);
|
||||
const query = qsparse(search.substring(1));
|
||||
|
||||
return Object.keys(query).every(
|
||||
|
@ -223,7 +223,7 @@ fis.match('*.html:jsx', {
|
||||
|
||||
// 这些用了 esm
|
||||
fis.match(
|
||||
'{echarts/extension/**.js,zrender/**.js,ansi-to-react/lib/index.js,markdown-it-html5-media/**.js}',
|
||||
'{echarts/extension/**.js,zrender/**.js,markdown-it-html5-media/**.js}',
|
||||
{
|
||||
parser: fis.plugin('typescript', {
|
||||
sourceMap: false,
|
||||
|
17
package.json
17
package.json
@ -30,7 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^28.1.0",
|
||||
"echarts": "5.3.3",
|
||||
"echarts": "5.4.0",
|
||||
"fis-optimizer-terser": "^1.0.1",
|
||||
"fis-parser-sass": "^1.2.0",
|
||||
"fis-parser-svgr": "^1.0.0",
|
||||
@ -46,12 +46,12 @@
|
||||
"fis3-preprocessor-js-require-css": "^0.1.3",
|
||||
"fis3-preprocessor-js-require-file": "^0.1.3",
|
||||
"husky": "^8.0.0",
|
||||
"jest": "^28.1.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"lerna": "^5.0.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"lerna": "^5.5.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"ts-jest": "^29.0.2",
|
||||
"zrender": "^5.3.2"
|
||||
},
|
||||
"jest": {
|
||||
@ -85,11 +85,6 @@
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/.rollup.cache/"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -156,15 +156,25 @@ test(`filter:url_encode`, () => {
|
||||
).toBe('%3D');
|
||||
});
|
||||
|
||||
test(`filter:url_encode`, () => {
|
||||
expect(
|
||||
resolveVariableAndFilter('${a|url_decode}', {
|
||||
a: '%3D'
|
||||
})
|
||||
).toBe('=');
|
||||
describe('filter:url_decode', () => {
|
||||
test(`filter:url_decode:normal`, () => {
|
||||
expect(
|
||||
resolveVariableAndFilter('${a|url_decode}', {
|
||||
a: '%3D'
|
||||
})
|
||||
).toBe('=');
|
||||
});
|
||||
|
||||
test(`filter:url_decode:error`, () => {
|
||||
expect(
|
||||
resolveVariableAndFilter('${a|url_decode}', {
|
||||
a: '%'
|
||||
})
|
||||
).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test(`filter:url_encode`, () => {
|
||||
test(`filter:default`, () => {
|
||||
expect(
|
||||
resolveVariableAndFilter('${a|default:-}', {
|
||||
a: ''
|
||||
|
@ -10,7 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
@ -18,16 +18,16 @@
|
||||
"@types/jest": "^28.1.0",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"jest": "^28.1.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.73.0",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-auto-external": "^2.0.0",
|
||||
"rollup-plugin-license": "^2.7.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"ts-jest": "^29.0.2",
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
@ -76,7 +76,12 @@
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"\\.(ts|tsx)$": "ts-jest"
|
||||
"\\.(ts|tsx)$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"diagnostics": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"setupFiles": [
|
||||
"jest-canvas-mock"
|
||||
@ -92,12 +97,7 @@
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/.rollup.cache/"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
const api = normalizeApi((action as any).api);
|
||||
if (typeof api.url === 'string') {
|
||||
let fileName = action.fileName || 'data.txt';
|
||||
if (api.url.indexOf('.') !== -1) {
|
||||
if (!action.fileName && api.url.indexOf('.') !== -1) {
|
||||
fileName = api.url.split('/').pop();
|
||||
}
|
||||
saveAs(api.url, fileName);
|
||||
|
@ -16,7 +16,7 @@ import {ScopedContext} from './Scoped';
|
||||
import {Schema, SchemaNode} from './types';
|
||||
import {DebugWrapper} from './utils/debug';
|
||||
import getExprProperties from './utils/filter-schema';
|
||||
import {anyChanged, chainEvents, autobind, createObject} from './utils/helper';
|
||||
import {anyChanged, chainEvents, autobind} from './utils/helper';
|
||||
import {SimpleMap} from './utils/SimpleMap';
|
||||
|
||||
import {bindEvent, dispatchEvent, RendererEvent} from './utils/renderer-event';
|
||||
|
@ -9,13 +9,12 @@ import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
import {dataMapping} from './utils/tpl-builtin';
|
||||
import {RendererEnv, RendererProps} from './factory';
|
||||
import {
|
||||
noop,
|
||||
autobind,
|
||||
qsstringify,
|
||||
qsparse,
|
||||
createObject,
|
||||
findTree,
|
||||
TreeItem
|
||||
TreeItem,
|
||||
parseQuery
|
||||
} from './utils/helper';
|
||||
import {RendererData, ActionObject} from './types';
|
||||
|
||||
@ -212,7 +211,7 @@ function createScopedTools(
|
||||
component.receive(values, subPath);
|
||||
} else if (name === 'window' && env && env.updateLocation) {
|
||||
const query = {
|
||||
...(location.search ? qsparse(location.search.substring(1)) : {}),
|
||||
...parseQuery(location),
|
||||
...values
|
||||
};
|
||||
const link = location.pathname + '?' + qsstringify(query);
|
||||
|
@ -3,7 +3,13 @@ import {RendererStore, IRendererStore, IIRendererStore} from './store/index';
|
||||
import {getEnv, destroy} from 'mobx-state-tree';
|
||||
import {wrapFetcher} from './utils/api';
|
||||
import {normalizeLink} from './utils/normalizeLink';
|
||||
import {findIndex, promisify, qsparse, string2regExp} from './utils/helper';
|
||||
import {
|
||||
findIndex,
|
||||
promisify,
|
||||
qsparse,
|
||||
string2regExp,
|
||||
parseQuery
|
||||
} from './utils/helper';
|
||||
import {
|
||||
fetcherResult,
|
||||
SchemaNode,
|
||||
@ -302,7 +308,7 @@ export const defaultOptions: RenderOptions = {
|
||||
return false;
|
||||
}
|
||||
const query = qsparse(search.substring(1));
|
||||
const currentQuery = qsparse(location.search.substring(1));
|
||||
const currentQuery = parseQuery(location);
|
||||
return Object.keys(query).every(key => query[key] === currentQuery[key]);
|
||||
} else if (pathname === location.pathname) {
|
||||
return true;
|
||||
|
@ -1522,7 +1522,14 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
formLabelAlign: labelAlign !== 'left' ? 'right' : labelAlign,
|
||||
formLabelWidth: labelWidth,
|
||||
controlWidth,
|
||||
disabled: disabled || (control as Schema).disabled || form.loading,
|
||||
/**
|
||||
* form.loading有为true时才下发disabled属性,否则不显性设置disbaled为false
|
||||
* Form中包含容器类组件时,这些组件会将此处的disbaled继续下发至子组件,导致SchemaRenderer中props.disabled覆盖schema.disabled
|
||||
*/
|
||||
disabled:
|
||||
disabled ||
|
||||
(control as Schema).disabled ||
|
||||
(form.loading ? true : undefined),
|
||||
btnDisabled: disabled || form.loading || form.validating,
|
||||
onAction: this.handleAction,
|
||||
onQuery: this.handleQuery,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Instance, types} from 'mobx-state-tree';
|
||||
import {createObject, qsparse} from '../utils/helper';
|
||||
import {createObject, parseQuery} from '../utils/helper';
|
||||
import {ServiceStore} from './service';
|
||||
|
||||
export const RootStore = ServiceStore.named('RootStore')
|
||||
@ -30,15 +30,7 @@ export const RootStore = ServiceStore.named('RootStore')
|
||||
self.runtimeErrorStack = errorStack;
|
||||
},
|
||||
updateLocation(location?: any) {
|
||||
const query =
|
||||
(location && location.query) ||
|
||||
(location &&
|
||||
location.search &&
|
||||
qsparse(location.search.substring(1))) ||
|
||||
(window.location.search &&
|
||||
qsparse(window.location.search.substring(1)));
|
||||
|
||||
self.query = query;
|
||||
self.query = parseQuery(location);
|
||||
},
|
||||
setVisible(id: string, value: boolean) {
|
||||
const state = {
|
||||
|
@ -1016,6 +1016,9 @@ export const TableStore = iRendererStore
|
||||
self.selectedRows.clear();
|
||||
// self.expandedRows.clear();
|
||||
|
||||
/* 避免输入内容为非数组挂掉 */
|
||||
rows = !Array.isArray(rows) ? [] : rows;
|
||||
|
||||
let arr: Array<SRow> = rows.map((item, index) => {
|
||||
if (!isObject(item)) {
|
||||
item = {
|
||||
|
@ -190,7 +190,18 @@ extendsFilters({
|
||||
}
|
||||
return encodeURIComponent(input);
|
||||
},
|
||||
url_decode: input => decodeURIComponent(input),
|
||||
url_decode: (input: string) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = decodeURIComponent(input);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`[amis] ${e?.name ?? 'URIError'}: input string is not valid.`
|
||||
);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
default: (input, defaultValue, strict = false) =>
|
||||
(strict ? input : input ? input : undefined) ??
|
||||
(() => {
|
||||
|
@ -3,6 +3,7 @@ import isEqual from 'lodash/isEqual';
|
||||
import isNaN from 'lodash/isNaN';
|
||||
import uniq from 'lodash/uniq';
|
||||
import last from 'lodash/last';
|
||||
import merge from 'lodash/merge';
|
||||
import {Schema, PlainObject, FunctionPropertyNames} from '../types';
|
||||
import {evalExpression} from './tpl';
|
||||
import qs from 'qs';
|
||||
@ -1705,3 +1706,25 @@ export function isNumeric(value: any): boolean {
|
||||
}
|
||||
return /^[-+]?(?:\d*[.])?\d+$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取URL链接中的query参数(包含hash mode)
|
||||
*
|
||||
* @param location Location对象,或者类Location结构的对象
|
||||
*/
|
||||
export function parseQuery(
|
||||
location?: Location | {query?: any; search?: any; [propName: string]: any}
|
||||
): Record<string, any> {
|
||||
const query =
|
||||
(location && !(location instanceof Location) && location?.query) ||
|
||||
(location && location?.search && qsparse(location.search.substring(1))) ||
|
||||
(window.location.search && qsparse(window.location.search.substring(1)));
|
||||
/* 处理hash中的query */
|
||||
const hashQuery =
|
||||
window.location?.hash && typeof window.location?.hash === 'string'
|
||||
? qsparse(window.location.hash.replace(/^#.*\?/gi, ''))
|
||||
: {};
|
||||
const normalizedQuery = isPlainObject(query) ? query : {};
|
||||
|
||||
return merge(normalizedQuery, hashQuery);
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ export function evalExpression(expression: string, data?: object): boolean {
|
||||
expression[expression.length - 1] === '}'
|
||||
) {
|
||||
// 启用新版本的公式表达式
|
||||
return evalFormula(expression, data);
|
||||
return !!evalFormula(expression, data);
|
||||
}
|
||||
|
||||
// 后续改用 FormulaExec['js']
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`lexer:filter 1`] = `
|
||||
Array [
|
||||
[
|
||||
"<Raw> $abc is ",
|
||||
"<OpenScript> \${",
|
||||
"<Identifier> abc",
|
||||
@ -19,7 +19,7 @@ Array [
|
||||
`;
|
||||
|
||||
exports[`lexer:simple 1`] = `
|
||||
Array [
|
||||
[
|
||||
"<Raw> expression result is ",
|
||||
"<OpenScript> \${",
|
||||
"<Identifier> a",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -59,9 +59,12 @@ test('formula:expression2', () => {
|
||||
});
|
||||
|
||||
test('formula:expression3', () => {
|
||||
expect(evalFormual('${a} === "b"', {a: 'b'})).toBe(true);
|
||||
// expect(evalFormual('${a} === "b"', {a: 'b'})).toBe(true);
|
||||
expect(evalFormual('b === "b"')).toBe(false);
|
||||
expect(evalFormual('${a}', {a: 'b'})).toBe('b');
|
||||
// expect(evalFormual('${a}', {a: 'b'})).toBe('b');
|
||||
|
||||
expect(evalFormual('obj.x.a', {obj: {x: {a: 1}}})).toBe(1);
|
||||
expect(evalFormual('obj.y.a', {obj: {x: {a: 1}}})).toBe(undefined);
|
||||
});
|
||||
|
||||
test('formula:if', () => {
|
||||
@ -84,10 +87,13 @@ test('formula:or', () => {
|
||||
});
|
||||
|
||||
test('formula:xor', () => {
|
||||
expect(evalFormual('XOR(0, 1)')).toBe(false);
|
||||
expect(evalFormual('XOR(1, 0)')).toBe(false);
|
||||
expect(evalFormual('XOR(1, 1)')).toBe(true);
|
||||
expect(evalFormual('XOR(0, 0)')).toBe(true);
|
||||
expect(evalFormual('XOR(0, 1)')).toBe(true);
|
||||
expect(evalFormual('XOR(1, 0)')).toBe(true);
|
||||
expect(evalFormual('XOR(1, 1)')).toBe(false);
|
||||
expect(evalFormual('XOR(0, 0)')).toBe(false);
|
||||
|
||||
expect(evalFormual('XOR(0, 0, 1)')).toBe(true);
|
||||
expect(evalFormual('XOR(0, 1, 1)')).toBe(false);
|
||||
});
|
||||
|
||||
test('formula:ifs', () => {
|
||||
|
@ -41,26 +41,26 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@types/doctrine": "0.0.5",
|
||||
"@types/jest": "^28.1.0",
|
||||
"@types/lodash": "^4.14.175",
|
||||
"doctrine": "^3.0.0",
|
||||
"jest": "^28.1.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.60.2",
|
||||
"rollup-plugin-license": "^2.6.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sass": "^1.54.0",
|
||||
"sass": "^1.54.9",
|
||||
"sass-loader": "^12.1.0",
|
||||
"style-loader": "^3.2.1",
|
||||
"stylelint": "^13.0.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"ts-jest": "^29.0.2",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.3.5"
|
||||
@ -77,7 +77,12 @@
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"\\.(ts|tsx)$": "ts-jest"
|
||||
"\\.(ts|tsx)$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"diagnostics": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"setupFiles": [
|
||||
"jest-canvas-mock"
|
||||
@ -89,12 +94,7 @@
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/__tests__/jest.setup.js"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ export class Evaluator {
|
||||
const filter = filters.shift()!;
|
||||
const fn = this.filters[filter.name];
|
||||
if (!fn) {
|
||||
throw new Error(`filter \`${filter.name}\` not exits`);
|
||||
throw new Error(`filter \`${filter.name}\` not exists.`);
|
||||
}
|
||||
context.filter = filter;
|
||||
input = fn.apply(
|
||||
@ -542,7 +542,7 @@ export class Evaluator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 异或处理,两个表达式同时为「真」,或者同时为「假」,则结果返回为「真」
|
||||
* 异或处理,多个表达式组中存在奇数个真时认为真。
|
||||
*
|
||||
* @example XOR(condition1, condition2)
|
||||
* @param {expression} condition1 - 条件表达式1
|
||||
@ -551,8 +551,8 @@ export class Evaluator {
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
fnXOR(c1: () => any, c2: () => any) {
|
||||
return !!c1() === !!c2();
|
||||
fnXOR(...condtions: Array<() => any>) {
|
||||
return !!(condtions.filter(c => c()).length % 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -896,10 +896,15 @@ export class Evaluator {
|
||||
*/
|
||||
fnUPPERMONEY(n: number) {
|
||||
n = this.formatNumber(n);
|
||||
const maxLen = 14;
|
||||
if (n.toString().split('.')[0]?.length > maxLen) {
|
||||
return `最大数额只支持到兆(既小数点前${maxLen}位)`;
|
||||
}
|
||||
|
||||
const fraction = ['角', '分'];
|
||||
const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
|
||||
const unit = [
|
||||
['元', '万', '亿'],
|
||||
['元', '万', '亿', '兆'],
|
||||
['', '拾', '佰', '仟']
|
||||
];
|
||||
const head = n < 0 ? '欠' : '';
|
||||
|
@ -294,7 +294,10 @@ export function lexer(input: string, options?: LexerOptions) {
|
||||
}
|
||||
|
||||
function openScript() {
|
||||
if (mainState === mainStates.Template) {
|
||||
if (
|
||||
mainState === mainStates.Template ||
|
||||
mainState === mainStates.EXPRESSION
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,8 @@
|
||||
"amis-formula": "^2.1.0",
|
||||
"classnames": "2.3.1",
|
||||
"codemirror": "^5.63.0",
|
||||
"downshift": "6.1.7",
|
||||
"echarts": "5.3.3",
|
||||
"downshift": "6.1.12",
|
||||
"echarts": "5.4.0",
|
||||
"froala-editor": "3.1.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"jsbarcode": "^3.11.5",
|
||||
@ -53,7 +53,7 @@
|
||||
"moment": "^2.19.4",
|
||||
"monaco-editor": "0.30.1",
|
||||
"prop-types": "^15.6.1",
|
||||
"rc-input-number": "^7.3.4",
|
||||
"rc-input-number": "^7.3.9",
|
||||
"rc-progress": "^3.1.4",
|
||||
"react-color": "^2.19.3",
|
||||
"react-hook-form": "7.30.0",
|
||||
@ -62,7 +62,7 @@
|
||||
"react-textarea-autosize": "8.3.3",
|
||||
"react-transition-group": "4.4.2",
|
||||
"react-visibility-sensor": "5.1.1",
|
||||
"sortablejs": "1.14.0",
|
||||
"sortablejs": "1.15.0",
|
||||
"tinymce": "^6.1.2",
|
||||
"tslib": "^2.3.1",
|
||||
"uncontrollable": "7.2.1"
|
||||
@ -70,7 +70,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@svgr/rollup": "^6.2.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@ -78,21 +78,21 @@
|
||||
"@types/jest": "^28.1.0",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"jest": "^28.1.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"postcss-import": "^14.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.73.0",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-auto-external": "^2.0.0",
|
||||
"rollup-plugin-license": "^2.7.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"sass": "^1.54.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"sass": "^1.54.9",
|
||||
"ts-jest": "^29.0.2",
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -112,7 +112,12 @@
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"\\.(ts|tsx)$": "ts-jest"
|
||||
"\\.(ts|tsx)$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"diagnostics": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"setupFiles": [
|
||||
"jest-canvas-mock"
|
||||
@ -129,12 +134,7 @@
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/.rollup.cache/"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
|
@ -409,6 +409,10 @@
|
||||
background: var(--Form-input-onDisabled-bg);
|
||||
border-color: var(--Form-input-onDisabled-borderColor);
|
||||
transition: all var(--animation-duration);
|
||||
|
||||
& > input {
|
||||
color: var(--text--muted-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-spinner {
|
||||
@ -471,3 +475,9 @@
|
||||
color: var(--icon-onHover-color);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -30,6 +30,8 @@
|
||||
margin-left: px2rem(-2px);
|
||||
height: px2rem(32px);
|
||||
line-height: px2rem(32px);
|
||||
@include truncate();
|
||||
max-width: var(--Tabs--vertical-width);
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
@ -46,8 +48,8 @@
|
||||
border-color: var(--primary);
|
||||
}
|
||||
> a:hover {
|
||||
color: #528EFF;
|
||||
border-color: #528EFF;
|
||||
color: #528eff;
|
||||
border-color: #528eff;
|
||||
}
|
||||
> a:active {
|
||||
color: #144bcc;
|
||||
@ -72,6 +74,11 @@
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
> a {
|
||||
@include truncate();
|
||||
max-width: var(--Tabs--vertical-width);
|
||||
}
|
||||
|
||||
> a:first-child {
|
||||
font-size: var(--Tabs-linkFontSize);
|
||||
outline: 0;
|
||||
|
@ -420,7 +420,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@if var(--Table-strip-bg) !=transparent {
|
||||
@if $Table-strip-bg != transparent {
|
||||
background: transparent;
|
||||
|
||||
&.#{$ns}Table-tr--odd {
|
||||
|
@ -16,6 +16,8 @@
|
||||
border-radius: var(--Form-input-borderRadius);
|
||||
background: var(--Form-input-bg);
|
||||
padding: var(--Form-input-paddingY) var(--Form-input-paddingX);
|
||||
/* 避免和clear btn 重叠 */
|
||||
padding-right: calc(var(--Form-input-paddingX) + var(--Form-fontSize));
|
||||
font-size: var(--Form-input-fontSize);
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -54,13 +56,13 @@
|
||||
}
|
||||
|
||||
.has-error--maxLength &-counter {
|
||||
background: var(--danger);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
&-clear {
|
||||
@include input-clear();
|
||||
position: absolute;
|
||||
right: var(--Form-input-paddingX);
|
||||
right: var(--Form-input-paddingY);
|
||||
top: var(--Form-input-paddingY);
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,11 @@
|
||||
|
||||
&-searchBox {
|
||||
height: px2rem(52px);
|
||||
margin: 0 px2rem(16px);
|
||||
padding: 0 px2rem(16px);
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
&-search {
|
||||
@ -30,6 +31,7 @@
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: var(--UserSelect--content-bg);
|
||||
margin-bottom: px2rem(16px);
|
||||
}
|
||||
|
||||
&-wrap {
|
||||
@ -38,6 +40,17 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
margin-bottom: px2rem(16px);
|
||||
background: var(--UserSelect--content-bg);
|
||||
}
|
||||
|
||||
&-footer {
|
||||
background: var(--white);
|
||||
padding: px2rem(10px) px2rem(16px) 0;
|
||||
|
||||
.#{$ns}Button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-navbar {
|
||||
@ -73,6 +86,7 @@
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
background: var(--white);
|
||||
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
@ -95,6 +109,8 @@
|
||||
position: relative;
|
||||
flex: 1;
|
||||
background: var(--UserSelect--content-bg);
|
||||
margin-top: px2rem(16px);
|
||||
margin-bottom: px2rem(16px);
|
||||
}
|
||||
|
||||
&-scroll {
|
||||
@ -108,7 +124,6 @@
|
||||
|
||||
&-memberList-box {
|
||||
width: 100vw;
|
||||
margin-top: px2rem(16px);
|
||||
}
|
||||
|
||||
&-memberList,
|
||||
@ -238,6 +253,7 @@
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
&-selectNum {
|
||||
@ -396,11 +412,28 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.#{$ns}UserSelect-wrap {
|
||||
height: calc(100% - 16px);
|
||||
}
|
||||
|
||||
&-footer {
|
||||
padding: px2rem(16px) px2rem(16px) 0;
|
||||
background: var(--white);
|
||||
|
||||
.#{$ns}Button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.#{$ns}Tabs-content {
|
||||
background-color: var(--UserSelect--content-bg);
|
||||
}
|
||||
|
||||
> div {
|
||||
&:first-child {
|
||||
flex: none;
|
||||
|
@ -36,6 +36,9 @@ $link-color: $info;
|
||||
@import '../variables';
|
||||
@import '../properties';
|
||||
|
||||
/* 此处放置需要override的变量,因为部分变量已经在variables.scss中定义 */
|
||||
$Table-strip-bg: transparent;
|
||||
|
||||
:root {
|
||||
--fontFamilyBase: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
|
||||
@ -158,8 +161,7 @@ $link-color: $info;
|
||||
--Tabs--vertical-onActive-container-bg: #fff;
|
||||
--Tabs--vertical-onActive-container-borderRight: 1px solid #f0f0f0;
|
||||
|
||||
$Table-strip-bg: transparent;
|
||||
--Table-strip-bg: transparent;
|
||||
--Table-strip-bg: #{$Table-strip-bg};
|
||||
--Table-thead-bg: #fafafa;
|
||||
--Table-onHover-bg: rgb(250, 250, 250);
|
||||
--Table-onHover-borderColor: var(--Table-borderColor);
|
||||
|
@ -28,6 +28,9 @@ $Wizard-steps-liAfterBorder: none !important;
|
||||
@import '../variables';
|
||||
@import '../properties';
|
||||
|
||||
/* 此处放置需要override的变量,因为部分变量已经在variables.scss中定义 */
|
||||
$Table-strip-bg: transparent;
|
||||
|
||||
// yunshe4.0 font-size
|
||||
$T1: 10px;
|
||||
$T2: 12px;
|
||||
@ -186,6 +189,7 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
|
||||
--Form-select-valueIcon-color--dark: #{$G8};
|
||||
--Form-select-multiple-bgColor: #{$G10};
|
||||
--Form-select-value-bgColor--dark: #{$G4};
|
||||
--Form-selectValue-onDisabled-color: #{$G6};
|
||||
|
||||
--InputGroup-select-borderWidth: #{px2rem(1px)};
|
||||
--InputGroup-select-onFocused-bg: #eaf6fe;
|
||||
@ -391,7 +395,7 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
|
||||
--Table-thead-bg: #{$G10};
|
||||
--Table-thead-borderColor: #fff;
|
||||
--Table-thead-iconColor: #999;
|
||||
--Table-strip-bg: transparent;
|
||||
--Table-strip-bg: #{$Table-strip-bg};
|
||||
--Table-onHover-bg: #{$B1};
|
||||
--Table-onHover-bg-rgb: 245, 251, 255;
|
||||
--Table-onHover-borderColor: #eceff8;
|
||||
|
@ -35,6 +35,9 @@ $link-color: $info;
|
||||
@import '../variables';
|
||||
@import '../properties';
|
||||
|
||||
/* 此处放置需要override的变量,因为部分变量已经在variables.scss中定义 */
|
||||
$Table-strip-bg: $Panel-bg;
|
||||
|
||||
:root {
|
||||
--Panel-bg: #{$Panel-bg};
|
||||
--background-head: #191c22;
|
||||
|
@ -219,7 +219,7 @@ export class AnchorNav extends React.Component<AnchorNavProps, AnchorNavState> {
|
||||
key={index}
|
||||
onClick={() => this.handleSelect(name)}
|
||||
>
|
||||
<a>{title}</a>
|
||||
<a title={title}>{title}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import TooltipWrapper, {TooltipObject, Trigger} from './TooltipWrapper';
|
||||
import {pickEventsProps} from 'amis-core';
|
||||
import {ClassNamesFn, themeable} from 'amis-core';
|
||||
import {Icon} from './icons';
|
||||
import Spinner from './Spinner';
|
||||
interface ButtonProps extends React.DOMAttributes<HTMLButtonElement> {
|
||||
id?: string;
|
||||
className?: string;
|
||||
@ -103,18 +104,19 @@ export class Button extends React.Component<ButtonProps> {
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
>
|
||||
{loading && !disabled ? (
|
||||
<span
|
||||
{loading && !disabled && (
|
||||
<Spinner
|
||||
size="sm"
|
||||
show
|
||||
icon="loading-outline"
|
||||
className={cx(
|
||||
overrideClassName
|
||||
? ''
|
||||
: {[`Button--loading Button--loading--${level}`]: level},
|
||||
loadingClassName
|
||||
)}
|
||||
>
|
||||
<Icon icon="loading-outline" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
|
@ -1178,8 +1178,11 @@ export class DateRangePicker extends React.Component<
|
||||
? minDate
|
||||
: startDate
|
||||
: minDate || startDate;
|
||||
|
||||
if (minDate && currentDate.isBefore(minDate, precision)) {
|
||||
// 在 dateTimeRange 的场景下,如果选择了开始时间的时间点不为 0,比如 2020-10-1 10:10,这时 currentDate 传入的当天值是 2020-10-1 00:00,这个值在起始时间后面,导致没法再选这一天了,所以在这时需要先通过将时间都转成 00 再比较
|
||||
if (
|
||||
minDate &&
|
||||
currentDate.startOf('day').isBefore(minDate.startOf('day'), precision)
|
||||
) {
|
||||
return false;
|
||||
} else if (maxDate && currentDate.isAfter(maxDate, precision)) {
|
||||
return false;
|
||||
|
@ -10,7 +10,11 @@ import {localeable} from 'amis-core';
|
||||
export class GroupedSelection extends BaseSelection<BaseSelectionProps> {
|
||||
valueArray: Array<Option>;
|
||||
|
||||
renderOption(option: Option, index: number) {
|
||||
renderOption(
|
||||
option: Option,
|
||||
index: number,
|
||||
key: string = `${index}`
|
||||
): JSX.Element {
|
||||
const {
|
||||
labelClassName,
|
||||
disabled,
|
||||
@ -24,6 +28,16 @@ export class GroupedSelection extends BaseSelection<BaseSelectionProps> {
|
||||
const valueArray = this.valueArray;
|
||||
|
||||
if (Array.isArray(option.children)) {
|
||||
if (!option.label) {
|
||||
return (
|
||||
<>
|
||||
{option.children.map((child: Option, index: number) =>
|
||||
this.renderOption(child, index)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
|
@ -31,6 +31,7 @@ export interface ImageAction {
|
||||
export interface ImageGalleryProps extends ThemeProps, LocaleProps {
|
||||
children: React.ReactNode;
|
||||
modalContainer?: () => HTMLElement;
|
||||
/** 操作栏 */
|
||||
actions?: ImageAction[];
|
||||
}
|
||||
|
||||
@ -47,6 +48,10 @@ export interface ImageGalleryState {
|
||||
scale: number;
|
||||
/** 图片旋转角度 */
|
||||
rotate: number;
|
||||
/** 是否开启操作栏 */
|
||||
showToolbar?: boolean;
|
||||
/** 工具栏配置 */
|
||||
actions?: ImageAction[];
|
||||
}
|
||||
|
||||
export class ImageGallery extends React.Component<
|
||||
@ -80,7 +85,9 @@ export class ImageGallery extends React.Component<
|
||||
index: -1,
|
||||
items: [],
|
||||
scale: 1,
|
||||
rotate: 0
|
||||
rotate: 0,
|
||||
showToolbar: false,
|
||||
actions: ImageGallery.defaultProps.actions
|
||||
};
|
||||
|
||||
@autobind
|
||||
@ -96,11 +103,24 @@ export class ImageGallery extends React.Component<
|
||||
title?: string;
|
||||
caption?: string;
|
||||
index?: number;
|
||||
showToolbar?: boolean;
|
||||
toolbarActions?: ImageAction[];
|
||||
}) {
|
||||
const {actions} = this.props;
|
||||
const validActionKeys = Object.values(ImageActionKey);
|
||||
|
||||
this.setState({
|
||||
isOpened: true,
|
||||
items: info.list ? info.list : [info],
|
||||
index: info.index || 0
|
||||
index: info.index || 0,
|
||||
/* children组件可以控制工具栏的展示 */
|
||||
showToolbar: !!info.showToolbar,
|
||||
/** 外部传入合法key值的actions才会生效 */
|
||||
actions: Array.isArray(info.toolbarActions)
|
||||
? info.toolbarActions.filter(action =>
|
||||
validActionKeys.includes(action?.key)
|
||||
)
|
||||
: actions
|
||||
});
|
||||
}
|
||||
|
||||
@ -212,8 +232,8 @@ export class ImageGallery extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, classnames: cx, modalContainer, actions} = this.props;
|
||||
const {index, items, rotate, scale} = this.state;
|
||||
const {children, classnames: cx, modalContainer} = this.props;
|
||||
const {index, items, rotate, scale, showToolbar, actions} = this.state;
|
||||
const __ = this.props.translate;
|
||||
|
||||
return (
|
||||
@ -249,7 +269,7 @@ export class ImageGallery extends React.Component<
|
||||
style={{transform: `scale(${scale}) rotate(${rotate}deg)`}}
|
||||
/>
|
||||
|
||||
{Array.isArray(actions) && actions.length > 0
|
||||
{showToolbar && Array.isArray(actions) && actions.length > 0
|
||||
? this.renderToolbar(actions)
|
||||
: null}
|
||||
|
||||
|
@ -478,7 +478,6 @@ export class Pagination extends React.Component<
|
||||
<Select
|
||||
key="perpage"
|
||||
className={cx('Pagination-perpage', 'Pagination-item')}
|
||||
overlayPlacement="right-bottom-right-top"
|
||||
clearable={false}
|
||||
disabled={disabled}
|
||||
value={perPage}
|
||||
|
@ -10,6 +10,8 @@ import {themeable, ThemeProps} from 'amis-core';
|
||||
import Transition, {ENTERED, ENTERING} from 'react-transition-group/Transition';
|
||||
import {Icon, hasIcon} from './icons';
|
||||
import {generateIcon} from 'amis-core';
|
||||
import {types} from 'mobx-state-tree';
|
||||
import {observable, reaction} from 'mobx';
|
||||
|
||||
const fadeStyles: {
|
||||
[propName: string]: string;
|
||||
@ -35,6 +37,60 @@ export interface SpinnerProps extends ThemeProps {
|
||||
overlay?: boolean; // 是否显示遮罩层,有children属性才生效
|
||||
}
|
||||
|
||||
const SpinnerSharedStore = types
|
||||
.model('SpinnerSharedStore', {})
|
||||
.volatile(self => {
|
||||
return {
|
||||
// 保存所有可以进入 loading 状态(props.show = true)的 Spinner 的父级容器
|
||||
spinnerContainers: observable.set([] as HTMLElement[], {
|
||||
deep: false
|
||||
})
|
||||
};
|
||||
})
|
||||
.actions(self => {
|
||||
return {
|
||||
push: (spinnerContainer: HTMLElement) => {
|
||||
if (self.spinnerContainers.has(spinnerContainer)) {
|
||||
return;
|
||||
}
|
||||
self.spinnerContainers.add(spinnerContainer);
|
||||
},
|
||||
remove: (spinnerContainer: HTMLElement) => {
|
||||
self.spinnerContainers.delete(spinnerContainer);
|
||||
},
|
||||
/**
|
||||
* 判断当前 Spinner 是否可以进入 loading 状态
|
||||
* @param spinnerContainerWillCheck 待检查的 Spinner 父容器
|
||||
* @returns {boolean} 是否可以进入 loading
|
||||
*/
|
||||
checkLoading: (spinnerContainerWillCheck: HTMLElement | null) => {
|
||||
if (self.spinnerContainers.has(spinnerContainerWillCheck)) {
|
||||
if (!self.spinnerContainers.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let loading = true;
|
||||
|
||||
// 检查缓存的容器中是否有当前容器的父级元素
|
||||
self.spinnerContainers.forEach(container => {
|
||||
if (
|
||||
container.contains(spinnerContainerWillCheck) &&
|
||||
container !== spinnerContainerWillCheck
|
||||
) {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
return loading;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const store = SpinnerSharedStore.create({});
|
||||
|
||||
export class Spinner extends React.Component<SpinnerProps> {
|
||||
static defaultProps = {
|
||||
show: true,
|
||||
@ -48,10 +104,64 @@ export class Spinner extends React.Component<SpinnerProps> {
|
||||
overlay: false
|
||||
};
|
||||
|
||||
state = {
|
||||
spinning: false
|
||||
};
|
||||
|
||||
parent: HTMLElement | null = null;
|
||||
|
||||
spinnerRef = (dom: HTMLElement) => {
|
||||
if (dom) {
|
||||
this.parent = dom.parentNode as HTMLElement;
|
||||
}
|
||||
};
|
||||
|
||||
componentDidUpdate(prev: SpinnerProps) {
|
||||
if (!prev.show && this.props.show) {
|
||||
// 先根据 props.show 触发一次 loading,否则元素没有渲染,无法找到 parent
|
||||
this.setState({spinning: true});
|
||||
}
|
||||
|
||||
if (this.parent) {
|
||||
if (this.props.show) {
|
||||
store.push(this.parent);
|
||||
} else if (this.state.spinning) {
|
||||
store.remove(this.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
// 对于 通过 条件语句控制 Spinner 是否展示的情况,需要在这里处理 : show && <Spinner show overlay />
|
||||
if (this.props.show) {
|
||||
// 先根据 props.show 触发一次 loading,否则元素没有渲染,无法找到 parent
|
||||
this.setState({spinning: true});
|
||||
|
||||
if (this.parent) {
|
||||
store.push(this.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// 卸载 reaction
|
||||
this.loadingChecker();
|
||||
// 删除 当前 parent 元素
|
||||
store.remove(this.parent!);
|
||||
}
|
||||
|
||||
loadingChecker = reaction(
|
||||
() => store.spinnerContainers.size,
|
||||
() => {
|
||||
this.setState({
|
||||
spinning: store.checkLoading(this.parent)
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
render() {
|
||||
const {
|
||||
classnames: cx,
|
||||
show,
|
||||
className,
|
||||
spinnerClassName,
|
||||
size = '',
|
||||
@ -65,7 +175,12 @@ export class Spinner extends React.Component<SpinnerProps> {
|
||||
const timeout = {enter: delay, exit: 0};
|
||||
|
||||
return (
|
||||
<Transition mountOnEnter unmountOnExit in={show} timeout={timeout}>
|
||||
<Transition
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
in={this.state.spinning}
|
||||
timeout={timeout}
|
||||
>
|
||||
{(status: string) => {
|
||||
return (
|
||||
<>
|
||||
@ -76,6 +191,7 @@ export class Spinner extends React.Component<SpinnerProps> {
|
||||
|
||||
{/* spinner图标和文案 */}
|
||||
<div
|
||||
ref={this.spinnerRef as any}
|
||||
data-testid="spinner"
|
||||
className={cx(
|
||||
`Spinner`,
|
||||
|
@ -202,6 +202,7 @@ export class Textarea extends React.Component<TextAreaProps, TextAreaState> {
|
||||
placeholder={placeholder}
|
||||
autoCorrect="off"
|
||||
spellCheck="false"
|
||||
maxLength={maxLength}
|
||||
readOnly={readOnly}
|
||||
minRows={minRows || undefined}
|
||||
maxRows={maxRows || undefined}
|
||||
|
@ -133,6 +133,7 @@ export default class TinymceEditor extends React.Component<TinymceEditorProps> {
|
||||
...this.props.config,
|
||||
target: this.elementRef.current,
|
||||
readOnly: this.props.disabled,
|
||||
promotion: false,
|
||||
setup: (editor: any) => {
|
||||
this.editor = editor;
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {Payload, themeable, ThemeProps} from 'amis-core';
|
||||
import {eachTree, Payload, themeable, ThemeProps} from 'amis-core';
|
||||
import {LocaleProps, localeable} from 'amis-core';
|
||||
import {ResultBox} from '.';
|
||||
import type {Option} from 'amis-core';
|
||||
@ -53,7 +53,11 @@ export interface UserSelectProps extends ThemeProps, LocaleProps {
|
||||
isRef?: boolean,
|
||||
param?: PlainObject
|
||||
) => Promise<Option[]>;
|
||||
onChange: (value: Array<Option> | Option, isReplace?: boolean) => void;
|
||||
onChange: (
|
||||
value: Array<Option> | Option,
|
||||
isReplace?: boolean,
|
||||
isDelete?: boolean
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface UserSelectState {
|
||||
@ -108,7 +112,7 @@ export class UserSelect extends React.Component<
|
||||
if (prevProps.options !== options) {
|
||||
if (
|
||||
options &&
|
||||
options.length === 1 &&
|
||||
options.length &&
|
||||
options[0].leftOptions &&
|
||||
Array.isArray(options[0].children)
|
||||
) {
|
||||
@ -214,6 +218,7 @@ export class UserSelect extends React.Component<
|
||||
swapSelectPosition(oldIndex: number, newIndex: number) {
|
||||
const tempSelection = this.state.tempSelection;
|
||||
tempSelection.splice(newIndex, 0, tempSelection.splice(oldIndex, 1)[0]);
|
||||
|
||||
this.setState({tempSelection});
|
||||
}
|
||||
|
||||
@ -255,10 +260,10 @@ export class UserSelect extends React.Component<
|
||||
|
||||
@autobind
|
||||
onOpen() {
|
||||
const {selection} = this.state;
|
||||
const {selection} = this.props;
|
||||
this.setState({
|
||||
isOpened: true,
|
||||
tempSelection: selection.slice()
|
||||
selection: selection || []
|
||||
});
|
||||
}
|
||||
|
||||
@ -269,6 +274,7 @@ export class UserSelect extends React.Component<
|
||||
inputValue: '',
|
||||
isSearch: false,
|
||||
searchList: [],
|
||||
selection: [],
|
||||
breadList: []
|
||||
});
|
||||
}
|
||||
@ -307,10 +313,13 @@ export class UserSelect extends React.Component<
|
||||
onChange(option);
|
||||
return;
|
||||
}
|
||||
|
||||
let selection = this.state.selection.slice();
|
||||
// 直接替换的option 肯定是数组
|
||||
if (isReplace) {
|
||||
selection = option as Option[];
|
||||
// ResultBox 删除场景
|
||||
onChange(selection);
|
||||
} else {
|
||||
let selectionVals = selection.map((option: Option) => option[valueField]);
|
||||
let pos = selectionVals.indexOf(option[valueField]);
|
||||
@ -325,16 +334,25 @@ export class UserSelect extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
onChange(multiple ? selection : selection?.[0]);
|
||||
// onChange(multiple ? selection : selection?.[0]);
|
||||
this.setState({
|
||||
selection
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSubmit() {
|
||||
const {onChange, multiple} = this.props;
|
||||
const {selection} = this.state;
|
||||
const value = multiple ? selection : selection?.[0];
|
||||
onChange(value);
|
||||
this.handleBack();
|
||||
}
|
||||
|
||||
@autobind
|
||||
onDelete(option: Option, isTemp: boolean = false) {
|
||||
const {valueField = 'value'} = this.props;
|
||||
const {valueField = 'value', controlled, onChange} = this.props;
|
||||
const {tempSelection, selection} = this.state;
|
||||
let _selection = isTemp ? tempSelection : selection;
|
||||
_selection = _selection.filter(
|
||||
@ -343,7 +361,11 @@ export class UserSelect extends React.Component<
|
||||
if (isTemp) {
|
||||
this.setState({tempSelection: _selection});
|
||||
} else {
|
||||
this.setState({selection: _selection});
|
||||
if (controlled) {
|
||||
onChange(option, false, true);
|
||||
} else {
|
||||
this.setState({selection: _selection});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,6 +377,17 @@ export class UserSelect extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSort() {
|
||||
const {controlled} = this.props;
|
||||
this.setState({
|
||||
isSelectOpened: true,
|
||||
tempSelection: controlled
|
||||
? this.props.selection?.slice() || []
|
||||
: this.state.selection.slice()
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEdit() {
|
||||
const {multiple, onChange, controlled} = this.props;
|
||||
@ -381,6 +414,31 @@ export class UserSelect extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleClear() {
|
||||
this.setState({tempSelection: []});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getResult() {
|
||||
const {
|
||||
valueField = 'value',
|
||||
labelField = 'label',
|
||||
options = []
|
||||
} = this.props;
|
||||
const _selection = this.props.selection?.slice() || [];
|
||||
|
||||
eachTree(options, (item: Option) => {
|
||||
const res = _selection.find(
|
||||
(item2: Option) => item2[valueField] === item[valueField]
|
||||
);
|
||||
if (res) {
|
||||
res[labelField] = item[labelField];
|
||||
}
|
||||
});
|
||||
return _selection;
|
||||
}
|
||||
|
||||
renderIcon(option: Option, isSelect?: boolean) {
|
||||
const {labelField = 'label', classnames: cx, isRef} = this.props;
|
||||
const {isSearch} = this.state;
|
||||
@ -389,7 +447,7 @@ export class UserSelect extends React.Component<
|
||||
if (option.isRef || ((isSearch || isSelect) && isRef)) {
|
||||
return (
|
||||
<span className={cx('UserSelect-text-userPic')}>
|
||||
{option[labelField].slice(0, 1)}
|
||||
{option[labelField]?.slice(0, 1)}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
@ -602,9 +660,7 @@ export class UserSelect extends React.Component<
|
||||
|
||||
const {breadList, options, isSearch, searchList, searchLoading} =
|
||||
this.state;
|
||||
let selection = controlled
|
||||
? this.props.selection || []
|
||||
: this.state.selection;
|
||||
const selection = controlled ? this.props.selection : this.state.selection;
|
||||
|
||||
return (
|
||||
<div className={cx(`UserSelect-wrap`)}>
|
||||
@ -690,12 +746,7 @@ export class UserSelect extends React.Component<
|
||||
</ul>
|
||||
<span
|
||||
className={cx('UserSelect-selectSort-box')}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
isSelectOpened: true,
|
||||
tempSelection: selection.slice()
|
||||
})
|
||||
}
|
||||
onClick={this.handleSort}
|
||||
>
|
||||
<Icon
|
||||
icon="menu"
|
||||
@ -745,6 +796,18 @@ export class UserSelect extends React.Component<
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!controlled ? (
|
||||
<div className={cx('UserSelect-footer')}>
|
||||
<button
|
||||
type="button"
|
||||
className={cx('Button Button--md Button--primary')}
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
{__('UserSelect.sure')}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -754,13 +817,10 @@ export class UserSelect extends React.Component<
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
placeholder = '请选择',
|
||||
showResultBox,
|
||||
controlled,
|
||||
onChange
|
||||
showResultBox
|
||||
} = this.props;
|
||||
|
||||
const {isOpened, tempSelection, isSelectOpened, isEdit} = this.state;
|
||||
let selection = controlled ? this.props.selection : this.state.selection;
|
||||
const {isOpened, isEdit, isSelectOpened} = this.state;
|
||||
|
||||
return (
|
||||
<div className={cx('UserSelect')}>
|
||||
@ -768,7 +828,7 @@ export class UserSelect extends React.Component<
|
||||
<ResultBox
|
||||
className={cx('UserSelect-input', isOpened ? 'is-active' : '')}
|
||||
allowInput={false}
|
||||
result={selection}
|
||||
result={this.getResult()}
|
||||
onResultChange={value => this.handleSelectChange(value, true)}
|
||||
onResultClick={this.onOpen}
|
||||
placeholder={placeholder}
|
||||
@ -832,13 +892,13 @@ export class UserSelect extends React.Component<
|
||||
{isEdit ? (
|
||||
<span
|
||||
className={cx('UserSelect-select-head-btnClear')}
|
||||
onClick={() => this.setState({tempSelection: []})}
|
||||
onClick={this.handleClear}
|
||||
>
|
||||
{__('UserSelect.clear')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{this.renderselectList(tempSelection)}
|
||||
{this.renderselectList(this.state.tempSelection)}
|
||||
</div>
|
||||
</div>
|
||||
</PopUp>
|
||||
|
@ -54,7 +54,6 @@ export interface UserTabSelectState {
|
||||
inputValue: string;
|
||||
breadList: Array<any>;
|
||||
options: Array<Option>;
|
||||
tempSelection: Array<Option>;
|
||||
selection: Array<Option>;
|
||||
searchList: Array<Option>;
|
||||
searchLoading: boolean;
|
||||
@ -79,7 +78,6 @@ export class UserTabSelect extends React.Component<
|
||||
options: [],
|
||||
breadList: [],
|
||||
searchList: [],
|
||||
tempSelection: [],
|
||||
selection: props.selection ? props.selection : [],
|
||||
isSearch: false,
|
||||
searchLoading: false,
|
||||
@ -106,37 +104,46 @@ export class UserTabSelect extends React.Component<
|
||||
inputValue: '',
|
||||
searchList: [],
|
||||
searchLoading: false,
|
||||
activeKey: 0
|
||||
activeKey: 0,
|
||||
selection: []
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
onOpen() {
|
||||
const {selection} = this.state;
|
||||
const {selection = []} = this.props;
|
||||
this.setState({
|
||||
isOpened: true,
|
||||
tempSelection: selection.slice()
|
||||
selection: selection.slice()
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleBack() {
|
||||
this.onClose();
|
||||
handleSubmit() {
|
||||
const {onChange} = this.props;
|
||||
onChange(this.state.selection);
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSelectChange(option: Option | Array<Option>, isReplace?: boolean) {
|
||||
handleSelectChange(
|
||||
option: Option | Array<Option>,
|
||||
isReplace?: boolean,
|
||||
isDelete?: boolean
|
||||
) {
|
||||
const {multiple, valueField = 'value'} = this.props;
|
||||
let selection = this.state.selection.slice();
|
||||
let selectionVals = selection.map((option: Option) => option[valueField]);
|
||||
if (isReplace && Array.isArray(option)) {
|
||||
if (isDelete) {
|
||||
selection = selection.filter(
|
||||
(item: Option) => item[valueField] !== (option as Option)[valueField]
|
||||
);
|
||||
} else if (isReplace && Array.isArray(option)) {
|
||||
selection = option.slice();
|
||||
} else if (!Array.isArray(option)) {
|
||||
let pos = selectionVals.indexOf(option[valueField]);
|
||||
if (pos !== -1) {
|
||||
selection.splice(selection.indexOf(option), 1);
|
||||
selection.splice(pos, 1);
|
||||
} else {
|
||||
if (multiple) {
|
||||
selection.push(option);
|
||||
@ -152,6 +159,17 @@ export class UserTabSelect extends React.Component<
|
||||
return false;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleImmediateChange(option: Array<Option>) {
|
||||
const {onChange} = this.props;
|
||||
if (Array.isArray(option)) {
|
||||
this.setState({
|
||||
selection: option
|
||||
});
|
||||
onChange(option);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleTabChange(key: number) {
|
||||
this.setState({
|
||||
@ -159,26 +177,50 @@ export class UserTabSelect extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getResult() {
|
||||
const {
|
||||
selection,
|
||||
tabOptions,
|
||||
valueField = 'value',
|
||||
labelField = 'label'
|
||||
} = this.props;
|
||||
const _selection = selection?.slice() || [];
|
||||
if (tabOptions) {
|
||||
for (let item of tabOptions) {
|
||||
for (let item2 of item.options) {
|
||||
const res = _selection.find(
|
||||
item => item[valueField] === item2[valueField]
|
||||
);
|
||||
if (res) {
|
||||
res[labelField] = item2[labelField];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _selection;
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
onChange,
|
||||
placeholder = '请选择',
|
||||
tabOptions,
|
||||
onSearch,
|
||||
deferLoad,
|
||||
data
|
||||
} = this.props;
|
||||
const {activeKey, isOpened, selection} = this.state;
|
||||
const {activeKey, isOpened} = this.state;
|
||||
|
||||
return (
|
||||
<div className={cx('UserTabSelect')}>
|
||||
<ResultBox
|
||||
className={cx('UserTabSelect-input', isOpened ? 'is-active' : '')}
|
||||
allowInput={false}
|
||||
result={selection}
|
||||
onResultChange={value => this.handleSelectChange(value, true)}
|
||||
result={this.getResult()}
|
||||
onResultChange={this.handleImmediateChange}
|
||||
onResultClick={this.onOpen}
|
||||
placeholder={placeholder}
|
||||
useMobileUI
|
||||
@ -191,7 +233,7 @@ export class UserTabSelect extends React.Component<
|
||||
>
|
||||
<div className={cx('UserTabSelect-wrap')}>
|
||||
<div className={cx('UserSelect-navbar')}>
|
||||
<span className="left-arrow-box" onClick={this.handleBack}>
|
||||
<span className="left-arrow-box" onClick={this.onClose}>
|
||||
<Icon icon="left-arrow" className="icon" />
|
||||
</span>
|
||||
<div className={cx('UserSelect-navbar-title')}>人员选择</div>
|
||||
@ -212,7 +254,7 @@ export class UserTabSelect extends React.Component<
|
||||
className="TabsTransfer-tab"
|
||||
>
|
||||
<UserSelect
|
||||
selection={selection}
|
||||
selection={this.state.selection}
|
||||
showResultBox={false}
|
||||
{...item}
|
||||
options={
|
||||
@ -251,6 +293,16 @@ export class UserTabSelect extends React.Component<
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
|
||||
<div className={cx('UserTabSelect-footer')}>
|
||||
<button
|
||||
type="button"
|
||||
className={cx('Button Button--md Button--primary')}
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
{__('UserSelect.sure')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</PopUp>
|
||||
</div>
|
||||
|
@ -83,7 +83,7 @@ export class ConditionField extends React.Component<
|
||||
}
|
||||
|
||||
// 选了值,还原options
|
||||
onPopClose(e: React.MouseEvent, onClose: () => void) {
|
||||
onPopClose(onClose: () => void) {
|
||||
this.setState({searchText: ''});
|
||||
onClose();
|
||||
}
|
||||
@ -117,14 +117,14 @@ export class ConditionField extends React.Component<
|
||||
options={this.filterOptions(this.props.options)}
|
||||
value={value}
|
||||
onChange={(value: any) => {
|
||||
this.onPopClose(null, onClose);
|
||||
this.onPopClose(onClose);
|
||||
onChange(value.name);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ListSelection
|
||||
multiple={false}
|
||||
onClick={(e: any) => this.onPopClose(e, onClose)}
|
||||
onClick={() => this.onPopClose(onClose)}
|
||||
options={this.filterOptions(this.props.options)}
|
||||
value={[value]}
|
||||
option2value={option2value}
|
||||
|
@ -138,6 +138,24 @@ export class FormulaEditor extends React.Component<
|
||||
evalMode: true
|
||||
};
|
||||
|
||||
static replaceStrByIndex(
|
||||
str: string,
|
||||
idx: number,
|
||||
key: string,
|
||||
replaceKey: string
|
||||
) {
|
||||
const from = str.slice(0, idx);
|
||||
const left = str.slice(idx);
|
||||
return from + left.replace(key, replaceKey);
|
||||
}
|
||||
|
||||
static getRegExpByMode(evalMode: boolean, key: string) {
|
||||
const reg = evalMode
|
||||
? `\\b${key}\\b`
|
||||
: `\\$\\{[^\\{\\}]*\\b${key}\\b[^\\{\\}]*\\}`;
|
||||
return new RegExp(reg);
|
||||
}
|
||||
|
||||
static highlightValue(
|
||||
value: string,
|
||||
variables: Array<VariableItem>,
|
||||
@ -174,14 +192,18 @@ export class FormulaEditor extends React.Component<
|
||||
let from = 0;
|
||||
let idx = -1;
|
||||
while (~(idx = content.indexOf(v, from))) {
|
||||
// 处理一下 \b 匹配不到的字符,比如 中文、[] 等
|
||||
const encodeHtml = html.replace(v, REPLACE_KEY);
|
||||
const curNameEg = new RegExp(`\\b${REPLACE_KEY}\\b`, 'g'); // 避免变量识别冲突,比如:name、me 被识别成 na「me」
|
||||
const encodeHtml = FormulaEditor.replaceStrByIndex(
|
||||
html,
|
||||
idx,
|
||||
v,
|
||||
REPLACE_KEY
|
||||
);
|
||||
const reg = FormulaEditor.getRegExpByMode(evalMode, REPLACE_KEY);
|
||||
|
||||
// 如果匹配到则高亮,没有匹配到替换成原值
|
||||
if (curNameEg.test(encodeHtml)) {
|
||||
if (reg.test(encodeHtml)) {
|
||||
html = encodeHtml.replace(
|
||||
curNameEg,
|
||||
REPLACE_KEY,
|
||||
`<span class="c-field">${varMap[v]}</span>`
|
||||
);
|
||||
} else {
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import type CodeMirror from 'codemirror';
|
||||
import {eachTree} from 'amis-core';
|
||||
import type {FormulaEditorProps, VariableItem} from './Editor';
|
||||
import {FormulaEditorProps, VariableItem, FormulaEditor} from './Editor';
|
||||
|
||||
export function editorFactory(
|
||||
dom: HTMLElement,
|
||||
@ -179,6 +179,7 @@ export class FormulaPlugin {
|
||||
const vars = Object.keys(varMap).sort((a, b) => b.length - a.length);
|
||||
const editor = this.editor;
|
||||
const lines = editor.lineCount();
|
||||
const {evalMode = true} = this.getProps();
|
||||
for (let line = 0; line < lines; line++) {
|
||||
const content = editor.getLine(line);
|
||||
|
||||
@ -205,10 +206,15 @@ export class FormulaPlugin {
|
||||
let from = 0;
|
||||
let idx = -1;
|
||||
while (~(idx = content.indexOf(v, from))) {
|
||||
const encode = content.replace(v, REPLACE_KEY);
|
||||
const curNameEg = new RegExp(`\\b${REPLACE_KEY}\\b`, 'g');
|
||||
const encode = FormulaEditor.replaceStrByIndex(
|
||||
content,
|
||||
idx,
|
||||
v,
|
||||
REPLACE_KEY
|
||||
);
|
||||
const reg = FormulaEditor.getRegExpByMode(evalMode, REPLACE_KEY);
|
||||
|
||||
if (curNameEg.test(encode)) {
|
||||
if (reg.test(encode)) {
|
||||
this.markText(
|
||||
{
|
||||
line: line,
|
||||
|
@ -152,6 +152,9 @@ register('de-DE', {
|
||||
'Form.unique': 'Aktueller Wert ist nicht eindeutig',
|
||||
'Form.validateFailed': 'Fehler bei der Überprüfung der Formulareingabe',
|
||||
'Form.nestedError': 'Form kann nicht als Nachkomme von Form erscheinen',
|
||||
'Iframe.invalid': 'Ungültige Iframe-URL',
|
||||
'Iframe.invalidProtocol':
|
||||
'HTTP-URL-Iframe kann nicht in https verwendet werden',
|
||||
'Image.configError':
|
||||
'Es können nur eine Beschneidung oder mehrere festgelegt werden',
|
||||
'Image.crop': 'Bild beschneiden',
|
||||
@ -196,6 +199,7 @@ register('de-DE', {
|
||||
'Quarter.placeholder': 'Quartal auswählen',
|
||||
'Repeat.pre': 'Pro',
|
||||
'reset': 'Zurücksetzen',
|
||||
'save': 'Konservierung',
|
||||
'saveFailed': 'Fehler beim Speichern',
|
||||
'saveSuccess': 'Erfolgreich gespeichert',
|
||||
'search': 'Suchen',
|
||||
@ -375,6 +379,7 @@ register('de-DE', {
|
||||
'UserSelect.resultSort': 'Ergebnissortierung auswählen',
|
||||
'UserSelect.selected': 'Ausgewählt',
|
||||
'UserSelect.clear': 'leer',
|
||||
'UserSelect.sure': 'Submit',
|
||||
'SchemaType.string': 'String',
|
||||
'SchemaType.number': 'Number',
|
||||
'SchemaType.integer': 'integer',
|
||||
|
@ -148,6 +148,8 @@ register('en-US', {
|
||||
'Form.unique': 'Current value is not unique',
|
||||
'Form.validateFailed': 'Form input validation failed',
|
||||
'Form.nestedError': 'Form cannot appear as a descendant of form',
|
||||
'Iframe.invalid': 'Invalid iframe url',
|
||||
'Iframe.invalidProtocol': 'Can not use http url iframe in https',
|
||||
'Image.configError': 'Can only set one of crop or multiple',
|
||||
'Image.crop': 'Crop image',
|
||||
'Image.dragDrop': `Drag 'n' drop some photos here`,
|
||||
@ -188,6 +190,7 @@ register('en-US', {
|
||||
'Quarter.placeholder': 'Select a quarter',
|
||||
'Repeat.pre': 'Per',
|
||||
'reset': 'Reset',
|
||||
'save': 'Save',
|
||||
'saveFailed': 'Save failed',
|
||||
'saveSuccess': 'Saved successfully',
|
||||
'search': 'Search',
|
||||
@ -363,6 +366,7 @@ register('en-US', {
|
||||
'UserSelect.resultSort': 'Select result sort',
|
||||
'UserSelect.selected': 'Selected',
|
||||
'UserSelect.clear': 'empty',
|
||||
'UserSelect.sure': 'submit',
|
||||
'SchemaType.string': 'String',
|
||||
'SchemaType.number': 'Number',
|
||||
'SchemaType.integer': 'integer',
|
||||
|
@ -152,6 +152,8 @@ register('zh-CN', {
|
||||
'Form.unique': '当前值不唯一',
|
||||
'Form.validateFailed': '依赖的部分字段没有通过验证',
|
||||
'Form.nestedError': '表单不要直接嵌套在表单下面',
|
||||
'Iframe.invalid': 'iframe 地址不合法',
|
||||
'Iframe.invalidProtocol': '无法加载 http 协议的 iframe',
|
||||
'Image.configError': '图片多选配置和裁剪配置只能设置一个',
|
||||
'Image.crop': '裁剪图片',
|
||||
'Image.dragDrop': '将图片拖拽到此处',
|
||||
@ -193,6 +195,7 @@ register('zh-CN', {
|
||||
'Quarter.placeholder': '请选择季度',
|
||||
'Repeat.pre': '每',
|
||||
'reset': '重置',
|
||||
'save': '保存',
|
||||
'saveFailed': '保存失败',
|
||||
'saveSuccess': '保存成功',
|
||||
'search': '搜索',
|
||||
@ -358,6 +361,7 @@ register('zh-CN', {
|
||||
'UserSelect.resultSort': '选择结果排序',
|
||||
'UserSelect.selected': '已选',
|
||||
'UserSelect.clear': '清空',
|
||||
'UserSelect.sure': '确定',
|
||||
'SchemaType.string': '文本',
|
||||
'SchemaType.number': '数字',
|
||||
'SchemaType.integer': '整数',
|
||||
|
@ -1,8 +1,33 @@
|
||||
import React = require('react');
|
||||
import {cleanup, fireEvent, render, waitFor} from '@testing-library/react';
|
||||
/**
|
||||
* 组件名称:CRUD 增删改查
|
||||
* 单测内容:
|
||||
* 01. interval & headerToolbar & footerToolbar
|
||||
* 02. stopAutoRefreshWhen 停止自动刷新条件
|
||||
* 03. loadDataOnce 前端分页加载
|
||||
* 04. list模式
|
||||
* 05. card模式
|
||||
* 06. source 数据源 & alwaysShowPagination 总是显示分页
|
||||
* 07. filter 过滤器
|
||||
* 08. draggable & itemDraggableOn 拖拽
|
||||
* 09. quickEdit & quickSaveApi 快速编辑
|
||||
* 10. quickSaveItemApi 即时保存
|
||||
* 11. bulkActions 批量操作
|
||||
* 12. sortable & orderBy & orderDir & orderField 排序
|
||||
* 13. keepItemSelectionOnPageChange & maxKeepItemSelectionLength & labelTpl
|
||||
* 14. autoGenerateFilter 自动生成查询表单
|
||||
* 15. group 分组
|
||||
*/
|
||||
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved
|
||||
} from '@testing-library/react';
|
||||
import '../../src';
|
||||
import {clearStoresCache, render as amisRender} from '../../src';
|
||||
import {makeEnv, wait} from '../helper';
|
||||
import {makeEnv as makeEnvRaw, wait} from '../helper';
|
||||
import rows from '../mockData/rows';
|
||||
|
||||
afterEach(() => {
|
||||
@ -11,6 +36,9 @@ afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
/** 避免updateLocation里的console.error */
|
||||
const makeEnv = args => makeEnvRaw({updateLocation: () => {}, ...args});
|
||||
|
||||
async function fetcher(config: any) {
|
||||
return {
|
||||
status: 200,
|
||||
@ -171,16 +199,8 @@ test('Renderer:crud list', async () => {
|
||||
makeEnv({fetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).not.toBeInTheDocument();
|
||||
expect(container.querySelectorAll('.cxd-ListItem').length > 5).toBeTruthy();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
@ -223,15 +243,6 @@ test('Renderer:crud cards', async () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).not.toBeInTheDocument();
|
||||
expect(container.querySelector('.cxd-Card-title')).toBeInTheDocument();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Form:options:autoFill:data 1`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "a",
|
||||
"aId": 233,
|
||||
"aLabel": "OptionA",
|
||||
@ -10,7 +10,7 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`Form:options:autoFill:data 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "b",
|
||||
"aId": "",
|
||||
"aLabel": "OptionB",
|
||||
@ -19,30 +19,30 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`Form:options:autoFill:multiple:data 1`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "a",
|
||||
"aIds": Array [
|
||||
"aIds": [
|
||||
233,
|
||||
],
|
||||
"aLabels": Array [
|
||||
"aLabels": [
|
||||
"OptionA",
|
||||
],
|
||||
"aValues": Array [
|
||||
"aValues": [
|
||||
"a",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Form:options:autoFill:multiple:data 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "b",
|
||||
"aIds": Array [
|
||||
"aIds": [
|
||||
undefined,
|
||||
],
|
||||
"aLabels": Array [
|
||||
"aLabels": [
|
||||
"OptionB",
|
||||
],
|
||||
"aValues": Array [
|
||||
"aValues": [
|
||||
"b",
|
||||
],
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ exports[`Renderer:FormItem:validateApi:success 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:FormItem:validateApi:success 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "123",
|
||||
}
|
||||
`;
|
||||
|
@ -129,18 +129,18 @@ exports[`Renderer:Form 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:Form 2`] = `
|
||||
Object {
|
||||
"body": Object {
|
||||
{
|
||||
"body": {
|
||||
"a": "123",
|
||||
},
|
||||
"config": Object {
|
||||
"config": {
|
||||
"errorMessage": "saveFailed",
|
||||
"method": "post",
|
||||
"onFailed": [Function],
|
||||
"onSuccess": [Function],
|
||||
"successMessage": "saveSuccess",
|
||||
},
|
||||
"data": Object {
|
||||
"data": {
|
||||
"a": "123",
|
||||
},
|
||||
"method": "post",
|
||||
@ -837,7 +837,7 @@ exports[`Renderer:Form:onValidate 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:Form:onValidate 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
@ -1007,7 +1007,7 @@ exports[`Renderer:Form:onValidate 3`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:Form:onValidate 4`] = `
|
||||
Object {
|
||||
{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
@ -1429,7 +1429,7 @@ exports[`Renderer:Form:valdiate 2`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:Form:valdiate 3`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "123",
|
||||
}
|
||||
`;
|
||||
|
@ -67,7 +67,7 @@ exports[`Form:initData 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Form:initData 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
}
|
||||
@ -237,15 +237,15 @@ exports[`Form:initData:remote 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Form:initData:remote 2`] = `
|
||||
Object {
|
||||
"config": Object {
|
||||
{
|
||||
"config": {
|
||||
"cancelExecutor": [Function],
|
||||
"errorMessage": "fetchFailed",
|
||||
"onSuccess": [Function],
|
||||
"successMessage": undefined,
|
||||
},
|
||||
"method": "get",
|
||||
"query": Object {
|
||||
"query": {
|
||||
"c": "123",
|
||||
},
|
||||
"url": "/api/xxx?c=123",
|
||||
@ -253,7 +253,7 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`Form:initData:remote 3`] = `
|
||||
Object {
|
||||
{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "123",
|
||||
@ -424,4 +424,4 @@ exports[`Form:initData:without-super 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Form:initData:without-super 2`] = `Object {}`;
|
||||
exports[`Form:initData:without-super 2`] = `{}`;
|
||||
|
@ -1418,7 +1418,7 @@ exports[`Renderer:select table with labelField & valueField 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:select table with labelField & valueField 2`] = `
|
||||
Object {
|
||||
{
|
||||
"a": "zhugeliang,libai",
|
||||
}
|
||||
`;
|
||||
|
@ -1654,6 +1654,7 @@ exports[`Renderer:text with counter and maxLength 1`] = `
|
||||
<input
|
||||
autocomplete="off"
|
||||
class=""
|
||||
maxlength="10"
|
||||
name="text"
|
||||
placeholder=""
|
||||
size="10"
|
||||
@ -1787,6 +1788,7 @@ exports[`Renderer:text with counter and maxLength 2`] = `
|
||||
<input
|
||||
autocomplete="off"
|
||||
class=""
|
||||
maxlength="10"
|
||||
name="text"
|
||||
placeholder=""
|
||||
size="10"
|
||||
@ -1920,6 +1922,8 @@ exports[`Renderer:text with minLength 1`] = `
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="test-text-class-two"
|
||||
maxlength="8"
|
||||
minlength="5"
|
||||
name="text"
|
||||
placeholder=""
|
||||
size="10"
|
||||
|
@ -164,6 +164,7 @@ exports[`Renderer:textarea with maxLength & clearable & resetValue 1`] = `
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class="cxd-TextareaControl-input cxd-TextareaControl-input--counter"
|
||||
maxlength="9"
|
||||
name="text"
|
||||
spellcheck="false"
|
||||
>
|
||||
|
@ -99,14 +99,12 @@ test('Form:initData:super', async () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toBeCalled();
|
||||
expect(onSubmit.mock.calls[0][0]).toMatchInlineSnapshot(
|
||||
`
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
`
|
||||
);
|
||||
expect(onSubmit.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,60 +1,70 @@
|
||||
import React = require('react');
|
||||
/**
|
||||
* 组件名称:Image/Images 图片/图片集
|
||||
* 单测内容:
|
||||
* 1. Image图片
|
||||
* 2. Images图片集
|
||||
*/
|
||||
|
||||
import {render} from '@testing-library/react';
|
||||
import '../../src';
|
||||
import {render as amisRender} from '../../src';
|
||||
import {makeEnv} from '../helper';
|
||||
|
||||
test('Renderer:image', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'image',
|
||||
defaultImage: 'https://www.baidu.com/img/bd_logo1.png',
|
||||
title: '图片',
|
||||
description: '图片描述',
|
||||
imageClassName: 'b',
|
||||
className: 'show'
|
||||
},
|
||||
{},
|
||||
makeEnv({})
|
||||
)
|
||||
);
|
||||
describe('Renderer:image', () => {
|
||||
test('image:basic', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'image',
|
||||
defaultImage: 'https://www.baidu.com/img/bd_logo1.png',
|
||||
title: '图片',
|
||||
description: '图片描述',
|
||||
imageClassName: 'b',
|
||||
className: 'show'
|
||||
},
|
||||
{},
|
||||
makeEnv({})
|
||||
)
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('Renderer:images', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
data: {
|
||||
imageList: [
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80'
|
||||
describe('Renderer:images', () => {
|
||||
test('images:basic', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
data: {
|
||||
imageList: [
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80',
|
||||
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80'
|
||||
]
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'images',
|
||||
source: '${imageList}'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'images',
|
||||
name: 'imageList'
|
||||
}
|
||||
]
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'images',
|
||||
source: '${imageList}'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'images',
|
||||
name: 'imageList'
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
makeEnv({})
|
||||
)
|
||||
);
|
||||
{},
|
||||
makeEnv({})
|
||||
)
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -24,14 +24,18 @@ exports[`Renderer:anchorNav 1`] = `
|
||||
<li
|
||||
class="cxd-AnchorNav-link is-active"
|
||||
>
|
||||
<a>
|
||||
<a
|
||||
title="基本信息"
|
||||
>
|
||||
基本信息
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="cxd-AnchorNav-link"
|
||||
>
|
||||
<a>
|
||||
<a
|
||||
title="工作信息"
|
||||
>
|
||||
工作信息
|
||||
</a>
|
||||
</li>
|
||||
@ -396,7 +400,9 @@ exports[`Renderer:anchorNav horizontal 1`] = `
|
||||
<li
|
||||
class="cxd-AnchorNav-link is-active"
|
||||
>
|
||||
<a>
|
||||
<a
|
||||
title="基本信息"
|
||||
>
|
||||
基本信息
|
||||
</a>
|
||||
</li>
|
||||
|
@ -2933,60 +2933,6 @@ exports[`Renderer:crud basic interval headerToolbar footerToolbar 1`] = `
|
||||
`;
|
||||
|
||||
exports[`Renderer:crud cards 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-Page"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-content"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-main"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-body"
|
||||
>
|
||||
<div
|
||||
class="cxd-Crud is-loading"
|
||||
>
|
||||
<div
|
||||
class="cxd-Cards cxd-Crud-body"
|
||||
>
|
||||
<div
|
||||
class="cxd-Cards-fixedTop"
|
||||
/>
|
||||
<div
|
||||
class="cxd-Cards-placeholder"
|
||||
>
|
||||
<span
|
||||
class="cxd-TplField"
|
||||
>
|
||||
<span>
|
||||
没有用户信息
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<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--default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:crud cards 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-Page"
|
||||
@ -3846,56 +3792,11 @@ exports[`Renderer:crud cards 2`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:crud list 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-Page"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-content"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-main"
|
||||
>
|
||||
<div
|
||||
class="cxd-Page-body"
|
||||
>
|
||||
<div
|
||||
class="cxd-Crud is-loading"
|
||||
>
|
||||
<div
|
||||
class="cxd-List cxd-Crud-body"
|
||||
>
|
||||
<div
|
||||
class="cxd-List-heading"
|
||||
>
|
||||
list title
|
||||
</div>
|
||||
<div
|
||||
class="cxd-List-placeholder"
|
||||
>
|
||||
<span
|
||||
class="cxd-TplField"
|
||||
>
|
||||
<span>
|
||||
当前组内, 还没有配置任何权限.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-Spinner-overlay in"
|
||||
class="cxd-Spinner-overlay"
|
||||
/>
|
||||
<div
|
||||
class="cxd-Spinner cxd-Spinner--overlay in"
|
||||
class="cxd-Spinner cxd-Spinner--overlay"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<div
|
||||
@ -3911,7 +3812,7 @@ exports[`Renderer:crud list 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:crud list 2`] = `
|
||||
exports[`Renderer:crud list 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-Page"
|
||||
@ -4293,6 +4194,17 @@ exports[`Renderer:crud list 2`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-Spinner-overlay"
|
||||
/>
|
||||
<div
|
||||
class="cxd-Spinner cxd-Spinner--overlay"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<div
|
||||
class="cxd-Spinner-icon cxd-Spinner-icon--default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Renderer:image 1`] = `
|
||||
exports[`Renderer:image image:basic 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-ImageField cxd-ImageField--thumb show"
|
||||
@ -35,7 +35,7 @@ exports[`Renderer:image 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:images 1`] = `
|
||||
exports[`Renderer:images images:basic 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="cxd-Page"
|
||||
|
@ -25,7 +25,7 @@ exports[`Renderer:Page 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "This is Title",
|
||||
}
|
||||
}
|
||||
@ -56,7 +56,7 @@ exports[`Renderer:Page 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "This is SubTitle",
|
||||
}
|
||||
}
|
||||
@ -72,7 +72,7 @@ exports[`Renderer:Page 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "This is toolbar",
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ exports[`Renderer:Page 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "This is body",
|
||||
}
|
||||
}
|
||||
@ -992,12 +992,23 @@ exports[`Renderer:Page initApi reFetch when condition changes 1`] = `
|
||||
<div
|
||||
className="cxd-Page-body"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-overlay in"
|
||||
/>
|
||||
<div
|
||||
className="cxd-Spinner cxd-Spinner--overlay in"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="cxd-TplField"
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 1",
|
||||
}
|
||||
}
|
||||
@ -1023,12 +1034,23 @@ exports[`Renderer:Page initApi reFetch when condition changes 2`] = `
|
||||
<div
|
||||
className="cxd-Page-body"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-overlay in"
|
||||
/>
|
||||
<div
|
||||
className="cxd-Spinner cxd-Spinner--overlay in"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="cxd-TplField"
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 2",
|
||||
}
|
||||
}
|
||||
@ -1968,7 +1990,7 @@ exports[`Renderer:Page initData 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 1",
|
||||
}
|
||||
}
|
||||
@ -1994,12 +2016,23 @@ exports[`Renderer:Page initFetchOn trigger initApi fetch when condition becomes
|
||||
<div
|
||||
className="cxd-Page-body"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-overlay in"
|
||||
/>
|
||||
<div
|
||||
className="cxd-Spinner cxd-Spinner--overlay in"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<div
|
||||
className="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="cxd-TplField"
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 6",
|
||||
}
|
||||
}
|
||||
@ -2030,7 +2063,7 @@ exports[`Renderer:Page location query 1`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 5",
|
||||
}
|
||||
}
|
||||
@ -2061,7 +2094,7 @@ exports[`Renderer:Page location query 2`] = `
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "The variable value is 6",
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`store:index 1`] = `
|
||||
Object {
|
||||
{
|
||||
"storeType": "RendererStore",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`store:index 2`] = `
|
||||
Object {
|
||||
{
|
||||
"storeType": "RendererStore",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`store:index 3`] = `
|
||||
Object {
|
||||
{
|
||||
"storeType": "RendererStore",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`store:index 4`] = `
|
||||
Object {
|
||||
{
|
||||
"storeType": "RendererStore",
|
||||
}
|
||||
`;
|
||||
|
@ -1,12 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`store:ServiceStore 1`] = `
|
||||
Object {
|
||||
{
|
||||
"action": undefined,
|
||||
"busying": false,
|
||||
"checking": false,
|
||||
"childrenIds": Array [],
|
||||
"data": Object {},
|
||||
"childrenIds": [],
|
||||
"data": {},
|
||||
"dialogData": undefined,
|
||||
"dialogOpen": false,
|
||||
"disposed": false,
|
||||
@ -21,7 +21,7 @@ Object {
|
||||
"msg": "",
|
||||
"parentId": "",
|
||||
"path": "",
|
||||
"pristine": Object {},
|
||||
"pristine": {},
|
||||
"saving": false,
|
||||
"schema": null,
|
||||
"schemaKey": "",
|
||||
@ -31,13 +31,13 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`store:ServiceStore fetchInitData failed 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"action": undefined,
|
||||
"busying": false,
|
||||
"checking": false,
|
||||
"childrenIds": Array [],
|
||||
"data": Object {},
|
||||
"childrenIds": [],
|
||||
"data": {},
|
||||
"dialogData": undefined,
|
||||
"dialogOpen": false,
|
||||
"disposed": false,
|
||||
@ -52,19 +52,19 @@ Array [
|
||||
"msg": "",
|
||||
"parentId": "",
|
||||
"path": "",
|
||||
"pristine": Object {},
|
||||
"pristine": {},
|
||||
"saving": false,
|
||||
"schema": null,
|
||||
"schemaKey": "",
|
||||
"storeType": "ServiceStore",
|
||||
"updatedAt": 0,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"action": undefined,
|
||||
"busying": false,
|
||||
"checking": false,
|
||||
"childrenIds": Array [],
|
||||
"data": Object {},
|
||||
"childrenIds": [],
|
||||
"data": {},
|
||||
"dialogData": undefined,
|
||||
"dialogOpen": false,
|
||||
"disposed": false,
|
||||
@ -79,7 +79,7 @@ Array [
|
||||
"msg": "",
|
||||
"parentId": "",
|
||||
"path": "",
|
||||
"pristine": Object {},
|
||||
"pristine": {},
|
||||
"saving": false,
|
||||
"schema": null,
|
||||
"schemaKey": "",
|
||||
@ -90,13 +90,13 @@ Array [
|
||||
`;
|
||||
|
||||
exports[`store:ServiceStore fetchInitData success 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"action": undefined,
|
||||
"busying": false,
|
||||
"checking": false,
|
||||
"childrenIds": Array [],
|
||||
"data": Object {},
|
||||
"childrenIds": [],
|
||||
"data": {},
|
||||
"dialogData": undefined,
|
||||
"dialogOpen": false,
|
||||
"disposed": false,
|
||||
@ -111,18 +111,18 @@ Array [
|
||||
"msg": "",
|
||||
"parentId": "",
|
||||
"path": "",
|
||||
"pristine": Object {},
|
||||
"pristine": {},
|
||||
"saving": false,
|
||||
"schema": null,
|
||||
"schemaKey": "",
|
||||
"storeType": "ServiceStore",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"action": undefined,
|
||||
"busying": false,
|
||||
"checking": false,
|
||||
"childrenIds": Array [],
|
||||
"data": Object {
|
||||
"childrenIds": [],
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
@ -140,7 +140,7 @@ Array [
|
||||
"msg": "",
|
||||
"parentId": "",
|
||||
"path": "",
|
||||
"pristine": Object {
|
||||
"pristine": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
|
@ -42,12 +42,11 @@
|
||||
"dependencies": {
|
||||
"amis-core": "^2.2.0",
|
||||
"amis-ui": "^2.2.0",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"attr-accept": "2.2.2",
|
||||
"blueimp-canvastoblob": "2.1.0",
|
||||
"classnames": "2.3.1",
|
||||
"downshift": "6.1.7",
|
||||
"echarts": "5.3.3",
|
||||
"downshift": "6.1.12",
|
||||
"echarts": "5.4.0",
|
||||
"echarts-stat": "^1.2.0",
|
||||
"exceljs": "^4.3.0",
|
||||
"file-saver": "^2.0.2",
|
||||
@ -72,7 +71,7 @@
|
||||
"react-dropzone": "^11.4.2",
|
||||
"react-json-view": "1.21.3",
|
||||
"react-transition-group": "4.4.2",
|
||||
"sortablejs": "1.14.0",
|
||||
"sortablejs": "1.15.0",
|
||||
"tslib": "^2.3.1",
|
||||
"video-react": "0.15.0"
|
||||
},
|
||||
@ -80,7 +79,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@svgr/rollup": "^6.2.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@ -119,9 +118,9 @@
|
||||
"glob": "^7.2.0",
|
||||
"history": "^4.7.2",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^28.1.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json5": "^2.2.1",
|
||||
"lint-staged": "^12.3.3",
|
||||
@ -141,10 +140,10 @@
|
||||
"react-router-dom": "5.3.0",
|
||||
"react-test-renderer": "^18.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.73.0",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-auto-external": "^2.0.0",
|
||||
"rollup-plugin-license": "^2.7.0",
|
||||
"ts-jest": "^28.0.3",
|
||||
"ts-jest": "^29.0.2",
|
||||
"ts-json-schema-generator": "0.96.0",
|
||||
"ts-node": "^10.5.0",
|
||||
"typescript": "^4.6.4"
|
||||
@ -193,7 +192,12 @@
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"\\.(ts|tsx)$": "ts-jest"
|
||||
"\\.(ts|tsx)$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"diagnostics": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"setupFiles": [
|
||||
"jest-canvas-mock"
|
||||
@ -211,12 +215,7 @@
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/.rollup.cache/"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"amis-core": "*",
|
||||
|
@ -2,7 +2,9 @@ import {
|
||||
addRootWrapper,
|
||||
extendDefaultEnv,
|
||||
LazyComponent,
|
||||
render
|
||||
render,
|
||||
themeable,
|
||||
ThemeProps
|
||||
} from 'amis-core';
|
||||
|
||||
import {
|
||||
@ -55,4 +57,30 @@ addRootWrapper((props: any) => {
|
||||
);
|
||||
});
|
||||
|
||||
LazyComponent.defaultProps.placeholder = <Spinner />;
|
||||
const SimpleSpinner = themeable(
|
||||
(
|
||||
props: {
|
||||
className?: string;
|
||||
spinnerClassName?: string;
|
||||
} & ThemeProps
|
||||
) => {
|
||||
const cx = props.classnames;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="spinner"
|
||||
className={cx(`Spinner`, 'in', props.className)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
`Spinner-icon`,
|
||||
'Spinner-icon--default',
|
||||
props.spinnerClassName
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
LazyComponent.defaultProps.placeholder = <SimpleSpinner />;
|
||||
|
@ -44,7 +44,7 @@ import {ActionSchema} from './Action';
|
||||
import {CardsSchema} from './Cards';
|
||||
import {ListSchema} from './List';
|
||||
import {TableSchema} from './Table';
|
||||
import {isPureVariable, resolveVariableAndFilter} from 'amis-core';
|
||||
import {isPureVariable, resolveVariableAndFilter, parseQuery} from 'amis-core';
|
||||
|
||||
import type {PaginationProps} from './Pagination';
|
||||
|
||||
@ -448,14 +448,14 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
|
||||
if (syncLocation && location && (location.query || location.search)) {
|
||||
store.updateQuery(
|
||||
qsparse(location.search.substring(1)),
|
||||
parseQuery(location),
|
||||
undefined,
|
||||
pageField,
|
||||
perPageField
|
||||
);
|
||||
} else if (syncLocation && !location && window.location.search) {
|
||||
store.updateQuery(
|
||||
qsparse(window.location.search.substring(1)) as object,
|
||||
parseQuery(window.location),
|
||||
undefined,
|
||||
pageField,
|
||||
perPageField
|
||||
@ -545,7 +545,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
) {
|
||||
// 同步地址栏,那么直接检测 query 是否变了,变了就重新拉数据
|
||||
store.updateQuery(
|
||||
qsparse(props.location.search.substring(1)),
|
||||
parseQuery(props.location),
|
||||
undefined,
|
||||
props.pageField,
|
||||
props.perPageField
|
||||
|
@ -12,7 +12,8 @@ import {
|
||||
qsstringify,
|
||||
qsparse,
|
||||
isArrayChildrenModified,
|
||||
autobind
|
||||
autobind,
|
||||
parseQuery
|
||||
} from 'amis-core';
|
||||
import {ScopedContext, IScopedContext} from 'amis-core';
|
||||
import Button from 'amis-ui';
|
||||
@ -254,14 +255,14 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
|
||||
if (syncLocation && location && (location.query || location.search)) {
|
||||
store.updateQuery(
|
||||
qsparse(location.search.substring(1)),
|
||||
parseQuery(location),
|
||||
undefined,
|
||||
pageField,
|
||||
perPageField
|
||||
);
|
||||
} else if (syncLocation && !location && window.location.search) {
|
||||
store.updateQuery(
|
||||
qsparse(window.location.search.substring(1)) as object,
|
||||
parseQuery(window.location),
|
||||
undefined,
|
||||
pageField,
|
||||
perPageField
|
||||
@ -331,7 +332,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
) {
|
||||
// 同步地址栏,那么直接检测 query 是否变了,变了就重新拉数据
|
||||
store.updateQuery(
|
||||
qsparse(props.location.search.substring(1)),
|
||||
parseQuery(props.location),
|
||||
undefined,
|
||||
props.pageField,
|
||||
props.perPageField
|
||||
|
@ -579,9 +579,7 @@ export default class Drawer extends React.Component<DrawerProps> {
|
||||
onExited={this.handleExited}
|
||||
closeOnEsc={closeOnEsc}
|
||||
closeOnOutside={
|
||||
!store.drawerOpen &&
|
||||
!store.dialogOpen &&
|
||||
(closeOnOutside || !showCloseButton)
|
||||
!store.drawerOpen && !store.dialogOpen && closeOnOutside
|
||||
}
|
||||
container={
|
||||
drawerContainer
|
||||
|
@ -117,6 +117,11 @@ export interface ComboControlSchema extends FormBaseControlSchema {
|
||||
*/
|
||||
addable?: boolean;
|
||||
|
||||
/**
|
||||
* Add at top
|
||||
*/
|
||||
addattop?: boolean;
|
||||
|
||||
/**
|
||||
* 数组输入框的子项
|
||||
*/
|
||||
@ -459,7 +464,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
addItemWith(condition: ComboCondition) {
|
||||
const {flat, joinValues, delimiter, scaffold, disabled, submitOnChange} =
|
||||
const {flat, joinValues, addattop, delimiter, scaffold, disabled, submitOnChange} =
|
||||
this.props;
|
||||
|
||||
if (disabled) {
|
||||
@ -481,6 +486,10 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
value = value.join(delimiter || ',');
|
||||
}
|
||||
|
||||
if (addattop === true){
|
||||
value.unshift(value.pop());
|
||||
}
|
||||
|
||||
this.props.onChange(value, submitOnChange, true);
|
||||
}
|
||||
|
||||
@ -488,6 +497,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
const {
|
||||
flat,
|
||||
joinValues,
|
||||
addattop,
|
||||
delimiter,
|
||||
scaffold,
|
||||
disabled,
|
||||
@ -527,6 +537,10 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
value = value.join(delimiter || ',');
|
||||
}
|
||||
|
||||
if (addattop === true){
|
||||
value.unshift(value.pop());
|
||||
}
|
||||
|
||||
this.props.onChange(value, submitOnChange, true);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, {Suspense} from 'react';
|
||||
import React from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import {autobind, createObject} from 'amis-core';
|
||||
import {FormItem, FormControlProps, FormBaseControl} from 'amis-core';
|
||||
import {autobind, createObject, isObject} from 'amis-core';
|
||||
import {FormItem, FormControlProps} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import type {CellValue, CellRichTextValue} from 'exceljs';
|
||||
|
||||
/**
|
||||
* Excel 解析
|
||||
@ -115,6 +116,69 @@ export default class ExcelControl extends React.PureComponent<
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前单元格数据是否为富文本
|
||||
*
|
||||
* @reference https://github.com/exceljs/exceljs#rich-text
|
||||
*/
|
||||
isRichTextValue(value: any) {
|
||||
return !!(
|
||||
value &&
|
||||
isObject(value) &&
|
||||
value.hasOwnProperty('richText') &&
|
||||
Array.isArray(value?.richText)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将富文本类型的单元格内容转化为Plain Text
|
||||
*
|
||||
* @param {CellRichTextValue} cellValue 单元格值
|
||||
* @param {Boolean} html 是否输出为html格式
|
||||
*/
|
||||
richText2PlainString(cellValue: CellRichTextValue, html = false) {
|
||||
const result = cellValue.richText.map(({text, font = {}}) => {
|
||||
let outputStr = text;
|
||||
|
||||
/* 如果以HTML格式输出,简单处理一下样式 */
|
||||
if (html) {
|
||||
let styles = '';
|
||||
const htmlTag = font?.bold
|
||||
? 'strong'
|
||||
: font?.italic
|
||||
? 'em'
|
||||
: font?.vertAlign === 'superscript'
|
||||
? 'sup'
|
||||
: font?.vertAlign === 'subscript'
|
||||
? 'sub'
|
||||
: 'span';
|
||||
|
||||
if (font?.strike) {
|
||||
styles += 'text-decoration: line-through;';
|
||||
} else if (font?.underline) {
|
||||
styles += 'text-decoration: underline;';
|
||||
}
|
||||
|
||||
if (font?.outline) {
|
||||
styles += 'outline: solid;';
|
||||
}
|
||||
|
||||
if (font?.size) {
|
||||
styles += `font-size: ${font.size}px;`;
|
||||
}
|
||||
|
||||
outputStr = `<${htmlTag} ${
|
||||
styles ? `style=${styles}` : ''
|
||||
}>${text}</${htmlTag}>`;
|
||||
}
|
||||
|
||||
return outputStr;
|
||||
});
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取单个 sheet 的内容
|
||||
*/
|
||||
@ -134,7 +198,11 @@ export default class ExcelControl extends React.PureComponent<
|
||||
worksheet.eachRow((row: any, rowNumber: number) => {
|
||||
// 将第一列作为字段名
|
||||
if (rowNumber == 1) {
|
||||
firstRowValues = row.values;
|
||||
firstRowValues = (row.values ?? []).map((item: CellValue) =>
|
||||
this.isRichTextValue(item)
|
||||
? this.richText2PlainString(item as CellRichTextValue)
|
||||
: item
|
||||
);
|
||||
} else {
|
||||
const data: any = {};
|
||||
if (includeEmpty) {
|
||||
|
@ -468,7 +468,7 @@ export default class ImageControl extends React.Component<
|
||||
componentDidUpdate(prevProps: ImageProps) {
|
||||
const props = this.props;
|
||||
|
||||
if (prevProps.value !== props.value && this.emitValue !== props.value) {
|
||||
if (prevProps.value !== props.value) {
|
||||
const value: string | Array<string | FileValue> | FileValue = props.value;
|
||||
const multiple = props.multiple;
|
||||
const joinValues = props.joinValues;
|
||||
|
@ -64,7 +64,12 @@ export interface TextControlSchema extends FormOptionsSchema {
|
||||
borderMode?: 'full' | 'half' | 'none';
|
||||
|
||||
/**
|
||||
* 限制文字个数
|
||||
* 限制文字最小输入个数
|
||||
*/
|
||||
minLength?: number;
|
||||
|
||||
/**
|
||||
* 限制文字最大输入个数
|
||||
*/
|
||||
maxLength?: number;
|
||||
|
||||
@ -581,6 +586,8 @@ export default class TextControl extends React.PureComponent<
|
||||
? ''
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: value instanceof Date
|
||||
? value.toISOString()
|
||||
: JSON.stringify(value);
|
||||
}
|
||||
|
||||
@ -608,6 +615,7 @@ export default class TextControl extends React.PureComponent<
|
||||
borderMode,
|
||||
showCounter,
|
||||
maxLength,
|
||||
minLength,
|
||||
translate: __
|
||||
} = this.props;
|
||||
let type = this.props.type?.replace(/^(?:native|input)\-/, '');
|
||||
@ -711,7 +719,9 @@ export default class TextControl extends React.PureComponent<
|
||||
onFocus: this.handleFocus,
|
||||
onBlur: this.handleBlur,
|
||||
onChange: this.handleInputChange,
|
||||
onKeyDown: this.handleKeyDown
|
||||
onKeyDown: this.handleKeyDown,
|
||||
maxLength,
|
||||
minLength
|
||||
})}
|
||||
autoComplete="off"
|
||||
size={10}
|
||||
@ -818,7 +828,8 @@ export default class TextControl extends React.PureComponent<
|
||||
suffix,
|
||||
data,
|
||||
showCounter,
|
||||
maxLength
|
||||
maxLength,
|
||||
minLength
|
||||
} = this.props;
|
||||
|
||||
const type = this.props.type?.replace(/^(?:native|input)\-/, '');
|
||||
@ -850,6 +861,8 @@ export default class TextControl extends React.PureComponent<
|
||||
onBlur={this.handleBlur}
|
||||
max={max}
|
||||
min={min}
|
||||
maxLength={maxLength}
|
||||
minLength={minLength}
|
||||
autoComplete="off"
|
||||
size={10}
|
||||
step={step}
|
||||
|
@ -281,6 +281,7 @@ export default class PickerControl extends React.PureComponent<
|
||||
additionalOptions.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
additionalOptions.length && setOptions(options.concat(additionalOptions));
|
||||
const rendererEvent = await dispatchEvent(
|
||||
@ -294,6 +295,25 @@ export default class PickerControl extends React.PureComponent<
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
|
||||
@autobind
|
||||
async handleItemClick(itemlabel: string, itemid: string) {
|
||||
const {
|
||||
data,
|
||||
dispatchEvent,
|
||||
setOptions
|
||||
} = this.props;
|
||||
|
||||
const rendererEvent = await dispatchEvent(
|
||||
'itemclick',
|
||||
createObject(data, {'label': itemlabel, 'id': itemid})
|
||||
);
|
||||
if (rendererEvent?.prevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
removeItem(index: number) {
|
||||
const {
|
||||
selectedOptions,
|
||||
@ -393,7 +413,13 @@ export default class PickerControl extends React.PureComponent<
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span className={`${ns}Picker-valueLabel`}>
|
||||
<span
|
||||
className={`${ns}Picker-valueLabel`}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.handleItemClick(getVariable(item, labelField || 'label'), getVariable(item, 'id')|| '');
|
||||
}}
|
||||
>
|
||||
{labelTpl ? (
|
||||
<Html html={filter(labelTpl, item)} />
|
||||
) : (
|
||||
|
@ -201,11 +201,13 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
this.input && this.input.focus();
|
||||
}
|
||||
|
||||
getValue(value: Option | Array<Option> | string | void) {
|
||||
getValue(
|
||||
value: Option | Array<Option> | string | void,
|
||||
additonalOptions: Array<any> = []
|
||||
) {
|
||||
const {joinValues, extractValue, delimiter, multiple, valueField, options} =
|
||||
this.props;
|
||||
let newValue: string | Option | Array<Option> | void = value;
|
||||
let additonalOptions: Array<any> = [];
|
||||
|
||||
(Array.isArray(value) ? value : value ? [value] : []).forEach(
|
||||
(option: any) => {
|
||||
@ -268,8 +270,12 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
async changeValue(value: Option | Array<Option> | string | void) {
|
||||
const {onChange, setOptions, options, data, dispatchEvent} = this.props;
|
||||
|
||||
let newValue: string | Option | Array<Option> | void = this.getValue(value);
|
||||
let additonalOptions: Array<any> = [];
|
||||
let newValue: string | Option | Array<Option> | void = this.getValue(
|
||||
value,
|
||||
additonalOptions
|
||||
);
|
||||
|
||||
// 不设置没法回显
|
||||
additonalOptions.length && setOptions(options.concat(additonalOptions));
|
||||
|
||||
@ -531,7 +537,7 @@ class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProp
|
||||
if (
|
||||
selectMode === 'associated' &&
|
||||
options &&
|
||||
options.length === 1 &&
|
||||
options.length &&
|
||||
options[0].leftOptions &&
|
||||
Array.isArray(options[0].children)
|
||||
) {
|
||||
|
@ -207,6 +207,23 @@ export class BaseTransferRenderer<
|
||||
joinValues || extractValue
|
||||
? value[(valueField as string) || 'value']
|
||||
: value;
|
||||
const indexes = findTreeIndex(
|
||||
options,
|
||||
optionValueCompare(
|
||||
value[(valueField as string) || 'value'],
|
||||
(valueField as string) || 'value'
|
||||
)
|
||||
);
|
||||
|
||||
if (!indexes) {
|
||||
newOptions.push(value);
|
||||
} else if (optionModified) {
|
||||
const origin = getTree(newOptions, indexes);
|
||||
newOptions = spliceTree(newOptions, indexes, 1, {
|
||||
...origin,
|
||||
...value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(newOptions.length > options.length || optionModified) &&
|
||||
@ -258,7 +275,7 @@ export class BaseTransferRenderer<
|
||||
const result =
|
||||
payload.data.options || payload.data.items || payload.data;
|
||||
if (!Array.isArray(result)) {
|
||||
throw new Error('CRUD.invalidArray');
|
||||
throw new Error(__('CRUD.invalidArray'));
|
||||
}
|
||||
|
||||
return result.map(item => {
|
||||
|
@ -86,7 +86,7 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
|
||||
if (
|
||||
selectMode === 'associated' &&
|
||||
options &&
|
||||
options.length === 1 &&
|
||||
options.length &&
|
||||
options[0].leftOptions &&
|
||||
Array.isArray(options[0].children)
|
||||
) {
|
||||
|
@ -655,6 +655,7 @@ export default class TreeSelectControl extends React.Component<
|
||||
onKeyDown={this.handleInputKeyDown}
|
||||
clearable={clearable}
|
||||
allowInput={searchable || isEffectiveApi(autoComplete)}
|
||||
hasDropDownArrow
|
||||
>
|
||||
{loading ? <Spinner size="sm" /> : undefined}
|
||||
</ResultBox>
|
||||
|
@ -1,13 +1,7 @@
|
||||
import React from 'react';
|
||||
import {FormHorizontal, Renderer, RendererProps} from 'amis-core';
|
||||
import {Schema} from 'amis-core';
|
||||
import pick from 'lodash/pick';
|
||||
import {
|
||||
BaseSchema,
|
||||
SchemaClassName,
|
||||
SchemaCollection,
|
||||
SchemaObject
|
||||
} from '../Schema';
|
||||
import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema';
|
||||
|
||||
import {ucFirst} from 'amis-core';
|
||||
import {Spinner} from 'amis-ui';
|
||||
|
@ -170,7 +170,9 @@ export default class IFrame extends React.Component<IFrameProps, object> {
|
||||
style,
|
||||
allow,
|
||||
sandbox,
|
||||
referrerpolicy
|
||||
referrerpolicy,
|
||||
translate: __,
|
||||
env
|
||||
} = this.props;
|
||||
|
||||
let tempStyle: any = {};
|
||||
@ -192,7 +194,15 @@ export default class IFrame extends React.Component<IFrameProps, object> {
|
||||
finalSrc &&
|
||||
!/^(\.\/|\.\.\/|\/|https?\:\/\/|\/\/)/.test(finalSrc)
|
||||
) {
|
||||
return <p>请填写合法的 iframe 地址</p>;
|
||||
return <p>{__('Iframe.invalid')}</p>;
|
||||
}
|
||||
|
||||
if (
|
||||
location.protocol === 'https:' &&
|
||||
finalSrc &&
|
||||
finalSrc.startsWith('http://')
|
||||
) {
|
||||
env.notify('error', __('Iframe.invalidProtocol'));
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,13 +1,24 @@
|
||||
import React from 'react';
|
||||
import {Renderer, RendererProps} from 'amis-core';
|
||||
import {filter} from 'amis-core';
|
||||
import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
|
||||
import {themeable, ThemeProps} from 'amis-core';
|
||||
import {autobind, getPropValue} from 'amis-core';
|
||||
import {Icon} from 'amis-ui';
|
||||
import {LocaleProps, localeable} from 'amis-core';
|
||||
import {BaseSchema, SchemaClassName, SchemaTpl, SchemaUrlPath} from '../Schema';
|
||||
import {resolveVariable} from 'amis-core';
|
||||
import {handleAction} from 'amis-core';
|
||||
import type {
|
||||
ImageAction,
|
||||
ImageActionKey
|
||||
} from 'amis-ui/lib/components/ImageGallery';
|
||||
|
||||
export interface ImageToolbarAction {
|
||||
key: keyof typeof ImageActionKey;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
iconClassName?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片展示控件。
|
||||
@ -124,6 +135,16 @@ export interface ImageSchema extends BaseSchema {
|
||||
* 链接的 target
|
||||
*/
|
||||
htmlTarget?: string;
|
||||
|
||||
/**
|
||||
* 是否展示图片工具栏
|
||||
*/
|
||||
showToolbar?: boolean;
|
||||
|
||||
/**
|
||||
* 工具栏配置
|
||||
*/
|
||||
toolbarActions?: ImageToolbarAction[];
|
||||
}
|
||||
|
||||
export interface ImageThumbProps
|
||||
@ -284,6 +305,8 @@ export interface ImageFieldProps extends RendererProps {
|
||||
thumbRatio: '1:1' | '4:3' | '16:9';
|
||||
originalSrc?: string; // 原图
|
||||
enlargeAble?: boolean;
|
||||
showToolbar?: boolean;
|
||||
toolbarActions?: ImageAction[];
|
||||
onImageEnlarge?: (
|
||||
info: {
|
||||
src: string;
|
||||
@ -292,6 +315,8 @@ export interface ImageFieldProps extends RendererProps {
|
||||
caption?: string;
|
||||
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
|
||||
thumbRatio?: '1:1' | '4:3' | '16:9';
|
||||
showToolbar?: boolean;
|
||||
toolbarActions?: ImageAction[];
|
||||
},
|
||||
target: any
|
||||
) => void;
|
||||
@ -317,7 +342,13 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
|
||||
thumbMode,
|
||||
thumbRatio
|
||||
}: ImageThumbProps) {
|
||||
const {onImageEnlarge, enlargeTitle, enlargeCaption} = this.props;
|
||||
const {
|
||||
onImageEnlarge,
|
||||
enlargeTitle,
|
||||
enlargeCaption,
|
||||
showToolbar,
|
||||
toolbarActions
|
||||
} = this.props;
|
||||
|
||||
onImageEnlarge &&
|
||||
onImageEnlarge(
|
||||
@ -327,7 +358,9 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
|
||||
title: enlargeTitle || title,
|
||||
caption: enlargeCaption || caption,
|
||||
thumbMode,
|
||||
thumbRatio
|
||||
thumbRatio,
|
||||
showToolbar,
|
||||
toolbarActions
|
||||
},
|
||||
this.props
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user