Merge branch 'master' into doc-editor.md

This commit is contained in:
findreamer 2023-11-16 20:03:16 +08:00
commit 987a993666
234 changed files with 11107 additions and 5501 deletions

View File

@ -220,9 +220,12 @@ order: 36
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`来获取事件产生的数据(`< 2.3.2 及以下版本 ${event.data.[事件参数名]}`详细请查看[事件动作](../../docs/concepts/event-action)
| 事件名称 | 事件参数 | 说明 |
| -------- | ------------------------ | ---------------- |
| change | `value: string` 组件的值 | 时间值变化时触发 |
| 事件名称 | 事件参数 | 说明 |
| ---------- | ------------------------ | ------------------ |
| change | `value: string` 组件的值 | 时间值变化时触发 |
| click | `value: string` 组件的值 | 点击日期时触发 |
| mouseenter | `value: string` 组件的值 | 鼠标移入日期时触发 |
| mouseleave | `value: string` 组件的值 | 鼠标移出日期时触发 |
## 动作表

View File

@ -614,6 +614,24 @@ Cards 模式支持 [Cards](./cards) 中的所有功能。
如果数据量比较大不适合一次性加载,可以配置 `deferApi` 接口,结合行数据中标记 `defer: true` 属性,实现懒加载。
注意 `deferApi` 结果返回跟 `api` 返回不一样,`deferApi` 返回的是节点详情,而不是列表,列表应该是节点信息的 children 属性里面。也就是说 deferApi 还可以进一步完善节点信息。返回格式参考如下:
```
{
status: 0,
data: {
id: 1,
xxxProp: 'abc',
children: [
{
id: 11,
name: '子节点'
}
]
}
}
```
```schema: scope="body"
{
"type": "crud",
@ -1054,6 +1072,51 @@ amis 只负责生成下拉选择器组件,并将搜索参数传递给接口,
你可以通过[数据映射](../../docs/concepts/data-mapping),在`api`中获取这些参数。
#### 下拉数据源
过滤器的数据域支持API接口和上下文数据(`3.6.0`及以上版本)
```schema
{
"type": "page",
"data": {
"options": [
{"label": "4", "value": 3},
{"label": "5", "value": 5},
{"label": "5.5", "value": 5.5},
{"label": "6", "value": 6}
]
},
"body": [
{
"type": "crud",
"syncLocation": false,
"api": "/api/mock2/sample",
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "grade",
"label": "CSS grade",
"filterable": {
"source": "/api/mock2/crud/filterOptions"
}
},
{
"name": "version",
"label": "Version",
"filterable": {
"source": "${options}"
}
}
]
}
]
}
```
### 快速编辑
可以通过给列配置:`"quickEdit":true`和`quickSaveApi` 可以实现表格内快速编辑并批量保存的功能。
@ -3085,6 +3148,32 @@ CRUD 中不限制有多少个单条操作、添加一个操作对应的添加一
}
```
### 匹配函数
> `3.5.0` 及以上版本
支持自定义匹配函数`matchFunc`,当开启`loadDataOnce`时,会基于该函数计算的匹配结果进行过滤,主要用于处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景,函数签名如下:
```typescript
interface CRUDMatchFunc {
(
/* 当前列表的全量数据 */
items: any,
/* 最近一次接口返回的全量数据 */
itemsRaw: any,
/** 相关配置 */
options?: {
/* 查询参数 */
query: Record<string, any>;
/* 列配置 */
columns: any;
}
): boolean;
}
```
具体效果请参考[示例](../../../examples/crud/match-func)。
## 动态列
> since 1.1.6
@ -3220,62 +3309,63 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据,
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| ------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| type | `string` | | `type` 指定为 CRUD 渲染器 |
| mode | `string` | `"table"` | `"table" 、 "cards" 或者 "list"` |
| title | `string` | `""` | 可设置成空,当设置成空时,没有标题栏 |
| className | `string` | | 表格外层 Dom 的类名 |
| api | [API](../../docs/types/api) | | CRUD 用来获取列表数据的 api。 |
| deferApi | [API](../../docs/types/api) | | 当行数据中有 defer 属性时,用此接口进一步加载内容 |
| loadDataOnce | `boolean` | | 是否一次性加载所有数据(前端分页) |
| loadDataOnceFetchOnFilter | `boolean` | `true` | 在开启 loadDataOnce 时filter 时是否去重新请求 api |
| source | `string` | | 数据映射接口返回某字段的值,不设置会默认使用接口返回的`${items}`或者`${rows}`,也可以设置成上层数据源的内容 |
| filter | [Form](./form/index) | | 设置过滤器,当该表单提交后,会把数据带给当前 `mode` 刷新列表。 |
| filterTogglable | `boolean` \| `{label: string; icon: string; activeLabel: string; activeIcon?: stirng;}` | `false` | 是否可显隐过滤器 |
| filterDefaultVisible | `boolean` | `true` | 设置过滤器默认是否可见。 |
| initFetch | `boolean` | `true` | 是否初始化的时候拉取数据, 只针对有 filter 的情况, 没有 filter 初始都会拉取数据 |
| interval | `number` | `3000` | 刷新时间(最低 1000) |
| silentPolling | `boolean` | `false` | 配置刷新时是否隐藏加载动画 |
| stopAutoRefreshWhen | `string` | `""` | 通过[表达式](../../docs/concepts/expression)来配置停止刷新的条件 |
| stopAutoRefreshWhenModalIsOpen | `boolean` | `false` | 当有弹框时关闭自动刷新,关闭弹框又恢复 |
| syncLocation | `boolean` | `true` | 是否将过滤条件的参数同步到地址栏 |
| draggable | `boolean` | `false` | 是否可通过拖拽排序 |
| resizable | `boolean` | `true` | 是否可以调整列宽度 |
| itemDraggableOn | `boolean` | | 用[表达式](../../docs/concepts/expression)来配置是否可拖拽排序 |
| [saveOrderApi](#saveOrderApi) | [API](../../docs/types/api) | | 保存排序的 api。 |
| [quickSaveApi](#quickSaveApi) | [API](../../docs/types/api) | | 快速编辑后用来批量保存的 API。 |
| [quickSaveItemApi](#quickSaveItemApi) | [API](../../docs/types/api) | | 快速编辑配置成及时保存时使用的 API。 |
| bulkActions | Array<[Action](./action)> | | 批量操作列表,配置后,表格可进行选中操作。 |
| messages | `Object` | | 覆盖消息提示,如果不指定,将采用 api 返回的 message |
| messages.fetchFailed | `string` | | 获取失败时提示 |
| messages.saveOrderFailed | `string` | | 保存顺序失败提示 |
| messages.saveOrderSuccess | `string` | | 保存顺序成功提示 |
| messages.quickSaveFailed | `string` | | 快速保存失败提示 |
| messages.quickSaveSuccess | `string` | | 快速保存成功提示 |
| primaryField | `string` | `"id"` | 设置 ID 字段名。 |
| perPage | `number` | 10 | 设置一页显示多少条数据。 |
| orderBy | `string` | | 默认排序字段,这个是传给后端,需要后端接口实现 |
| orderDir | `asc` \| `desc` | | 排序方向 |
| defaultParams | `Object` | | 设置默认 filter 默认参数,会在查询的时候一起发给后端 |
| pageField | `string` | `"page"` | 设置分页页码字段名。 |
| perPageField | `string` | `"perPage"` | 设置分页一页显示的多少条数据的字段名。注意:最好与 defaultParams 一起使用,请看下面例子。 |
| pageDirectionField | `string` | `"pageDir"` | 分页方向字段名可能是 forward 或者 backward |
| perPageAvailable | `Array<number>` | `[5, 10, 20, 50, 100]` | 设置一页显示多少条数据下拉框可选条数。 |
| orderField | `string` | | 设置用来确定位置的字段名,设置后新的顺序将被赋值到该字段中。 |
| hideQuickSaveBtn | `boolean` | `false` | 隐藏顶部快速保存提示 |
| autoJumpToTopOnPagerChange | `boolean` | `false` | 当切分页的时候,是否自动跳顶部。 |
| syncResponse2Query | `boolean` | `true` | 将返回数据同步到过滤器上。 |
| keepItemSelectionOnPageChange | `boolean` | `true` | 保留条目选择,默认分页、搜素后,用户选择条目会被清空,开启此选项后会保留用户选择,可以实现跨页面批量操作。 |
| labelTpl | `string` | | 单条描述模板,`keepItemSelectionOnPageChange`设置为`true`后会把所有已选择条目列出来,此选项可以用来定制条目展示文案。 |
| headerToolbar | Array | `['bulkActions', 'pagination']` | 顶部工具栏配置 |
| footerToolbar | Array | `['statistics', 'pagination']` | 底部工具栏配置 |
| alwaysShowPagination | `boolean` | `false` | 是否总是显示分页 |
| affixHeader | `boolean` | `true` | 是否固定表头(table 下) |
| autoGenerateFilter | `Object \| boolean` | | 是否开启查询区域,开启后会根据列元素的 `searchable` 属性值,自动生成查询条件表单 |
| resetPageAfterAjaxItemAction | `boolean` | `false` | 单条数据 ajax 操作后是否重置页码为第一页 |
| autoFillHeight | `boolean``{height: number}` | | 内容区域自适应高度 |
| canAccessSuperData | `boolean` | `true` | 指定是否可以自动获取上层的数据并映射到表格行数据上,如果列也配置了该属性,则列的优先级更高 |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| type | `string` | | `type` 指定为 CRUD 渲染器 |
| mode | `string` | `"table"` | `"table" 、 "cards" 或者 "list"` |
| title | `string` | `""` | 可设置成空,当设置成空时,没有标题栏 |
| className | `string` | | 表格外层 Dom 的类名 |
| api | [API](../../docs/types/api) | | CRUD 用来获取列表数据的 api。 |
| deferApi | [API](../../docs/types/api) | | 当行数据中有 defer 属性时,用此接口进一步加载内容 |
| loadDataOnce | `boolean` | | 是否一次性加载所有数据(前端分页) |
| loadDataOnceFetchOnFilter | `boolean` | `true` | 在开启 loadDataOnce 时filter 时是否去重新请求 api |
| source | `string` | | 数据映射接口返回某字段的值,不设置会默认使用接口返回的`${items}`或者`${rows}`,也可以设置成上层数据源的内容 |
| filter | [Form](./form/index) | | 设置过滤器,当该表单提交后,会把数据带给当前 `mode` 刷新列表。 |
| filterTogglable | `boolean` \| `{label: string; icon: string; activeLabel: string; activeIcon?: stirng;}` | `false` | 是否可显隐过滤器 |
| filterDefaultVisible | `boolean` | `true` | 设置过滤器默认是否可见。 |
| initFetch | `boolean` | `true` | 是否初始化的时候拉取数据, 只针对有 filter 的情况, 没有 filter 初始都会拉取数据 |
| interval | `number` | `3000` | 刷新时间(最低 1000) |
| silentPolling | `boolean` | `false` | 配置刷新时是否隐藏加载动画 |
| stopAutoRefreshWhen | `string` | `""` | 通过[表达式](../../docs/concepts/expression)来配置停止刷新的条件 |
| stopAutoRefreshWhenModalIsOpen | `boolean` | `false` | 当有弹框时关闭自动刷新,关闭弹框又恢复 |
| syncLocation | `boolean` | `true` | 是否将过滤条件的参数同步到地址栏 |
| draggable | `boolean` | `false` | 是否可通过拖拽排序 |
| resizable | `boolean` | `true` | 是否可以调整列宽度 |
| itemDraggableOn | `boolean` | | 用[表达式](../../docs/concepts/expression)来配置是否可拖拽排序 |
| [saveOrderApi](#saveOrderApi) | [API](../../docs/types/api) | | 保存排序的 api。 |
| [quickSaveApi](#quickSaveApi) | [API](../../docs/types/api) | | 快速编辑后用来批量保存的 API。 |
| [quickSaveItemApi](#quickSaveItemApi) | [API](../../docs/types/api) | | 快速编辑配置成及时保存时使用的 API。 |
| bulkActions | Array<[Action](./action)> | | 批量操作列表,配置后,表格可进行选中操作。 |
| messages | `Object` | | 覆盖消息提示,如果不指定,将采用 api 返回的 message |
| messages.fetchFailed | `string` | | 获取失败时提示 |
| messages.saveOrderFailed | `string` | | 保存顺序失败提示 |
| messages.saveOrderSuccess | `string` | | 保存顺序成功提示 |
| messages.quickSaveFailed | `string` | | 快速保存失败提示 |
| messages.quickSaveSuccess | `string` | | 快速保存成功提示 |
| primaryField | `string` | `"id"` | 设置 ID 字段名。 |
| perPage | `number` | 10 | 设置一页显示多少条数据。 |
| orderBy | `string` | | 默认排序字段,这个是传给后端,需要后端接口实现 |
| orderDir | `asc` \| `desc` | | 排序方向 |
| defaultParams | `Object` | | 设置默认 filter 默认参数,会在查询的时候一起发给后端 |
| pageField | `string` | `"page"` | 设置分页页码字段名。 |
| perPageField | `string` | `"perPage"` | 设置分页一页显示的多少条数据的字段名。注意:最好与 defaultParams 一起使用,请看下面例子。 |
| pageDirectionField | `string` | `"pageDir"` | 分页方向字段名可能是 forward 或者 backward |
| perPageAvailable | `Array<number>` | `[5, 10, 20, 50, 100]` | 设置一页显示多少条数据下拉框可选条数。 |
| orderField | `string` | | 设置用来确定位置的字段名,设置后新的顺序将被赋值到该字段中。 |
| hideQuickSaveBtn | `boolean` | `false` | 隐藏顶部快速保存提示 |
| autoJumpToTopOnPagerChange | `boolean` | `false` | 当切分页的时候,是否自动跳顶部。 |
| syncResponse2Query | `boolean` | `true` | 将返回数据同步到过滤器上。 |
| keepItemSelectionOnPageChange | `boolean` | `true` | 保留条目选择,默认分页、搜素后,用户选择条目会被清空,开启此选项后会保留用户选择,可以实现跨页面批量操作。 |
| labelTpl | `string` | | 单条描述模板,`keepItemSelectionOnPageChange`设置为`true`后会把所有已选择条目列出来,此选项可以用来定制条目展示文案。 |
| headerToolbar | Array | `['bulkActions', 'pagination']` | 顶部工具栏配置 |
| footerToolbar | Array | `['statistics', 'pagination']` | 底部工具栏配置 |
| alwaysShowPagination | `boolean` | `false` | 是否总是显示分页 |
| affixHeader | `boolean` | `true` | 是否固定表头(table 下) |
| autoGenerateFilter | `Object \| boolean` | | 是否开启查询区域,开启后会根据列元素的 `searchable` 属性值,自动生成查询条件表单 |
| resetPageAfterAjaxItemAction | `boolean` | `false` | 单条数据 ajax 操作后是否重置页码为第一页 |
| autoFillHeight | `boolean``{height: number}` | | 内容区域自适应高度 |
| canAccessSuperData | `boolean` | `true` | 指定是否可以自动获取上层的数据并映射到表格行数据上,如果列也配置了该属性,则列的优先级更高 |
| matchFunc | `string` | [`CRUDMatchFunc`](#匹配函数) | 自定义匹配函数, 当开启`loadDataOnce`时,会基于该函数计算的匹配结果进行过滤,主要用于处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景 | `3.5.0` |
注意除了上面这些属性CRUD 在不同模式下的属性需要参考各自的文档,比如
@ -3287,12 +3377,13 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据,
除了 Table 组件默认支持的列配置CRUD 的列配置还额外支持以下属性:
| 属性名 | 类型 | 默认值 | 说明 |
| ---------- | --------------------------------------------------------------- | ------- | --------------------------------------------------------------------------- |
| sortable | `boolean` | `false` | 是否可排序 |
| searchable | `boolean` \| `Schema` | `false` | 是否可快速搜索,开启`autoGenerateFilter`后,`searchable`支持配置`Schema` |
| filterable | `boolean` \| [`QuickFilterConfig`](./crud.md#quickfilterconfig) | `false` | 是否可快速搜索,`options`属性为静态选项,支持设置`source`属性从接口获取选项 |
| quickEdit | `boolean` \| [`QuickEditConfig`](./crud.md#quickeditconfig) | - | 快速编辑,一般需要配合`quickSaveApi`接口使用 |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------------ | --------------------------------------------------------------- | ------- | --------------------------------------------------------------------------- | --- |
| sortable | `boolean` | `false` | 是否可排序 |
| searchable | `boolean` \| `Schema` | `false` | 是否可快速搜索,开启`autoGenerateFilter`后,`searchable`支持配置`Schema` |
| filterable | `boolean` \| [`QuickFilterConfig`](./crud.md#quickfilterconfig) | `false` | 是否可快速搜索,`options`属性为静态选项,支持设置`source`属性从接口获取选项 |
| quickEdit | `boolean` \| [`QuickEditConfig`](./crud.md#quickeditconfig) | - | 快速编辑,一般需要配合`quickSaveApi`接口使用 |
| quickEditEnabledOn | `SchemaExpression` | - | 开启快速编辑条件[表达式](../../docs/concepts/expression) | |
#### QuickFilterConfig
@ -3300,19 +3391,18 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据,
| ------------- | ----------------------------- | ------- | -------------------------------------------------------- | ------- |
| options | `Array<any>` | - | 静态选项 | |
| multiple | `boolean` | `false` | 是否支持多选 | |
| source | [`Api`](../../docs/types/api) | - | 选项 API 接口 | |
| source | [`Api`](../../docs/types/api) \| `string` | - | 选项 API 接口 | `3.6.0`版本后支持上下文变量 |
| refreshOnOpen | `boolean` | `false` | 配置 source 前提下,每次展开筛选浮层是否重新加载选项数据 | `2.9.0` |
| strictMode | `boolean` | `false` | 严格模式,开启严格模式后,会采用 JavaScript 严格相等比较 | `2.3.0` |
#### QuickEditConfig
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------------ | ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------- | ---- |
| type | `SchemaType` | - | 表单项组件类型 | |
| body | `SchemaCollection` | - | 组件容器,支持多个表单项组件 | |
| mode | `'inline' \| 'popOver'` | `'popOver'` | 编辑模式inline 为行内编辑popOver 为浮层编辑 | |
| saveImmediately | `boolean``{api: Api}` | `false` | 是否修改后即时保存,一般需要配合`quickSaveItemApi`接口使用,也可以直接配置[`Api`](../../docs/types/api) | |
| quickEditEnabledOn | `SchemaExpression` | - | 开启快速编辑条件[表达式](../../docs/concepts/expression) | |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| --------------- | ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------- | ---- |
| type | `SchemaType` | - | 表单项组件类型 | |
| body | `SchemaCollection` | - | 组件容器,支持多个表单项组件 | |
| mode | `'inline' \| 'popOver'` | `'popOver'` | 编辑模式inline 为行内编辑popOver 为浮层编辑 | |
| saveImmediately | `boolean``{api: Api}` | `false` | 是否修改后即时保存,一般需要配合`quickSaveItemApi`接口使用,也可以直接配置[`Api`](../../docs/types/api) | |
### columns-toggler 属性表

View File

@ -132,16 +132,62 @@ List 的内容、Card 卡片的内容配置同上
}
```
## 设置展示时区
通过配置 `displayTimeZone` 参数,可以设置展示时区,默认展示当前时区。
```schema: scope="body"
{
"type": "crud",
"api": {
"method": "get",
"url": "/whatever/api",
"mockResponse": {
"status": 200,
"data": [
{
"date": "2023-10-27 15:00:00"
}
]
}
},
"columns": [
{
"type": "date",
"label": "Asia/Shanghai",
"name": "date",
"displayTimeZone": "Asia/Shanghai",
"format": "YYYY-MM-DD HH:mm:ss"
},
{
"type": "date",
"label": "America/Los_Angeles",
"name": "date",
"displayTimeZone": "America/Los_Angeles",
"format": "YYYY-MM-DD HH:mm:ss"
},
{
"type": "date",
"name": "date",
"label": "Asia/Tokyo",
"displayTimeZone": "Asia/Tokyo",
"format": "YYYY-MM-DD HH:mm:ss"
}
]
}
```
## 属性表
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| --------------- | --------- | ------------ | -------------------------------------------------------------------------------------------------- | ----------------------- |
| type | `string` | | 如果在 Table、Card 和 List 中,为`"date"`;在 Form 中用作静态展示,为`"static-date"` |
| className | `string` | | 外层 CSS 类名 |
| value | `string` | | 显示的日期数值 |
| name | `string` | | 在其他组件中,时,用作变量映射 |
| placeholder | `string` | `-` | 占位内容 |
| displayFormat | `string` | `YYYY-MM-DD` | 展示格式, 更多格式类型请参考 [文档](https://momentjs.com/docs/#/displaying/format/) | 版本号 3.4.0 及以上支持 |
| valueFormat | `string` | `X` | 数据格式,默认为时间戳。更多格式类型请参考 [文档](https://momentjs.com/docs/#/displaying/format/) |
| fromNow | `boolean` | `false` | 是否显示相对当前的时间描述,比如: 11 小时前、3 天前、1 年前等fromNow 为 true 时format 不生效。 |
| updateFrequency | `number` | `60000` | 更新频率, 默认为 1 分钟 |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| --------------- | --------- | ------------ | ------------------------------------------------------------------------------------------------------ | ----------------------- |
| type | `string` | | 如果在 Table、Card 和 List 中,为`"date"`;在 Form 中用作静态展示,为`"static-date"` |
| className | `string` | | 外层 CSS 类名 |
| value | `string` | | 显示的日期数值 |
| name | `string` | | 在其他组件中,时,用作变量映射 |
| placeholder | `string` | `-` | 占位内容 |
| displayFormat | `string` | `YYYY-MM-DD` | 展示格式, 更多格式类型请参考 [文档](https://momentjs.com/docs/#/displaying/format/) | 版本号 3.4.0 及以上支持 |
| valueFormat | `string` | `X` | 数据格式,默认为时间戳。更多格式类型请参考 [文档](https://momentjs.com/docs/#/displaying/format/) |
| fromNow | `boolean` | `false` | 是否显示相对当前的时间描述,比如: 11 小时前、3 天前、1 年前等fromNow 为 true 时format 不生效。 |
| updateFrequency | `number` | `60000` | 更新频率, 默认为 1 分钟 |
| displayTimeZone | `string` | | 设置日期展示时区可设置清单参考https://gist.github.com/diogocapela/12c6617fc87607d11fd62d2a4f42b02a |

View File

@ -379,6 +379,34 @@ order: 14
}
```
## 确认模式
> `3.6.0`及以上版本
设置`"closeOnSelect": false`,点选日期时间后,不会自动关闭浮层,需要点击底部工具栏的确认才会关闭。点击**取消按钮**或者**浮层外部区域**也会关闭浮层,并将值重置为初始状态。
> 注意:该特性仅对`input-datetime`有效,其他日期时间组件无效。开启内嵌模式后,该特性无效。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"debug": true,
"body": [
{
"type": "input-datetime",
"name": "datetime",
"label": "日期时间",
"shortcuts": ["yesterday", "today", "tomorrow"],
"closeOnSelect": false,
"valueFormat": "YYYY-MM-DD HH:mm:ss",
"displayFormat": "YYYY-MM-DD HH:mm:ss",
"clearable": true
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置

View File

@ -302,6 +302,48 @@ order: 21
}
```
## 上传文件列表
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"debug": true,
"data": {
"files": [
{
"id":"2ba48d02d349",
"value":"http://amis.bj.bcebos.com/amis/2017-11/1510713111265/fis3-react.md",
"url":"http://amis.bj.bcebos.com/amis/2017-11/1510713111265/fis3-react.md",
"filename":"file1.md",
"name":"file1.md",
"state":"uploaded"
},
{
"id":"14723e0bc640",
"value":"http://amis.bj.bcebos.com/amis/2017-11/1510713111265/fis3-react.md",
"url":"http://amis.bj.bcebos.com/amis/2017-11/1510713111265/fis3-react.md",
"filename":"file2.md",
"name":"file2.md",
"state":"uploaded"
}
]
},
"body": [
{
"type": "input-file",
"name": "files",
"label": false,
"mode": "horizontal",
"accept": "*",
"receiver": "/api/mock2/upload/random",
"multiple": true,
"joinValues": false
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置

View File

@ -287,7 +287,7 @@ order: 38
| step | `number \| string` | `1` | 步长,支持变量 | `3.3.0`后支持变量 |
| showSteps | `boolean` | `false` | 是否显示步长 |
| parts | `number` or `number[]` | `1` | 分割的块数<br/>主持数组传入分块的节点 |
| marks | <code>{ [number &#124; string]: ReactNode }</code> or <code>{ [number &#124; string]: { style: CSSProperties, label: ReactNode } }</code> | | 刻度标记<br/>- 支持自定义样式<br/>- 设置百分比 |
| marks | <code>{ [number &#124; string]: string &#124; number &#124; SchemaObject }</code> or <code>{ [number &#124; string]: { style: CSSProperties, label: string } }</code> | | 刻度标记<br/>- 支持自定义样式<br/>- 设置百分比 |
| tooltipVisible | `boolean` | `false` | 是否显示滑块标签 |
| tooltipPlacement | `auto` or `bottom` or `left` or `right` | `top` | 滑块标签的位置,默认`auto`,方向自适应<br/>前置条件tooltipVisible 不为 false 时有效 |
| tipFormatter | `function` | | 控制滑块标签显隐函数<br/>前置条件tooltipVisible 不为 false 时有效 |

View File

@ -917,6 +917,11 @@ order: 54
| deleteSuccess | `index: number` 所在行记录索引 <br /> `item: object` 所在行记录 <br/> `[name]: object[]`列表记录 | 配置了`deleteApi`,调用接口成功时触发 |
| deleteFail | `index: number` 所在行记录索引 <br /> `item: object` 所在行记录 <br/> `[name]: object[]`列表记录<br />`error: object` `deleteApi`请求失败后返回的错误信息 | 配置了`deleteApi`,调用接口失败时触发 |
| change | `[name]: object[]` 列表记录 | 组件数据发生改变时触发 |
| orderChange | `movedItems: item[]` 已排序数据 | 手动拖拽行排序时触发 |
| rowClick | `item: object` 行点击数据<br/>`index: number` 行索引 | 单击整行时触发 |
| rowDbClick | `item: object` 行点击数据<br/>`index: number` 行索引 | 双击整行时触发 |
| rowMouseEnter | `item: object` 行移入数据<br/>`index: number` 行索引 | 移入整行时触发 |
| rowMouseLeave | `item: object` 行移出数据<br/>`index: number` 行索引 | 移出整行时触发 |
### add
@ -1563,6 +1568,287 @@ order: 54
}
```
### orderChange
在开启拖拽排序行记录后才会用到,排序确认后触发。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
}
]
},
"body": [
{
"showIndex": true,
"type": "input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"draggable": true,
"onEvent": {
"orderChange": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "${event.data.movedItems.length|json}行发生移动"
}
}
]
}
}
}
]
}
```
### rowClick
点击行记录。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
}
]
},
"body": [
{
"showIndex": true,
"type": "input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"onEvent": {
"rowClick": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "行单击数据:${event.data.item|json};行索引:${event.data.index}"
}
}
]
}
}
}
]
}
```
### rowDbClick
双击行记录。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
}
]
},
"body": [
{
"showIndex": true,
"type": "input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"onEvent": {
"rowDbClick": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "行单击数据:${event.data.item|json};行索引:${event.data.index}"
}
}
]
}
}
}
]
}
```
### rowMouseEnter
鼠标移入行记录。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
}
]
},
"body": [
{
"showIndex": true,
"type": "input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"onEvent": {
"rowMouseEnter": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "行索引:${event.data.index}"
}
}
]
}
}
}
]
}
```
### rowMouseLeave
鼠标移出行记录。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
}
]
},
"body": [
{
"showIndex": true,
"type": "input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"onEvent": {
"rowMouseLeave": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "行索引:${event.data.index}"
}
}
]
}
}
}
]
}
```
## 动作表
当前组件对外暴露以下特性动作,其他组件可以通过指定 actionType: 动作名称、componentId: 该组件 id 来触发这些动作,动作配置可以通过 args: {动作配置项名称: xxx}来配置具体的参数,详细请查看事件动作。
@ -1574,6 +1860,7 @@ order: 54
| setValue | `value: object \| Array<object>` 替换的值<br /> `index?: number` 可选,替换第几行数据,如果没有指定,则替换全部表格数据 | 替换表格数据 |
| clear | - | 清空表格数据 |
| reset | - | 将表格数据重置为`resetValue`,若没有配置`resetValue`,则清空表格数据 |
| initDrag | - | 开启表格拖拽排序功能 |
### addItem
@ -2199,3 +2486,83 @@ order: 54
}
}
```
### initDrag
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "button",
"label": "开始表格排序",
"onEvent": {
"click": {
"actions": [
{
"componentId": "drag-input-table",
"actionType": "initDrag"
}
]
}
}
},
{
"type": "input-table",
"label": "表格表单",
"id": "drag-input-table",
"name": "table",
"columns": [
{
"name": "a",
"label": "A"
},
{
"name": "b",
"label": "B"
}
],
"addable": true,
"footerAddBtn": {
"label": "新增",
"icon": "fa fa-plus",
"hidden": true
},
"strictMode": true,
"minLength": 0,
"needConfirm": false,
"showTableAddBtn": false
}
],
"data": {
"table": [
{
"id": 1,
"a": "a1",
"b": "b1"
},
{
"id": 2,
"a": "a2",
"b": "b2"
},
{
"id": 3,
"a": "a3",
"b": "b3"
},
{
"id": 4,
"a": "a4",
"b": "b4"
},
{
"id": 5,
"a": "a5",
"b": "b5"
}
]
}
}
```

