diff --git a/docs/zh-CN/components/form/input-range.md b/docs/zh-CN/components/form/input-range.md index b3831f361..15a908c4e 100755 --- a/docs/zh-CN/components/form/input-range.md +++ b/docs/zh-CN/components/form/input-range.md @@ -287,7 +287,7 @@ order: 38 | step | `number \| string` | `1` | 步长,支持变量 | `3.3.0`后支持变量 | | showSteps | `boolean` | `false` | 是否显示步长 | | parts | `number` or `number[]` | `1` | 分割的块数
主持数组传入分块的节点 | -| marks | { [number | string]: ReactNode } or { [number | string]: { style: CSSProperties, label: ReactNode } } | | 刻度标记
- 支持自定义样式
- 设置百分比 | +| marks | { [number | string]: string | number | SchemaObject } or { [number | string]: { style: CSSProperties, label: string } } | | 刻度标记
- 支持自定义样式
- 设置百分比 | | tooltipVisible | `boolean` | `false` | 是否显示滑块标签 | | tooltipPlacement | `auto` or `bottom` or `left` or `right` | `top` | 滑块标签的位置,默认`auto`,方向自适应
前置条件:tooltipVisible 不为 false 时有效 | | tipFormatter | `function` | | 控制滑块标签显隐函数
前置条件:tooltipVisible 不为 false 时有效 | diff --git a/docs/zh-CN/components/form/input-table.md b/docs/zh-CN/components/form/input-table.md index bf20dd56b..581fd92b8 100755 --- a/docs/zh-CN/components/form/input-table.md +++ b/docs/zh-CN/components/form/input-table.md @@ -917,6 +917,11 @@ order: 54 | deleteSuccess | `index: number` 所在行记录索引
`item: object` 所在行记录
`[name]: object[]`列表记录 | 配置了`deleteApi`,调用接口成功时触发 | | deleteFail | `index: number` 所在行记录索引
`item: object` 所在行记录
`[name]: object[]`列表记录
`error: object` `deleteApi`请求失败后返回的错误信息 | 配置了`deleteApi`,调用接口失败时触发 | | change | `[name]: object[]` 列表记录 | 组件数据发生改变时触发 | +| orderChange | `movedItems: item[]` 已排序数据 | 手动拖拽行排序时触发 | +| rowClick | `item: object` 行点击数据
`index: number` 行索引 | 单击整行时触发 | +| rowDbClick | `item: object` 行点击数据
`index: number` 行索引 | 双击整行时触发 | +| rowMouseEnter | `item: object` 行移入数据
`index: number` 行索引 | 移入整行时触发 | +| rowMouseLeave | `item: object` 行移出数据
`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` 替换的值
`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" + } + ] + } +} +``` diff --git a/docs/zh-CN/components/form/transfer.md b/docs/zh-CN/components/form/transfer.md index c2c05e5f3..dfaa00e19 100644 --- a/docs/zh-CN/components/form/transfer.md +++ b/docs/zh-CN/components/form/transfer.md @@ -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`或`Array` | | [选项组](./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` | ## 事件表 diff --git a/docs/zh-CN/components/image.md b/docs/zh-CN/components/image.md index 52b722469..98e0a8c55 100755 --- a/docs/zh-CN/components/image.md +++ b/docs/zh-CN/components/image.md @@ -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" + } + ] + } + } + } + ] + } +} +``` diff --git a/docs/zh-CN/components/markdown.md b/docs/zh-CN/components/markdown.md index afb192363..bbb153c63 100644 --- a/docs/zh-CN/components/markdown.md +++ b/docs/zh-CN/components/markdown.md @@ -81,6 +81,30 @@ order: 58 可以使用 `![text](video.mp4)` 语法来嵌入视频。 +## 支持 latex + +> 3.6.0 及以上版本 + +公式渲染使用 KaTeX 实现,由于体积太大默认不提供,需要自己去[下载](https://github.com/KaTeX/KaTeX/releases),在页面中引入以下三个文件: + +``` + + + +``` + +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 及以上版本 diff --git a/docs/zh-CN/components/pagination.md b/docs/zh-CN/components/pagination.md index 6e4949833..078af0f87 100644 --- a/docs/zh-CN/components/pagination.md +++ b/docs/zh-CN/components/pagination.md @@ -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` 当前页码的值
| 当前页码值改变时触发 | \ No newline at end of file +> | change | `page: number` 当前页码的值
`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}" + } + } + ] + } + } + } + ] +} +``` diff --git a/docs/zh-CN/components/table.md b/docs/zh-CN/components/table.md index 1667515df..7e2ac69d0 100755 --- a/docs/zh-CN/components/table.md +++ b/docs/zh-CN/components/table.md @@ -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 鼠标移入行记录。 diff --git a/docs/zh-CN/concepts/event-action.md b/docs/zh-CN/concepts/event-action.md index d8538fd51..58bce45a3 100644 --- a/docs/zh-CN/concepts/event-action.md +++ b/docs/zh-CN/concepts/event-action.md @@ -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": "派发鼠标移出事件" } } ] diff --git a/index.html b/index.html index 214e3fe50..5407f3bdb 100644 --- a/index.html +++ b/index.html @@ -24,6 +24,7 @@ rel="stylesheet" href="/node_modules/@fortawesome/fontawesome-free/css/v4-shims.css" /> + @@ -93,5 +94,10 @@ const initialState = {}; bootstrap(document.getElementById('root'), initialState); + + diff --git a/mock/cfc/mock/options/loadDataOnce.js b/mock/cfc/mock/options/loadDataOnce.js new file mode 100644 index 000000000..f0c991201 --- /dev/null +++ b/mock/cfc/mock/options/loadDataOnce.js @@ -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 + }); +}); diff --git a/mock/cfc/mock/options/transfer.js b/mock/cfc/mock/options/transfer.js new file mode 100644 index 000000000..da3cf3e40 --- /dev/null +++ b/mock/cfc/mock/options/transfer.js @@ -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 + }); +}); diff --git a/package.json b/package.json index 7e427cba3..9db14f393 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/amis-core/src/actions/CmptAction.ts b/packages/amis-core/src/actions/CmptAction.ts index 92877ab42..6e526e5a4 100644 --- a/packages/amis-core/src/actions/CmptAction.ts +++ b/packages/amis-core/src/actions/CmptAction.ts @@ -36,6 +36,19 @@ 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; + } + } + } if (!key) { console.warn('请提供目标组件的componentId或componentName'); @@ -59,23 +72,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, diff --git a/packages/amis-core/src/env.tsx b/packages/amis-core/src/env.tsx index 830f41098..6a311dc0d 100644 --- a/packages/amis-core/src/env.tsx +++ b/packages/amis-core/src/env.tsx @@ -87,6 +87,16 @@ export interface RendererEnv { affixOffsetBottom?: number; richTextToken: string; + + /** + * 默认的选址组件提供商,目前支持仅 baidu + */ + locationPickerVendor?: string; + + /** + * 选址组件的 ak + */ + locationPickerAK?: string; loadRenderer: ( schema: Schema, path: string, diff --git a/packages/amis-core/src/renderers/Form.tsx b/packages/amis-core/src/renderers/Form.tsx index 3b27ded2a..82b8e417b 100644 --- a/packages/amis-core/src/renderers/Form.tsx +++ b/packages/amis-core/src/renderers/Form.tsx @@ -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 { 'onChange', 'onFailed', 'onFinished', + 'onValidate', + 'onValidChange', 'onSaved', 'canAccessSuperData', 'lazyChange', @@ -460,8 +464,7 @@ export default class Form extends React.Component { [propName: string]: Array<() => Promise>; } = {}; asyncCancel: () => void; - disposeOnValidate: () => void; - disposeRulesValidate: () => void; + toDispose: Array<() => void> = []; shouldLoadInitApi: boolean = false; timer: ReturnType; mounted: boolean; @@ -532,6 +535,7 @@ export default class Form extends React.Component { store, messages: {fetchSuccess, fetchFailed}, onValidate, + onValidChange, promptPageLeave, env, rules @@ -541,49 +545,63 @@ export default class Form extends React.Component { 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 { // 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 { return this.props.store.validated; } - validate( + async validate( forceValidate?: boolean, - throwErrors: boolean = false + throwErrors: boolean = false, + toastErrors: boolean = true ): Promise { 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') { diff --git a/packages/amis-core/src/renderers/Options.tsx b/packages/amis-core/src/renderers/Options.tsx index 8746525a0..c037608eb 100644 --- a/packages/amis-core/src/renderers/Options.tsx +++ b/packages/amis-core/src/renderers/Options.tsx @@ -37,6 +37,7 @@ import { FormBaseControl } from './Item'; import {IFormItemStore} from '../store/formItem'; +import {isObject} from 'amis-core'; export type OptionsControlComponent = React.ComponentType; @@ -230,7 +231,11 @@ export interface OptionsControlProps selectedOptions: Array