View File

@ -878,12 +878,95 @@ icon:
}
```
## 分页
> `3.6.0`及以上版本
当数据量庞大时,可以开启数据源分页,此时左侧列表底部会出现分页控件,相关配置参考属性表。通常在提交表单中使用分页场景,处理数据量较大的数据源。如果需要在表单中回显已选值,建议同时设置`{"joinValues": false, "extractValue": false}`因为已选数据可能位于不同的分页如果仅使用value值作为提交值可能会导致右侧结果区无法正确渲染。
> 仅列表list和表格table展示模式支持分页接口的数据结构参考[CRUD数据源接口格式](../crud#数据结构)
```schema: scope="body"
{
"type": "form",
"debug": true,
"body": [
{
"label": "默认",
"type": "transfer",
"name": "transfer",
"joinValues": false,
"extractValue": false,
"source": "/api/mock2/options/transfer?page=${page}&perPage=${perPage}",
"pagination": {
"enable": true,
"layout": ["pager", "perpage", "total"],
"popOverContainerSelector": ".cxd-Panel--form"
},
"value": [
{"label": "Laura Lewis", "value": "1", "id": 1},
{"label": "Christopher Rodriguez", "value": "3", "id": 3},
{"label": "Laura Miller", "value": "12", "id": 12},
{"label": "Patricia Robinson", "value": "14", "id": 14}
]
}
]
}
```
### 前端分页
> `3.6.0`及以上版本
当使用数据域变量作为数据源时,支持实现前端一次性加载并分页
```schema: scope="body"
{
"type": "form",
"debug": true,
"body": [
{
"type": "service",
"api": {
"url": "/api/mock2/options/loadDataOnce",
"method": "get",
"responseData": {
"transferOptions": "${items}"
}
},
"body": [
{
"label": "默认",
"type": "transfer",
"name": "transfer",
"joinValues": false,
"extractValue": false,
"source": "${transferOptions}",
"pagination": {
"enable": true,
"layout": ["pager", "perpage", "total"],
"popOverContainerSelector": ".cxd-Panel--form"
},
"value": [
{"label": "Laura Lewis", "value": "1", "id": 1},
{"label": "Christopher Rodriguez", "value": "3", "id": 3},
{"label": "Laura Miller", "value": "12", "id": 12},
{"label": "Patricia Robinson", "value": "14", "id": 14}
]
}
]
}
]
}
```
## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 |
| -------------------------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| -------------------------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
| options | `Array<object>`或`Array<string>` | | [选项组](./options#%E9%9D%99%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-options) |
| source | `string`或 [API](../../../docs/types/api) | | [动态选项组](./options#%E5%8A%A8%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-source) |
| delimeter | `string` | `false` | [拼接符](./options#%E6%8B%BC%E6%8E%A5%E7%AC%A6-delimiter) |
@ -909,6 +992,13 @@ icon:
| valueTpl | `string` \| [SchemaNode](../../docs/types/schemanode) | | 用来自定义值的展示 |
| itemHeight | `number` | `32` | 每个选项的高度,用于虚拟渲染 |
| virtualThreshold | `number` | `100` | 在选项数量超过多少时开启虚拟渲染 |
| pagination | `object` | | 分页配置 | `3.6.0` |
| pagination.className | `string` | | 分页控件CSS类名 | `3.6.0` |
| pagination.enable | `boolean` | | 是否开启分页 | `3.6.0` |
| pagination.layout | `string` \| `string[]` | `["pager"]` | 通过控制 layout 属性的顺序,调整分页结构布局 | `3.6.0` |
| pagination.perPageAvailable | `number[]` | `[10, 20, 50, 100]` | 指定每页可以显示多少条 | `3.6.0` |
| pagination.maxButtons | `number` | `5` | 最多显示多少个分页按钮,最小为 5 | `3.6.0` |
| pagination.popOverContainerSelector | `string` | | 切换每页条数的控件挂载点 | `3.6.0` |
## 事件表

View File

@ -433,31 +433,33 @@ List 的内容、Card 卡片的内容配置同上
## 属性表
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------------ | ------------------------------------ | --------- | --------------------------------------------------------------------------------------------- | ------- |
| type | `string` | | 如果在 Table、Card 和 List 中,为`"image"`;在 Form 中用作静态展示,为`"static-image"` |
| className | `string` | | 外层 CSS 类名 |
| innerClassName | `string` | | 组件内层 CSS 类名 |
| imageClassName | `string` | | 图片 CSS 类名 |
| thumbClassName | `string` | | 图片缩率图 CSS 类名 |
| height | `string` | | 图片缩率高度 |
| width | `string` | | 图片缩率宽度 |
| title | `string` | | 标题 |
| imageCaption | `string` | | 描述 |
| placeholder | `string` | | 占位文本 |
| defaultImage | `string` | | 无数据时显示的图片 |
| src | `string` | | 缩略图地址 |
| href | [模板](../../docs/concepts/template) | | 外部链接地址 |
| originalSrc | `string` | | 原图地址 |
| enlargeAble | `boolean` | | 支持放大预览 |
| enlargeTitle | `string` | | 放大预览的标题 |
| enlargeCaption | `string` | | 放大预览的描述 |
| enlargeWithGallary | `string` | `true` | 在表格中,图片的放大功能会默认展示所有图片信息,设置为`false`将关闭放大模式下图片集列表的展示 |
| 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` |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------------ | ------------------------------------------------ | --------- | --------------------------------------------------------------------------------------------- | ------- |
| type | `string` | | 如果在 Table、Card 和 List 中,为`"image"`;在 Form 中用作静态展示,为`"static-image"` |
| className | `string` | | 外层 CSS 类名 |
| innerClassName | `string` | | 组件内层 CSS 类名 |
| imageClassName | `string` | | 图片 CSS 类名 |
| thumbClassName | `string` | | 图片缩率图 CSS 类名 |
| height | `string` | | 图片缩率高度 |
| width | `string` | | 图片缩率宽度 |
| title | `string` | | 标题 |
| imageCaption | `string` | | 描述 |
| placeholder | `string` | | 占位文本 |
| defaultImage | `string` | | 无数据时显示的图片 |
| src | `string` | | 缩略图地址 |
| href | [模板](../../docs/concepts/template) | | 外部链接地址 |
| originalSrc | `string` | | 原图地址 |
| enlargeAble | `boolean` | | 支持放大预览 |
| enlargeTitle | `string` | | 放大预览的标题 |
| enlargeCaption | `string` | | 放大预览的描述 |
| enlargeWithGallary | `string` | `true` | 在表格中,图片的放大功能会默认展示所有图片信息,设置为`false`将关闭放大模式下图片集列表的展示 |
| 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` |
| maxScale | `number` 或 [模板](../../docs/concepts/template) | | 执行调整图片比例动作时的最大百分比 | `3.4.4` |
| minScale | `number` 或 [模板](../../docs/concepts/template) | | 执行调整图片比例动作时的最小百分比 | `3.4.4` |
#### ImageAction
@ -475,3 +477,170 @@ interface ImageAction {
disabled?: boolean;
}
```
## 事件表
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细查看[事件动作](../../docs/concepts/event-action)。
| 事件名称 | 事件参数 | 说明 |
| ---------- | ---------- | -------------- |
| click | 上下文数据 | 点击图片时触发 |
| mouseenter | 上下文数据 | 鼠标移入时触发 |
| mouseleave | 上下文数据 | 鼠标移入时触发 |
### click / mouseenter / mouseleave
点击图片 / 鼠标移入图片 / 鼠标移出图片,可以尝试通过${event.context.nativeEvent}获取鼠标事件对象。
```schema: scope="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",
"onEvent": {
"click": {
"actions": [
{
"actionType": "toast",
"args": {
"msg": "图片被点击了"
}
}
]
},
"mouseenter": {
"actions": [
{
"actionType": "toast",
"args": {
"msg": "鼠标移入图片"
}
}
]
},
"mouseleave": {
"actions": [
{
"actionType": "toast",
"args": {
"msg": "鼠标移出图片"
}
}
]
}
}
}
```
## 动作表
当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
| 动作名称 | 动作配置 | 说明 |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| preview | - | 预览图片 |
| zoom | `scale: number``scale: `[模板](../../docs/concepts/template),定义每次放大或缩小图片的百分比大小,正值为放大,负值为缩小,默认 50 | 调整图片比例,将图片等比例放大或缩小 |
### preview
预览图片,可以通过配置`originalSrc`来指定预览的原图地址。
```schema: scope="body"
{
"type": "page",
"body": {
"type": "container",
"body": [
{
"type": "container",
"body": [
{
"type": "image",
"className": "mb-1",
"src": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"originalSrc": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg",
"id": "previewImage"
}
]
},
{
"type": "action",
"label": "预览图片",
"onEvent": {
"click": {
"actions": [
{
"actionType": "preview",
"componentId": "previewImage"
}
]
}
}
}
]
}
}
```
### zoom
调整图片比例,将图片等比例放大或缩小。可以通过配置图片的`maxScale`和`minScale`来限制调整的比例。
```schema: scope="body"
{
"type": "page",
"body": {
"type": "container",
"body": [
{
"type": "flex",
"items": [
{
"type": "image",
"innerClassName": "no-border",
"className": "mt-5 mb-5",
"src": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"maxScale": 200,
"minScale": 20,
"id": "zoomImage"
}
]
},
{
"type": "action",
"label": "放大图片",
"onEvent": {
"click": {
"actions": [
{
"actionType": "zoom",
"args": {
"scale": 50,
},
"componentId": "zoomImage"
}
]
}
}
},
{
"type": "action",
"label": "缩小图片",
"className": "mx-1",
"onEvent": {
"click": {
"actions": [
{
"actionType": "zoom",
"args": {
"scale": -50,
},
"componentId": "zoomImage"
}
]
}
}
}
]
}
}
```

View File

@ -81,6 +81,30 @@ order: 58
可以使用 `![text](video.mp4)` 语法来嵌入视频。
## 支持 latex
> 3.6.0 及以上版本
公式渲染使用 KaTeX 实现,由于体积太大默认不提供,需要自己去[下载](https://github.com/KaTeX/KaTeX/releases),在页面中引入以下三个文件:
```
<link rel="stylesheet" href="katex/katex.min.css">
<script src="katex/katex.min.js"></script>
<script src="katex/contrib/auto-render.min.js"></script>
```
markdown 中的 `$``$$` 包裹的内容就能以公式展现,比如 `$\sqrt{a^2 + b^2}$`,如果是在代码中 `\` 要转义为 `\\`
```schema
{
"type": "page",
"body": {
"type": "markdown",
"value": "$$\\hat{f} (\\xi)=\\int_{-\\infty}^{\\infty}f(x)e^{-2\\pi ix\\xi}dx$$"
}
}
```
## 高级配置
> 1.8.1 及以上版本

View File

@ -68,9 +68,111 @@ order: 73
| disabled | `boolean` | false | 是否禁用 |
| onPageChange | page、perPage 改变时会触发 | (page: number, perPage: number) => void; | 分页改变触发 |
## 事件表
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
> | 事件名称 | 事件参数 | 说明 |
> | -------- | ------------------------------------- | ------------------------------------------- |
> | change | `[value]: object` 当前页码的值<br/> | 当前页码值改变时触发 |
> | change | `page: number` 当前页码的值<br/>`perPage: number` 每页显示的记录数 | 当前页码值改变时触发 |
### change
切换页码时,通过更新 service 数据域中的 page 来实现联动刷新 table 表格数据。
```schema: scope="body"
{
"type": "service",
"id": "service_01",
"api": "/api/mock2/crud/table?page=${page}",
"data": {
"page": 1
},
"body": [
{
"type": "table",
"title": "表格1",
"source": "$rows",
"columns": [
{
"name": "engine",
"label": "Engine"
},
{
"name": "version",
"label": "Version"
}
]
},
{
"type": "pagination",
"activePage": "${page}",
"hasNext": true,
"onEvent": {
"change": {
"actions": [
{
"actionType": "setValue",
"componentId": "service_01",
"args": {
"value": {
"page": "${event.data.page}"
}
}
}
]
}
}
}
]
}
```
切换页码时,通过向 service 发送 page 并重新加载 service 数据来实现联动刷新 table 表格数据。
```schema: scope="body"
{
"type": "service",
"id": "service_02",
"api": "/api/mock2/crud/table?page=${page}",
"data": {
"page": 1
},
"body": [
{
"type": "table",
"title": "表格1",
"source": "$rows",
"columns": [
{
"name": "engine",
"label": "Engine"
},
{
"name": "version",
"label": "Version"
}
]
},
{
"type": "pagination",
"activePage": "${page}",
"hasNext": true,
"onEvent": {
"change": {
"actions": [
{
"actionType": "reload",
"componentId": "service_02",
"data": {
"page": "${event.data.page}"
}
}
]
}
}
}
]
}
```

View File

@ -2324,6 +2324,70 @@ popOver 的其它配置请参考 [popover](./popover)
}
```
### rowDbClick
双击整行时触发。
```schema: scope="body"
{
"type": "service",
"api": "/api/mock2/sample?perPage=10",
"body": [
{
"type": "table",
"source": "$rows",
"onEvent": {
"rowDbClick": {
"actions": [
{
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "行单击数据:${event.data.item|json};行索引:${event.data.index}"
}
}
]
}
},
"columns": [
{
"name": "id",
"label": "ID",
"searchable": true
},
{
"name": "engine",
"label": "Rendering engine",
"filterable": {
"options": [
"Internet Explorer 4.0",
"Internet Explorer 5.0"
]
}
},
{
"name": "browser",
"label": "Browser",
"sortable": true
},
{
"name": "platform",
"label": "Platform(s)"
},
{
"name": "version",
"label": "Engine version"
},
{
"name": "grade",
"label": "CSS grade"
}
]
}
]
}
```
### rowMouseEnter
鼠标移入行记录。

File diff suppressed because it is too large Load Diff

View File

@ -75,42 +75,42 @@ order: 9
```schema
{
type: 'page',
body: [
"type": "page",
"body": [
{
type: 'button',
label: '尝试点击、鼠标移入/移出',
level: 'primary',
onEvent: {
click: {
actions: [
"type": "button",
"label": "尝试点击、鼠标移入/移出",
"level": "primary",
"onEvent": {
"click": {
"actions": [
{
actionType: 'toast',
args: {
msgType: 'info',
msg: '派发点击事件'
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "派发点击事件"
}
}
]
},
mouseenter: {
actions: [
"mouseenter": {
"actions": [
{
actionType: 'toast',
args: {
msgType: 'info',
msg: '派发鼠标移入事件'
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "派发鼠标移入事件"
}
}
]
},
mouseleave: {
actions: [
"mouseleave": {
"actions": [
{
actionType: 'toast',
args: {
msgType: 'info',
msg: '派发鼠标移出事件'
"actionType": "toast",
"args": {
"msgType": "info",
"msg": "派发鼠标移出事件"
}
}
]
@ -2281,7 +2281,7 @@ run action ajax
},
{
type: 'form',
id: 'form_001_form_01',
id: 'form_001',
title: '表单1优先级低',
name: 'sub-form1',
body: [
@ -2298,6 +2298,7 @@ run action ajax
actions: [
{
actionType: 'reload',
componentId: 'form_001',
data: {
myname: '${myrole}', // 从事件数据中取
}
@ -2340,6 +2341,7 @@ run action ajax
actions: [
{
actionType: 'reload',
componentId: 'form_002',
data: {
myrole: '${myrole}',
age: '${age}'
@ -2378,6 +2380,7 @@ run action ajax
actions: [
{
actionType: 'reload',
componentId: 'form_003',
data: {
job: '${myrole}'
}

View File

@ -8,6 +8,46 @@ export default {
'*': '其他'
},
items: [
{
link: 'https://www.microsoft1.com/',
icon: __uri('../../static/ie.png'),
browser: 'Internet Explorer 4.2 2',
platform: 'Win 95+',
notExport: '1',
grade: 'A',
engine: {
name: 'Trident1',
version: '4/2'
},
date: '1591326307',
num: '12312334234234523',
children: [
{
link: 'https://www.microsoft2.com/',
engine: {
name: 'Trident2',
version: '4/2'
},
browser: 'Internet Explorer 4.0',
platform: 'Win 95+',
version: '4',
grade: 'X',
id: 1001
},
{
link: 'https://www.microsoft3.com/',
engine: {
name: 'Trident3',
version: '3/2'
},
browser: 'Internet Explorer 5.0',
platform: 'Win 95+',
version: '5',
grade: 'C',
id: 1002
}
]
},
{
link: 'https://www.microsoft.com/',
icon: __uri('../../static/ie.png'),

View File

@ -0,0 +1,298 @@
export default {
type: 'page',
title: '匹配函数',
remark: '使用前端分页处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景',
body: [
{
type: 'container',
style: {
padding: '8px',
marginBottom: '8px',
backgroundColor: '#f5f5f5',
borderRadius: '4px'
},
body: [
{
"type": "tpl",
tpl: '<h4>匹配函数签名:</h4>',
"inline": false
},
{
"type": "code",
"language": "typescript",
"value": `interface CRUDMatchFunc {
(
/* 当前列表的全量数据 */
items: any,
/* 最近一次接口返回的全量数据 */
itemsRaw: any,
/** 相关配置 */
options?: {
/* 查询参数 */
query: Record<string, any>,
/* 列配置 */
columns: any;
}
): boolean;
}`
}
]
},
{
"type": "crud",
"syncLocation": false,
"api": "/api/mock2/crud/loadDataOnce",
"loadDataOnce": true,
"loadDataOnceFetchOnFilter": false,
"perPage": 5,
"matchFunc": `
const {query = {}, columns} = options;
let result = items.concat();
Object.keys(query).forEach(key => {
const value = query[key];
if (value == null) {
return;
}
if (key === 'status') {
result = result.filter(item => item.status === Boolean(value));
} else if (key === 'time') {
if (typeof value === 'string') {
const [start, end] = value.split(",");
result = result.filter(item => {
const time = Number(item.time);
return time >= Number(start) && time <= Number(end);
});
}
}
});
return result;`,
"filter": {
"debug": true,
"body": [
{
"type": "switch",
"name": "status",
"label": "已核验",
"size": "sm"
},
{
"type": "input-datetime-range",
"name": "time",
"label": "时间",
"size": "full"
}
],
"actions": [
{
"type": "reset",
"label": "重置"
},
{
"type": "submit",
"level": "primary",
"label": "查询"
}
]
},
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "version",
"label": "Engine version",
"searchable": {
"type": "select",
"name": "version",
"label": "Engine version",
"clearable": true,
"multiple": true,
"searchable": true,
"checkAll": true,
"options": [
"1.7",
"3.3",
"5.6"
],
"maxTagCount": 10,
"extractValue": true,
"joinValues": false,
"delimiter": ",",
"defaultCheckAll": false,
"checkAllLabel": "全选"
}
},
{
"name": "grade",
"label": "CSS grade"
},
{
"name": "status",
"label": "已核验",
"type": "tpl",
"tpl": "${status === true ? '是' : '否'}",
"filterable": {
"options": [
{"label": "是", "value": true},
{"label": "否", "value": false}
]
}
},
{
"name": "time",
"type": "date",
"label": "时间",
"format": "YYYY-MM-DD HH:mm:ss"
}
]
},
{
"type": "divider",
},
{
"type": "tpl",
tpl: '<h2>使用数据域变量作为数据源:</h2>',
"inline": false
},
{
"type": "service",
"api": {
"url": "/api/mock2/crud/loadDataOnce",
"method": "get",
"responseData": {
"table": "${rows}"
}
},
"body": [
{
"type": "crud",
"syncLocation": false,
"source": "${table}",
"loadDataOnce": true,
"loadDataOnceFetchOnFilter": false,
"perPage": 5,
"matchFunc": `
const {query = {}, columns} = options;
let result = itemsRaw.concat();
Object.keys(query).forEach(key => {
const value = query[key];
if (value == null) {
return;
}
if (key === 'status') {
result = result.filter(item => item.status === Boolean(value));
} else if (key === 'time') {
if (typeof value === 'string') {
const [start, end] = value.split(",");
result = result.filter(item => {
const time = Number(item.time);
return time >= Number(start) && time <= Number(end);
});
}
}
});
return result;`,
"filter": {
"debug": true,
"body": [
{
"type": "switch",
"name": "status",
"label": "已核验",
"size": "sm"
},
{
"type": "input-datetime-range",
"name": "time",
"label": "时间",
"size": "full"
}
],
"actions": [
{
"type": "reset",
"label": "重置"
},
{
"type": "submit",
"level": "primary",
"label": "查询"
}
]
},
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "version",
"label": "Engine version",
"searchable": {
"type": "select",
"name": "version",
"label": "Engine version",
"clearable": true,
"multiple": true,
"searchable": true,
"checkAll": true,
"options": [
"1.7",
"3.3",
"5.6"
],
"maxTagCount": 10,
"extractValue": true,
"joinValues": false,
"delimiter": ",",
"defaultCheckAll": false,
"checkAllLabel": "全选"
}
},
{
"name": "grade",
"label": "CSS grade"
},
{
"name": "status",
"label": "已核验",
"type": "tpl",
"tpl": "${status === true ? '是' : '否'}",
"filterable": {
"options": [
{"label": "是", "value": true},
{"label": "否", "value": false}
]
}
},
{
"name": "time",
"type": "date",
"label": "时间",
"format": "YYYY-MM-DD HH:mm:ss"
}
]
}
]
}
]
};

View File

@ -58,6 +58,7 @@ import ExportCSVExcelSchema from './CRUD/ExportCSVExcel';
import CRUDDynamicSchema from './CRUD/Dynamic';
import CRUDSimplePagerSchema from './CRUD/SimplePager';
import CRUDParsePrimitiveQuerySchema from './CRUD/ParsePrimitiveQuery';
import CRUDMatchFuncSchema from './CRUD/MatchFunc';
import ItemActionchema from './CRUD/ItemAction';
import SdkTest from './Sdk/Test';
import JSONSchemaForm from './Form/Schem';
@ -438,9 +439,20 @@ export const examples = [
component: makeSchemaRenderer(PopOverCrudSchema)
},
{
label: '一次性加载',
path: '/examples/crud/load-once',
component: makeSchemaRenderer(LoadOnceTableCrudSchema)
label: '前端分页',
icon: 'fa fa-list-ol',
children: [
{
label: '一次性加载',
path: '/examples/crud/load-once',
component: makeSchemaRenderer(LoadOnceTableCrudSchema)
},
{
label: '匹配函数',
path: '/examples/crud/match-func',
component: makeSchemaRenderer(CRUDMatchFuncSchema)
}
]
},
{
label: '点击联动',

View File

@ -24,4 +24,4 @@ export default {
aside: '边栏',
toolbar: '工具栏',
initApi: '/api/mock2/page/initDataError'
};
}

View File

@ -241,7 +241,7 @@ fis.match('/examples/mod.js', {
isMod: false
});
fis.match('markdown-it/**', {
fis.match('{markdown-it,moment-timezone}/**', {
preprocessor: fis.plugin('js-require-file')
});
@ -688,6 +688,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
postprocessor: convertSCSSIE11
});
const ghPages = fis.media('gh-pages');
ghPages.set('project.files', ['examples/index.html']);
ghPages.match('*.scss', {
parser: fis.plugin('sass', {

View File

@ -24,6 +24,7 @@
rel="stylesheet"
href="/node_modules/@fortawesome/fontawesome-free/css/v4-shims.css"
/>
<link rel="stylesheet" href="/node_modules/katex/dist/katex.min.css" />
<link rel="stylesheet" href="/node_modules/prismjs/themes/prism.css" />
<link rel="stylesheet" href="/examples/doc.css" />
@ -93,5 +94,10 @@
const initialState = {};
bootstrap(document.getElementById('root'), initialState);
</script>
<script defer src="/node_modules/katex/dist/katex.min.js"></script>
<script
defer
src="/node_modules/katex/dist/contrib/auto-render.min.js"
></script>
</body>
</html>

View File

@ -5,6 +5,6 @@
"packages/amis-ui",
"packages/amis"
],
"useWorkspaces": true,
"version": "3.4.2"
"useWorkspaces": false,
"version": "3.5.1"
}

View File

@ -0,0 +1,32 @@
module.exports = function (req, res) {
const ret = {
status: 0,
msg: '',
data: {
options: [
{
"label": "A",
"value": "A"
},
{
"label": "B",
"value": "B"
},
{
"label": "C",
"value": "C"
},
{
"label": "D",
"value": "D"
},
{
"label": "X",
"value": "X"
}
]
}
};
res.json(ret);
};

View File

@ -0,0 +1,552 @@
/** 前端分页的接口 */
module.exports = function (req, res) {
const perPage = 10;
const page = req.query.page || 1;
let items = data.concat();
const validQueryKey = Object.keys(req.query).filter(
item => item !== 'keywords' && req.query[item] != null
);
if (validQueryKey.length > 0) {
items = items.filter(item =>
validQueryKey.every(key => {
if (key === 'status') {
return item[key] === (req.query[key] === 'true' ? true : false);
}
if (key === 'time') {
const [start, end] = req.query[key];
return Number(itme[key]) >= Number(start) && Number(itme[key]) <= Number(end);
}
return !!~req.query[key].indexOf(item[key] || '');
})
);
}
const ret = {
status: 0,
msg: 'ok',
data: {
count: items.length,
rows: items
}
};
res.json(ret);
};
const data = [
{
"browser": "Internet Explorer 4.0",
"platform": "Win 95+",
"version": "4",
"grade": "X",
"status": true,
"time": "1698364800"
},
{
"browser": "Internet Explorer 5.0",
"platform": "Win 95+",
"version": "5",
"grade": "C",
"status": false,
"time": "1698364800"
},
{
"browser": "Internet Explorer 5.5",
"platform": "Win 95+",
"version": "5.5",
"grade": "A",
"status": true,
"time": "1698364800"
},
{
"engine": "Trident",
"browser": "Internet Explorer 6",
"version": "6",
"grade": "A",
"status": false,
"time": "1698364800"
},
{
"engine": "Trident",
"browser": "Internet Explorer 7",
"version": "7",
"grade": "A",
"status": true,
"time": "1698364800"
},
{
"engine": "Trident",
"browser": "AOL browser (AOL desktop)",
"platform": "Win XP",
"grade": "A",
"version": "1",
"status": false,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Firefox 1.0",
"platform": "Win 98+ / OSX.2+",
"grade": "A",
"version": "1.8",
"status": true,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Firefox 1.5",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"status": false,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Firefox 2.0",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"status": true,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Firefox 3.0",
"platform": "Win 2k+ / OSX.3+",
"version": "1.9",
"grade": "A",
"status": false,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Camino 1.0",
"platform": "OSX.2+",
"version": "1.8",
"grade": "A",
"status": true,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Camino 1.5",
"platform": "OSX.3+",
"version": "1.8",
"grade": "A",
"status": false,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Netscape 7.2",
"platform": "Win 95+ / Mac OS 8.6-9.2",
"version": "1.7",
"grade": "A",
"status": true,
"time": "1698364800"
},
{
"engine": "Gecko",
"browser": "Netscape Browser 8",
"platform": "Win 98SE+",
"version": "1.7",
"grade": "A",
"status": false,
"time": "1698796800"
},
{
"engine": "Gecko",
"browser": "Netscape Navigator 9",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"grade": "A",
"status": true,
"time": "1698796800"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.0",
"platform": "Win 95+ / OSX.1+",
"version": "1",
"grade": "A",
"status": false,
"time": "1698796800"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.1",
"platform": "Win 95+ / OSX.1+",
"version": "1.1",
"grade": "A",
"status": true,
"time": "1698796800"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.2",
"platform": "Win 95+ / OSX.1+",
"version": "1.2",
"grade": "A",
"status": false,
"time": "1698969600"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.3",
"platform": "Win 95+ / OSX.1+",
"version": "1.3",
"grade": "A",
"status": true,
"time": "1698969600"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.4",
"platform": "Win 95+ / OSX.1+",
"version": "1.4",
"grade": "A",
"status": false,
"time": "1698969600"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.5",
"platform": "Win 95+ / OSX.1+",
"version": "1.5",
"grade": "A",
"status": true,
"time": "1698969600"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.6",
"platform": "Win 95+ / OSX.1+",
"version": "1.6",
"grade": "A",
"status": false,
"time": "1698969600"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.7",
"platform": "Win 98+ / OSX.1+",
"version": "1.7",
"grade": "A",
"status": true,
"time": "1701388800"
},
{
"engine": "Gecko",
"browser": "Mozilla 1.8",
"platform": "Win 98+ / OSX.1+",
"version": "1.8",
"grade": "A",
"status": false,
"time": "1701388800"
},
{
"engine": "Gecko",
"browser": "Seamonkey 1.1",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"grade": "A",
"status": true,
"time": "1701388800"
},
{
"engine": "Gecko",
"browser": "Epiphany 2.20",
"platform": "Gnome",
"version": "1.8",
"grade": "A",
"status": false,
"time": "1701388800"
},
{
"engine": "Webkit",
"browser": "Safari 1.2",
"platform": "OSX.3",
"version": "125.5",
"grade": "A",
"status": true,
"time": "1701388800"
},
{
"engine": "Webkit",
"browser": "Safari 1.3",
"platform": "OSX.3",
"version": "312.8",
"grade": "A",
"status": false,
"time": "1702166400"
},
{
"engine": "Webkit",
"browser": "Safari 2.0",
"platform": "OSX.4+",
"version": "419.3",
"grade": "A",
"status": true,
"time": "1702166400"
},
{
"engine": "Webkit",
"browser": "Safari 3.0",
"platform": "OSX.4+",
"version": "522.1",
"grade": "A",
"status": false,
"time": "1702166400"
},
{
"engine": "Webkit",
"browser": "OmniWeb 5.5",
"platform": "OSX.4+",
"version": "420",
"grade": "A",
"status": true,
"time": "1702166400"
},
{
"engine": "Webkit",
"browser": "iPod Touch / iPhone",
"platform": "iPod",
"version": "420.1",
"grade": "A",
"status": false,
"time": "1702166400"
},
{
"engine": "Webkit",
"browser": "S60",
"platform": "S60",
"version": "413",
"grade": "A",
"status": true,
"time": "1702166400"
},
{
"engine": "Presto",
"browser": "Opera 7.0",
"platform": "Win 95+ / OSX.1+",
"version": "-",
"grade": "A",
"status": false,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 7.5",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": true,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 8.0",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": false,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 8.5",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": true,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 9.0",
"platform": "Win 95+ / OSX.3+",
"version": "-",
"grade": "A",
"status": false,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 9.2",
"platform": "Win 88+ / OSX.3+",
"version": "-",
"grade": "A",
"status": true,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera 9.5",
"platform": "Win 88+ / OSX.3+",
"version": "-",
"grade": "A",
"status": false,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Opera for Wii",
"platform": "Wii",
"version": "-",
"grade": "A",
"status": true,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Nokia N800",
"platform": "N800",
"version": "-",
"grade": "A",
"status": false,
"time": "1702771200"
},
{
"engine": "Presto",
"browser": "Nintendo DS browser",
"platform": "Nintendo DS",
"version": "8.5",
"grade": "C",
"status": true,
"time": "1703548800"
},
{
"engine": "KHTML",
"browser": "Konqureror 3.1",
"platform": "KDE 3.1",
"version": "3.1",
"grade": "C",
"status": false,
"time": "1703548800"
},
{
"engine": "KHTML",
"browser": "Konqureror 3.3",
"platform": "KDE 3.3",
"version": "3.3",
"grade": "A",
"status": true,
"time": "1703548800"
},
{
"engine": "KHTML",
"browser": "Konqureror 3.5",
"platform": "KDE 3.5",
"version": "3.5",
"grade": "A",
"status": false,
"time": "1703548800"
},
{
"engine": "Tasman",
"browser": "Internet Explorer 4.5",
"platform": "Mac OS 8-9",
"version": "-",
"grade": "X",
"status": true,
"time": "1703548800"
},
{
"engine": "Tasman",
"browser": "Internet Explorer 5.1",
"platform": "Mac OS 7.6-9",
"version": "1",
"grade": "C",
"status": false,
"time": "1703548800"
},
{
"engine": "Tasman",
"browser": "Internet Explorer 5.2",
"platform": "Mac OS 8-X",
"version": "1",
"grade": "C",
"status": true,
"time": "1703548800"
},
{
"engine": "Misc",
"browser": "NetFront 3.1",
"platform": "Embedded devices",
"version": "-",
"grade": "C",
"status": false,
"time": "1703548800"
},
{
"engine": "Misc",
"browser": "NetFront 3.4",
"platform": "Embedded devices",
"version": "-",
"grade": "A",
"status": true,
"time": "1703548800"
},
{
"engine": "Misc",
"browser": "Dillo 0.8",
"platform": "Embedded devices",
"version": "-",
"grade": "X",
"status": false,
"time": "1703548800"
},
{
"engine": "Misc",
"browser": "Links",
"platform": "Text only",
"version": "-",
"grade": "X",
"status": true,
"time": "1703721600"
},
{
"engine": "Misc",
"browser": "Lynx",
"platform": "Text only",
"version": "-",
"grade": "X",
"status": false,
"time": "1703721600"
},
{
"engine": "Misc",
"browser": "IE Mobile",
"platform": "Windows Mobile 6",
"version": "-",
"grade": "C",
"status": true,
"time": "1703721600"
},
{
"engine": "Misc",
"browser": "PSP browser",
"platform": "PSP",
"version": "-",
"grade": "C",
"status": false,
"time": "1703721600"
},
{
"engine": "Other browsers",
"browser": "All others",
"platform": "-",
"version": "-",
"grade": "U",
"status": true,
"time": "1703721600"
}
].map(function (item, index) {
return Object.assign({}, item, {
id: index + 1
});
});

View File

@ -0,0 +1,238 @@
/** 前端分页的接口 */
module.exports = function (req, res) {
res.json({
status: 0,
msg: 'ok',
data: {
count: data.length,
items: data
}
});
};
const data = [
{
"label": "Laura Lewis",
"value": "1"
},
{
"label": "David Gonzalez",
"value": "2"
},
{
"label": "Christopher Rodriguez",
"value": "3"
},
{
"label": "Sarah Young",
"value": "4"
},
{
"label": "James Jones",
"value": "5"
},
{
"label": "Larry Robinson",
"value": "6"
},
{
"label": "Christopher Perez",
"value": "7"
},
{
"label": "Sharon Davis",
"value": "8"
},
{
"label": "Kenneth Anderson",
"value": "9"
},
{
"label": "Deborah Lewis",
"value": "10"
},
{
"label": "Jennifer Lewis",
"value": "11"
},
{
"label": "Laura Miller",
"value": "12"
},
{
"label": "Larry Harris",
"value": "13"
},
{
"label": "Patricia Robinson",
"value": "14"
},
{
"label": "Mark Davis",
"value": "15"
},
{
"label": "Jessica Harris",
"value": "16"
},
{
"label": "Anna Brown",
"value": "17"
},
{
"label": "Lisa Young",
"value": "18"
},
{
"label": "Donna Williams",
"value": "19"
},
{
"label": "Shirley Davis",
"value": "20"
},
{
"label": "Richard Clark",
"value": "21"
},
{
"label": "Cynthia Martinez",
"value": "22"
},
{
"label": "Kimberly Walker",
"value": "23"
},
{
"label": "Timothy Anderson",
"value": "24"
},
{
"label": "Betty Lee",
"value": "25"
},
{
"label": "Jeffrey Allen",
"value": "26"
},
{
"label": "Karen Martinez",
"value": "27"
},
{
"label": "Anna Lopez",
"value": "28"
},
{
"label": "Dorothy Anderson",
"value": "29"
},
{
"label": "David Perez",
"value": "30"
},
{
"label": "Dorothy Martin",
"value": "31"
},
{
"label": "George Johnson",
"value": "32"
},
{
"label": "Donald Jackson",
"value": "33"
},
{
"label": "Mary Brown",
"value": "34"
},
{
"label": "Deborah Martinez",
"value": "35"
},
{
"label": "Donald Jackson",
"value": "36"
},
{
"label": "Lisa Robinson",
"value": "37"
},
{
"label": "Laura Martinez",
"value": "38"
},
{
"label": "Timothy Taylor",
"value": "39"
},
{
"label": "Joseph Martinez",
"value": "40"
},
{
"label": "Karen Wilson",
"value": "41"
},
{
"label": "Karen Walker",
"value": "42"
},
{
"label": "William Martinez",
"value": "43"
},
{
"label": "Linda Brown",
"value": "44"
},
{
"label": "Elizabeth Brown",
"value": "45"
},
{
"label": "Anna Moore",
"value": "46"
},
{
"label": "Robert Martinez",
"value": "47"
},
{
"label": "Edward Hernandez",
"value": "48"
},
{
"label": "Elizabeth Hall",
"value": "49"
},
{
"label": "Linda Jackson",
"value": "50"
},
{
"label": "Brian Jones",
"value": "51"
},
{
"label": "Amy Thompson",
"value": "52"
},
{
"label": "Kimberly Wilson",
"value": "53"
},
{
"label": "Nancy Garcia",
"value": "54"
},
{
"label": "Mary Thompson",
"value": "55"
}
].map(function (item, index) {
return Object.assign({}, item, {
id: index + 1
});
});

View File

@ -0,0 +1,242 @@
/** Transfer分页接口 */
module.exports = function (req, res) {
const perPage = Number(req.query.perPage || 10);
const page = Number(req.query.page || 1);
res.json({
status: 0,
msg: 'ok',
data: {
count: data.length,
page: page,
items: data.concat().splice((page - 1) * perPage, perPage)
}
});
};
const data = [
{
"label": "Laura Lewis",
"value": "1"
},
{
"label": "David Gonzalez",
"value": "2"
},
{
"label": "Christopher Rodriguez",
"value": "3"
},
{
"label": "Sarah Young",
"value": "4"
},
{
"label": "James Jones",
"value": "5"
},
{
"label": "Larry Robinson",
"value": "6"
},
{
"label": "Christopher Perez",
"value": "7"
},
{
"label": "Sharon Davis",
"value": "8"
},
{
"label": "Kenneth Anderson",
"value": "9"
},
{
"label": "Deborah Lewis",
"value": "10"
},
{
"label": "Jennifer Lewis",
"value": "11"
},
{
"label": "Laura Miller",
"value": "12"
},
{
"label": "Larry Harris",
"value": "13"
},
{
"label": "Patricia Robinson",
"value": "14"
},
{
"label": "Mark Davis",
"value": "15"
},
{
"label": "Jessica Harris",
"value": "16"
},
{
"label": "Anna Brown",
"value": "17"
},
{
"label": "Lisa Young",
"value": "18"
},
{
"label": "Donna Williams",
"value": "19"
},
{
"label": "Shirley Davis",
"value": "20"
},
{
"label": "Richard Clark",
"value": "21"
},
{
"label": "Cynthia Martinez",
"value": "22"
},
{
"label": "Kimberly Walker",
"value": "23"
},
{
"label": "Timothy Anderson",
"value": "24"
},
{
"label": "Betty Lee",
"value": "25"
},
{
"label": "Jeffrey Allen",
"value": "26"
},
{
"label": "Karen Martinez",
"value": "27"
},
{
"label": "Anna Lopez",
"value": "28"
},
{
"label": "Dorothy Anderson",
"value": "29"
},
{
"label": "David Perez",
"value": "30"
},
{
"label": "Dorothy Martin",
"value": "31"
},
{
"label": "George Johnson",
"value": "32"
},
{
"label": "Donald Jackson",
"value": "33"
},
{
"label": "Mary Brown",
"value": "34"
},
{
"label": "Deborah Martinez",
"value": "35"
},
{
"label": "Donald Jackson",
"value": "36"
},
{
"label": "Lisa Robinson",
"value": "37"
},
{
"label": "Laura Martinez",
"value": "38"
},
{
"label": "Timothy Taylor",
"value": "39"
},
{
"label": "Joseph Martinez",
"value": "40"
},
{
"label": "Karen Wilson",
"value": "41"
},
{
"label": "Karen Walker",
"value": "42"
},
{
"label": "William Martinez",
"value": "43"
},
{
"label": "Linda Brown",
"value": "44"
},
{
"label": "Elizabeth Brown",
"value": "45"
},
{
"label": "Anna Moore",
"value": "46"
},
{
"label": "Robert Martinez",
"value": "47"
},
{
"label": "Edward Hernandez",
"value": "48"
},
{
"label": "Elizabeth Hall",
"value": "49"
},
{
"label": "Linda Jackson",
"value": "50"
},
{
"label": "Brian Jones",
"value": "51"
},
{
"label": "Amy Thompson",
"value": "52"
},
{
"label": "Kimberly Wilson",
"value": "53"
},
{
"label": "Nancy Garcia",
"value": "54"
},
{
"label": "Mary Thompson",
"value": "55"
}
].map(function (item, index) {
return Object.assign({}, item, {
id: index + 1
});
});

View File

@ -0,0 +1,31 @@
/** 测试上传,随机成功 */
module.exports = function(req, res) {
const pool = [1,2,3,4,5,6,7,8,9,10];
let result = pool.slice();
for( let i = 0; i < pool.length; i++) {
let k = Math.floor(Math.random() * (pool.length - i) + i);
[result[i], result[k]] = [result[k], result[i]];
}
const randomNum = result[0];
if (randomNum > 5) {
return res.json({
status: 0,
msg: '上传成功',
data: {
"value": `http://amis.bj.bcebos.com/amis/random/${randomNum}`,
"url": `http://amis.bj.bcebos.com/amis/random/${randomNum}`,
"filename": `random${randomNum}.js`
}
});
}
else {
return res.json({
status: 500,
msg: '上传失败',
data: null
});
}
}

View File

@ -40,9 +40,9 @@
]
},
"dependencies": {
"path-to-regexp": "^6.2.0",
"postcss": "^8.4.14",
"qs": "6.9.7",
"path-to-regexp": "^6.2.0"
"qs": "6.9.7"
},
"devDependencies": {
"@babel/generator": "^7.22.9",
@ -79,6 +79,7 @@
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"js-yaml": "^4.1.0",
"katex": "^0.16.9",
"lerna": "^6.6.2",
"lint-staged": "^12.1.2",
"magic-string": "^0.26.7",

View File

@ -1,6 +1,6 @@
{
"name": "amis-core",
"version": "3.4.2",
"version": "3.5.1",
"description": "amis-core",
"main": "lib/index.js",
"module": "esm/index.js",
@ -46,7 +46,7 @@
"esm"
],
"dependencies": {
"amis-formula": "^3.4.2",
"amis-formula": "^3.5.1",
"classnames": "2.3.2",
"file-saver": "^2.0.2",
"hoist-non-react-statics": "^3.3.2",
@ -57,12 +57,12 @@
"mobx-state-tree": "^3.17.3",
"moment": "^2.19.4",
"papaparse": "^5.3.0",
"path-to-regexp": "6.2.0",
"qs": "6.9.7",
"react-intersection-observer": "9.5.2",
"react-json-view": "1.21.3",
"tslib": "^2.3.1",
"uncontrollable": "7.2.1",
"path-to-regexp": "6.2.0"
"uncontrollable": "7.2.1"
},
"peerDependencies": {
"amis-formula": "*",

View File

@ -36,13 +36,28 @@ export class CmptAction implements RendererAction {
*/
const key = action.componentId || action.componentName;
const dataMergeMode = action.dataMergeMode || 'merge';
const path = action.args?.path;
/** 如果args中携带path参数, 则认为是全局变量赋值, 否则认为是组件变量赋值 */
if (action.actionType === 'setValue' && path && typeof path === 'string') {
const beforeSetData = renderer?.props?.env?.beforeSetData;
if (beforeSetData && typeof beforeSetData === 'function') {
const res = await beforeSetData(renderer, action, event);
if (res === false) {
return;
}
}
}
// 如果key没指定则默认是当前组件
let component = key
? event.context.scoped?.[
action.componentId ? 'getComponentById' : 'getComponentByName'
](key)
: null;
: renderer;
// 如果key指定来但是没找到组件则报错
if (key && !component) {
const msg =
'尝试执行一个不存在的目标组件动作请检查目标组件非隐藏状态且正确指定了componentId或componentName';
@ -54,23 +69,6 @@ export class CmptAction implements RendererAction {
}
if (action.actionType === 'setValue') {
const beforeSetData = renderer?.props?.env?.beforeSetData;
const path = action.args?.path;
/** 如果args中携带path参数, 则认为是全局变量赋值, 否则认为是组件变量赋值 */
if (
path &&
typeof path === 'string' &&
beforeSetData &&
typeof beforeSetData === 'function'
) {
const res = await beforeSetData(renderer, action, event);
if (res === false) {
return;
}
}
if (component?.setData) {
return component?.setData(
action.args?.value,

View File

@ -28,6 +28,8 @@ export interface WsObject {
}
export interface RendererEnv {
/* 强制隐藏组件内部的报错信息,会覆盖组件内部属性 */
forceSilenceInsideError?: boolean;
session?: string;
fetcher: (api: Api, data?: any, options?: object) => Promise<Payload>;
isCancel: (val: any) => boolean;
@ -87,6 +89,16 @@ export interface RendererEnv {
affixOffsetBottom?: number;
richTextToken: string;
/**
* baidu
*/
locationPickerVendor?: string;
/**
* ak
*/
locationPickerAK?: string;
loadRenderer: (
schema: Schema,
path: string,

View File

@ -2,26 +2,31 @@
* @file 使
*/
import {findObjectsWithKey} from './utils/helper';
import {JSONValueMap, findObjectsWithKey} from './utils/helper';
import isPlainObject from 'lodash/isPlainObject';
const isMobile = (window as any).matchMedia?.('(max-width: 768px)').matches
? true
: false;
// 这里不能用 addSchemaFilter 是因为还需要更深层的替换,比如 select 里的 options
export const envOverwrite = (schema: any, locale?: string) => {
if (locale) {
let schemaNodes = findObjectsWithKey(schema, locale);
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode[locale]);
delete schemaNode[locale];
}
}
return JSONValueMap(
schema,
(value: any) => {
if (!isPlainObject(value)) {
return value;
}
if (isMobile) {
let schemaNodes = findObjectsWithKey(schema, 'mobile');
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode['mobile']);
delete schemaNode['mobile'];
}
}
if (locale && value[locale]) {
const newValue = Object.assign({}, value, value[locale]);
delete newValue[locale];
return newValue;
} else if (isMobile && value.mobile) {
const newValue = Object.assign({}, value, value.mobile);
delete newValue.mobile;
return newValue;
}
},
true
);
};

View File

@ -308,7 +308,7 @@ function AMISRenderer({
}
// 根据环境覆盖 schema这个要在最前面做不然就无法覆盖 validations
envOverwrite(schema, locale);
schema = envOverwrite(schema, locale);
schema = replaceText(schema, options.replaceText, env.replaceTextIgnoreKeys);
return (

View File

@ -50,6 +50,7 @@ import {isAlive} from 'mobx-state-tree';
import type {LabelAlign} from './Item';
import {injectObjectChain} from '../utils';
import {reaction} from 'mobx';
export interface FormHorizontal {
left?: number;
@ -371,6 +372,7 @@ export interface FormProps
onFailed?: (reason: string, errors: any) => any;
onFinished: (values: object, action: any) => any;
onValidate: (values: object, form: any) => any;
onValidChange?: (valid: boolean, props: any) => void; // 表单数据合法性变更
messages: {
fetchSuccess?: string;
fetchFailed?: string;
@ -443,6 +445,8 @@ export default class Form extends React.Component<FormProps, object> {
'onChange',
'onFailed',
'onFinished',
'onValidate',
'onValidChange',
'onSaved',
'canAccessSuperData',
'lazyChange',
@ -460,8 +464,7 @@ export default class Form extends React.Component<FormProps, object> {
[propName: string]: Array<() => Promise<any>>;
} = {};
asyncCancel: () => void;
disposeOnValidate: () => void;
disposeRulesValidate: () => void;
toDispose: Array<() => void> = [];
shouldLoadInitApi: boolean = false;
timer: ReturnType<typeof setTimeout>;
mounted: boolean;
@ -532,6 +535,7 @@ export default class Form extends React.Component<FormProps, object> {
store,
messages: {fetchSuccess, fetchFailed},
onValidate,
onValidChange,
promptPageLeave,
env,
rules
@ -541,49 +545,63 @@ export default class Form extends React.Component<FormProps, object> {
if (onValidate) {
const finalValidate = promisify(onValidate);
this.disposeOnValidate = this.addHook(async () => {
const result = await finalValidate(store.data, store);
this.toDispose.push(
this.addHook(async () => {
const result = await finalValidate(store.data, store);
if (result && isObject(result)) {
Object.keys(result).forEach(key => {
let msg = result[key];
const items = store.getItemsByPath(key);
if (result && isObject(result)) {
Object.keys(result).forEach(key => {
let msg = result[key];
const items = store.getItemsByPath(key);
// 没有找到
if (!Array.isArray(items) || !items.length) {
return;
}
// 没有找到
if (!Array.isArray(items) || !items.length) {
return;
}
// 在setError之前提前把残留的error信息清除掉否则每次onValidate后都会一直把报错 append 上去
items.forEach(item => item.clearError());
// 在setError之前提前把残留的error信息清除掉否则每次onValidate后都会一直把报错 append 上去
items.forEach(item => item.clearError());
if (msg) {
msg = Array.isArray(msg) ? msg : [msg];
items.forEach(item => item.addError(msg));
}
if (msg) {
msg = Array.isArray(msg) ? msg : [msg];
items.forEach(item => item.addError(msg));
}
delete result[key];
});
delete result[key];
});
isEmpty(result)
? store.clearRestError()
: store.setRestError(Object.keys(result).map(key => result[key]));
}
});
isEmpty(result)
? store.clearRestError()
: store.setRestError(Object.keys(result).map(key => result[key]));
}
})
);
}
// 表单校验结果发生变化时,触发 onValidChange
if (onValidChange) {
this.toDispose.push(
reaction(
() => store.valid,
valid => onValidChange(valid, this.props)
)
);
}
if (Array.isArray(rules) && rules.length) {
this.disposeRulesValidate = this.addHook(() => {
if (!store.valid) {
return;
}
this.toDispose.push(
this.addHook(() => {
if (!store.valid) {
return;
}
rules.forEach(
item =>
!evalExpression(item.rule, store.data) &&
store.addRestError(item.message, item.name)
);
});
rules.forEach(
item =>
!evalExpression(item.rule, store.data) &&
store.addRestError(item.message, item.name)
);
})
);
}
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
@ -655,8 +673,8 @@ export default class Form extends React.Component<FormProps, object> {
// this.lazyHandleChange.flush();
this.lazyEmitChange.cancel();
this.asyncCancel && this.asyncCancel();
this.disposeOnValidate && this.disposeOnValidate();
this.disposeRulesValidate && this.disposeRulesValidate();
this.toDispose.forEach(fn => fn());
this.toDispose = [];
window.removeEventListener('beforeunload', this.beforePageUnload);
this.unBlockRouting?.();
}
@ -836,30 +854,27 @@ export default class Form extends React.Component<FormProps, object> {
return this.props.store.validated;
}
validate(
async validate(
forceValidate?: boolean,
throwErrors: boolean = false
throwErrors: boolean = false,
toastErrors: boolean = true
): Promise<boolean> {
const {store, dispatchEvent, data, messages, translate: __} = this.props;
this.flush();
return store
.validate(
this.hooks['validate'] || [],
forceValidate,
throwErrors,
typeof messages?.validateFailed === 'string'
? __(filter(messages.validateFailed, store.data))
: undefined
)
.then((result: boolean) => {
if (result) {
dispatchEvent('validateSucc', data);
} else {
dispatchEvent('validateError', data);
}
return result;
});
const result = await store.validate(
this.hooks['validate'] || [],
forceValidate,
throwErrors,
toastErrors === false
? ''
: typeof messages?.validateFailed === 'string'
? __(filter(messages.validateFailed, store.data))
: undefined
);
dispatchEvent(result ? 'validateSucc' : 'validateError', data);
return result;
}
setErrors(errors: {[propName: string]: string}, tag = 'remote') {

View File

@ -28,13 +28,12 @@ import {
ClassName,
Schema
} from '../types';
import {filter} from '../utils/tpl';
import {HocStoreFactory} from '../WithStore';
import {wrapControl} from './wrapControl';
import debounce from 'lodash/debounce';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {findDOMNode} from 'react-dom';
import {dataMapping, setThemeClassName} from '../utils';
import {dataMapping, setThemeClassName, traceProps} from '../utils';
import Overlay from '../components/Overlay';
import PopOver from '../components/PopOver';
import CustomStyle from '../components/CustomStyle';
@ -1022,12 +1021,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
style={labelWidth != null ? {width: labelWidth} : undefined}
>
<span>
{label
? render(
'label',
typeof label === 'string' ? filter(label, data) : label
)
: null}
{label ? render('label', label) : null}
{required && (label || labelRemark) ? (
<span className={cx(`Form-star`)}>*</span>
) : null}
@ -1163,12 +1157,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
{label && renderLabel !== false ? (
<label className={cx(`Form-label`, getItemLabelClassName(props))}>
<span>
{label
? render(
'label',
typeof label === 'string' ? filter(label, data) : label
)
: null}
{label ? render('label', label) : null}
{required && (label || labelRemark) ? (
<span className={cx(`Form-star`)}>*</span>
) : null}
@ -1355,12 +1344,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
style={labelWidth != null ? {width: labelWidth} : undefined}
>
<span>
{label
? render(
'label',
typeof label === 'string' ? filter(label, data) : label
)
: label}
{label ? render('label', label) : label}
{required && (label || labelRemark) ? (
<span className={cx(`Form-star`)}>*</span>
) : null}
@ -1494,10 +1478,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
style={labelWidth != null ? {width: labelWidth} : undefined}
>
<span>
{render(
'label',
typeof label === 'string' ? filter(label, data) : label
)}
{render('label', label)}
{required && (label || labelRemark) ? (
<span className={cx(`Form-star`)}>*</span>
) : null}

View File

@ -37,6 +37,7 @@ import {
FormBaseControl
} from './Item';
import {IFormItemStore} from '../store/formItem';
import {isObject} from 'amis-core';
export type OptionsControlComponent = React.ComponentType<FormControlProps>;
@ -230,7 +231,11 @@ export interface OptionsControlProps
selectedOptions: Array<Option>;
setOptions: (value: Array<any>, skipNormalize?: boolean) => void;
setLoading: (value: boolean) => void;
reloadOptions: (setError?: boolean) => void;
reloadOptions: (
setError?: boolean,
isInit?: boolean,
data?: Record<string, any>
) => void;
deferLoad: (option: Option) => void;
leftDeferLoad: (option: Option, leftOptions: Option) => void;
expandTreeOptions: (nodePathArr: any[]) => void;
@ -443,15 +448,12 @@ export function registerOptionsControl(config: OptionsConfig) {
);
if (prevOptions !== options) {
formItem.setOptions(
normalizeOptions(
options || [],
undefined,
props.valueField || 'value'
),
this.changeOptionValue,
props.data
formItem.loadOptionsFromDataScope(
props.source as string,
props.data,
this.changeOptionValue
);
this.normalizeValue();
}
} else if (
@ -792,20 +794,16 @@ export function registerOptionsControl(config: OptionsConfig) {
}
@autobind
reloadOptions(setError?: boolean, isInit = false) {
const {source, formItem, data, onChange, setPrinstineValue, valueField} =
reloadOptions(setError?: boolean, isInit = false, data = this.props.data) {
const {source, formItem, onChange, setPrinstineValue, valueField} =
this.props;
if (formItem && isPureVariable(source as string)) {
isAlive(formItem) &&
formItem.setOptions(
normalizeOptions(
resolveVariableAndFilter(source as string, data, '| raw') || [],
undefined,
valueField
),
this.changeOptionValue,
data
formItem.loadOptionsFromDataScope(
source as string,
data,
this.changeOptionValue
);
return;
} else if (!formItem || !isEffectiveApi(source, data)) {

View File

@ -31,7 +31,13 @@ import {FormBaseControl, FormItemWrap} from './Item';
import {Api} from '../types';
import {TableStore} from '../store/table';
import pick from 'lodash/pick';
import {callStrFunction, changedEffect, tokenize} from '../utils';
import {
callStrFunction,
changedEffect,
cloneObject,
setVariable,
tokenize
} from '../utils';
export interface ControlOutterProps extends RendererProps {
formStore?: IFormStore;
@ -150,7 +156,8 @@ export function wrapControl<
minLength,
maxLength,
validateOnChange,
label
label,
pagination
}
} = this.props;
@ -162,6 +169,7 @@ export function wrapControl<
this.handleBlur = this.handleBlur.bind(this);
this.validate = this.validate.bind(this);
this.flushChange = this.flushChange.bind(this);
this.renderChild = this.renderChild.bind(this);
let name = this.props.$schema.name;
// 如果 name 是表达式
@ -223,7 +231,8 @@ export function wrapControl<
validateOnChange,
label,
inputGroupControl,
extraName
extraName,
pagination
});
// issue 这个逻辑应该在 combo 里面自己实现。
@ -373,7 +382,8 @@ export function wrapControl<
'minLength',
'maxLength',
'label',
'extraName'
'extraName',
'pagination'
],
prevProps.$schema,
props.$schema,
@ -822,6 +832,24 @@ export function wrapControl<
}
}
renderChild(
region: string,
node?: any,
subProps: {
[propName: string]: any;
} = {}
) {
const {render, data, store} = this.props;
const model = this.model;
return render(region, node, {
data: model
? model.getMergedData(data || store?.data)
: data || store?.data,
...subProps
});
}
render() {
const {
controlWidth,
@ -848,7 +876,9 @@ export function wrapControl<
formItem: this.model,
formMode: control.mode || formMode,
ref: this.controlRef,
data: data || store?.data,
data: model
? model.getMergedData(data || store?.data)
: data || store?.data,
name: model?.name ?? control.name,
value,
changeMotivation: model?.changeMotivation,
@ -861,7 +891,8 @@ export function wrapControl<
prinstine: model ? model.prinstine : undefined,
setPrinstineValue: this.setPrinstineValue,
onValidate: this.validate,
onFlushChange: this.flushChange
onFlushChange: this.flushChange,
render: this.renderChild // 如果覆盖,那么用的就是 form 上的 render这个里面用到的 data 是比较旧的。
// !没了这个, tree 里的 options 渲染会出问题
// todo 理论上不应该影响,待确认
// _filteredOptions: this.model?.filteredOptions

View File

@ -35,7 +35,8 @@ export const ComboStore = iRendererStore
minLength: 0,
maxLength: 0,
length: 0,
activeKey: 0
activeKey: 0,
memberValidMap: types.optional(types.frozen(), {})
})
.views(self => {
function getForms() {
@ -170,13 +171,21 @@ export const ComboStore = iRendererStore
self.activeKey = key;
}
function setMemberValid(valid: boolean, index: number) {
self.memberValidMap = {
...self.memberValidMap,
[index]: valid
};
}
return {
config,
setActiveKey,
bindUniuqueItem,
unBindUniuqueItem,
addForm,
onChildStoreDispose
onChildStoreDispose,
setMemberValid
};
});

View File

@ -18,6 +18,22 @@ import {normalizeApiResponseData} from '../utils/api';
import {matchSorter} from 'match-sorter';
import {filter} from '../utils/tpl';
interface MatchFunc {
(
/* 当前列表的全量数据 */
items: any,
/* 最近一次接口返回的全量数据 */
itemsRaw: any,
/** 相关配置 */
options?: {
/* 查询参数 */
query: Record<string, any>;
/* 列配置 */
columns: any;
}
): any;
}
class ServerError extends Error {
type = 'ServerError';
}
@ -125,7 +141,32 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
...values
};
if (isObjectShallowModified(originQuery, query, false)) {
/**
* CASE
* @reference https://tc39.es/ecma262/#sec-islooselyequal
*/
const exceptedLooselyRules: [any, any][] = [
[0, ''],
[false, ''],
[false, '0'],
[false, 0],
[true, 1],
[true, '1']
];
if (
isObjectShallowModified(originQuery, query, (lhs: any, rhs: any) => {
if (
exceptedLooselyRules.some(
rule => rule.includes(lhs) && rule.includes(rhs)
)
) {
return lhs !== rhs;
}
return lhs != rhs;
})
) {
if (query[pageField || 'page']) {
self.page = parseInt(query[pageField || 'page'], 10);
}
@ -161,10 +202,12 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
loadDataMode?: boolean;
syncResponse2Query?: boolean;
columns?: Array<any>;
matchFunc?: MatchFunc;
} = {}
) {
try {
if (!options.forceReload && options.loadDataOnce && self.total) {
const matchFunc = options.matchFunc;
let items = options.source
? resolveVariableAndFilter(
options.source,
@ -176,41 +219,49 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
)
: self.items.concat();
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(self.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
/** 字段的格式类型无法穷举,所以支持使用函数过滤 */
if (matchFunc && typeof matchFunc === 'function') {
items = matchFunc(items, self.data.itemsRaw, {
query: self.query,
columns: options.columns
});
} else {
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(self.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
});
}
}
if (self.query.orderBy) {
@ -589,8 +640,10 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
source: string,
options: {
columns?: Array<any>;
matchFunc?: MatchFunc | null;
}
) {
const matchFunc = options.matchFunc;
let items: Array<any> = resolveVariableAndFilter(source, scope, '| raw');
if (!Array.isArray(items) && !self.items.length) {
@ -599,41 +652,49 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
items = Array.isArray(items) ? items : [];
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(self.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
/** 字段的格式类型无法穷举,所以支持使用函数过滤 */
if (matchFunc && typeof matchFunc === 'function') {
items = matchFunc(items, items.concat(), {
query: self.query,
columns: options.columns
});
} else {
if (Array.isArray(options.columns)) {
options.columns.forEach((column: any) => {
let value: any =
typeof column.name === 'string'
? getVariable(self.query, column.name)
: undefined;
const key = column.name;
if (value != null && key) {
// value可能为null、undefined、''、0
if (Array.isArray(value)) {
if (value.length > 0) {
const arr = [...items];
let arrItems: Array<any> = [];
value.forEach(item => {
arrItems = [
...arrItems,
...matchSorter(arr, item, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
})
];
});
items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
}
} else {
items = matchSorter(items, value, {
keys: [key],
threshold: matchSorter.rankings.CONTAINS
});
}
}
});
}
}
if (self.query.orderBy) {

View File

@ -7,11 +7,13 @@ import {
Instance
} from 'mobx-state-tree';
import isEqualWith from 'lodash/isEqualWith';
import uniqWith from 'lodash/uniqWith';
import {FormStore, IFormStore} from './form';
import {str2rules, validate as doValidate} from '../utils/validations';
import {Api, Payload, fetchOptions, ApiObject} from '../types';
import {ComboStore, IComboStore, IUniqueGroup} from './combo';
import {evalExpression} from '../utils/tpl';
import {resolveVariableAndFilter} from '../utils/tpl-builtin';
import {buildApi, isEffectiveApi} from '../utils/api';
import findIndex from 'lodash/findIndex';
import {
@ -24,7 +26,9 @@ import {
spliceTree,
filterTree,
eachTree,
mapTree
mapTree,
setVariable,
cloneObject
} from '../utils/helper';
import {flattenTree} from '../utils/helper';
import find from 'lodash/find';
@ -96,6 +100,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
joinValues: true,
extractValue: false,
options: types.optional(types.frozen<Array<any>>(), []),
optionsRaw: types.optional(types.frozen<Array<any>>(), []),
expressionsInOptions: false,
selectFirst: false,
autoFill: types.frozen(),
@ -111,7 +116,18 @@ export const FormItemStore = StoreNode.named('FormItemStore')
/** 当前表单项所属的InputGroup父元素, 用于收集InputGroup的子元素 */
inputGroupControl: types.optional(types.frozen(), {}),
colIndex: types.frozen(),
rowIndex: types.frozen()
rowIndex: types.frozen(),
/** Transfer组件分页模式 */
pagination: types.optional(types.frozen(), {
enable: false,
/** 当前页数 */
page: 1,
/** 每页显示条数 */
perPage: 10,
/** 总条数 */
total: 0
}),
accumulatedOptions: types.optional(types.frozen<Array<any>>(), [])
})
.views(self => {
function getForm(): any {
@ -173,6 +189,26 @@ export const FormItemStore = StoreNode.named('FormItemStore')
return getLastOptionValue();
},
/** 数据源接口数据是否开启分页 */
get enableSourcePagination(): boolean {
return !!self.pagination.enable;
},
/** 数据源接口开启分页时当前页码 */
get sourcePageNum(): number {
return self.pagination.page ?? 1;
},
/** 数据源接口开启分页时每页显示条数 */
get sourcePerPageNum(): number {
return self.pagination.perPage ?? 10;
},
/** 数据源接口开启分页时数据总条数 */
get sourceTotalNum(): number {
return self.pagination.total ?? 0;
},
getSelectedOptions: (
value: any = self.tmpValue,
nodeValueArray?: any[] | undefined
@ -265,6 +301,14 @@ export const FormItemStore = StoreNode.named('FormItemStore')
? value.split(delimiter || ',').map((v: string) => v.trim())
: [];
return values;
},
getMergedData(data: any) {
const result = cloneObject(data);
setVariable(result, self.name, self.tmpValue);
setVariable(result, '__value', self.tmpValue);
setVariable(result, '__name', self.name);
return result;
}
};
})
@ -298,7 +342,8 @@ export const FormItemStore = StoreNode.named('FormItemStore')
minLength,
validateOnChange,
label,
inputGroupControl
inputGroupControl,
pagination
}: {
extraName?: string;
required?: boolean;
@ -328,6 +373,11 @@ export const FormItemStore = StoreNode.named('FormItemStore')
path: string;
[propsName: string]: any;
};
pagination?: {
enable?: boolean;
page?: number;
perPage?: number;
};
}) {
if (typeof rules === 'string') {
rules = str2rules(rules);
@ -362,6 +412,15 @@ export const FormItemStore = StoreNode.named('FormItemStore')
inputGroupControl?.name != null &&
(self.inputGroupControl = inputGroupControl);
if (pagination && isObject(pagination) && !!pagination.enable) {
self.pagination = {
enable: true,
page: pagination.page ? pagination.page || 1 : 1,
perPage: pagination.perPage ? pagination.perPage || 10 : 10,
total: 0
};
}
if (
typeof rules !== 'undefined' ||
typeof required !== 'undefined' ||
@ -546,6 +605,23 @@ export const FormItemStore = StoreNode.named('FormItemStore')
}
}
function setPagination(params: {
page?: number;
perPage?: number;
total?: number;
}) {
const {page, perPage, total} = params || {};
if (self.enableSourcePagination) {
self.pagination = {
...self.pagination,
...(page != null && typeof page === 'number' ? {page} : {}),
...(perPage != null && typeof perPage === 'number' ? {perPage} : {}),
...(total != null && typeof total === 'number' ? {total} : {})
};
}
}
function setOptions(
options: Array<object>,
onChange?: (value: any) => void,
@ -557,6 +633,15 @@ export const FormItemStore = StoreNode.named('FormItemStore')
options = filterTree(options, item => item);
const originOptions = self.options.concat();
self.options = options;
/** 开启分页后当前选项内容需要累加 */
self.accumulatedOptions = self.enableSourcePagination
? uniqWith(
[...originOptions, ...options],
(lhs, rhs) =>
lhs[self.valueField ?? 'value'] ===
rhs[self.valueField ?? 'value']
)
: options;
syncOptions(originOptions, data);
let selectedOptions;
@ -712,6 +797,14 @@ export const FormItemStore = StoreNode.named('FormItemStore')
options = normalizeOptions(options as any, undefined, self.valueField);
if (self.enableSourcePagination) {
self.pagination = {
...self.pagination,
page: parseInt(json.data?.page, 10) || 1,
total: parseInt(json.data?.total ?? json.data?.count, 10) || 0
};
}
if (config?.extendsOptions && self.selectedOptions.length > 0) {
self.selectedOptions.forEach((item: any) => {
const exited = findTree(
@ -742,6 +835,41 @@ export const FormItemStore = StoreNode.named('FormItemStore')
return json;
});
/**
* source变量解析后是全量的数据源
*/
function loadOptionsFromDataScope(
source: string,
ctx: Record<string, any>,
onChange?: (value: any) => void
) {
let options: any[] = resolveVariableAndFilter(source, ctx, '| raw');
if (!Array.isArray(options)) {
return [];
}
options = normalizeOptions(options, undefined, self.valueField);
if (self.enableSourcePagination) {
self.pagination = {
...self.pagination,
...(ctx?.page ? {page: ctx?.page} : {}),
...(ctx?.perPage ? {perPage: ctx?.perPage} : {}),
total: options.length
};
options = options.slice(
(self.pagination.page - 1) * self.pagination.perPage,
self.pagination.page * self.pagination.perPage
);
}
setOptions(options, onChange, ctx);
return options;
}
const loadAutoUpdateData: (
api: Api,
data?: object,
@ -1367,8 +1495,10 @@ export const FormItemStore = StoreNode.named('FormItemStore')
setError,
addError,
clearError,
setPagination,
setOptions,
loadOptions,
loadOptionsFromDataScope,
deferLoadOptions,
deferLoadLeftOptions,
expandTreeOptions,

View File

@ -979,7 +979,12 @@ export const TableStore = iRendererStore
return tableRef;
}
function update(config: Partial<STableStore>) {
function update(
config: Partial<STableStore>,
options?: {
resolveDefinitions?: (ref: string) => any;
}
) {
config.primaryField !== undefined &&
(self.primaryField = config.primaryField);
config.selectable !== undefined && (self.selectable = config.selectable);
@ -1039,8 +1044,21 @@ export const TableStore = iRendererStore
if (config.columns && Array.isArray(config.columns)) {
let columns: Array<SColumn> = config.columns
.filter(column => column)
.concat();
.map(column => {
if (
options?.resolveDefinitions &&
typeof (column as any)?.$ref == 'string' &&
(column as any).$ref
) {
return {
...options.resolveDefinitions((column as any).$ref),
...column
};
}
return column;
})
.filter(column => column);
// 更新列顺序afterCreate生命周期中更新columns不会触发组件的render
const key = getPersistDataKey(columns);

View File

@ -449,10 +449,6 @@ export const TableStore2 = ServiceStore.named('TableStore2')
if (item?.parentId) {
const parent: IRow2 = self.getRowById(item.parentId) as any;
const offset = parent.children.indexOf(item) - fromIndex;
toIndex += offset;
fromIndex += offset;
const newRows = parent.children.concat();
newRows.splice(fromIndex, 1);
newRows.splice(toIndex, 0, item);
@ -464,7 +460,6 @@ export const TableStore2 = ServiceStore.named('TableStore2')
const newRows = self.rows.concat();
newRows.splice(fromIndex, 1);
newRows.splice(toIndex, 0, item);
newRows.forEach((item, index) => (item.newIndex = index));
self.rows.replace(newRows);
}

View File

@ -657,6 +657,11 @@ export interface BaseSchemaWithoutType {
*
*/
editorSetting?: {
/**
* createupdateremove
*/
behavior?: string;
/**
* 便
*/
@ -666,6 +671,8 @@ export interface BaseSchemaWithoutType {
* 便
*/
mock?: any;
[propName: string]: any;
};
/**

View File

@ -251,9 +251,9 @@ export function rmUndefined(obj: PlainObject) {
export function isObjectShallowModified(
prev: any,
next: any,
strictMode: boolean = true,
strictModeOrFunc: boolean | ((lhs: any, rhs: any) => boolean) = true,
ignoreUndefined: boolean = false,
statck: Array<any> = []
stack: Array<any> = []
): boolean {
if (Array.isArray(prev) && Array.isArray(next)) {
return prev.length !== next.length
@ -262,9 +262,9 @@ export function isObjectShallowModified(
isObjectShallowModified(
prev,
next[index],
strictMode,
strictModeOrFunc,
ignoreUndefined,
statck
stack
)
);
}
@ -278,10 +278,16 @@ export function isObjectShallowModified(
null == next ||
!isObject(prev) ||
!isObject(next) ||
isObservable(prev) ||
isObservable(next)
// 不是 Object.create 创建的对象
// 不是 plain object
prev.constructor !== Object ||
next.constructor !== Object
) {
return strictMode ? prev !== next : prev != next;
if (strictModeOrFunc && typeof strictModeOrFunc === 'function') {
return strictModeOrFunc(prev, next);
}
return strictModeOrFunc ? prev !== next : prev != next;
}
if (ignoreUndefined) {
@ -299,11 +305,11 @@ export function isObjectShallowModified(
}
// 避免循环引用死循环。
if (~statck.indexOf(prev)) {
if (~stack.indexOf(prev)) {
return false;
}
statck.push(prev);
stack.push(prev);
for (let i: number = keys.length - 1; i >= 0; i--) {
const key = keys[i];
@ -311,9 +317,9 @@ export function isObjectShallowModified(
isObjectShallowModified(
prev[key],
next[key],
strictMode,
strictModeOrFunc,
ignoreUndefined,
statck
stack
)
) {
return true;
@ -1948,6 +1954,7 @@ export function JSONValueMap(
host: Object,
stack: Array<Object>
) => any,
deepFirst: boolean = false,
stack: Array<Object> = []
) {
if (!isPlainObject(json) && !Array.isArray(json)) {
@ -1960,40 +1967,42 @@ export function JSONValueMap(
host: any,
stack: Array<any> = []
) => {
let maped: any = mapper(origin, key, host, stack);
if (deepFirst) {
const value = JSONValueMap(origin, mapper, deepFirst, stack);
return mapper(value, key, host, stack) ?? value;
}
if (maped === origin && (isPlainObject(origin) || Array.isArray(origin))) {
return JSONValueMap(origin, mapper, stack);
let maped: any = mapper(origin, key, host, stack) ?? origin;
// 如果不是深度优先,上层的对象都修改了,就不继续递归进到新返回的对象了
if (maped === origin) {
return JSONValueMap(origin, mapper, deepFirst, stack);
}
return maped;
};
if (Array.isArray(json)) {
let flag = false;
let mapped = json.map((value, index) => {
let result: any = iterator(value, index, json, [json].concat(stack));
if (result !== value) {
flag = true;
return result;
}
return value;
let modified = false;
let arr = json.map((value, index) => {
let newValue: any = iterator(value, index, json, [json].concat(stack));
modified = modified || newValue !== value;
return newValue;
});
return flag ? mapped : json;
return modified ? arr : json;
}
let flag = false;
let modified = false;
const toUpdate: any = {};
Object.keys(json).forEach(key => {
const value: any = json[key];
let result: any = iterator(value, key, json, [json].concat(stack));
if (result !== value) {
flag = true;
modified = true;
toUpdate[key] = result;
return;
}
});
return flag
return modified
? {
...json,
...toUpdate

View File

@ -163,7 +163,11 @@ export function formatStyle(
const styles: string[] = [];
const fn = (key: string, value: string) => {
key = valueMap[key] || key;
styles.push(`${kebabCase(key)}: ${value};`);
styles.push(
`${kebabCase(key)}: ${
value + (weights?.important ? ' !important' : '')
};`
);
};
Object.keys(statusMap[status]).forEach(key => {
if (key !== '$$id') {
@ -191,15 +195,11 @@ export function formatStyle(
} else {
const value = style;
if (key === 'iconSize') {
fn('width', value + (weights?.important ? ' !important' : ''));
fn('height', value + (weights?.important ? ' !important' : ''));
fn(
'font-size',
value + (weights?.important ? ' !important' : '')
);
fn('width', value);
fn('height', value);
fn('font-size', value);
} else {
value &&
fn(key, value + (weights?.important ? ' !important' : ''));
value && fn(key, value);
}
}
}
@ -252,12 +252,8 @@ export function insertCustomStyle(
}
let {value} = formatStyle(themeCss, classNames, id, defaultData);
if (value) {
value = customStyleClassPrefix
? `${customStyleClassPrefix} ${value}`
: value;
insertStyle(value, id.replace('u:', ''), doc);
}
value = customStyleClassPrefix ? `${customStyleClassPrefix} ${value}` : value;
insertStyle(value, id.replace('u:', ''), doc);
}
/**

View File

@ -244,7 +244,7 @@
// 左侧组件面板/info提示弹窗
.ae-RendererThumb {
max-width: 328px;
min-height: 120px;
min-height: 70px;
padding: 5px;
font-size: 12px;
color: #151b26;
@ -271,7 +271,7 @@
.ae-Renderer-preview {
position: relative;
max-height: 200px;
overflow: hidden;
// overflow: hidden;
}
}

View File

@ -29,10 +29,6 @@
}
ul {
width: 100%;
li {
height: auto;
padding: #{px2rem(7px)} #{px2rem(12px)};
}
}
}
&-content-oldentry {

View File

@ -43,7 +43,7 @@ export class CommonConfigWrapper extends NodeWrapper {
let {$$editor, $$node, $schema, store, ...rest} = this.props;
const renderer = $$editor.renderer;
rest = JSONPipeOut(rest);
rest = JSONPipeOut(rest, false);
if ($$editor.filterProps) {
rest = $$editor.filterProps.call($$editor.plugin, rest, $$node);

View File

@ -373,11 +373,16 @@ export default class Editor extends Component<EditorProps> {
// 删除快捷键
if (this.store.activeId) {
const node = store.getNodeById(this.store.activeId);
if (node && store.activeRegion) {
if (
node &&
store.activeRegion &&
node.info?.regions &&
node.info.regions.length > 1
) {
toast.warning('区域节点不可以直接删除。');
} else if (store.isRootSchema(this.store.activeId)) {
toast.warning('根节点不允许删除。');
} else if (node && node.moveable) {
} else if (node && (node.removable || node.removable === undefined)) {
this.manager.del(this.store.activeId);
} else {
toast.warning('当前元素不允许删除。');

View File

@ -44,7 +44,7 @@ export default class IFramePreview extends React.Component<IFramePreviewProps> {
this.initialContent = `<!DOCTYPE html><html><head>${styles.join(
''
)}</head><body><div class="ae-IFramePreview"></div></body></html>`;
)}</head><body><div class="ae-IFramePreview AMISCSSWrapper"></div></body></html>`;
}
componentDidMount() {
@ -101,7 +101,7 @@ export default class IFramePreview extends React.Component<IFramePreviewProps> {
return (
<Frame
className={`ae-PreviewIFrame`}
className={'ae-PreviewIFrame'}
initialContent={this.initialContent}
ref={this.iframeRefFunc}
contentDidMount={this.iframeContentDidMount}

View File

@ -227,8 +227,6 @@ export function makeWrapper(
return Wrapper as any;
}
// 将之前选择的弹窗和本次现有弹窗schema替换为$ref引用
/**
* schema替换为$ref引用
* @param schema
@ -240,7 +238,7 @@ function replaceDialogtoRef(
dialogId: string,
dialogRefsName: string
) {
let replacedSchema = null;
let replacedSchema = schema;
const dialog = JSONGetById(schema, dialogId);
if (dialog) {
replacedSchema = JSONUpdate(schema, dialogId, {$ref: dialogRefsName}, true);
@ -512,7 +510,7 @@ function SchemaFrom({
// 添加弹窗事件后自动选中弹窗
if (store.activeDialogPath) {
let activeId = store.getSchemaByPath(
store.activeDialogPath.split('/')
store.activeDialogPath.split('/').filter(item => item !== '')
)?.$$id;
activeId && store.setPreviewDialogId(activeId);
store.setActiveDialogPath('');

View File

@ -51,5 +51,7 @@ export const env: RenderOptions = {
toast[type]
? toast[type](msg, type === 'error' ? '系统错误' : '系统消息')
: console.warn('[Notify]', type, msg);
}
},
/* 强制隐藏组件内部的报错信息,会覆盖组件内部属性 */
forceSilenceInsideError: false
};

View File

@ -304,7 +304,9 @@ export interface RendererInfo extends RendererScaffoldInfo {
sharedContext?: Record<string, any>;
dialogTitle?: string; //弹窗标题用于弹窗大纲的展示
dialogType?: string; //区分确认对话框类型
subEditorVariable?: Array<{label: string; children: any}>; // 传递给子编辑器的组件自定义变量如listSelect的选项名称和值
getSubEditorVariable?: (
schema?: any
) => Array<{label: string; children: any}>; // 传递给子编辑器的组件自定义变量如listSelect的选项名称和值
}
export type BasicRendererInfo = Omit<
@ -1051,7 +1053,7 @@ export abstract class BasePlugin implements PluginInterface {
isListComponent: plugin.isListComponent,
rendererName: plugin.rendererName,
memberImmutable: plugin.memberImmutable,
subEditorVariable: plugin.subEditorVariable
getSubEditorVariable: plugin.getSubEditorVariable
};
}
}

View File

@ -646,10 +646,6 @@ export const EditorNode = types
let schema = root.getSchema(info.id);
let patched = schema;
if (!patched?.id) {
patched = {...patched, id: 'u:' + guid()};
}
if (
(Array.isArray(info.regions) && info.regions.length) ||
Array.isArray(info.patchContainers)
@ -673,9 +669,15 @@ export const EditorNode = types
},
component?.props
) || patched;
patched = JSONPipeIn(patched);
if (patched !== schema) {
root.changeValueById(info.id, patched, undefined, true, true);
root.changeValueById(
info.id,
JSONPipeIn(patched),
undefined,
true,
true
);
}
},

View File

@ -73,13 +73,30 @@ let themeUselessPropKeys: Array<string> = [];
* $$id 便
* @param obj
*/
export function JSONPipeIn(obj: any, generateId = false): any {
if (!isObject(obj) || obj.$$typeof) {
return obj;
}
export function JSONPipeIn(
obj: any,
reGenerateId = false,
idMap: any = {}
): any {
if (Array.isArray(obj)) {
return obj.map((item, index) => JSONPipeIn(item, generateId));
let flag = false; // 有必要时才去改变对象的引用
const ret = obj.map(item => {
const patched = JSONPipeIn(item, reGenerateId, idMap);
if (patched !== item) {
flag = true;
}
return patched;
});
return flag ? ret : obj;
} else if (!isObject(obj) || obj.constructor !== Object) {
if (typeof obj === 'string') {
Object.keys(idMap).forEach(oldId => {
const newId = idMap[oldId];
obj = obj.replaceAll(oldId, newId);
});
}
return obj;
}
let toUpdate: any = {};
@ -118,45 +135,31 @@ export function JSONPipeIn(obj: any, generateId = false): any {
obj = style2ThemeCss(obj);
// 重新生成组件ID
if (generateId) {
if (reGenerateId) {
flag = true;
/** 脚手架构建的Schema提前构建好了组件 ID此时无需生成 ID避免破坏事件动作 */
if (!obj.__origin || obj.__origin !== 'scaffold') {
toUpdate.id = generateNodeId();
if (
(!obj.__origin || obj.__origin !== 'scaffold') &&
(typeof obj.id !== 'string' || !obj.id || obj.id.startsWith('u:'))
) {
const newId = generateNodeId();
obj.id && (idMap[obj.id] = newId);
toUpdate.id = newId;
}
} else if (!obj.id) {
flag = true;
toUpdate.id = generateNodeId();
}
}
Object.keys(obj).forEach(key => {
let prop = obj[key];
let item = obj[key];
let patched = JSONPipeIn(item, reGenerateId, idMap);
if (Array.isArray(prop)) {
// 如果没有修改过还是用原来的对象
// 为了方便上层diff。
let flag2 = false;
let patched = prop.map((item: any) => {
let patched = JSONPipeIn(item, generateId);
if (patched !== item) {
flag2 = true;
}
return patched;
});
if (flag2) {
flag = true;
toUpdate[key] = patched;
}
} else {
let patched = JSONPipeIn(prop, generateId);
if (patched !== prop) {
flag = true;
toUpdate[key] = patched;
}
if (patched !== item) {
flag = true;
toUpdate[key] = patched;
}
});
@ -1212,7 +1215,10 @@ export async function resolveVariablesFromScope(node: any, manager: any) {
// 子编辑器内读取的host节点自定义变量非数据域方式如listSelect的选项值
let hostNodeVaraibles = [];
if (manager?.store?.isSubEditor) {
hostNodeVaraibles = manager.config?.hostNode?.info?.subEditorVariable || [];
hostNodeVaraibles =
manager.config?.hostNode?.info?.getSubEditorVariable?.(
manager.config?.hostNode.schema
) || [];
}
const variables: VariableItem[] =

View File

@ -255,10 +255,10 @@ export const formItemControl: (
[
getSchemaTpl('visible'),
getSchemaTpl('hidden'),
getSchemaTpl('clearValueOnHidden'),
supportStatic ? getSchemaTpl('static') : null,
// TODO: 下面的部分表单项才有,是不是判断一下是否是表单项
getSchemaTpl('disabled'),
getSchemaTpl('clearValueOnHidden')
getSchemaTpl('disabled')
],
panels?.status?.body,
panels?.status?.replace,

View File

@ -13,6 +13,7 @@ export class AudioPlugin extends BasePlugin {
name = '音频';
isBaseComponent = true;
description = '音频控件,可以用来播放各种音频文件。';
docLink = '/amis/zh-CN/components/audio';
tags = ['功能'];
icon = 'fa fa-music';
pluginIcon = 'audio-plugin';

View File

@ -413,14 +413,16 @@ export class ButtonPlugin extends BasePlugin {
? [
getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
...getEventControlConfig(this.manager, context),
rawType: 'button'
}),
getOldActionSchema(this.manager, context)
]
: [
getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
...getEventControlConfig(this.manager, context),
rawType: 'button'
})
]
}

View File

@ -36,7 +36,7 @@ import type {
} from 'amis-editor-core';
import {normalizeApi} from 'amis-core';
import isPlainObject from 'lodash/isPlainObject';
import omit from 'lodash/omit';
import findLastIndex from 'lodash/findLastIndex';
interface ColumnItem {
label: string;
@ -371,6 +371,9 @@ export class CRUDPlugin extends BasePlugin {
type: 'button',
actionType: 'dialog',
level: 'primary',
editorSetting: {
behavior: 'create'
},
dialog: {
title: '新增',
body: {
@ -385,6 +388,9 @@ export class CRUDPlugin extends BasePlugin {
type: 'button',
actionType: 'dialog',
level: 'link',
editorSetting: {
behavior: 'update'
},
dialog: {
title: '编辑',
body: {
@ -399,6 +405,9 @@ export class CRUDPlugin extends BasePlugin {
type: 'button',
actionType: 'dialog',
level: 'link',
editorSetting: {
behavior: 'view'
},
dialog: {
title: '查看详情',
body: {
@ -415,7 +424,10 @@ export class CRUDPlugin extends BasePlugin {
level: 'link',
className: 'text-danger',
confirmText: '确定要删除?',
api: 'delete:/xxx/delete'
api: 'delete:/xxx/delete',
editorSetting: {
behavior: 'delete'
}
},
bulkDelete: {
type: 'button',
@ -423,12 +435,18 @@ export class CRUDPlugin extends BasePlugin {
label: '批量删除',
actionType: 'ajax',
confirmText: '确定要删除?',
api: '/xxx/batch-delete'
api: '/xxx/batch-delete',
editorSetting: {
behavior: 'bulkDelete'
}
},
bulkUpdate: {
type: 'button',
label: '批量编辑',
actionType: 'dialog',
editorSetting: {
behavior: 'bulkUpdate'
},
dialog: {
title: '批量编辑',
size: 'md',
@ -543,7 +561,7 @@ export class CRUDPlugin extends BasePlugin {
}
},
{
name: 'features',
name: '__features',
label: '启用功能',
type: 'checkboxes',
joinValues: false,
@ -575,10 +593,10 @@ export class CRUDPlugin extends BasePlugin {
type: 'input-number',
label: '每列显示几个字段',
value: 3,
name: 'filterColumnCount'
name: '__filterColumnCount'
}
],
visibleOn: 'data.features && data.features.includes("filter")'
visibleOn: "${__features && CONTAINS(__features, 'filter')}"
},
{
name: 'columns',
@ -641,33 +659,99 @@ export class CRUDPlugin extends BasePlugin {
]
}
],
pipeIn: (value: any) => {
const __features = [];
// 收集 filter
if (value.filter) {
__features.push('filter');
}
// 收集 列操作
const lastIndex = findLastIndex(
value.columns || [],
(item: any) => item.type === 'operation'
);
if (lastIndex !== -1) {
const operBtns: Array<string> = ['update', 'view', 'delete'];
(value.columns[lastIndex].buttons || []).forEach((btn: any) => {
if (operBtns.includes(btn.editorSetting?.behavior || '')) {
__features.push(btn.editorSetting?.behavior);
}
});
}
// 收集批量操作
if (Array.isArray(value.bulkActions)) {
value.bulkActions.forEach((item: any) => {
if (item.editorSetting?.behavior) {
__features.push(item.editorSetting?.behavior);
}
});
}
// 收集新增
if (
Array.isArray(value.headerToolbar) &&
value.headerToolbar.some(
(item: any) => item.editorSetting?.behavior === 'create'
)
) {
__features.push('create');
}
return {
...value,
__filterColumnCount: value?.filter?.columnCount || 3,
__features: __features,
__LastFeatures: [...__features]
};
},
pipeOut: (value: any) => {
let valueSchema = cloneDeep(value);
// 查看/删除 操作,可选择是否使用接口返回值预填充
const features: Array<any> = valueSchema.features;
const oper: {
type: 'operation';
label?: string;
buttons: Array<ActionSchema>;
} = {
type: 'operation',
label: '操作',
buttons: []
};
const itemBtns: Array<string> = ['update', 'view', 'delete'];
const hasFeatures = get(features, 'length');
valueSchema.bulkActions = [];
/** 统一api格式 */
valueSchema.api =
typeof valueSchema.api === 'string'
? normalizeApi(valueSchema.api)
: valueSchema.api;
hasFeatures &&
features.forEach((item: string) => {
if (itemBtns.includes(item)) {
let schema;
const features: string[] = valueSchema.__features;
const lastFeatures: string[] = valueSchema.__LastFeatures;
const willAddedList = features.filter(
item => !lastFeatures.includes(item)
);
const willRemoveList = lastFeatures.filter(
item => !features.includes(item)
);
const operButtons: any[] = [];
const operBtns: string[] = ['update', 'view', 'delete'];
if (!valueSchema.bulkActions) {
valueSchema.bulkActions = [];
} else {
// 删除 未勾选的批量操作
valueSchema.bulkActions = valueSchema.bulkActions.filter(
(item: any) =>
!willRemoveList.includes(item.editorSetting?.behavior)
);
}
// 删除 未勾选的 filter
if (willRemoveList.includes('filter') && valueSchema.filter) {
delete valueSchema.filter;
}
// 删除 未勾选的 新增
if (
willRemoveList.includes('create') &&
Array.isArray(valueSchema.headerToolbar)
) {
valueSchema.headerToolbar = valueSchema.headerToolbar.filter(
(item: any) => item.editorSetting?.behavior !== 'create'
);
}
willAddedList.length &&
willAddedList.forEach((item: string) => {
if (operBtns.includes(item)) {
// 列操作按钮
let schema;
if (item === 'update') {
schema = cloneDeep(this.btnSchemas.update);
schema.dialog.body.body = value.columns
@ -692,9 +776,7 @@ export class CRUDPlugin extends BasePlugin {
? valueSchema.api
: {...valueSchema.api, method: 'post'};
}
// 添加操作按钮
this.addItem(oper.buttons, schema);
schema && operButtons.push(schema);
} else {
// 批量操作
if (item === 'bulkUpdate') {
@ -730,13 +812,14 @@ export class CRUDPlugin extends BasePlugin {
};
valueSchema.headerToolbar = [createSchemaBase, 'bulkActions'];
}
// 查询
let keysFilter = Object.keys(valueSchema.filter || {});
if (item === 'filter' && !keysFilter.length) {
if (valueSchema.filterEnabledList) {
valueSchema.filter = {
title: '查询条件'
};
valueSchema.filter.columnCount = value.filterColumnCount;
valueSchema.filter.columnCount = value.__filterColumnCount;
valueSchema.filter.mode = 'horizontal';
valueSchema.filter.body = valueSchema.filterEnabledList.map(
(item: any) => {
@ -751,10 +834,30 @@ export class CRUDPlugin extends BasePlugin {
}
}
});
const hasOperate = valueSchema.columns.find(
// 处理列操作按钮
const lastIndex = findLastIndex(
value.columns || [],
(item: any) => item.type === 'operation'
);
hasFeatures && !hasOperate && valueSchema.columns.push(oper);
if (lastIndex === -1) {
if (operButtons.length) {
valueSchema.columns.push({
type: 'operation',
label: '操作',
buttons: operButtons
});
}
} else {
const operColumn = valueSchema.columns[lastIndex];
operColumn.buttons = (operColumn.buttons || [])
.filter(
(btn: any) =>
!willRemoveList.includes(btn.editorSetting?.behavior)
)
.concat(operButtons);
}
return valueSchema;
},
canRebuild: true

View File

@ -81,7 +81,7 @@ export class BaseCRUDPlugin extends BasePlugin {
$schema = '/schemas/CRUD2Schema.json';
docLink = '/amis/zh-CN/components/crud2';
docLink = '/amis/zh-CN/components/table2';
tags = ['数据容器'];

View File

@ -0,0 +1,191 @@
import {registerEditorPlugin, RendererPluginEvent} from 'amis-editor-core';
import {BaseEventContext, BasePlugin, tipedLabel} from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control';
import {FormulaDateType} from '../renderer/FormulaControl';
import type {Schema} from 'amis';
export class CalendarPlugin extends BasePlugin {
static id = 'CalendarPlugin';
// 关联渲染器名字
rendererName = 'calendar';
$schema = '/schemas/Calendar.json';
// 组件名称
name = '日历日程';
isBaseComponent = true;
icon = 'fa fa-calendar';
pluginIcon = 'inputDatetime';
panelTitle = '日历日程';
description = '展示日历及日程。';
docLink = '/amis/zh-CN/components/calendar';
tags = ['展示'];
scaffold = {
type: 'calendar'
};
previewSchema = {
...this.scaffold
};
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'change',
eventLabel: '值变化',
description: '时间值变化时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
value: {
type: 'string',
title: '当前日期'
}
}
}
}
}
]
},
{
eventName: 'click',
eventLabel: '点击',
description: '点击时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
value: {
type: 'string',
title: '当前日期'
}
}
}
}
}
]
},
{
eventName: 'mouseenter',
eventLabel: '鼠标移入',
description: '鼠标移入时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
value: {
type: 'string',
title: '当前日期'
}
}
}
}
}
]
},
{
eventName: 'mouseleave',
eventLabel: '鼠标移出',
description: '鼠标移出时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
value: {
type: 'string',
title: '当前日期'
}
}
}
}
}
]
}
];
actions = [
{
actionType: 'clear',
actionLabel: '清空',
description: '清空'
},
{
actionType: 'reset',
actionLabel: '重置',
description: '将值重置为resetValue若没有配置resetValue则清空'
},
{
actionType: 'setValue',
actionLabel: '赋值',
description: '触发组件数据更新'
}
];
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
return [
getSchemaTpl('tabs', [
{
title: '属性',
body: getSchemaTpl('collapseGroup', [
{
title: '基本',
body: [
getSchemaTpl('valueFormula', {
rendererSchema: {
type: 'input-date'
},
placeholder: '请选择静态值',
header: '表达式或相对值',
DateTimeType: FormulaDateType.IsDate,
label: '默认值'
})
]
},
getSchemaTpl('status')
])
},
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
getSchemaTpl('style:classNames', {
isFormItem: false
})
])
},
{
title: '事件',
className: 'p-none',
body: [
getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
})
]
}
])
];
};
}
registerEditorPlugin(CalendarPlugin);

View File

@ -80,25 +80,6 @@ export class CardPlugin extends BasePlugin {
title: '常规',
body: flatten([
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
{
children: (
<Button
size="sm"
className="m-b-sm"
level="info"
block
onClick={() =>
// this.manager.showInsertPanel('actions', context.id)
this.manager.showRendererPanel(
'按钮',
'请从左侧组件面板中点击添加按钮元素'
)
}
>
</Button>
)
},
{
children: (
<div>

View File

@ -21,6 +21,7 @@ export class CollapseGroupPlugin extends BasePlugin {
isBaseComponent = true;
description =
'折叠面板,当信息量较大且分类较多时,可使用折叠面板进行分类收纳。';
docLink = '/amis/zh-CN/components/collapse';
tags = ['布局容器'];
icon = 'fa fa-align-justify';
pluginIcon = 'collapse-plugin';

View File

@ -23,6 +23,7 @@ export class ContainerPlugin extends LayoutBasePlugin {
name = '容器';
isBaseComponent = true;
description = '一个简单的容器,可以将多个渲染器放置在一起。';
docLink = '/amis/zh-CN/components/container';
tags = ['布局容器'];
order = -2;
icon = 'fa fa-square-o';

View File

@ -40,8 +40,10 @@ export class DatePlugin extends BasePlugin {
// 组件名称
name = '日期展示';
isBaseComponent = true;
disabledRendererPlugin = true; // 可用 DatetimePlugin 实现
description =
'主要用来关联字段名做日期展示支持各种格式如X时间戳YYYY-MM-DD HH:mm:ss。';
docLink = '/amis/zh-CN/components/date';
tags = ['展示'];
icon = 'fa fa-calendar';
pluginIcon = 'date-plugin';

View File

@ -5,24 +5,76 @@ import {DatePlugin} from './Date';
const dateFormatOptions = [
{
label: 'X(时间戳)',
value: 'X'
label: '时间戳',
children: [
{
label: 'X(时间戳)',
value: 'X'
},
{
label: 'x(毫秒时间戳)',
value: 'x'
}
]
},
{
label: 'x(毫秒时间戳)',
value: 'x'
label: '日期格式',
children: [
{
label: 'YYYY-MM-DD',
value: 'YYYY-MM-DD'
},
{
label: 'YYYY/MM/DD',
value: 'YYYY/MM/DD'
},
{
label: 'YYYY年MM月DD日',
value: 'YYYY年MM月DD日'
}
]
},
{
label: 'YYYY-MM-DD HH:mm:ss',
value: 'YYYY-MM-DD HH:mm:ss'
label: '时间格式',
children: [
{
label: 'HH:mm:ss',
value: 'HH:mm:ss',
timeFormat: 'HH:mm:ss'
},
{
label: 'HH:mm',
value: 'HH:mm',
timeFormat: 'HH:mm'
},
{
label: 'HH时mm分',
value: 'HH时mm分',
timeFormat: 'HH:mm'
},
{
label: 'HH时mm分ss秒',
value: 'HH时mm分ss秒',
timeFormat: 'HH:mm:ss'
}
]
},
{
label: 'YYYY/MM/DD HH:mm:ss',
value: 'YYYY/MM/DD HH:mm:ss'
},
{
label: 'YYYY年MM月DD日 HH时mm分ss秒',
value: 'YYYY年MM月DD日 HH时mm分ss秒'
label: '日期时间格式',
children: [
{
label: 'YYYY-MM-DD HH:mm:ss',
value: 'YYYY-MM-DD HH:mm:ss'
},
{
label: 'YYYY/MM/DD HH:mm:ss',
value: 'YYYY/MM/DD HH:mm:ss'
},
{
label: 'YYYY年MM月DD日 HH时mm分ss秒',
value: 'YYYY年MM月DD日 HH时mm分ss秒'
}
]
}
];
const valueDateFormatOptions = [
@ -39,13 +91,15 @@ export class DatetimePlugin extends DatePlugin {
scaffold = {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
value: Math.round(Date.now() / 1000)
};
name = '日期时间展示';
isBaseComponent = true;
disabledRendererPlugin = false; // 避免被 DatePlugin 覆盖
pluginIcon = 'datetime-plugin';
docLink = '/amis/zh-CN/components/date';
previewSchema = {
...this.scaffold,
format: 'YYYY-MM-DD HH:mm:ss',
@ -67,13 +121,18 @@ export class DatetimePlugin extends DatePlugin {
label: '日期时间值'
},
{
type: 'input-text',
type: 'nested-select',
name: 'format',
// searchable: true,
// selectMode: 'chained', // tree、chained
hideNodePathLabel: true,
onlyLeaf: true,
label: tipedLabel(
'显示格式',
'请参考 <a href="https://momentjs.com/" target="_blank">moment</a> 中的格式用法。'
),
clearable: true,
// creatable: true,
options: dateFormatOptions,
pipeIn: defaultValue('YYYY-MM-DD HH:mm:ss')
},
@ -97,7 +156,24 @@ export class DatetimePlugin extends DatePlugin {
getSchemaTpl('status')
])
},
getSchemaTpl('onlyClassNameTab')
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', {
exclude: ['layout'],
baseExtra: [
getSchemaTpl('theme:font', {
label: '文字',
name: 'themeCss.baseControlClassName.font'
})
]
}),
{
title: 'CSS类名',
body: [getSchemaTpl('className')]
}
])
}
])
];
};

View File

@ -22,6 +22,7 @@ export class DividerPlugin extends BasePlugin {
icon = 'fa fa-minus';
pluginIcon = 'divider-plugin';
description = '用来展示一个分割线,可用来做视觉上的隔离。';
docLink = '/amis/zh-CN/components/divider';
scaffold = {
type: 'divider'
};

View File

@ -20,6 +20,7 @@ export class EachPlugin extends BasePlugin {
memberImmutable = true;
description = '功能渲染器,可以基于现有变量循环输出渲染器。';
searchKeywords = '循环渲染器';
docLink = '/amis/zh-CN/components/each';
tags = ['功能'];
icon = 'fa fa-repeat';
pluginIcon = 'each-plugin';

View File

@ -458,11 +458,6 @@ export class ComboControlPlugin extends BasePlugin {
getSchemaTpl('description')
]
},
getSchemaTpl('status', {
isFormItem: true,
readonly: true
}),
getSchemaTpl('validation', {tag: ValidatorTag.MultiSelect}),
getSchemaTpl('collapseGroup', [
{
className: 'p-none',
@ -523,7 +518,12 @@ export class ComboControlPlugin extends BasePlugin {
})
]
}
])
]),
getSchemaTpl('status', {
isFormItem: true,
readonly: true
}),
getSchemaTpl('validation', {tag: ValidatorTag.MultiSelect})
])
]
},
@ -679,7 +679,7 @@ export class ComboControlPlugin extends BasePlugin {
}`;
let isColumnChild = false;
if (trigger) {
if (trigger && items) {
isColumnChild = someTree(items.children, item => item.id === trigger?.id);
if (isColumnChild) {
@ -698,7 +698,7 @@ export class ComboControlPlugin extends BasePlugin {
}
}
const pool = items.children.concat();
const pool = items?.children?.concat() || [];
while (pool.length) {
const current = pool.shift() as EditorNodeType;

View File

@ -1,11 +1,10 @@
import cx from 'classnames';
import flatten from 'lodash/flatten';
import cloneDeep from 'lodash/cloneDeep';
import {isObject, getRendererByName} from 'amis-core';
import {isObject, getRendererByName, setVariable} from 'amis-core';
import {
BasePlugin,
tipedLabel,
getI18nEnabled,
ChangeEventContext,
BaseEventContext,
PluginEvent,
@ -31,6 +30,7 @@ import {
import {FormOperatorMap} from '../../builder/constants';
import {getEventControlConfig} from '../../renderer/event-control/helper';
import {FieldSetting} from '../../renderer/FieldSetting';
import {_isModelComp} from '../../util';
import type {FormSchema} from 'amis/lib/Schema';
import type {
@ -474,7 +474,7 @@ export class FormPlugin extends BasePlugin {
return {
type: 'container',
className: 'form-item-gap',
visibleOn: `data.feat === '${feat.value}' && (!data.dsType || data.dsType === '${builderKey}')`,
visibleOn: `$\{feat === '${feat.value}' && (!dsType || dsType === '${builderKey}')}`,
body: flatten([
builder.makeSourceSettingForm({
feat: feat.value,
@ -591,6 +591,29 @@ export class FormPlugin extends BasePlugin {
this._dynamicControls = {...this._dynamicControls, ...controls};
}
/** 获取可能的使用场景 */
guessDSFeatFromSchema(schema: Record<string, any>): FormPluginFeat {
const validFeat = [
DSFeatureEnum.Insert,
DSFeatureEnum.Edit,
DSFeatureEnum.BulkEdit,
DSFeatureEnum.View
];
if (schema.hasOwnProperty('feat')) {
return validFeat.includes(schema.feat)
? schema.feat
: DSFeatureEnum.Insert;
}
if (schema.initApi != null && schema.api != null) {
return DSFeatureEnum.Edit;
} else if (schema.initApi != null && schema.api == null) {
return DSFeatureEnum.View;
} else {
return DSFeatureEnum.Insert;
}
}
panelBodyCreator = (context: BaseEventContext) => {
const dc = this.dynamicControls;
const builder = this.dsManager.getBuilderBySchema(context.schema);
@ -611,25 +634,8 @@ export class FormPlugin extends BasePlugin {
justify: true
}
});
const i18nEnabled = getI18nEnabled();
const schema = context?.node?.schema ?? context?.schema;
/** 是否是模型表单 */
const isModelForm =
((typeof schema?.api === 'string'
? schema.api
: typeof schema?.api?.url === 'string'
? schema.api.url
: ''
).startsWith('model://') ||
(typeof schema?.initApi === 'string'
? schema.initApi
: typeof schema?.initApi?.url === 'string'
? schema.initApi.url
: ''
).startsWith('model://')) &&
!schema.api.strategy;
/** 数据源控件 */
/** 新版数据源控件 */
const generateDSControls = () => {
const dsTypeSelector = this.dsManager.getDSSelectorSchema(
{
@ -685,26 +691,28 @@ export class FormPlugin extends BasePlugin {
const dsSettings = flatten(
this.Features.map(feat =>
this.dsManager.buildCollectionFromBuilders(
(builder, builderKey, index) => ({
type: 'container',
className: 'form-item-gap',
visibleOn: `data.feat === '${
feat.value
}' && (data.dsType == null ? '${builderKey}' === '${
defaultDsType || ApiDSBuilderKey
}' : data.dsType === '${builderKey}')`,
body: flatten([
builder.makeSourceSettingForm({
feat: feat.value,
renderer: 'form',
inScaffold: false,
sourceSettings: {
renderLabel: true,
userOrders: false
}
})
])
})
(builder, builderKey, index) => {
return {
type: 'container',
className: 'form-item-gap',
visibleOn: `$\{feat === '${
feat.value
}' && (dsType == null ? '${builderKey}' === '${
defaultDsType || ApiDSBuilderKey
}' : dsType === '${builderKey}')}`,
body: flatten([
builder.makeSourceSettingForm({
feat: feat.value,
renderer: 'form',
inScaffold: false,
sourceSettings: {
renderLabel: true,
userOrders: false
}
})
])
};
}
)
)
);
@ -712,290 +720,359 @@ export class FormPlugin extends BasePlugin {
return [dsTypeSelector, ...dsSettings];
};
/** 数据源 */
const generateDSCollapse = () => {
if (isCRUDFilter) {
/** CRUD查询表头数据源交给CRUD托管 */
return null;
} else if (_isModelComp(schema)) {
/** 模型组件使用旧版数据源配置 */
return {
title: '数据源',
body: [
getSchemaTpl('apiControl', {
label: '保存接口',
sampleBuilder: () => {
return `{\n "status": 0,\n "msg": "",\n // 可以不返回,如果返回了数据将被 merge 进来。\n data: {}\n}`;
}
}),
getSchemaTpl('apiControl', {
name: 'asyncApi',
label: tipedLabel(
'异步检测接口',
'设置此属性后,表单提交发送保存接口后,还会继续轮询请求该接口,直到返回 finished 属性为 true 才 结束'
),
visibleOn: 'data.asyncApi != null'
}),
getSchemaTpl('apiControl', {
name: 'initAsyncApi',
label: tipedLabel(
'异步检测接口',
'设置此属性后,表单请求 initApi 后,还会继续轮询请求该接口,直到返回 finished 属性为 true 才 结束'
),
visibleOn: 'data.initAsyncApi != null'
}),
getSchemaTpl('apiControl', {
name: 'initApi',
label: '初始化接口',
sampleBuilder: () => {
const data = {};
const schema = context?.schema;
if (Array.isArray(schema?.body)) {
schema.body.forEach((control: any) => {
if (
control.name &&
!~['combo', 'input-array', 'form'].indexOf(control.type)
) {
setVariable(data, control.name, 'sample');
}
});
}
return JSON.stringify(
{
status: 0,
msg: '',
data: data
},
null,
2
);
}
})
]
};
} else {
return {
title: '数据源',
body: [
{
type: 'select',
name: 'feat',
label: '使用场景',
options: this.Features,
pipeIn: (
value: FormPluginFeat | undefined,
formStore: IFormStore
) => {
let feat = value;
if (!value) {
feat = this.guessDSFeatFromSchema(formStore?.data);
}
return feat;
},
onChange: (
value: FormPluginFeat,
oldValue: FormPluginFeat,
model: IFormItemStore,
form: IFormStore
) => {
if (value !== oldValue) {
form.setValues({
dsType: this.dsManager.getDefaultBuilderKey(),
initApi:
DSFeatureEnum.Insert === value ||
DSFeatureEnum.BulkEdit === value
? undefined
: '',
api: undefined
});
}
}
},
...generateDSControls()
]
};
}
};
return [
getSchemaTpl('tabs', [
{
title: '属性',
body: getSchemaTpl('collapseGroup', [
isCRUDFilter || isModelForm
? null
: {
title: '数据源',
body: [
{
type: 'select',
name: 'feat',
label: '使用场景',
options: this.Features,
pipeIn: (
value: FormPluginFeat | undefined,
formStore: IFormStore
) => {
let feat = value;
if (!value) {
feat =
formStore?.data?.initApi != null
? DSFeatureEnum.Edit
: DSFeatureEnum.Insert;
}
/** 存量数据可能未设置过feat, 需要在数据域中 set 一下 */
formStore.setValueByName('feat', feat);
return feat;
},
onChange: (
value: FormPluginFeat,
oldValue: FormPluginFeat,
model: IFormItemStore,
form: IFormStore
) => {
if (value !== oldValue) {
form.setValues({
dsType: this.dsManager.getDefaultBuilderKey(),
initApi:
DSFeatureEnum.Insert === value ||
DSFeatureEnum.BulkEdit === value
? undefined
: '',
api: undefined
});
}
}
},
...generateDSControls()
]
},
{
title: '基本',
body: [
{
name: 'title',
type: 'input-text',
label: '标题',
visibleOn: isWrapped
},
getSchemaTpl('switch', {
name: 'autoFocus',
label: tipedLabel(
'自动聚焦',
'设置后将让表单的第一个可输入的表单项获得焦点'
)
}),
getSchemaTpl('switch', {
name: 'persistData',
label: tipedLabel(
'本地缓存',
'开启后,表单的数据会缓存在浏览器中,切换页面或关闭弹框不会清空当前表单内的数据'
),
pipeIn: (value: boolean | string | undefined) => !!value
}),
{
type: 'container',
className: 'ae-ExtendMore mb-3',
visibleOn: 'data.persistData',
body: [
getSchemaTpl('tplFormulaControl', {
name: 'persistData',
label: tipedLabel(
'持久化Key',
'使用静态数据或者变量:<code>"\\${id}"</code>来为Form指定唯一的Key'
),
pipeIn: (value: boolean | string | undefined) =>
typeof value === 'string' ? value : ''
}),
{
type: 'input-array',
label: tipedLabel(
'保留字段集合',
'如果只需要保存Form中的部分字段值请配置需要保存的字段名称集合留空则保留全部字段'
),
name: 'persistDataKeys',
items: {
type: 'input-text',
placeholder: '请输入字段名',
options: flatten(schema?.body ?? schema?.controls ?? [])
.map((item: Record<string, any>) => {
const isFormItem = getRendererByName(
item?.type
)?.isFormItem;
return isFormItem && typeof item?.name === 'string'
? {label: item.name, value: item.name}
: false;
})
.filter(Boolean)
},
itemClassName: 'bg-transparent'
},
getSchemaTpl('switch', {
name: 'clearPersistDataAfterSubmit',
label: tipedLabel(
'提交成功后清空缓存',
'开启本地缓存并开启本配置项后,表单提交成功后,会自动清除浏览器中当前表单的缓存数据'
),
pipeIn: defaultValue(false)
})
]
},
getSchemaTpl('switch', {
name: 'canAccessSuperData',
label: tipedLabel(
'自动填充数据域同名变量',
'默认表单是可以获取到完整数据链中的数据的,如果想使表单的数据域独立,请关闭此配置'
),
pipeIn: defaultValue(true)
}),
getSchemaTpl('loadingConfig', {label: '加载设置'}, {context})
]
},
{
title: '提交设置',
body: [
{
name: 'submitText',
type: 'input-text',
label: tipedLabel(
'提交按钮名称',
'如果底部按钮不是自定义按钮时,可以通过该配置可以快速修改按钮名称,如果设置成空,则可以把默认按钮去掉。'
),
pipeIn: defaultValue('提交'),
visibleOn: `${isWrapped} && !this.actions && (!Array.isArray(this.body) || !this.body.some(function(item) {return !!~['submit','button','reset','button-group'].indexOf(item.type);}))`,
...justifyLayout(4)
},
getSchemaTpl('switch', {
name: 'submitOnChange',
label: tipedLabel(
'修改即提交',
'设置后,表单中每次有修改都会触发提交'
)
}),
getSchemaTpl('switch', {
name: 'resetAfterSubmit',
label: tipedLabel(
'提交后重置表单',
'表单提交后,让所有表单项的值还原成初始值'
)
}),
getSchemaTpl('switch', {
name: 'preventEnterSubmit',
label: tipedLabel(
'阻止回车提交',
'默认按回车键触发表单提交,开启后将阻止这一行为'
)
}),
// isCRUDFilter
// ? null
// : getSchemaTpl('switch', {
// name: 'submitOnInit',
// label: tipedLabel(
// '初始化后提交一次',
// '开启后,表单初始完成便会触发一次提交'
// )
// }),
isInDialog
? getSchemaTpl('switch', {
label: '提交后关闭对话框',
name: 'closeDialogOnSubmit',
pipeIn: (value: any) => value !== false
})
: null
// isCRUDFilter
// ? null
// : {
// label: tipedLabel(
// '提交其他组件',
// '可以通过设置此属性,把当前表单的值提交给目标组件,而不是自己来通过接口保存,请填写目标组件的 <code>name</code> 属性,多个组件请用逗号隔开。当 <code>target</code> 为 <code>window</code> 时,则把表单数据附属到地址栏。'
// ),
// name: 'target',
// type: 'input-text',
// placeholder: '请输入组件name',
// ...justifyLayout(4)
// },
// getSchemaTpl('reload', {
// test: !isCRUDFilter
// }),
// isCRUDFilter
// ? null
// : {
// type: 'ae-switch-more',
// mode: 'normal',
// label: tipedLabel(
// '提交后跳转',
// '当设置此值后,表单提交完后跳转到目标地址'
// ),
// formType: 'extend',
// form: {
// mode: 'horizontal',
// horizontal: {
// justify: true,
// left: 4
// },
// body: [
// {
// label: '跳转地址',
// name: 'redirect',
// type: 'input-text',
// placeholder: '请输入目标地址'
// }
// ]
// }
// }
]
},
{
title: '组合校验',
body: [
{
name: 'rules',
label: false,
type: 'combo',
multiple: true,
multiLine: true,
subFormMode: 'horizontal',
placeholder: '',
addBtn: {
label: '添加校验规则',
block: true,
icon: 'fa fa-plus',
className: cx('ae-Button--enhance')
body: getSchemaTpl(
'collapseGroup',
[
generateDSCollapse(),
{
title: '基本',
body: [
{
name: 'title',
type: 'input-text',
label: '标题',
visibleOn: isWrapped
},
items: [
{
type: 'ae-formulaControl',
name: 'rule',
label: '校验规则',
...justifyLayout(4)
getSchemaTpl('switch', {
name: 'autoFocus',
label: tipedLabel(
'自动聚焦',
'设置后将让表单的第一个可输入的表单项获得焦点'
)
}),
getSchemaTpl('switch', {
name: 'persistData',
label: tipedLabel(
'本地缓存',
'开启后,表单的数据会缓存在浏览器中,切换页面或关闭弹框不会清空当前表单内的数据'
),
pipeIn: (value: boolean | string | undefined) => !!value
}),
{
type: 'container',
className: 'ae-ExtendMore mb-3',
visibleOn: 'data.persistData',
body: [
getSchemaTpl('tplFormulaControl', {
name: 'persistData',
label: tipedLabel(
'持久化Key',
'使用静态数据或者变量:<code>"\\${id}"</code>来为Form指定唯一的Key'
),
pipeIn: (value: boolean | string | undefined) =>
typeof value === 'string' ? value : ''
}),
{
type: 'input-array',
label: tipedLabel(
'保留字段集合',
'如果只需要保存Form中的部分字段值请配置需要保存的字段名称集合留空则保留全部字段'
),
name: 'persistDataKeys',
items: {
type: 'input-text',
placeholder: '请输入字段名',
options: flatten(
schema?.body ?? schema?.controls ?? []
)
.map((item: Record<string, any>) => {
const isFormItem = getRendererByName(
item?.type
)?.isFormItem;
return isFormItem &&
typeof item?.name === 'string'
? {label: item.name, value: item.name}
: false;
})
.filter(Boolean)
},
itemClassName: 'bg-transparent'
},
getSchemaTpl('switch', {
name: 'clearPersistDataAfterSubmit',
label: tipedLabel(
'提交成功后清空缓存',
'开启本地缓存并开启本配置项后,表单提交成功后,会自动清除浏览器中当前表单的缓存数据'
),
pipeIn: defaultValue(false)
})
]
},
getSchemaTpl('switch', {
name: 'canAccessSuperData',
label: tipedLabel(
'自动填充数据域同名变量',
'默认表单是可以获取到完整数据链中的数据的,如果想使表单的数据域独立,请关闭此配置'
),
pipeIn: defaultValue(true)
}),
getSchemaTpl('loadingConfig', {label: '加载设置'}, {context})
]
},
{
title: '提交设置',
body: [
{
name: 'submitText',
type: 'input-text',
label: tipedLabel(
'提交按钮名称',
'如果底部按钮不是自定义按钮时,可以通过该配置可以快速修改按钮名称,如果设置成空,则可以把默认按钮去掉。'
),
pipeIn: defaultValue('提交'),
visibleOn: `${isWrapped} && !this.actions && (!Array.isArray(this.body) || !this.body.some(function(item) {return !!~['submit','button','reset','button-group'].indexOf(item.type);}))`,
...justifyLayout(4)
},
getSchemaTpl('switch', {
name: 'submitOnChange',
label: tipedLabel(
'修改即提交',
'设置后,表单中每次有修改都会触发提交'
)
}),
getSchemaTpl('switch', {
name: 'resetAfterSubmit',
label: tipedLabel(
'提交后重置表单',
'表单提交后,让所有表单项的值还原成初始值'
)
}),
getSchemaTpl('switch', {
name: 'preventEnterSubmit',
label: tipedLabel(
'阻止回车提交',
'默认按回车键触发表单提交,开启后将阻止这一行为'
)
}),
// isCRUDFilter
// ? null
// : getSchemaTpl('switch', {
// name: 'submitOnInit',
// label: tipedLabel(
// '初始化后提交一次',
// '开启后,表单初始完成便会触发一次提交'
// )
// }),
isInDialog
? getSchemaTpl('switch', {
label: '提交后关闭对话框',
name: 'closeDialogOnSubmit',
pipeIn: (value: any) => value !== false
})
: null
// isCRUDFilter
// ? null
// : {
// label: tipedLabel(
// '提交其他组件',
// '可以通过设置此属性,把当前表单的值提交给目标组件,而不是自己来通过接口保存,请填写目标组件的 <code>name</code> 属性,多个组件请用逗号隔开。当 <code>target</code> 为 <code>window</code> 时,则把表单数据附属到地址栏。'
// ),
// name: 'target',
// type: 'input-text',
// placeholder: '请输入组件name',
// ...justifyLayout(4)
// },
// getSchemaTpl('reload', {
// test: !isCRUDFilter
// }),
// isCRUDFilter
// ? null
// : {
// type: 'ae-switch-more',
// mode: 'normal',
// label: tipedLabel(
// '提交后跳转',
// '当设置此值后,表单提交完后跳转到目标地址'
// ),
// formType: 'extend',
// form: {
// mode: 'horizontal',
// horizontal: {
// justify: true,
// left: 4
// },
// body: [
// {
// label: '跳转地址',
// name: 'redirect',
// type: 'input-text',
// placeholder: '请输入目标地址'
// }
// ]
// }
// }
]
},
{
title: '组合校验',
body: [
{
name: 'rules',
label: false,
type: 'combo',
multiple: true,
multiLine: true,
subFormMode: 'horizontal',
placeholder: '',
addBtn: {
label: '添加校验规则',
block: true,
icon: 'fa fa-plus',
className: cx('ae-Button--enhance')
},
{
name: 'message',
label: '报错提示',
type: 'input-text',
...justifyLayout(4)
}
]
}
]
},
{
title: '状态',
body: [
getSchemaTpl('disabled'),
getSchemaTpl('visible'),
getSchemaTpl('static')
]
},
{
title: '高级',
body: [
getSchemaTpl('switch', {
name: 'debug',
label: tipedLabel('开启调试', '在表单顶部显示当前表单的数据')
})
]
}
])
items: [
{
type: 'ae-formulaControl',
name: 'rule',
label: '校验规则',
...justifyLayout(4)
},
{
name: 'message',
label: '报错提示',
type: 'input-text',
...justifyLayout(4)
}
]
}
]
},
{
title: '状态',
body: [
getSchemaTpl('disabled'),
getSchemaTpl('visible'),
getSchemaTpl('static')
]
},
{
title: '高级',
body: [
getSchemaTpl('switch', {
name: 'debug',
label: tipedLabel(
'开启调试',
'在表单顶部显示当前表单的数据'
)
})
]
}
].filter(Boolean)
)
},
{
title: '外观',
@ -1182,34 +1259,53 @@ export class FormPlugin extends BasePlugin {
* form
*/
patchSchema(schema: Schema, info: RendererConfig, props: any) {
let shouldUpdateSchema = false;
let patchedSchema: Schema = {...schema};
if (
Array.isArray(schema.actions) ||
schema.wrapWithPanel === false ||
(Array.isArray(schema.body) &&
schema.body.some(
(item: any) =>
item &&
!!~['submit', 'button', 'button-group', 'reset'].indexOf(
(item as any)?.body?.[0]?.type ||
(item as any)?.body?.type ||
(item as any).type
)
))
!(
Array.isArray(schema.actions) ||
schema.wrapWithPanel === false ||
(Array.isArray(schema.body) &&
schema.body.some(
(item: any) =>
item &&
!!~['submit', 'button', 'button-group', 'reset'].indexOf(
(item as any)?.body?.[0]?.type ||
(item as any)?.body?.type ||
(item as any).type
)
))
)
) {
return;
shouldUpdateSchema = true;
patchedSchema = {
...patchedSchema,
actions: [
{
type: 'submit',
label:
props?.translate(props?.submitText) ||
schema.submitText ||
'提交',
primary: true
}
]
};
}
return {
...schema,
actions: [
{
type: 'submit',
label:
props?.translate(props?.submitText) || schema.submitText || '提交',
primary: true
}
]
};
if (!_isModelComp(schema)) {
/** 存量数据可能未设置过feat, 需要添加一下 */
if (!schema.feat) {
shouldUpdateSchema = true;
patchedSchema = {
...patchedSchema,
feat: this.guessDSFeatFromSchema(schema)
};
}
}
return shouldUpdateSchema ? patchedSchema : undefined;
}
async getAvailableContextFields(

View File

@ -45,6 +45,8 @@ export class ExcelControlPlugin extends BasePlugin {
};
panelTitle = '上传 Excel';
panelJustify = true;
notRenderFormZone = true;
// 事件定义

View File

@ -1,15 +1,21 @@
import {isObject} from 'amis';
import {
EditorNodeType,
RendererPluginAction,
RendererPluginEvent
BasePlugin,
defaultValue,
getSchemaTpl,
tipedLabel,
registerEditorPlugin
} from 'amis-editor-core';
import {defaultValue, getSchemaTpl, tipedLabel} from 'amis-editor-core';
import {registerEditorPlugin} from 'amis-editor-core';
import {BasePlugin, BaseEventContext} from 'amis-editor-core';
import {ValidatorTag} from '../../validator';
import {getEventControlConfig} from '../../renderer/event-control/helper';
import type {IFormStore, IFormItemStore} from 'amis-core';
import type {
EditorNodeType,
RendererPluginAction,
RendererPluginEvent,
BaseEventContext
} from 'amis-editor-core';
export class RangeControlPlugin extends BasePlugin {
static id = 'RangeControlPlugin';
@ -135,6 +141,19 @@ export class RangeControlPlugin extends BasePlugin {
panelTitle = '滑块';
panelJustify = true;
filterProps(props: Record<string, any>, node: EditorNodeType) {
if (
props.marks &&
isObject(props.marks) &&
props.marks.hasOwnProperty('$$id')
) {
delete props.marks.$$id;
}
return props;
}
panelBodyCreator = (context: BaseEventContext) => {
return getSchemaTpl('tabs', [
{

View File

@ -653,6 +653,132 @@ export class TableControlPlugin extends BasePlugin {
}
}
]
},
{
eventName: 'orderChange',
eventLabel: '行排序',
description: '手动拖拽行排序事件',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
movedItems: {
type: 'array',
title: '已排序记录'
}
}
}
}
}
]
},
{
eventName: 'rowClick',
eventLabel: '行单击',
description: '点击整行事件',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
item: {
type: 'object',
title: '当前行记录'
},
index: {
type: 'number',
title: '当前行索引'
}
}
}
}
}
]
},
{
eventName: 'rowDbClick',
eventLabel: '行双击',
description: '双击整行事件',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
item: {
type: 'object',
title: '当前行记录'
},
index: {
type: 'number',
title: '当前行索引'
}
}
}
}
}
]
},
{
eventName: 'rowMouseEnter',
eventLabel: '鼠标移入行事件',
description: '移入整行时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
item: {
type: 'object',
title: '当前行记录'
},
index: {
type: 'number',
title: '当前行索引'
}
}
}
}
}
]
},
{
eventName: 'rowMouseLeave',
eventLabel: '鼠标移出行事件',
description: '移出整行时触发',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
item: {
type: 'object',
title: '当前行记录'
},
index: {
type: 'number',
title: '当前行索引'
}
}
}
}
}
]
}
];
@ -811,6 +937,11 @@ export class TableControlPlugin extends BasePlugin {
actionType: 'clear',
actionLabel: '清空',
description: '清空组件数据'
},
{
actionType: 'initDrag',
actionLabel: '开启排序',
description: '开启表格拖拽排序功能'
}
];
@ -1071,6 +1202,16 @@ export class TableControlPlugin extends BasePlugin {
name: 'affixHeader',
label: '是否固定表头',
pipeIn: defaultValue(false)
}),
getSchemaTpl('switch', {
name: 'showFooterAddBtn',
label: '展示底部新增按钮',
pipeIn: defaultValue(true)
}),
getSchemaTpl('switch', {
name: 'showTableAddBtn',
label: '展示操作列新增按钮',
pipeIn: defaultValue(true)
})
]
},
@ -1080,6 +1221,10 @@ export class TableControlPlugin extends BasePlugin {
getSchemaTpl('className', {
name: 'rowClassName',
label: '行样式'
}),
getSchemaTpl('className', {
name: 'toolbarClassName',
label: '工具栏'
})
]
})

View File

@ -41,7 +41,7 @@ export class TextControlPlugin extends BasePlugin {
description = '文本输入框支持普通文本、密码、URL、邮箱等多种内容输入';
docLink = '/amis/zh-CN/components/form/text';
docLink = '/amis/zh-CN/components/form/input-text';
tags = ['表单项'];

View File

@ -512,7 +512,6 @@ export class TreeControlPlugin extends BasePlugin {
getSchemaTpl('valueFormula', {
name: 'highlightTxt',
label: '高亮节点字符',
type: 'input-text',
visibleOn: 'data.type === "input-tree"'
}),
{

View File

@ -109,21 +109,26 @@ export class ListControlPlugin extends BasePlugin {
}
];
subEditorVariable: Array<{label: string; children: any}> = [
{
label: '当前选项',
children: [
{
label: '选项名称',
value: 'label'
},
{
label: '选项值',
value: 'value'
}
]
}
];
getSubEditorVariable(schema: any): Array<{label: string; children: any}> {
let labelField = schema?.labelField || 'label';
let valueField = schema?.valueField || 'value';
return [
{
label: '当前选项',
children: [
{
label: '选项名称',
value: labelField
},
{
label: '选项值',
value: valueField
}
]
}
];
}
panelBodyCreator = (context: BaseEventContext) => {
return formItemControl(
@ -201,7 +206,7 @@ export class ListControlPlugin extends BasePlugin {
body: [
{
type: 'tpl',
tpl: `\${${this.getDisplayField(value)}}`,
tpl: `\${${this.getDisplayField(data)}}`,
wrapperComponent: '',
inline: true
}
@ -275,16 +280,7 @@ export class ListControlPlugin extends BasePlugin {
}
getDisplayField(data: any) {
if (
data.source ||
(data.map &&
Array.isArray(data.map) &&
data.map[0] &&
Object.keys(data.map[0]).length > 1)
) {
return data.labelField ?? 'label';
}
return 'label';
return data?.labelField ?? 'label';
}
editDetail(id: string, field: string) {

View File

@ -50,7 +50,7 @@ export class PickerControlPlugin extends BasePlugin {
value: 'B'
}
],
modalClassName: 'app-popover'
modalClassName: 'app-popover :AMISCSSWrapper'
};
previewSchema: any = {
type: 'form',

View File

@ -1,10 +1,13 @@
import React from 'react';
import get from 'lodash/get';
import {getVariable} from 'amis-core';
import {Button} from 'amis';
import {
defaultValue,
getSchemaTpl,
setSchemaTpl,
tipedLabel
tipedLabel,
RendererPluginEvent
} from 'amis-editor-core';
import {registerEditorPlugin} from 'amis-editor-core';
import {BaseEventContext, BasePlugin} from 'amis-editor-core';
@ -21,6 +24,15 @@ setSchemaTpl('quickEdit', (patch: any, manager: any) => ({
hiddenOnDefault: true,
formType: 'extend',
pipeIn: (value: any) => !!value,
trueValue: {
mode: 'popOver',
type: 'container',
body: []
},
isChecked: (e: any) => {
const {data, name} = e;
return !!get(data, name);
},
form: {
body: [
{
@ -66,19 +78,34 @@ setSchemaTpl('quickEdit', (patch: any, manager: any) => ({
children: ({value, onChange, data}: any) => {
if (value === true) {
value = {};
} else if (typeof value === 'undefined') {
value = getVariable(data, 'quickEdit');
}
const originMode = value.mode;
value = {
type: 'input-text',
name: data.name,
...value
};
delete value.mode;
value = {...value};
const originMode = value.mode || 'popOver';
if (value.mode) {
delete value.mode;
}
value =
value.body && ['container', 'wrapper'].includes(value.type)
? {
// schema中存在容器用自己的就行
type: 'container',
body: [],
...value
}
: {
// schema中不存在容器打开子编辑器时需要包裹一层
type: 'container',
body: [
{
type: 'input-text',
name: data.name,
...value
}
]
};
// todo 多个快速编辑表单模式看来只能代码模式编辑了。
return (
<Button
block
@ -87,12 +114,6 @@ setSchemaTpl('quickEdit', (patch: any, manager: any) => ({
manager.openSubEditor({
title: '配置快速编辑类型',
value: value,
slot: {
type: 'form',
mode: 'normal',
body: ['$$'],
wrapWithPanel: false
},
onChange: (value: any) =>
onChange(
{
@ -263,7 +284,6 @@ export class StaticControlPlugin extends BasePlugin {
// 组件名称
name = '静态展示框';
isBaseComponent = true;
disabledRendererPlugin = true;
icon = 'fa fa-info';
pluginIcon = 'static-plugin';
description = '纯用来展示数据,可用来展示 json、date、image、progress 等数据';
@ -299,13 +319,6 @@ export class StaticControlPlugin extends BasePlugin {
{
title: '基本',
body: [
{
type: 'alert',
inline: false,
level: 'warning',
className: 'text-sm',
body: '<p>当前组件已停止维护,建议您使用<a href="/amis/zh-CN/components/form/formitem#%E9%85%8D%E7%BD%AE%E9%9D%99%E6%80%81%E5%B1%95%E7%A4%BA" target="_blank">静态展示</a>新特性实现表单项的静态展示。</p>'
},
getSchemaTpl('formItemName', {
required: false
}),
@ -390,6 +403,76 @@ export class StaticControlPlugin extends BasePlugin {
return props;
}
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'click',
eventLabel: '点击',
description: '点击时触发',
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
},
{
eventName: 'mouseenter',
eventLabel: '鼠标移入',
description: '鼠标移入时触发',
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
},
{
eventName: 'mouseleave',
eventLabel: '鼠标移出',
description: '鼠标移出时触发',
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
}
];
/*exchangeRenderer(id: string) {
this.manager.showReplacePanel(id, '展示');
}*/

View File

@ -192,13 +192,13 @@ export class TransferPlugin extends BasePlugin {
}),
visibleOn: 'data.options.length > 0'
}),
getSchemaTpl('labelRemark'),
getSchemaTpl('remark'),
getSchemaTpl('description'),
getSchemaTpl('switch', {
label: '统计数据',
name: 'statistics'
})
}),
getSchemaTpl('labelRemark'),
getSchemaTpl('remark'),
getSchemaTpl('description')
]
},
{

View File

@ -507,7 +507,6 @@ export class TreeSelectControlPlugin extends BasePlugin {
getSchemaTpl('valueFormula', {
name: 'highlightTxt',
label: '高亮节点字符',
type: 'input-text',
visibleOn: 'data.type === "input-tree"'
}),
{

View File

@ -18,6 +18,7 @@ export class IFramePlugin extends BasePlugin {
name = 'iFrame';
isBaseComponent = true;
description = '可以用来嵌入现有页面。';
docLink = '/amis/zh-CN/components/iframe';
tags = ['功能'];
icon = 'fa fa-window-maximize';
pluginIcon = 'iframe-plugin';

View File

@ -1,4 +1,9 @@
import {getI18nEnabled, registerEditorPlugin} from 'amis-editor-core';
import {
RendererPluginAction,
RendererPluginEvent,
getI18nEnabled,
registerEditorPlugin
} from 'amis-editor-core';
import {
ActiveEventContext,
BaseEventContext,
@ -8,6 +13,10 @@ import {
} from 'amis-editor-core';
import {defaultValue, getSchemaTpl, tipedLabel} from 'amis-editor-core';
import {mockValue} from 'amis-editor-core';
import {
getArgsWrapper,
getEventControlConfig
} from '../renderer/event-control/helper';
export class ImagePlugin extends BasePlugin {
static id = 'ImagePlugin';
@ -21,6 +30,7 @@ export class ImagePlugin extends BasePlugin {
isBaseComponent = true;
description =
'可以用来展示一张图片,支持静态设置图片地址,也可以配置 <code>name</code> 与变量关联。';
docLink = '/amis/zh-CN/components/image';
tags = ['展示'];
icon = 'fa fa-photo';
pluginIcon = 'image-plugin';
@ -33,6 +43,112 @@ export class ImagePlugin extends BasePlugin {
value: mockValue({type: 'image'})
};
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'click',
eventLabel: '点击',
description: '点击时触发',
defaultShow: true,
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
},
{
eventName: 'mouseenter',
eventLabel: '鼠标移入',
description: '鼠标移入时触发',
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
},
{
eventName: 'mouseleave',
eventLabel: '鼠标移出',
description: '鼠标移出时触发',
dataSchema: [
{
type: 'object',
properties: {
context: {
type: 'object',
title: '上下文',
properties: {
nativeEvent: {
type: 'object',
title: '鼠标事件对象'
}
}
}
}
}
]
}
];
// 动作定义
actions: RendererPluginAction[] = [
{
actionType: 'preview',
actionLabel: '预览',
description: '预览图片'
},
{
actionType: 'zoom',
actionLabel: '调整图片比例',
description: '将图片等比例放大或缩小',
schema: {
type: 'container',
body: [
getArgsWrapper([
getSchemaTpl('formulaControl', {
name: 'scale',
mode: 'horizontal',
variables: '${variables}',
horizontal: {
leftFixed: 4 // 需要设置下leftFixed否则这个字段的控件没有与其他字段的控件左对齐
},
label: tipedLabel(
'调整比例',
'定义每次放大或缩小图片的百分比大小正值为放大负值为缩小默认50'
),
value: 50,
size: 'lg'
})
])
]
}
}
];
panelTitle = '图片';
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
@ -62,7 +178,7 @@ export class ImagePlugin extends BasePlugin {
pipeIn: defaultValue('thumb'),
options: [
{
label: '缩图',
label: '缩图',
value: 'thumb'
},
{
@ -129,6 +245,24 @@ export class ImagePlugin extends BasePlugin {
getSchemaTpl('imageUrl', {
name: 'defaultImage',
label: tipedLabel('占位图', '无数据时显示的图片')
}),
getSchemaTpl('formulaControl', {
name: 'maxScale',
mode: 'horizontal',
label: tipedLabel(
'放大极限',
'定义动作调整图片大小的最大百分比默认200'
),
value: 200
}),
getSchemaTpl('formulaControl', {
name: 'minScale',
mode: 'horizontal',
label: tipedLabel(
'缩小极限',
'定义动作调整图片大小的最小百分比默认50'
),
value: 50
})
]
},
@ -244,6 +378,16 @@ export class ImagePlugin extends BasePlugin {
},
getSchemaTpl('theme:cssCode')
])
},
{
title: '事件',
className: 'p-none',
body: [
getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
})
]
}
]);
};

View File

@ -14,12 +14,13 @@ export class ImagesPlugin extends BasePlugin {
name = '图片集';
isBaseComponent = true;
description = '展示多张图片';
docLink = '/amis/zh-CN/components/images';
tags = ['展示'];
icon = 'fa fa-clone';
pluginIcon = 'images-plugin';
scaffold = {
type: 'images',
imageGallaryClassName: 'app-popover'
imageGallaryClassName: 'app-popover :AMISCSSWrapper'
};
previewSchema = {
...this.scaffold,

View File

@ -30,19 +30,39 @@ export const defaultFlexColumnSchema = (title?: string) => {
isFixedWidth: false
};
};
const defaultFlexPreviewSchema = (title?: string) => {
return {
type: 'tpl',
tpl: title,
wrapperComponent: '',
className: 'bg-light center',
style: {
display: 'block',
flex: '1 1 auto',
flexBasis: 'auto',
textAlign: 'center',
marginRight: 10
},
inline: false
};
};
// 默认的布局容器Schema
const defaultFlexContainerSchema = {
const defaultFlexContainerSchema = (
flexItemSchema: (title?: string) => any = defaultFlexColumnSchema
) => ({
type: 'flex',
className: 'p-1',
items: [
defaultFlexColumnSchema('第一列'),
defaultFlexColumnSchema('第二列'),
defaultFlexColumnSchema('第三列')
flexItemSchema('第一列'),
flexItemSchema('第二列'),
flexItemSchema('第三列')
],
style: {
position: 'relative'
}
};
});
export class FlexPluginBase extends LayoutBasePlugin {
static id = 'FlexPluginBase';
@ -59,10 +79,8 @@ export class FlexPluginBase extends LayoutBasePlugin {
'布局容器 是基于 CSS Flex 实现的布局效果,它比 Grid 和 HBox 对子节点位置的可控性更强,比用 CSS 类的方式更易用';
docLink = '/amis/zh-CN/components/flex';
tags = ['布局容器'];
scaffold: any = defaultFlexContainerSchema;
previewSchema = {
...this.scaffold
};
scaffold: any = defaultFlexContainerSchema();
previewSchema = defaultFlexContainerSchema(defaultFlexPreviewSchema);
panelTitle = '布局容器';
@ -107,7 +125,7 @@ export class FlexPluginBase extends LayoutBasePlugin {
getSchemaTpl('layout:flex-setting', {
label: '弹性布局设置',
direction: curRendererSchema.direction,
justify: curRendererSchema.justify,
justify: curRendererSchema.justify || 'center',
alignItems: curRendererSchema.alignItems
}),
@ -227,11 +245,11 @@ export class FlexPluginBase extends LayoutBasePlugin {
];
buildEditorToolbar(
{id, info, schema}: BaseEventContext,
{id, info, schema, node}: BaseEventContext,
toolbars: Array<BasicToolbarItem>
) {
const store = this.manager.store;
const parent = store.getSchemaParentById(id);
// const store = this.manager.store;
const parent = node.parent?.schema; // 或者 store.getSchemaParentById(id, true);
const draggableContainer = this.manager.draggableContainer(id);
const isFlexItem = this.manager?.isFlexItem(id);
const isFlexColumnItem = this.manager?.isFlexColumnItem(id);
@ -266,7 +284,7 @@ export class FlexPluginBase extends LayoutBasePlugin {
className: 'ae-InsertBefore is-vertical',
onClick: () =>
this.manager.appendSiblingSchema(
defaultFlexContainerSchema,
defaultFlexContainerSchema(),
true,
true
)
@ -279,7 +297,7 @@ export class FlexPluginBase extends LayoutBasePlugin {
className: 'ae-InsertAfter is-vertical',
onClick: () =>
this.manager.appendSiblingSchema(
defaultFlexContainerSchema,
defaultFlexContainerSchema(),
false,
true
)

View File

@ -8,7 +8,7 @@ export default class Layout_fixed extends FlexPluginBase {
name = '悬浮容器';
isBaseComponent = true;
pluginIcon = 'layout-fixed-plugin';
description = '悬浮容器: 基于 CSS Flex 实现的特殊布局容器。';
description = '悬浮容器: 基于 CSS Fixed 实现的特殊布局容器。';
order = 0;
scaffold: any = {
type: 'container',
@ -25,6 +25,16 @@ export default class Layout_fixed extends FlexPluginBase {
wrapperBody: false,
originPosition: 'right-bottom'
};
previewSchema: any = {
type: 'container',
body: [],
style: {
position: 'static',
display: 'block'
},
size: 'none',
wrapperBody: false
};
panelTitle = '悬浮容器';
}

View File

@ -72,9 +72,6 @@ export class LinkPlugin extends BasePlugin {
})
]
},
getSchemaTpl('status', {
disabled: true
}),
getSchemaTpl('collapseGroup', [
{
title: '高级设置',
@ -89,7 +86,10 @@ export class LinkPlugin extends BasePlugin {
}
]
}
])
]),
getSchemaTpl('status', {
disabled: true
})
])
},
{

View File

@ -29,6 +29,7 @@ export class NavPlugin extends BasePlugin {
scaffold = {
type: 'nav',
stacked: true,
popupClassName: 'app-popover :AMISCSSWrapper',
links: [
{
label: '页面1',

View File

@ -50,29 +50,16 @@ export class OperationPlugin extends BasePlugin {
panelTitle = '操作栏';
panelBodyCreator = (context: BaseEventContext) => {
return [
getSchemaTpl('className', {
name: 'innerClassName'
}),
return getSchemaTpl('tabs', [
{
children: (
<Button
block
className="m-b-sm ae-Button--enhance"
onClick={() => {
// this.manager.showInsertPanel('buttons', context.id, '按钮');
this.manager.showRendererPanel(
'按钮',
'请从左侧组件面板中点击添加新的按钮'
);
}}
>
</Button>
)
title: '外观',
body: [
getSchemaTpl('className', {
name: 'innerClassName'
})
]
}
];
]);
};
buildSubRenderers(

View File

@ -1,5 +1,6 @@
import {Button} from 'amis';
import React from 'react';
import get from 'lodash/get';
import {getI18nEnabled, registerEditorPlugin} from 'amis-editor-core';
import {
BasePlugin,
@ -69,6 +70,10 @@ export class TableCellPlugin extends BasePlugin {
getSchemaTpl('switch', {
name: 'quickEdit',
label: '启用快速编辑',
isChecked: (e: any) => {
const {data, name} = e;
return !!get(data, name);
},
pipeIn: (value: any) => !!value
}),
@ -120,18 +125,32 @@ export class TableCellPlugin extends BasePlugin {
} else if (typeof value === 'undefined') {
value = getVariable(data, 'quickEdit');
}
const originMode = value.mode;
value = {
type: 'input-text',
name: data.name,
...value
};
delete value.mode;
value = {...value};
const originMode = value.mode || 'popOver';
if (value.mode) {
delete value.mode;
}
value =
value.body && ['container', 'wrapper'].includes(value.type)
? {
// schema中存在容器用自己的就行
type: 'container',
body: [],
...value
}
: {
// schema中不存在容器打开子编辑器时需要包裹一层
type: 'container',
body: [
{
type: 'input-text',
name: data.name,
...value
}
]
};
// todo 多个快速编辑表单模式看来只能代码模式编辑了。
return (
<Button
level="info"
@ -142,12 +161,6 @@ export class TableCellPlugin extends BasePlugin {
this.manager.openSubEditor({
title: '配置快速编辑类型',
value: value,
slot: {
type: 'form',
mode: 'normal',
body: ['$$'],
wrapWithPanel: false
},
onChange: value =>
onChange(
{

View File

@ -228,13 +228,7 @@ export class PagePlugin extends BasePlugin {
{
title: '数据',
body: [
// page组件下掉组件静态数据配置项可通过页面变量来定义页面中的变量
// getSchemaTpl('combo-container', {
// type: 'input-kv',
// mode: 'normal',
// name: 'data',
// label: '组件静态数据'
// }),
getSchemaTpl('pageData'),
getSchemaTpl('apiControl', {
name: 'initApi',
mode: 'row',

View File

@ -21,6 +21,7 @@ export class PaginationPlugin extends BasePlugin {
name = '分页组件';
isBaseComponent = true;
description = '分页组件,可以对列表进行分页展示,提高页面性能';
docLink = '/amis/zh-CN/components/pagination';
tags = ['展示'];
icon = 'fa fa-window-minimize';
lastLayoutSetting = ['pager'];
@ -61,9 +62,13 @@ export class PaginationPlugin extends BasePlugin {
type: 'object',
title: '数据',
properties: {
value: {
type: 'string',
page: {
type: 'number',
title: '当前页码值'
},
perPage: {
type: 'number',
title: '每页显示的记录数'
}
},
description: '当前数据域,可以通过.字段名读取对应的值'

View File

@ -271,10 +271,6 @@ export class ServicePlugin extends BasePlugin {
...generateDSControls()
]
},
{
title: '状态',
body: [getSchemaTpl('visible'), getSchemaTpl('hidden')]
},
{
title: '高级',
body: [
@ -317,6 +313,10 @@ export class ServicePlugin extends BasePlugin {
'/**\n * @param data 上下文数据\n * @param setData 更新数据的函数\n * @param env 环境变量\n */\ninterface DataProvider {\n (data: any, setData: (data: any) => void, env: any): void;\n}\n'
}
]
},
{
title: '状态',
body: [getSchemaTpl('visible'), getSchemaTpl('hidden')]
}
])
]

View File

@ -321,6 +321,7 @@ export class SwitchContainerPlugin extends LayoutBasePlugin {
name: 'items',
label: '状态列表',
addTip: '新增组件状态',
minLength: 1,
items: [
{
type: 'input-text',
@ -356,6 +357,10 @@ export class SwitchContainerPlugin extends LayoutBasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
getSchemaTpl('theme:base', {
collapsed: false,
extra: []
}),
{
title: '布局',
body: [
@ -460,7 +465,15 @@ export class SwitchContainerPlugin extends LayoutBasePlugin {
getSchemaTpl('layout:stickyPosition')
]
},
...getSchemaTpl('theme:common', {exclude: ['layout']})
{
title: '自定义样式',
body: [
{
type: 'theme-cssCode',
label: false
}
]
}
])
},
{

View File

@ -349,6 +349,32 @@ export class TablePlugin extends BasePlugin {
}
]
},
{
eventName: 'rowDbClick',
eventLabel: '行双击',
description: '双击整行事件',
dataSchema: [
{
type: 'object',
properties: {
data: {
type: 'object',
title: '数据',
properties: {
item: {
type: 'object',
title: '当前行记录'
},
index: {
type: 'number',
title: '当前行索引'
}
}
}
}
}
]
},
{
eventName: 'rowMouseEnter',
eventLabel: '鼠标移入行事件',

View File

@ -61,12 +61,12 @@ export class TableCell2Plugin extends BasePlugin {
wrapperResolve: (dom: HTMLDivElement) => {
// 固定这种结构 amis里改了 这里也得改
const parent = dom.parentElement?.parentElement;
const groupId = parent?.getAttribute('data-group-id');
const col = parent?.getAttribute('data-col');
const wrapper = dom.closest('table')!.parentElement?.parentElement;
return [].slice.call(
wrapper?.querySelectorAll(
`th[data-group-id="${groupId}"],
td[data-group-id="${groupId}"]`
`th[data-col="${col}"],
td[data-col="${col}"]`
)
);
}
@ -431,17 +431,14 @@ export class TableCell2Plugin extends BasePlugin {
mode: 'normal',
formType: 'extend',
bulk: true,
defaultData: {
quickEdit: {
mode: 'popOver'
}
},
trueValue: {
mode: 'popOver'
mode: 'popOver',
type: 'container',
body: []
},
isChecked: (e: any) => {
const {data, name} = e;
return get(data, name);
return !!get(data, name);
},
form: {
body: [
@ -489,27 +486,31 @@ export class TableCell2Plugin extends BasePlugin {
} else if (typeof value === 'undefined') {
value = getVariable(data, 'quickEdit');
}
const originMode = value?.mode || 'popOver';
value = {
...value,
type: 'form',
mode: 'normal',
wrapWithPanel: false,
body: value?.body?.length
? value.body
: [
{
type: 'input-text',
name: data.key
}
]
};
value = {...value};
const originMode = value.mode || 'popOver';
if (value.mode) {
delete value.mode;
}
value =
value.body && ['container', 'wrapper'].includes(value.type)
? {
// schema中存在容器用自己的就行
type: 'container',
body: [],
...value
}
: {
// schema中不存在容器打开子编辑器时需要包裹一层
type: 'container',
body: [
{
type: 'input-text',
name: data.name,
...value
}
]
};
// todo 多个快速编辑表单模式看来只能代码模式编辑了。
return (
<Button

View File

@ -188,6 +188,9 @@ export class TableViewPlugin extends BasePlugin {
];
panelTitle = '表格视图';
panelJustify = true;
panelBody = [
getSchemaTpl('tabs', [
{
@ -240,7 +243,8 @@ export class TableViewPlugin extends BasePlugin {
pipeIn: defaultValue('#eceff8')
}
]
}
},
getSchemaTpl('status')
])
]
},
@ -248,10 +252,6 @@ export class TableViewPlugin extends BasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [...getSchemaTpl('theme:common')])
},
{
title: '状态',
body: [getSchemaTpl('visible')]
}
])
];

View File

@ -38,9 +38,10 @@ export class TimePlugin extends DatePlugin {
rendererName = 'time';
name = '时间展示';
isBaseComponent = true;
disabledRendererPlugin = true; // 可用 DatetimePlugin 实现
pluginIcon = 'time-plugin';
docLink = '/amis/zh-CN/components/date';
scaffold = {
type: 'time',
value: Math.round(Date.now() / 1000),

View File

@ -238,24 +238,44 @@ export class TooltipWrapperPlugin extends BasePlugin {
name: 'showArrow',
inputClassName: 'is-inline'
},
{
label: '延迟打开',
type: 'input-number',
min: 0,
step: 100,
type: 'input-group',
name: 'mouseEnterDelay',
suffix: 'ms',
pipeIn: defaultValue(0)
label: '延迟打开',
body: [
{
type: 'input-number',
min: 0,
step: 100,
name: 'mouseEnterDelay',
pipeIn: defaultValue(0)
},
{
type: 'tpl',
addOnclassName: 'border-0 bg-none',
tpl: 'ms'
}
]
},
{
label: '延迟关闭',
type: 'input-number',
min: 0,
step: 100,
type: 'input-group',
name: 'mouseLeaveDelay',
suffix: 'ms',
pipeIn: defaultValue(0)
label: '延迟关闭',
body: [
{
label: '延迟关闭',
type: 'input-number',
min: 0,
step: 100,
name: 'mouseLeaveDelay',
pipeIn: defaultValue(0)
},
{
type: 'tpl',
addOnclassName: 'border-0 bg-none',
tpl: 'ms'
}
]
}
]
}

View File

@ -67,7 +67,11 @@ export class WebComponentPlugin extends BasePlugin {
getSchemaTpl('combo-container', {
type: 'input-kv',
mode: 'normal',
draggable: false,
name: 'props',
valueSchema: getSchemaTpl('formulaControl', {
placeholder: 'Value'
}),
label: '属性'
})
]

View File

@ -63,6 +63,7 @@ export * from './Form/UUID'; // UUID
export * from './Form/LocationPicker'; // 地理位置
export * from './Form/InputSubForm'; // 子表单项
export * from './Form/Hidden'; // 隐藏域
export * from './Form/Static'; // 静态展示框
// 功能
export * from './Button'; // 按钮
@ -105,6 +106,7 @@ export * from './Images'; // 图片集
export * from './Time'; // 时间展示
export * from './Date'; // 日期展示
export * from './Datetime'; // 日期时间展示
export * from './Calendar'; // 日历日程展示
export * from './Tag'; // 标签
export * from './Json'; // JSON展示
export * from './Progress'; // 进度展示
@ -135,7 +137,6 @@ export * from './Form/InputMonthRange';
export * from './Form/InputPassword';
export * from './Form/InputQuarter';
export * from './Form/InputQuarterRange';
export * from './Form/Static';
export * from './Form/InputTime';
export * from './Form/InputTimeRange';
export * from './Form/TreeSelect';

View File

@ -144,10 +144,7 @@ export class FieldSetting extends React.Component<
(prevValue?.length !== value?.length || !isEqual(prevValue, value)) &&
!isEqual(value, prevState?.fields)
) {
this.setState({
loading: true,
fields: Array.isArray(value) ? value : []
});
this.setState({fields: Array.isArray(value) ? value : []});
}
}

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