mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
Merge branch 'master' into feat-BCE-FE-1706
This commit is contained in:
commit
9068aa2afd
@ -10,5 +10,9 @@ insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[**.{py}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
2
.github/workflows/gh-pages.yml
vendored
2
.github/workflows/gh-pages.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: build gh-pages
|
||||
run: |
|
||||
npm i --legacy-peer-deps
|
||||
cd packages/ooxml-viewer
|
||||
cd packages/office-viewer
|
||||
npm i --legacy-peer-deps
|
||||
cd ../../
|
||||
npm run build --workspaces
|
||||
|
4
.github/workflows/pr-test.yml
vendored
4
.github/workflows/pr-test.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||
run: |
|
||||
npm i --legacy-peer-deps
|
||||
cd packages/ooxml-viewer
|
||||
cd packages/office-viewer
|
||||
npm i --legacy-peer-deps
|
||||
cd ../../
|
||||
npm run build --workspace amis-formula
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
- name: test
|
||||
run: |
|
||||
npm test --workspaces
|
||||
cd packages/ooxml-viewer
|
||||
cd packages/office-viewer
|
||||
npm test
|
||||
cd ../../
|
||||
sh deploy-gh-pages.sh
|
||||
|
24
.gitpod.yml
Normal file
24
.gitpod.yml
Normal file
@ -0,0 +1,24 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
- port: 8888
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
|
||||
tasks:
|
||||
- name: SDK
|
||||
command: |
|
||||
npm i
|
||||
npm run build --workspace amis --workspace amis-ui
|
||||
npx -y serve packages/amis/
|
||||
- name: Amis
|
||||
command: |
|
||||
gp await-port 3000
|
||||
npm run start
|
@ -17,7 +17,7 @@
|
||||
"path": "./packages/amis"
|
||||
},
|
||||
{
|
||||
"path": "./packages/ooxml-viewer"
|
||||
"path": "./packages/office-viewer"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ icon 也可以是 url 地址,比如
|
||||
|
||||
## 操作前确认
|
||||
|
||||
可以通过配置`confirmText`,实现在任意操作前,弹出提示框确认是否进行该操作。
|
||||
可以通过配置`confirmText`,实现在任意操作前,弹出提示框确认是否进行该操作。同时可以通过配置 `confirmTitle` 来设置弹窗标题
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
@ -182,6 +182,7 @@ icon 也可以是 url 地址,比如
|
||||
"type": "button",
|
||||
"actionType": "ajax",
|
||||
"confirmText": "确认要发出这个请求?",
|
||||
"confirmTitle": "炸弹",
|
||||
"api": "/api/mock2/form/saveForm"
|
||||
}
|
||||
```
|
||||
@ -1029,6 +1030,7 @@ action 还可以使用 `body` 来渲染其他组件,让那些不支持行为
|
||||
| activeClassName | `string` | `is-active` | 给按钮高亮添加类名。 |
|
||||
| block | `boolean` | - | 用`display:"block"`来显示按钮。 |
|
||||
| confirmText | [模板](../../docs/concepts/template) | - | 当设置后,操作在开始前会询问用户。可用 `${xxx}` 取值。 |
|
||||
| confirmTitle | [模板](../../docs/concepts/template) | - | 确认框标题,前提是 confirmText 有内容,支持模版语法 |
|
||||
| reload | `string` | - | 指定此次操作完后,需要刷新的目标组件名字(组件的`name`值,自己配置的),多个请用 `,` 号隔开。 |
|
||||
| tooltip | `string` | - | 鼠标停留时弹出该段文字,也可以配置对象类型:字段为`title`和`content`。可用 `${xxx}` 取值。 |
|
||||
| disabledTip | `'string' \| 'TooltipObject'` | - | 被禁用后鼠标停留时弹出该段文字,也可以配置对象类型:字段为`title`和`content`。可用 `${xxx}` 取值。 |
|
||||
|
@ -213,23 +213,144 @@ order: 36
|
||||
|
||||
## CollapseGroup 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| - | - | - | - |
|
||||
| type | `string` | `"collapse-group"` | 指定为 collapse-group 渲染器 |
|
||||
| activeKey | `Array<string \| number \| never> \| string \| number` | - | 初始化激活面板的key |
|
||||
| accordion | `boolean` | `false` | 手风琴模式 |
|
||||
| expandIcon | `SchemaNode` | - | 自定义切换图标 |
|
||||
| expandIconPosition | `string` | `"left"` | 设置图标位置,可选值`left \| right` |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | ------------------------------------------------------ | ------------------ | ----------------------------------- |
|
||||
| type | `string` | `"collapse-group"` | 指定为 collapse-group 渲染器 |
|
||||
| activeKey | `Array<string \| number \| never> \| string \| number` | - | 初始化激活面板的 key |
|
||||
| accordion | `boolean` | `false` | 手风琴模式 |
|
||||
| expandIcon | `SchemaNode` | - | 自定义切换图标 |
|
||||
| expandIconPosition | `string` | `"left"` | 设置图标位置,可选值`left \| right` |
|
||||
|
||||
## CollapseGroup 事件表
|
||||
|
||||
当前组件会对外派发以下事件,可以通过 onEvent 来监听这些事件,并通过 actions 来配置执行的动作,在 actions 中可以通过${事件参数名}或${event.data.[事件参数名]}来获取事件产生的数据,详细查看事件动作。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| change | `activeKeys: Array<string \| number>` 当前展开的索引列表 <br /> `collapseId: string \| number` 折叠器索引 <br/> `collapsed: boolean` 折叠器状态 | 折叠面板折叠状态改变时触发 |
|
||||
|
||||
### change
|
||||
|
||||
折叠面板折叠状态改变时触发。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "collapse-group",
|
||||
"activeKey": [
|
||||
"1"
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "collapse",
|
||||
"key": "1",
|
||||
"active": true,
|
||||
"header": "标题1",
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "这里是内容1",
|
||||
"wrapperComponent": "",
|
||||
"inline": false,
|
||||
"id": "u:757ad799da08"
|
||||
}
|
||||
],
|
||||
"id": "u:b1b68dfbb08d"
|
||||
},
|
||||
{
|
||||
"type": "collapse",
|
||||
"key": "2",
|
||||
"header": "标题2",
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "这里是内容1",
|
||||
"wrapperComponent": "",
|
||||
"inline": false,
|
||||
"id": "u:92caa03f227e"
|
||||
}
|
||||
],
|
||||
"id": "u:621a22c8b18c"
|
||||
}
|
||||
],
|
||||
"id": "u:23e4c5ec9c89",
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"weight": 0,
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msgType": "info",
|
||||
"position": "top-right",
|
||||
"closeButton": true,
|
||||
"showIcon": true,
|
||||
"title": "折叠状态改变",
|
||||
"msg": "activeKeys: ${event.data.activeKeys | json}, collapseId: ${event.data.collapseId}, collapsed: ${event.data.collapsed}",
|
||||
"className": "theme-toast-action-scope"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Collapse 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| - | - | - | - |
|
||||
| type | `string` | `"collapse"` | 指定为 collapse 渲染器 |
|
||||
| disabled | `boolean` | `false` | 禁用 |
|
||||
| collapsed | `boolean` | `true` | 初始状态是否折叠 |
|
||||
| key | `string \| number` | - | 标识 |
|
||||
| header | `string \| SchemaNode` | - | 标题 |
|
||||
| body | `string \| SchemaNode` | - | 内容 |
|
||||
| showArrow | `boolean` | `true` | 是否展示图标 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------- | ---------------------- | ------------ | ---------------------- |
|
||||
| type | `string` | `"collapse"` | 指定为 collapse 渲染器 |
|
||||
| disabled | `boolean` | `false` | 禁用 |
|
||||
| collapsed | `boolean` | `true` | 初始状态是否折叠 |
|
||||
| key | `string \| number` | - | 标识 |
|
||||
| header | `string \| SchemaNode` | - | 标题 |
|
||||
| body | `string \| SchemaNode` | - | 内容 |
|
||||
| showArrow | `boolean` | `true` | 是否展示图标 |
|
||||
|
||||
## Collapse 事件表
|
||||
|
||||
当前组件会对外派发以下事件,可以通过 onEvent 来监听这些事件,并通过 actions 来配置执行的动作,在 actions 中可以通过${事件参数名}或${event.data.[事件参数名]}来获取事件产生的数据,详细查看事件动作。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | ------------------------------- | ------------------------ |
|
||||
| change | `collapsed: boolean` 折叠器状态 | 折叠器折叠状态改变时触发 |
|
||||
|
||||
### change
|
||||
|
||||
折叠面板折叠状态改变时触发。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "collapse",
|
||||
"header": "标题",
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "内容",
|
||||
"wrapperComponent": "",
|
||||
"inline": false,
|
||||
"id": "u:6588c12ee3b0"
|
||||
}
|
||||
],
|
||||
"id": "u:62aa2f0c7fd9",
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"weight": 0,
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msgType": "info",
|
||||
"position": "top-right",
|
||||
"closeButton": true,
|
||||
"showIcon": true,
|
||||
"title": "collapsedChange",
|
||||
"msg": "collapsed: ${event.data.collapsed}",
|
||||
"className": "theme-toast-action-scope"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -2183,6 +2183,60 @@ crud 组件支持通过配置`headerToolbar`和`footerToolbar`属性,实现在
|
||||
}
|
||||
```
|
||||
|
||||
#### 指定导出行
|
||||
|
||||
> 3.2.0 及以上版本
|
||||
|
||||
可以通过配置 `rowSlice` 属性来控制导出哪些行
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "crud",
|
||||
"syncLocation": false,
|
||||
"api": "/api/mock2/sample",
|
||||
"headerToolbar": [{
|
||||
"type": "export-excel",
|
||||
"label": "导出 1, 4, 5 行",
|
||||
"rowSlice": "0,3:5"
|
||||
}],
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform(s)"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"label": "Engine version"
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "CSS grade"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`rowSlice` 支持以下写法
|
||||
|
||||
- 取单个值 '1,2,3',代表取 1、2、3 索引的内容
|
||||
- 取范围 '3:10',代表取 3-9 索引的内容
|
||||
- ':' 代表所有行
|
||||
- '1:' 代表从第二行开始到结束
|
||||
- 结束可以是负数 ':-1',代表除了最后一个元素的所有元素,开始为空代表 0
|
||||
- 前两种的组合 '1,3:10',代表取 1 索引和 3-9 索引的内容
|
||||
|
||||
#### 通过 api 导出 Excel
|
||||
|
||||
> 1.1.6 以上版本支持
|
||||
|
@ -994,6 +994,102 @@ selectMode 为`chained`时,使用`source`字段
|
||||
}
|
||||
```
|
||||
|
||||
## 拖拽控制
|
||||
|
||||
当`draggable`为`false`时,关闭拖拽
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "condition-builder",
|
||||
"label": "条件组件",
|
||||
"title": "条件组合设置",
|
||||
"draggable": false,
|
||||
"name": "conditions",
|
||||
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
|
||||
"pickerIcon": {
|
||||
"type": "icon",
|
||||
"icon": "edit",
|
||||
"className": "w-4 h-4"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"label": "文本",
|
||||
"type": "text",
|
||||
"name": "text"
|
||||
},
|
||||
{
|
||||
"label": "数字",
|
||||
"type": "number",
|
||||
"name": "number"
|
||||
},
|
||||
{
|
||||
"label": "布尔",
|
||||
"type": "boolean",
|
||||
"name": "boolean"
|
||||
},
|
||||
{
|
||||
"label": "选项",
|
||||
"type": "select",
|
||||
"name": "select",
|
||||
"options": [
|
||||
{
|
||||
"label": "A",
|
||||
"value": "a"
|
||||
},
|
||||
{
|
||||
"label": "B",
|
||||
"value": "b"
|
||||
},
|
||||
{
|
||||
"label": "C",
|
||||
"value": "c"
|
||||
},
|
||||
{
|
||||
"label": "D",
|
||||
"value": "d"
|
||||
},
|
||||
{
|
||||
"label": "E",
|
||||
"value": "e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "动态选项",
|
||||
"type": "select",
|
||||
"name": "select2",
|
||||
"source": "/api/mock2/form/getOptions?waitSeconds=1"
|
||||
},
|
||||
{
|
||||
"label": "日期",
|
||||
"children": [
|
||||
{
|
||||
"label": "日期",
|
||||
"type": "date",
|
||||
"name": "date"
|
||||
},
|
||||
{
|
||||
"label": "时间",
|
||||
"type": "time",
|
||||
"name": "time"
|
||||
},
|
||||
{
|
||||
"label": "日期时间",
|
||||
"type": "datetime",
|
||||
"name": "datetime"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
@ -1006,6 +1102,7 @@ selectMode 为`chained`时,使用`source`字段
|
||||
| fields | | | 字段配置 |
|
||||
| showANDOR | `boolean` | | 用于 simple 模式下显示切换按钮 |
|
||||
| showNot | `boolean` | | 是否显示「非」按钮 |
|
||||
| draggable | `boolean` | true | 是否可拖拽 |
|
||||
| searchable | `boolean` | | 字段是否可搜索 |
|
||||
| selectMode | `'list'` \| `'tree'` \| `'chained'` | `'list'` | 组合条件左侧选项类型。`'chained'`模式需要`3.2.0及以上版本` |
|
||||
| addBtnVisibleOn | `string` | | 表达式:控制按钮“添加条件”的显示。参数为`depth`、`breadth`,分别代表深度、长度。表达式需要返回`boolean`类型`3.2.0及以上版本` |
|
||||
|
@ -1469,13 +1469,402 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
|
||||
当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
|
||||
|
||||
| 动作名称 | 动作配置 | 说明 |
|
||||
| --------- | ------------------------------ | -------------------------- |
|
||||
| submit | - | 提交表单 |
|
||||
| reset | - | 重置表单 |
|
||||
| clear | - | 清空表单 |
|
||||
| validate | - | 校验表单 |
|
||||
| reload | - | 刷新(重新加载) |
|
||||
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
|
||||
| static | - | 表单切换为静态展示 |
|
||||
| nonstatic | - | 表单切换为普通输入态 |
|
||||
| 动作名称 | 动作配置 | 说明 |
|
||||
| --------- | --------------------------------------------------- | -------------------------- |
|
||||
| validate | `outputVar: string` 校验结果,默认为 validateResult | 校验表单 |
|
||||
| submit | `outputVar: string` 提交结果,默认为 submitResult | 提交表单 |
|
||||
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
|
||||
| reload | - | 刷新(重新加载) |
|
||||
| reset | - | 重置表单 |
|
||||
| clear | - | 清空表单 |
|
||||
| static | - | 表单切换为静态展示 |
|
||||
| nonstatic | - | 表单切换为普通输入态 |
|
||||
|
||||
### validate
|
||||
|
||||
校验结果默认缓存在`${event.data.validateResult}`,`true`表示校验成功,`false`表示检验失败。可以通过添加`outputVar`配置来修改缓存的变量。
|
||||
|
||||
校验结果的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
// 是否成功。非空表示失败
|
||||
"error": "依赖的部分字段没有通过验证",
|
||||
// 表单项报错信息。key值为该表单项的name值
|
||||
"errors": {
|
||||
"email": ["Email 格式不正确"],
|
||||
...
|
||||
},
|
||||
// 提交验证的表单数据
|
||||
"payload": {
|
||||
"name": "amis",
|
||||
"email": "amis@baidu"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "校验表单",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "validate",
|
||||
"componentId": "form_validate",
|
||||
"outputVar": "form_validate_result"
|
||||
},
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "validate_info",
|
||||
"args": {
|
||||
"value": "${event.data.form_validate_result|json}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'validate_info',
|
||||
id: 'validate_info',
|
||||
label: '校验结果:',
|
||||
static: true
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_validate",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "input-text",
|
||||
"label": "邮箱:",
|
||||
"required": true,
|
||||
"validations": {
|
||||
"isEmail": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### submit
|
||||
|
||||
提交结果默认缓存在`${event.data.submitResult}`,可以通过添加`outputVar`配置来修改缓存的变量。
|
||||
|
||||
提交结果的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
// 是否成功。是否成功。非空表示失败
|
||||
"error": "依赖的部分字段没有通过验证",
|
||||
// 错误信息。如果是校验失败,则errors为表单项报错信息,key值为该表单项的name值
|
||||
"errors": {
|
||||
...
|
||||
},
|
||||
// 提交的表单数据
|
||||
"payload": {
|
||||
"name": "amis",
|
||||
"email": "amis@baidu.com"
|
||||
},
|
||||
// 提交请求返回的响应结果数据
|
||||
"responseData": {
|
||||
"id": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "提交表单",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "submit",
|
||||
"componentId": "form_submit",
|
||||
"outputVar": "form_submit_result"
|
||||
},
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "submit_info",
|
||||
"args": {
|
||||
"value": "${event.data.form_submit_result|json}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'submit_info',
|
||||
id: 'submit_info',
|
||||
label: '提交结果:',
|
||||
static: true
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_submit",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "input-text",
|
||||
"label": "邮箱:",
|
||||
"required": true,
|
||||
"validations": {
|
||||
"isEmail": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### setValue
|
||||
|
||||
通过`setValue`来更新表单数据,其中`value`中的数据将和目标表单的数据做合并,即同名覆盖。
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "修改表单数据",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "form_setvalue",
|
||||
"args": {
|
||||
"value": {
|
||||
"name": "amis",
|
||||
"email": "amis@baidu.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_setvalue",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "input-text",
|
||||
"label": "邮箱:"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### reload
|
||||
|
||||
通过`reload`来重新请求表单的初始化接口,实现表单刷新。
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "刷新表单",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "reload",
|
||||
"componentId": "form_reload"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_reload",
|
||||
"debug": true,
|
||||
"initApi": "/api/mock2/form/initData",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": "input-text",
|
||||
"label": "作者:"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### reset
|
||||
|
||||
通过`reset`将表单数据重置为初始数据,初始数据可以是静态数据或初始化接口返回的数据。
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "重置表单",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "reset",
|
||||
"componentId": "form_reset"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_reset",
|
||||
"initApi": "/api/mock2/form/initData",
|
||||
"body": [
|
||||
{
|
||||
"type": "alert",
|
||||
"body": "修改表单项的值,然后点击【重置表单】,表单数据将被重置为初始化数据"
|
||||
},
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": "input-text",
|
||||
"label": "作者:"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### clear
|
||||
|
||||
通过`clear`来清空表单中的表单项数据,不包含`hidden`类型、未绑定表单项的初始化数据字段。
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "清空表单",
|
||||
className: "mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "clear",
|
||||
"componentId": "form_clear"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_clear",
|
||||
"debug": true,
|
||||
"initApi": "/api/mock2/form/initData",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名:"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": "hidden",
|
||||
"label": "作者:"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### static 和 nonstatic
|
||||
|
||||
```schema: scope="body"
|
||||
[
|
||||
{
|
||||
"type": "button",
|
||||
"label": "静态模式",
|
||||
"className": "mr-2 mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "static",
|
||||
"componentId": "form_static"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"label": "非静态模式",
|
||||
"className": "mr-2 mb-2",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "nonstatic",
|
||||
"componentId": "form_static"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"id": "form_static",
|
||||
"title": "表单",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "text",
|
||||
"label": "输入框",
|
||||
"mode": "horizontal",
|
||||
"value": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -370,6 +370,7 @@ order: 14
|
||||
| clearable | `boolean` | `true` | 是否可清除 |
|
||||
| embed | `boolean` | `false` | 是否内联 |
|
||||
| timeConstraints | `object` | `true` | 请参考 [input-time](./input-time#控制输入范围) 里的说明 |
|
||||
| isEndDate | `boolean` | `false` | 如果配置为 true,会自动默认为 23:59:59 秒 |
|
||||
|
||||
## 事件表
|
||||
|
||||
|
@ -56,7 +56,7 @@ order: 38
|
||||
|
||||
## 控制调整的粒度
|
||||
|
||||
使用 `step` 可以控制调整粒度,默认是 1。
|
||||
使用 `step` 可以控制调整粒度,默认是 1。`3.3.0`版本后支持使用变量。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
@ -255,14 +255,14 @@ order: 38
|
||||
|
||||
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
|
||||
| className | `string` | | css 类名 |
|
||||
| value | `number` or `string` or `{min: number, max: number}` or `[number, number]` | | |
|
||||
| min | `number` | `0` | 最小值 |
|
||||
| max | `number` | `100` | 最大值 |
|
||||
| min | `number \| string` | `0` | 最小值,支持变量 | `3.3.0`后支持变量 |
|
||||
| max | `number \| string` | `100` | 最大, 支持变量值 | `3.3.0`后支持变量 |
|
||||
| disabled | `boolean` | `false` | 是否禁用 |
|
||||
| step | `number` | `1` | 步长 |
|
||||
| step | `number \| string` | `1` | 步长,支持变量 | `3.3.0`后支持变量 |
|
||||
| showSteps | `boolean` | `false` | 是否显示步长 |
|
||||
| parts | `number` or `number[]` | `1` | 分割的块数<br/>主持数组传入分块的节点 |
|
||||
| marks | <code>{ [number | string]: ReactNode }</code> or <code>{ [number | string]: { style: CSSProperties, label: ReactNode } }</code> | | 刻度标记<br/>- 支持自定义样式<br/>- 设置百分比 |
|
||||
|
@ -12,6 +12,8 @@ order: 61
|
||||
|
||||
> 1.10.0 及以上版本
|
||||
|
||||
这个组件可以基于 JSON Schema 生成表单项,方便对接类似 OpenAPI/Swagger Specification 的接口规范,可基于接口定义自动生成 amis 表单项。
|
||||
|
||||
> 此组件还在实验阶段,很多 json-schema 属性没有对应实现,使用前请先确认你要的功能满足了需求
|
||||
|
||||
基于 json-schema 定义生成表单输入项。
|
||||
|
@ -239,6 +239,61 @@ order: 58
|
||||
}
|
||||
```
|
||||
|
||||
### 导航项收纳
|
||||
|
||||
垂直(`"stack": true`)模式下,如果子导航项比较多,也可以给导航项设置收纳模式,配置同`overflow`,仅支持一次性展开
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"expandPosition": "after",
|
||||
"style": {
|
||||
"width": 200
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "Nav1"
|
||||
}
|
||||
],
|
||||
"to": "#/"
|
||||
},
|
||||
{
|
||||
"label": "Nav2",
|
||||
"unfolded": true,
|
||||
"overflow": {
|
||||
"enable": true
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"to": "#/test2"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "#/test3"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-3",
|
||||
"to": "#/test1"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-4",
|
||||
"to": "#/test4"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-5",
|
||||
"to": "#/test5"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 动态导航
|
||||
|
||||
通过配置 source 来实现动态生成导航,source 可以是 api 地址或者变量,比如
|
||||
@ -323,13 +378,359 @@ order: 58
|
||||
}
|
||||
```
|
||||
|
||||
## 悬浮导航
|
||||
|
||||
可以通过设置`mode`属性来控制导航模式,不设置默认为内联导航模式
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"mode": "float",
|
||||
"style": {
|
||||
"width": "200px"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 导航缩起
|
||||
|
||||
`collapsed`属性控制导航的展开和缩起,缩起状态下,导航内容仅展示图标或第一个文字,悬浮展开全部内容或子导航项
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"collapsed": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 导航分割线
|
||||
|
||||
导航项`mode`属性控制导航项的展示模式,支持`divider`(分割线)和`group`(分组)两种模式,不设置默认为普通导航项
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"style": {
|
||||
"width": "160px"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
mode: 'divider'
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers",
|
||||
"disabled": true,
|
||||
"disabledTip": "导航项禁用"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 导航分组
|
||||
|
||||
分组模式(`"mode": "group"`)的导航项展示为分组标题形式
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"mode": "group",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers",
|
||||
"mode": "group",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 3-1",
|
||||
"to": "/docs/api-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 默认展开层级
|
||||
|
||||
当前导航最大层级为 4,可通过`defaultOpenLevel`来控制默认 2 层级全部展开
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"defaultOpenLevel": "2",
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1-1",
|
||||
"to": "/docs/api-2-1-1-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 3-1",
|
||||
"to": "/docs/api-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义展开按钮
|
||||
|
||||
可以设置`expandIcon`为 icon 的名称字符串
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"expandIcon": "close",
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 3-1",
|
||||
"to": "/docs/api-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
也可以将`expandIcon`设置为`SchemaObject`
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"expandIcon": {
|
||||
"type": "icon",
|
||||
"icon": "far fa-address-book"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 3-1",
|
||||
"to": "/docs/api-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------------------------- | ----------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
|
||||
| type | `string` | `"nav"` | 指定为 Nav 渲染器 |
|
||||
| mode | `string` | `"inline"` | 导航模式,悬浮或者内联,默认内联模式 |
|
||||
| collapsed | `boolean` | | 控制导航是否缩起 |
|
||||
| indentSize | `number` | `16` | 层级缩进值,仅内联模式下生效 |
|
||||
| level | `number` | | 控制导航最大展示层级数 |
|
||||
| defaultOpenLevel | `number` | | 控制导航最大默认展开层级 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| popupClassName | `string` | | 当为悬浮模式时,可自定义悬浮层样式 |
|
||||
| expandIcon | `string \| SchemaObject` | | 自定义展开按钮 |
|
||||
| expandPosition | `string` | | 展开按钮位置,`"before"`或者`"after"`,不设置默认在前面 |
|
||||
| stacked | `boolean` | `true` | 设置成 false 可以以 tabs 的形式展示 |
|
||||
| accordion | `boolean` | | 是否开启手风琴模式 |
|
||||
| source | `string` 或 [API](../../docs/types/api) | | 可以通过变量或 API 接口动态创建导航 |
|
||||
| deferApi | [API](../../docs/types/api) | | 用来延时加载选项详情的接口,可以不配置,不配置公用 source 接口。 |
|
||||
| itemActions | [SchemaNode](../../docs/types/schemanode) | | 更多操作相关配置 |
|
||||
@ -348,6 +749,11 @@ order: 58
|
||||
| links[x].activeOn | [表达式](../../docs/concepts/expression) | | 是否高亮的条件,留空将自动分析链接地址 |
|
||||
| links[x].defer | `boolean` | | 标记是否为懒加载项 |
|
||||
| links[x].deferApi | [API](../../docs/types/api) | | 可以不配置,如果配置优先级更高 |
|
||||
| links[x].disabled | `boolean` | | 是否禁用 |
|
||||
| links[x].disabledTip | `string` | | 禁用提示信息 |
|
||||
| links[x].className | `string` | | 菜单项自定义样式 |
|
||||
| links[x].mode | `string` | | 菜菜单项模式,分组模式:`"group"`、分割线:`"divider"` |
|
||||
| links[x].overflow | `NavOverflow` | | 导航项响应式收纳配置 |
|
||||
| overflow | `NavOverflow` | | 响应式收纳配置 |
|
||||
| overflow.enable | `boolean` | `false` | 是否开启响应式收纳 |
|
||||
| overflow.overflowLabel | `string \| SchemaObject` | | 菜单触发按钮的文字 |
|
||||
@ -357,4 +763,348 @@ order: 58
|
||||
| overflow.style | `React.CSSProperties` | | 自定义样式 |
|
||||
| overflow.overflowClassName | `string` | `""` | 菜单按钮 CSS 类名 |
|
||||
| overflow.overflowPopoverClassName | `string` | `""` | Popover 浮层 CSS 类名 |
|
||||
| overflow.overflowListClassName | `string` | `""` | 菜单外层 CSS 类名 |
|
||||
|
||||
## 事件表
|
||||
|
||||
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细查看[事件动作](../../docs/concepts/event-action)。
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| --------- | --------------------------------------------------------------- | ------------------------ |
|
||||
| loaded | `items: item[]` 数据源<br/>`item?: object` 懒加载时所点击导航项 | 异步加载数据源完成时触发 |
|
||||
| collapsed | `collapsed: boolean` 缩起展开状态 | 导航缩起展开时触发 |
|
||||
| toggled | `item: object` 导航项数据<br/>`open: boolean` 展开状态 | 点击导航展开按钮时触发 |
|
||||
| change | `value: item[]` 选中导航项数据 | 导航项选中有变化时触发 |
|
||||
| click | `item: object` 点击导航项数据 | 手动点击导航项时触发 |
|
||||
|
||||
### loaded
|
||||
|
||||
数据源加载完成,可以尝试将`source`配置为 api 地址或者开启懒加载。
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"source": "/api/options/nav?parentId=${value}",
|
||||
"onEvent": {
|
||||
"loaded": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msg": "已加载${event.data.items.length}条记录"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### collapsed
|
||||
|
||||
导航缩起,可以尝试修改导航的`collapsed`属性。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"collapsed": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"collapsed": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msg": "${event.data.collapsed ? '导航缩起' : '导航展开'}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### toggled
|
||||
|
||||
导航项收起展开,可以尝试点击导航项展开按钮。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"toggled": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msg": "${event.data.item.label}${event.data.open ? '展开' : '收起'}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### change
|
||||
|
||||
导航项选中,可以尝试手动修改任意导航项的`active`属性。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msg": "${event.data.value.length}项选中"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### click
|
||||
|
||||
导航项点击,可以尝试手动点击任意导航项。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"links": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3"
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "toast",
|
||||
"args": {
|
||||
"msg": "${event.data.item.label}被点击了,但不一定选中"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 动作表
|
||||
|
||||
当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
|
||||
|
||||
| 动作名称 | 动作配置 | 说明 |
|
||||
| ----------- | -------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| updateItems | `value: string`或`value: item[]` | 更新导航数据源为指定导航项的子导航数据 |
|
||||
| reset | | 重置导航数据源,和 updateItems 搭配使用,没有经过 updateItems 操作的,执行 reset 无效果 |
|
||||
|
||||
### updateItems / reset
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"label": "Nav 1",
|
||||
"to": "/docs/index",
|
||||
"icon": "fa fa-user"
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"unfolded": true,
|
||||
"active": true,
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "Nav 2-1-1",
|
||||
"to": "/docs/api-2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 2-2",
|
||||
"to": "/docs/api-2-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
"to": "/docs/renderers"
|
||||
}
|
||||
]
|
||||
},
|
||||
"body": {
|
||||
"type": "container",
|
||||
"body": [
|
||||
{
|
||||
"type": "action",
|
||||
"label": "设置数据源",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "updateItems",
|
||||
"args": {
|
||||
"value": "Nav 2"
|
||||
},
|
||||
"componentId": "asideNav"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"label": "重置数据源",
|
||||
"className": "mx-1",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "reset",
|
||||
"componentId": "asideNav"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"body": [
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"source": "${items}",
|
||||
"id": "asideNav"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -841,8 +841,9 @@ order: 68
|
||||
| defaultKey | `string` / `number` | | 组件初始化时激活的选项卡,hash 值或索引值,支持使用表达式 `2.7.1 以上版本` |
|
||||
| activeKey | `string` / `number` | | 激活的选项卡,hash 值或索引值,支持使用表达式,可响应上下文数据变化 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| linksClassName | `string` | | Tabs 标题区的类名 |
|
||||
| contentClassName | `string` | | Tabs 内容区的类名 |
|
||||
| tabsMode | `string` | | 展示模式,取值可以是 `line`、`card`、`radio`、`vertical`、`chrome`、`simple`、`strong`、`tiled`、`sidebar` |
|
||||
| tabsClassName | `string` | | Tabs Dom 的类名 |
|
||||
| tabs | `Array` | | tabs 内容 |
|
||||
| source | `string` | | tabs 关联数据,关联后可以重复生成选项卡 |
|
||||
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏 |
|
||||
|
@ -256,18 +256,16 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'ajax',
|
||||
args: {
|
||||
api: {
|
||||
url: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm?name=${name}',
|
||||
method: 'get',
|
||||
"responseData": {
|
||||
"resId": "${id}"
|
||||
}
|
||||
api: {
|
||||
url: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm?name=${name}',
|
||||
method: 'post',
|
||||
responseData: {
|
||||
"resId": "${id}",
|
||||
},
|
||||
messages: {
|
||||
success: '成功了!欧耶',
|
||||
failed: '失败了呢。。'
|
||||
}
|
||||
},
|
||||
},
|
||||
data: {
|
||||
age: 18
|
||||
@ -296,18 +294,16 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'ajax',
|
||||
args: {
|
||||
api: {
|
||||
url: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/initData',
|
||||
method: 'post'
|
||||
},
|
||||
api: {
|
||||
url: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/initData',
|
||||
method: 'post',
|
||||
messages: {
|
||||
success: '成功了!欧耶',
|
||||
failed: '失败了呢。。'
|
||||
},
|
||||
options: {
|
||||
silent: true
|
||||
}
|
||||
},
|
||||
options: {
|
||||
silent: true,
|
||||
},
|
||||
data: {
|
||||
age: 18
|
||||
@ -328,9 +324,7 @@ run action ajax
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
|
||||
> `< 1.8.0 及以下版本`,以下属性与 args 同级。
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| -------- | ----------------------------------- | ------ | ------------------------- |
|
||||
@ -377,57 +371,55 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog',
|
||||
args: {
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: '模态弹窗',
|
||||
id: 'dialog_001',
|
||||
data: {
|
||||
myage: '22'
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: '模态弹窗',
|
||||
id: 'dialog_001',
|
||||
data: {
|
||||
myage: '22'
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '<p>对,你打开了模态弹窗</p>',
|
||||
inline: false
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '<p>对,你打开了模态弹窗</p>',
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'myname',
|
||||
mode: 'horizontal',
|
||||
onEvent: {
|
||||
change: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'confirm',
|
||||
componentId: 'dialog_001'
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'myname',
|
||||
mode: 'horizontal',
|
||||
onEvent: {
|
||||
change: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'confirm',
|
||||
componentId: 'dialog_001'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
onEvent: {
|
||||
confirm: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'confirm'
|
||||
}
|
||||
}
|
||||
],
|
||||
onEvent: {
|
||||
confirm: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'confirm'
|
||||
}
|
||||
]
|
||||
},
|
||||
cancel: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'cancel'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
cancel: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'cancel'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -440,9 +432,7 @@ run action ajax
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
|
||||
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | ----------------------- | ------ | --------------------------------------------------------- |
|
||||
@ -466,63 +456,59 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog',
|
||||
args: {
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
id: 'dialog_002',
|
||||
title: '模态弹窗',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '打开子弹窗,然后关闭它的父亲',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog',
|
||||
args: {
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: '模态子弹窗',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭指定弹窗(关闭父弹窗)',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDialog',
|
||||
componentId: 'dialog_002'
|
||||
}
|
||||
]
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
id: 'dialog_002',
|
||||
title: '模态弹窗',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '打开子弹窗,然后关闭它的父亲',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: '模态子弹窗',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭指定弹窗(关闭父弹窗)',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDialog',
|
||||
componentId: 'dialog_002'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭当前弹窗',
|
||||
className: 'ml-2',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDialog'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭当前弹窗',
|
||||
className: 'ml-2',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDialog'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -557,38 +543,36 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'drawer',
|
||||
args: {
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
title: '模态抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '<p>对,你打开了模态抽屉</p>',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
onEvent: {
|
||||
confirm: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'confirm'
|
||||
}
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
title: '模态抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '<p>对,你打开了模态抽屉</p>',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
onEvent: {
|
||||
confirm: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'confirm'
|
||||
}
|
||||
]
|
||||
},
|
||||
cancel: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'cancel'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
cancel: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: 'cancel'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -601,9 +585,7 @@ run action ajax
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
|
||||
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | ----------------------- | ------ | --------------------------------------------------------- |
|
||||
@ -626,63 +608,59 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'drawer',
|
||||
args: {
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
id: 'drawer_1',
|
||||
title: '模态抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '打开子抽屉,然后关闭它的父亲',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'drawer',
|
||||
args: {
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
title: '模态子抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭指定抽屉(关闭父抽屉)',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDrawer',
|
||||
componentId: 'drawer_1'
|
||||
}
|
||||
]
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
id: 'drawer_1',
|
||||
title: '模态抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '打开子抽屉,然后关闭它的父亲',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'drawer',
|
||||
drawer: {
|
||||
type: 'drawer',
|
||||
title: '模态子抽屉',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭指定抽屉(关闭父抽屉)',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDrawer',
|
||||
componentId: 'drawer_1'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭当前抽屉',
|
||||
className: 'ml-2',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDrawer'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '关闭当前抽屉',
|
||||
className: 'ml-2',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'closeDrawer'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -699,51 +677,13 @@ run action ajax
|
||||
| ----------- | -------- | ------ | --------------- |
|
||||
| componentId | `string` | - | 指定抽屉组件 id |
|
||||
|
||||
### 打开对话框
|
||||
### 打开确认弹窗
|
||||
|
||||
通过配置`actionType: 'alert'`或`actionType: 'confirm'`打开不同对话框,该动作分别需实现 env.alert: (msg: string) => void 和 env.confirm: (msg: string, title?: string) => boolean | Promise<boolean>。
|
||||
通过配置`actionType: 'confirmDialog'`打开确认对话框。确认对话框弹出后,如果选择取消操作,将不会执行该动作后面的动作。如下面的例子,点击确认之后将弹出`toast`提示,点击取消则不会提示。
|
||||
|
||||
#### 提示对话框
|
||||
**普通文本内容**
|
||||
|
||||
```schema
|
||||
{
|
||||
type: 'page',
|
||||
data: {
|
||||
msg: '去吃饭了'
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '提示对话框(模态)',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'alert',
|
||||
args: {
|
||||
title: '提示',
|
||||
msg: '<a href="http://www.baidu.com" target="_blank">${msg}~</a>'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
|
||||
> `< 1.8.0 及以下版本`,以下属性与 args 同级。
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | -------- | -------- | -------------- |
|
||||
| title | `string` | 系统提示 | 对话框标题 |
|
||||
| msg | `string` | - | 对话框提示内容 |
|
||||
|
||||
#### 确认对话框
|
||||
动作需要实现 env.confirm: (msg: string, title?: string) => boolean | Promise<boolean>。
|
||||
|
||||
```schema
|
||||
{
|
||||
@ -762,10 +702,16 @@ run action ajax
|
||||
actions: [
|
||||
{
|
||||
actionType: 'confirmDialog',
|
||||
args: {
|
||||
dialog: {
|
||||
title: '${title}',
|
||||
msg: '<span style="color:red">${msg}</span>'
|
||||
}
|
||||
},
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: '确认ok啦!'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -775,14 +721,120 @@ run action ajax
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
**自定义弹窗内容**
|
||||
|
||||
> `< 1.8.0 及以下版本`,以下属性与 args 同级。
|
||||
可以通过`body`像配置弹窗一样配置确认弹窗的内容。
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | -------- | ------ | -------------- |
|
||||
| title | `string` | - | 对话框标题 |
|
||||
| msg | `string` | - | 对话框提示内容 |
|
||||
```schema
|
||||
{
|
||||
type: 'page',
|
||||
data: {
|
||||
title: '操作确认',
|
||||
msg: '确认提交吗?'
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '自定义确认对话框(模态)',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'confirmDialog',
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: '${title}',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
confirmBtnLevel: 'primary',
|
||||
data: {
|
||||
'&': '$$',
|
||||
title: '确认'
|
||||
},
|
||||
body: [
|
||||
{
|
||||
"type": "form",
|
||||
"initApi": "/api/mock2/form/initData",
|
||||
"title": "编辑用户信息",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "name",
|
||||
"label": "姓名"
|
||||
},
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "email",
|
||||
"label": "邮箱"
|
||||
},
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '${msg}'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msg: '确认ok啦!'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | ----------------------------- | ------ | ------------------------------------------------------------------- |
|
||||
| dialog | {msg:`string`}/`DialogObject` | - | 指定弹框内容。自定义弹窗内容可参考[Dialog](../../components/dialog) |
|
||||
|
||||
### 提示对话框
|
||||
|
||||
通过配置`actionType: 'alert'`打开提示对话框,该对话框只有确认按钮。该动作需要实现 env.alert: (msg: string) => void。
|
||||
|
||||
```schema
|
||||
{
|
||||
type: 'page',
|
||||
data: {
|
||||
msg: '去吃饭了'
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '提示对话框(模态)',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'alert',
|
||||
dialog: {
|
||||
title: '提示',
|
||||
msg: '<a href="http://www.baidu.com" target="_blank">${msg}~</a>'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | -------------------------------- | ---------------------------- | ---------- |
|
||||
| dialog | {title:`string`<br>msg:`string`} | {title: '系统提示', msg: ''} | 对话框配置 |
|
||||
|
||||
### 跳转链接
|
||||
|
||||
@ -1672,9 +1724,7 @@ run action ajax
|
||||
}
|
||||
```
|
||||
|
||||
**动作属性(args)**
|
||||
|
||||
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
|
||||
**动作属性**
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------ | ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@ -1800,11 +1850,11 @@ run action ajax
|
||||
|
||||
有时在执行自定义 JS 的时候,希望该过程中产生的数据可以分享给后面的动作使用,此时可以通过`event.setData()`来实现事件上下文的设置,这样后面动作都可以通过事件上下文来获取共享的数据。
|
||||
|
||||
> 注意:直接调用`event.setData()`将修改事件的原有上下文,如果不希望覆盖可以通过`event.setData({...event.data, {xxx: xxx}})`来进行数据的合并。
|
||||
> 注意:直接调用`event.setData()`将修改事件的原有上下文,如果不希望覆盖可以通过`event.setData({...event.data, ...{xxx: xxx}})`来进行数据的合并。
|
||||
|
||||
## 触发其他组件的动作
|
||||
## 触发组件的动作
|
||||
|
||||
通过配置`componentId`来触发指定组件的动作,组件动作配置通过`args`传入`(> 1.9.0 及以上版本)`,动作参数请查看对应的组件的[动作表](../../components/form/index#动作表),更多示例请查看[组件事件动作示例](../../../examples/event/form)。
|
||||
通过配置`componentId`或`componentName`来触发指定组件的动作(不配置将调用当前组件自己的动作),组件动作配置通过`args`传入`(> 1.9.0 及以上版本)`,动作参数请查看对应的组件的[动作表](../../components/form/index#动作表),更多示例请查看[组件事件动作示例](../../../examples/event/form)。
|
||||
|
||||
```schema
|
||||
{
|
||||
|
@ -703,7 +703,7 @@ render 有三个参数,后面会详细说明这三个参数内的属性
|
||||
(schema: any, path: string) => Promise<Function>;
|
||||
```
|
||||
|
||||
可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/__tests__/factory.test.tsx#L64-L91。
|
||||
可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/packages/amis-core/__tests__/factory.test.tsx#L64-L91。
|
||||
|
||||
#### affixOffsetTop: number
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
export default {
|
||||
type: 'app',
|
||||
brandName: 'APP 模式',
|
||||
api: '/api/mock2/sample',
|
||||
// logo:
|
||||
// '<svg t="1610181550507" style="width: 30px; height: 30px;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2528" width="200" height="200"><path d="M217.090828 534.490224c99.683327-20.926612 86.12145-137.343041 83.128279-162.838715-4.906753-39.223327-52.112891-107.812471-116.217908-102.372575-80.693834 7.058766-92.487438 120.948653-92.487438 120.948653C80.583828 442.927855 117.654119 555.449581 217.090828 534.490224L217.090828 534.490224zM322.936505 737.004567c-2.907213 8.211009-9.413394 29.170366-3.781116 47.381124 11.123338 40.874943 51.108005 34.208103 51.108005 34.208103l56.034201 0L426.297594 706.525392l-56.034201 0C345.158622 713.864544 325.65543 728.85291 322.936505 737.004567L322.936505 737.004567zM402.144498 339.233168c55.081503 0 99.57588-61.946864 99.57588-138.461515 0-76.491115-44.494377-138.40728-99.57588-138.40728-54.974056 0-99.599416 61.916165-99.599416 138.40728C302.545082 277.286304 347.170442 339.233168 402.144498 339.233168L402.144498 339.233168zM639.288546 348.395852c73.635067 9.331529 120.925117-67.407226 130.340557-125.57195 9.582239-58.081837-37.906332-125.577067-90.024339-137.174196-52.219315-11.712763-117.390617 70.044286-123.329886 123.305327C549.187459 274.094612 565.853024 339.149257 639.288546 348.395852L639.288546 348.395852zM819.642171 690.38069c0 0-113.866351-86.12145-180.37716-179.167612-90.108251-137.176243-218.121809-81.367169-260.908288-11.604292C335.745229 569.372685 269.286608 613.477182 259.841491 625.187899c-9.552563 11.489682-137.50984 79.010495-109.128443 202.314799s135.49802 131.180691 135.49802 131.180691 66.203818-3.168156 151.517879-21.829168c85.312014-18.48705 158.833495 4.598738 158.833495 4.598738s199.320605 65.22349 253.866918-60.3546C904.897903 755.497757 819.642171 690.38069 819.642171 690.38069L819.642171 690.38069zM482.333842 874.629018 342.241176 874.629018c-55.946196-1.313925-71.552639-45.587268-74.354452-51.943023-2.777253-6.473435-18.606777-36.478819-10.226922-87.473237 24.179702-76.490092 84.613096-84.695984 84.613096-84.695984l84.053348 0L426.326247 566.464449l56.036247 0 0 308.164568L482.333842 874.629018zM706.478831 874.629018l-140.090619 0c-55.16746-2.355651-56.124252-52.927443-56.124252-52.927443l0.086981-171.2155 56.037271 0L566.388213 790.57874c3.697205 15.438621 28.016077 28.015054 28.016077 28.015054l56.037271 0L650.441561 650.486074l56.038294 0L706.479855 874.629018 706.478831 874.629018zM931.037237 446.066335c0-27.816532-23.675212-111.617124-111.395066-111.617124-87.919399 0-99.660814 79.093383-99.660814 135.016043 0 53.344952 4.592598 127.816061 113.806999 125.490086C943.00071 592.633459 931.037237 474.058876 931.037237 446.066335L931.037237 446.066335zM931.037237 446.066335" p-id="2529"></path></svg>',
|
||||
header: {
|
||||
@ -44,6 +45,7 @@ export default {
|
||||
{
|
||||
label: '页面A-2',
|
||||
url: '2',
|
||||
|
||||
schema: {
|
||||
type: 'page',
|
||||
title: '页面A-2',
|
||||
@ -91,6 +93,8 @@ export default {
|
||||
label: '列表',
|
||||
url: '/crud/list',
|
||||
icon: 'fa fa-list',
|
||||
badge: '${count}',
|
||||
badgeClassName: 'bg-info',
|
||||
schemaApi: '/api/mock2/service/schema?type=crud'
|
||||
}
|
||||
]
|
||||
|
33
fis-conf.js
33
fis-conf.js
@ -85,9 +85,9 @@ fis.match('/mock/**', {
|
||||
useCompile: false
|
||||
});
|
||||
|
||||
fis.match('mod.js', {
|
||||
useCompile: false
|
||||
});
|
||||
// fis.match('mod.js', {
|
||||
// useCompile: false
|
||||
// });
|
||||
|
||||
fis.match('*.scss', {
|
||||
parser: fis.plugin('sass', {
|
||||
@ -214,6 +214,9 @@ fis.match('{*.ts,*.jsx,*.tsx,/examples/**.js,/src/**.js,/src/**.ts}', {
|
||||
isMod: true,
|
||||
rExt: '.js'
|
||||
});
|
||||
fis.match('/examples/mod.js', {
|
||||
isMod: false
|
||||
});
|
||||
|
||||
fis.match('markdown-it/**', {
|
||||
preprocessor: fis.plugin('js-require-file')
|
||||
@ -492,7 +495,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!markdown-it/**',
|
||||
'!markdown-it-html5-media/**',
|
||||
'!punycode/**',
|
||||
'!ooxml-viewer/**',
|
||||
'!office-viewer/**',
|
||||
'!fflate/**'
|
||||
],
|
||||
|
||||
@ -534,9 +537,14 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
|
||||
'barcode.js': ['src/components/BarCode.tsx', 'jsbarcode/**'],
|
||||
|
||||
'charts.js': ['zrender/**', 'echarts/**', 'echarts-stat/**', 'echarts-wordcloud/**'],
|
||||
'charts.js': [
|
||||
'zrender/**',
|
||||
'echarts/**',
|
||||
'echarts-stat/**',
|
||||
'echarts-wordcloud/**'
|
||||
],
|
||||
|
||||
'ooxml-viewer.js': ['ooxml-viewer/**', 'fflate/**'],
|
||||
'office-viewer.js': ['office-viewer/**', 'fflate/**'],
|
||||
|
||||
'rest.js': [
|
||||
'*.js',
|
||||
@ -561,7 +569,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!uc.micro/**',
|
||||
'!markdown-it/**',
|
||||
'!markdown-it-html5-media/**',
|
||||
'!ooxml-viewer/**',
|
||||
'!office-viewer/**',
|
||||
'!fflate/**'
|
||||
]
|
||||
}),
|
||||
@ -792,7 +800,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!punycode/**',
|
||||
'!amis-formula/**',
|
||||
'!fflate/**',
|
||||
'!ooxml-viewer/**',
|
||||
'!office-viewer/**',
|
||||
'!amis-core/**',
|
||||
'!amis-ui/**',
|
||||
'!amis/**'
|
||||
@ -838,7 +846,12 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
|
||||
'pkg/cropperjs.js': ['cropperjs/**', 'react-cropper/**'],
|
||||
|
||||
'pkg/charts.js': ['zrender/**', 'echarts/**', 'echarts-stat/**', 'echarts-wordcloud/**'],
|
||||
'pkg/charts.js': [
|
||||
'zrender/**',
|
||||
'echarts/**',
|
||||
'echarts-stat/**',
|
||||
'echarts-wordcloud/**'
|
||||
],
|
||||
|
||||
'pkg/api-mock.js': ['mock/*.ts'],
|
||||
|
||||
@ -853,7 +866,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
|
||||
'!/examples/components/EChartsEditor/Common.tsx'
|
||||
],
|
||||
|
||||
'pkg/ooxml-viewer.js': ['ooxml-viewer/**', 'fflate/**'],
|
||||
'pkg/office-viewer.js': ['office-viewer/**', 'fflate/**'],
|
||||
|
||||
'pkg/rest.js': [
|
||||
'**.{js,jsx,ts,tsx}',
|
||||
|
@ -5,5 +5,5 @@
|
||||
"packages/amis-ui",
|
||||
"packages/amis"
|
||||
],
|
||||
"version": "3.1.1"
|
||||
"version": "3.3.0-beta.4"
|
||||
}
|
||||
|
@ -56,7 +56,11 @@ function mockResponse(event, context, callback) {
|
||||
function createHeaders(headers) {
|
||||
let referer = '';
|
||||
|
||||
if (/^(https?\:\/\/[^:\/]+(?:\:\d+)?\/)/i.test(headers['Referer'])) {
|
||||
if (
|
||||
/^(https?\:\/\/[^:\/]+(?:\:\d+)?\/)/i.test(
|
||||
headers['Referer'] || headers['referer']
|
||||
)
|
||||
) {
|
||||
referer = RegExp.$1.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,6 @@
|
||||
"jest": {
|
||||
"verbose": true,
|
||||
"testEnvironment": "jsdom",
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"cobertura"
|
||||
@ -119,7 +118,7 @@
|
||||
"^amis\\-ui$": "<rootDir>/packages/amis-ui/src/index.tsx",
|
||||
"^amis\\-core$": "<rootDir>/packages/amis-core/src/index.tsx",
|
||||
"^amis\\-formula$": "<rootDir>/packages/amis-formula/src/index.ts",
|
||||
"^office\\-viewer$": "<rootDir>/packages/ooxml-viewer/src/index.ts",
|
||||
"^office\\-viewer$": "<rootDir>/packages/office-viewer/src/index.ts",
|
||||
"^amis$": "<rootDir>/packages/amis/src/index.tsx"
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
|
11
packages/amis-core/__tests__/utils/arraySlice.test.ts
Normal file
11
packages/amis-core/__tests__/utils/arraySlice.test.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {arraySlice} from '../../src/utils/arraySlice';
|
||||
|
||||
test(`arrayslice:test`, () => {
|
||||
const testArray = [0, 1, 2, 3, 4, 5];
|
||||
expect(arraySlice(testArray, ':')).toEqual([0, 1, 2, 3, 4, 5]);
|
||||
expect(arraySlice(testArray, '0,3,4')).toEqual([0, 3, 4]);
|
||||
expect(arraySlice(testArray, '1:2')).toEqual([1]);
|
||||
expect(arraySlice(testArray, '0:-2')).toEqual([0, 1, 2, 3]);
|
||||
expect(arraySlice(testArray, '1:-2')).toEqual([1, 2, 3]);
|
||||
expect(arraySlice(testArray, '0,1:-2')).toEqual([0, 1, 2, 3]);
|
||||
});
|
26
packages/amis-core/__tests__/utils/math.test.ts
Normal file
26
packages/amis-core/__tests__/utils/math.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {safeAdd, safeSub, numberFormatter} from '../../src/utils/math';
|
||||
|
||||
test(`math safeAdd:test`, () => {
|
||||
expect(safeAdd(0.1, 0.2)).toEqual(0.3);
|
||||
expect(safeAdd(0.111, 0.455)).toEqual(0.566);
|
||||
expect(safeAdd(NaN, 1)).toEqual(NaN);
|
||||
expect(safeAdd(NaN, NaN)).toEqual(NaN);
|
||||
});
|
||||
|
||||
test(`math safeSub:test`, () => {
|
||||
expect(safeSub(0.8, 0.1)).toEqual(0.7);
|
||||
expect(safeSub(0.1, 0.111111)).toEqual(-0.011111);
|
||||
expect(safeAdd(NaN, 1)).toEqual(NaN);
|
||||
expect(safeAdd(NaN, NaN)).toEqual(NaN);
|
||||
});
|
||||
|
||||
test('numberFormatter:test', () => {
|
||||
expect(numberFormatter(0)).toEqual('0');
|
||||
expect(numberFormatter(0, 2)).toEqual('0.00');
|
||||
expect(numberFormatter(0, 8)).toEqual('0.00000000');
|
||||
expect(numberFormatter(123456)).toEqual('123,456');
|
||||
expect(numberFormatter(123456, 2)).toEqual('123,456.00');
|
||||
expect(numberFormatter(1234567890000)).toEqual('1,234,567,890,000');
|
||||
expect(numberFormatter(1234567890000, 4)).toEqual('1,234,567,890,000.0000');
|
||||
expect(numberFormatter(1000000000000000)).toEqual('1,000,000,000,000,000');
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-core",
|
||||
"version": "3.1.5",
|
||||
"version": "3.3.0-beta.4",
|
||||
"description": "amis-core",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -45,7 +45,7 @@
|
||||
"esm"
|
||||
],
|
||||
"dependencies": {
|
||||
"amis-formula": "^3.1.1",
|
||||
"amis-formula": "^3.3.0-beta.4",
|
||||
"classnames": "2.3.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
@ -69,7 +69,6 @@
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"cobertura"
|
||||
|
@ -160,7 +160,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
window.open(mailto);
|
||||
} else if (action.actionType === 'dialog') {
|
||||
store.setCurrentAction(action);
|
||||
store.openDialog(ctx, undefined, undefined, delegate);
|
||||
store.openDialog(ctx, undefined, action.callback, delegate);
|
||||
} else if (action.actionType === 'drawer') {
|
||||
store.setCurrentAction(action);
|
||||
store.openDrawer(ctx, undefined, undefined, delegate);
|
||||
|
@ -25,6 +25,20 @@ export function withRootStore<
|
||||
})`;
|
||||
static contextType = RootStoreContext;
|
||||
static ComposedComponent = ComposedComponent as React.ComponentType<T>;
|
||||
ref: any;
|
||||
|
||||
constructor(props: OuterProps) {
|
||||
super(props);
|
||||
this.refFn = this.refFn.bind(this);
|
||||
}
|
||||
|
||||
getWrappedInstance() {
|
||||
return this.ref.control;
|
||||
}
|
||||
|
||||
refFn(ref: any) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
const rootStore: IRendererStore = this.context as any;
|
||||
@ -41,6 +55,7 @@ export function withRootStore<
|
||||
React.ComponentProps<T>
|
||||
> as any)}
|
||||
{...injectedProps}
|
||||
ref={this.refFn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export interface ListenerAction {
|
||||
description?: string; // 事件描述,actionType: broadcast
|
||||
componentId?: string; // 组件ID,用于直接执行指定组件的动作,指定多个组件时使用英文逗号分隔
|
||||
componentName?: string; // 组件Name,用于直接执行指定组件的动作,指定多个组件时使用英文逗号分隔
|
||||
args?: Record<string, any>; // 动作配置,可以配置数据映射
|
||||
args?: Record<string, any>; // 动作配置,可以配置数据映射。注意:存在schema配置的动作都不能放在args里面,避免数据域不同导致的解析错误问题
|
||||
data?: Record<string, any> | null; // 动作数据参数,可以配置数据映射
|
||||
dataMergeMode?: 'merge' | 'override'; // 参数模式,合并或者覆盖
|
||||
outputVar?: string; // 输出数据变量名
|
||||
@ -132,7 +132,7 @@ const getOmitActionProp = (type: string) => {
|
||||
omitList = ['drawer'];
|
||||
break;
|
||||
case 'confirmDialog':
|
||||
omitList = ['confirmDialog'];
|
||||
omitList = ['dialog'];
|
||||
break;
|
||||
case 'reload':
|
||||
omitList = ['resetPage'];
|
||||
|
@ -12,15 +12,13 @@ import {
|
||||
|
||||
export interface IAjaxAction extends ListenerAction {
|
||||
action: 'ajax';
|
||||
args: {
|
||||
api: Api;
|
||||
messages?: {
|
||||
success: string;
|
||||
failed: string;
|
||||
};
|
||||
options?: Record<string, any>;
|
||||
[propName: string]: any;
|
||||
api: Api;
|
||||
messages?: {
|
||||
success: string;
|
||||
failed: string;
|
||||
};
|
||||
options?: Record<string, any>;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,17 +43,25 @@ export class AjaxAction implements RendererAction {
|
||||
throw new Error('env.fetcher is required!');
|
||||
}
|
||||
if (this.fetcherType === 'download' && action.actionType === 'download') {
|
||||
// 兼容老的格式
|
||||
if ((action as any).args?.api) {
|
||||
(action as any).args.api.responseType = 'blob';
|
||||
}
|
||||
if ((action as any)?.api) {
|
||||
(action as any).api.responseType = 'blob';
|
||||
}
|
||||
}
|
||||
|
||||
const env = event.context.env;
|
||||
const silent = action?.options?.silent ?? action.args?.options?.silent;
|
||||
const messages =
|
||||
(action?.api as ApiObject)?.messages ??
|
||||
(action.args?.api as ApiObject)?.messages;
|
||||
try {
|
||||
const result = await env.fetcher(
|
||||
action.args?.api,
|
||||
action?.api ?? action.args?.api,
|
||||
action.data ?? {},
|
||||
action.args?.options ?? {}
|
||||
action?.options ?? action.args?.options ?? {}
|
||||
);
|
||||
const responseData =
|
||||
!isEmpty(result.data) || result.ok
|
||||
@ -75,18 +81,15 @@ export class AjaxAction implements RendererAction {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (!action.args?.options?.silent) {
|
||||
if (!silent) {
|
||||
if (!result.ok) {
|
||||
throw new ServerError(
|
||||
(action.args?.api as ApiObject)?.messages?.failed ??
|
||||
action.args?.messages?.failed ??
|
||||
result.msg,
|
||||
messages?.failed ?? action.args?.messages?.failed ?? result.msg,
|
||||
result
|
||||
);
|
||||
} else {
|
||||
const msg =
|
||||
(action.args?.api as ApiObject)?.messages?.success ??
|
||||
messages?.success ??
|
||||
action.args?.messages?.success ??
|
||||
result.msg ??
|
||||
result.defaultMsg;
|
||||
@ -106,7 +109,7 @@ export class AjaxAction implements RendererAction {
|
||||
|
||||
return result.data;
|
||||
} catch (e) {
|
||||
if (!action.args?.options?.silent) {
|
||||
if (!silent) {
|
||||
if (e.type === 'ServerError') {
|
||||
const result = (e as ServerError).response;
|
||||
env.notify(
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {createObject, isEmpty} from '../utils/helper';
|
||||
import {
|
||||
RendererAction,
|
||||
ListenerAction,
|
||||
@ -44,12 +45,11 @@ export class CmptAction implements RendererAction {
|
||||
* 触发组件未指定id或未指定响应组件componentId,则使用触发组件响应
|
||||
*/
|
||||
const key = action.componentId || action.componentName;
|
||||
let component =
|
||||
key && renderer.props.$schema[action.componentId ? 'id' : 'name'] !== key
|
||||
? event.context.scoped?.[
|
||||
action.componentId ? 'getComponentById' : 'getComponentByName'
|
||||
](key)
|
||||
: renderer;
|
||||
let component = key
|
||||
? event.context.scoped?.[
|
||||
action.componentId ? 'getComponentById' : 'getComponentByName'
|
||||
](key)
|
||||
: renderer;
|
||||
|
||||
const dataMergeMode = action.dataMergeMode || 'merge';
|
||||
|
||||
@ -117,7 +117,32 @@ export class CmptAction implements RendererAction {
|
||||
}
|
||||
|
||||
// 执行组件动作
|
||||
return component?.doAction?.(action, action.args);
|
||||
try {
|
||||
const result = await component?.doAction?.(action, action.args, true);
|
||||
|
||||
if (['validate', 'submit'].includes(action.actionType)) {
|
||||
event.setData(
|
||||
createObject(event.data, {
|
||||
[action.outputVar || `${action.actionType}Result`]: {
|
||||
error: '',
|
||||
payload: result?.__payload ?? component?.props?.store?.data,
|
||||
responseData: result?.__response
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
event.setData(
|
||||
createObject(event.data, {
|
||||
[action.outputVar || `${action.actionType}Result`]: {
|
||||
error: e.message,
|
||||
errors: e.name === 'ValidateError' ? e.detail : e,
|
||||
payload: component?.props?.store?.data
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,13 @@ import {
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
import {render} from '../index';
|
||||
import {createObject, filter, render} from '../index';
|
||||
import {reject} from 'lodash';
|
||||
|
||||
export interface IAlertAction extends ListenerAction {
|
||||
actionType: 'alert';
|
||||
dialog?: Schema;
|
||||
// 兼容历史,保留。为了和其他弹窗保持一致
|
||||
args: {
|
||||
msg: string;
|
||||
[propName: string]: any;
|
||||
@ -27,14 +30,17 @@ export interface IConfirmAction extends ListenerAction {
|
||||
|
||||
export interface IDialogAction extends ListenerAction {
|
||||
actionType: 'dialog';
|
||||
// 兼容历史,保留。不建议用args
|
||||
args: {
|
||||
dialog: SchemaNode;
|
||||
};
|
||||
dialog?: SchemaNode; // 兼容历史
|
||||
dialog?: SchemaNode;
|
||||
}
|
||||
|
||||
export interface IConfirmDialogAction extends ListenerAction {
|
||||
actionType: 'confirmDialog';
|
||||
dialog?: Schema;
|
||||
// 兼容历史,保留。不建议用args
|
||||
args: {
|
||||
msg: string;
|
||||
title: string;
|
||||
@ -70,7 +76,7 @@ export class DialogAction implements RendererAction {
|
||||
event,
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: action.args?.dialog || action.dialog,
|
||||
dialog: action.dialog ?? action.args?.dialog,
|
||||
reload: 'none'
|
||||
},
|
||||
action.data
|
||||
@ -121,7 +127,10 @@ export class AlertAction implements RendererAction {
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
event.context.env.alert?.(action.args?.msg, action.args?.title);
|
||||
event.context.env.alert?.(
|
||||
filter(action.dialog?.msg, event.data) ?? action.args?.msg,
|
||||
filter(action.dialog?.title, event.data) ?? action.args?.title
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,22 +143,49 @@ export class ConfirmAction implements RendererAction {
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
let content = action.args?.body
|
||||
? render(action.args.body)
|
||||
: action.args.msg;
|
||||
const type = action.dialog?.type ?? (action.args as any)?.type;
|
||||
|
||||
if (!type) {
|
||||
const confirmed = await event.context.env.confirm?.(
|
||||
filter(action.dialog?.msg, event.data) || action.args?.msg,
|
||||
filter(action.dialog?.title, event.data) || action.args?.title,
|
||||
{
|
||||
closeOnEsc:
|
||||
filter(action.dialog?.closeOnEsc, event.data) ||
|
||||
action.args?.closeOnEsc,
|
||||
size: filter(action.dialog?.size, event.data) || action.args?.size,
|
||||
confirmText:
|
||||
filter(action.dialog?.confirmText, event.data) ||
|
||||
action.args?.confirmText,
|
||||
cancelText:
|
||||
filter(action.dialog?.cancelText, event.data) ||
|
||||
action.args?.cancelText,
|
||||
confirmBtnLevel:
|
||||
filter(action.dialog?.confirmBtnLevel, event.data) ||
|
||||
action.args?.confirmBtnLevel,
|
||||
cancelBtnLevel:
|
||||
filter(action.dialog?.cancelBtnLevel, event.data) ||
|
||||
action.args?.cancelBtnLevel
|
||||
}
|
||||
);
|
||||
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
// 自定义弹窗内容
|
||||
const confirmed = await new Promise((resolve, reject) => {
|
||||
renderer.props.onAction?.(
|
||||
event,
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: action.dialog ?? action.args,
|
||||
reload: 'none',
|
||||
callback: (result: boolean) => resolve(result)
|
||||
},
|
||||
action.data
|
||||
);
|
||||
});
|
||||
|
||||
const confirmed = await event.context.env.confirm?.(
|
||||
content,
|
||||
action.args.title,
|
||||
{
|
||||
closeOnEsc: action.args.closeOnEsc,
|
||||
size: action.args.size,
|
||||
confirmText: action.args.confirmText,
|
||||
cancelText: action.args.cancelText,
|
||||
confirmBtnLevel: action.args.confirmBtnLevel,
|
||||
cancelBtnLevel: action.args.cancelBtnLevel
|
||||
}
|
||||
);
|
||||
return confirmed;
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,11 @@ import {
|
||||
|
||||
export interface IDrawerAction extends ListenerAction {
|
||||
actionType: 'drawer';
|
||||
// 兼容历史,保留。不建议用args
|
||||
args: {
|
||||
drawer: SchemaNode;
|
||||
};
|
||||
drawer?: SchemaNode; // 兼容历史
|
||||
drawer?: SchemaNode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,7 +33,7 @@ export class DrawerAction implements RendererAction {
|
||||
event,
|
||||
{
|
||||
actionType: 'drawer',
|
||||
drawer: action.args?.drawer || action.drawer,
|
||||
drawer: action.drawer ?? action.args?.drawer,
|
||||
reload: 'none'
|
||||
},
|
||||
action.data
|
||||
|
@ -49,6 +49,7 @@ import LazyComponent from '../components/LazyComponent';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
|
||||
import type {LabelAlign} from './Item';
|
||||
import {injectObjectChain} from '../utils';
|
||||
|
||||
export interface FormHorizontal {
|
||||
left?: number;
|
||||
@ -661,7 +662,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
const {data, store, dispatchEvent} = this.props;
|
||||
|
||||
if (store.fetching) {
|
||||
return;
|
||||
return value;
|
||||
}
|
||||
|
||||
// 派发init事件,参数为初始化数据
|
||||
@ -810,7 +811,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
const {interval, silentPolling, stopAutoRefreshWhen, data} = this.props;
|
||||
|
||||
clearTimeout(this.timer);
|
||||
interval &&
|
||||
value?.ok &&
|
||||
interval &&
|
||||
this.mounted &&
|
||||
(!stopAutoRefreshWhen || !evalExpression(stopAutoRefreshWhen, data)) &&
|
||||
(this.timer = setTimeout(
|
||||
@ -824,12 +826,20 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
return this.props.store.validated;
|
||||
}
|
||||
|
||||
validate(forceValidate?: boolean): Promise<boolean> {
|
||||
const {store, dispatchEvent, data} = this.props;
|
||||
validate(
|
||||
forceValidate?: boolean,
|
||||
throwErrors: boolean = false
|
||||
): Promise<boolean> {
|
||||
const {store, dispatchEvent, data, messages, translate: __} = this.props;
|
||||
|
||||
this.flush();
|
||||
return store
|
||||
.validate(this.hooks['validate'] || [], forceValidate)
|
||||
.validate(
|
||||
this.hooks['validate'] || [],
|
||||
forceValidate,
|
||||
throwErrors,
|
||||
__(messages && messages.validateFailed)
|
||||
)
|
||||
.then((result: boolean) => {
|
||||
if (result) {
|
||||
dispatchEvent('validateSucc', data);
|
||||
@ -863,7 +873,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
store.setValues(value, undefined, replace);
|
||||
}
|
||||
|
||||
submit(fn?: (values: object) => Promise<any>): Promise<any> {
|
||||
submit(
|
||||
fn?: (values: object) => Promise<any>,
|
||||
throwErrors: boolean = false
|
||||
): Promise<any> {
|
||||
const {store, messages, translate: __, dispatchEvent, data} = this.props;
|
||||
this.flush();
|
||||
const validateErrCb = () => dispatchEvent('validateError', data);
|
||||
@ -871,7 +884,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
fn,
|
||||
this.hooks['validate'] || [],
|
||||
__(messages && messages.validateFailed),
|
||||
validateErrCb
|
||||
validateErrCb,
|
||||
throwErrors
|
||||
);
|
||||
}
|
||||
|
||||
@ -1132,7 +1146,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
action.target &&
|
||||
this.reloadTarget(filterTarget(action.target, values), values);
|
||||
} else if (action.actionType === 'dialog') {
|
||||
store.openDialog(data);
|
||||
store.openDialog(data, undefined, action.callback);
|
||||
} else if (action.actionType === 'drawer') {
|
||||
store.openDrawer(data);
|
||||
} else if (isEffectiveApi(action.api || api, values)) {
|
||||
@ -1201,7 +1215,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
}
|
||||
}
|
||||
|
||||
// return values;
|
||||
return injectObjectChain(store.data, {
|
||||
__payload: values,
|
||||
__response: response
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// type为submit,但是没有配api以及target时,只派发事件
|
||||
@ -1209,7 +1226,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
}, throwErrors)
|
||||
.then(values => {
|
||||
// 有可能 onSubmit return false 了,那么后面的就不应该再执行了。
|
||||
if (values === false) {
|
||||
@ -1255,10 +1272,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
store.clear(onReset);
|
||||
} else if (action.actionType === 'validate') {
|
||||
store.setCurrentAction(action);
|
||||
this.validate(true);
|
||||
return this.validate(true, throwErrors);
|
||||
} else if (action.actionType === 'dialog') {
|
||||
store.setCurrentAction(action);
|
||||
store.openDialog(data);
|
||||
store.openDialog(data, undefined, action.callback);
|
||||
} else if (action.actionType === 'drawer') {
|
||||
store.setCurrentAction(action);
|
||||
store.openDrawer(data);
|
||||
@ -1326,9 +1343,28 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
|
||||
handleQuery(query: any) {
|
||||
if (this.props.initApi) {
|
||||
// 如果是分页动作,则看接口里面有没有用,没用则 return false
|
||||
// 让组件自己去排序
|
||||
if (
|
||||
query?.hasOwnProperty('orderBy') &&
|
||||
!isApiOutdated(
|
||||
this.props.initApi,
|
||||
this.props.initApi,
|
||||
this.props.store.data,
|
||||
createObject(this.props.store.data, query)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.receive(query);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.onQuery) {
|
||||
return this.props.onQuery(query);
|
||||
} else {
|
||||
this.props.onQuery?.(query);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1015,11 +1015,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
tooltip: labelRemark,
|
||||
useMobileUI,
|
||||
className: cx(`Form-labelRemark`),
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</span>
|
||||
@ -1048,11 +1044,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
tooltip: remark,
|
||||
className: cx(`Form-remark`),
|
||||
useMobileUI,
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
|
||||
@ -1146,11 +1138,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`),
|
||||
useMobileUI,
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</span>
|
||||
@ -1174,10 +1162,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
className: cx(`Form-remark`),
|
||||
tooltip: remark,
|
||||
useMobileUI,
|
||||
container:
|
||||
env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
|
||||
@ -1221,10 +1206,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
className: cx(`Form-remark`),
|
||||
tooltip: remark,
|
||||
useMobileUI,
|
||||
container:
|
||||
env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
|
||||
@ -1322,11 +1304,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`),
|
||||
useMobileUI,
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</span>
|
||||
@ -1349,11 +1327,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
className: cx(`Form-remark`),
|
||||
tooltip: remark,
|
||||
useMobileUI,
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
|
||||
@ -1449,11 +1423,8 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`),
|
||||
useMobileUI,
|
||||
container: props.popOverContainer
|
||||
? props.popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container:
|
||||
props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</span>
|
||||
@ -1474,10 +1445,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
icon: remark.icon || 'warning-mark',
|
||||
className: cx(`Form-remark`),
|
||||
tooltip: remark,
|
||||
container:
|
||||
env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: props.popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
|
@ -423,6 +423,7 @@ export function wrapControl<
|
||||
? 'formInited'
|
||||
: 'dataChanged'
|
||||
);
|
||||
this.checkValidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -513,32 +514,44 @@ export function wrapControl<
|
||||
}
|
||||
}
|
||||
|
||||
checkValidate() {
|
||||
if (!this.model) return; // 如果 model 为 undefined 则直接返回
|
||||
const validated = this.model.validated;
|
||||
const {formSubmited, validateOnChange} = this.props;
|
||||
|
||||
if (
|
||||
// 如果配置了 minLength 或者 maxLength 就切成及时验证
|
||||
// this.model.rules.minLength ||
|
||||
// this.model.rules.maxLength ||
|
||||
validateOnChange === true ||
|
||||
(validateOnChange !== false && (formSubmited || validated))
|
||||
) {
|
||||
this.validate();
|
||||
} else if (validateOnChange === false) {
|
||||
this.model?.reset();
|
||||
}
|
||||
}
|
||||
|
||||
async validate() {
|
||||
if (!this.model) return;
|
||||
const {formStore: form, data, formItemDispatchEvent} = this.props;
|
||||
let result;
|
||||
if (this.model) {
|
||||
if (
|
||||
this.model.unique &&
|
||||
form?.parentStore &&
|
||||
form.parentStore.storeType === ComboStore.name
|
||||
) {
|
||||
const combo = form.parentStore as IComboStore;
|
||||
const group = combo.uniques.get(
|
||||
this.model.name
|
||||
) as IUniqueGroup;
|
||||
const validPromises = group.items.map(item =>
|
||||
item.validate(data)
|
||||
);
|
||||
result = await Promise.all(validPromises);
|
||||
} else {
|
||||
const validPromises = form
|
||||
?.getItemsByName(this.model.name)
|
||||
.map(item => item.validate(data));
|
||||
if (validPromises && validPromises.length) {
|
||||
result = await Promise.all(validPromises);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.model.unique &&
|
||||
form?.parentStore &&
|
||||
form.parentStore.storeType === ComboStore.name
|
||||
) {
|
||||
const combo = form.parentStore as IComboStore;
|
||||
const group = combo.uniques.get(this.model.name) as IUniqueGroup;
|
||||
const validPromises = group.items.map(item =>
|
||||
item.validate(data)
|
||||
);
|
||||
result = await Promise.all(validPromises);
|
||||
} else {
|
||||
result = [await this.model.validate(data)];
|
||||
}
|
||||
|
||||
if (result && result.length) {
|
||||
if (result.indexOf(false) > -1) {
|
||||
formItemDispatchEvent('formItemValidateError', data);
|
||||
@ -655,20 +668,8 @@ export function wrapControl<
|
||||
return;
|
||||
}
|
||||
|
||||
const validated = this.model.validated;
|
||||
onChange?.(value, name!, submitOnChange === true);
|
||||
|
||||
if (
|
||||
// 如果配置了 minLength 或者 maxLength 就切成及时验证
|
||||
// this.model.rules.minLength ||
|
||||
// this.model.rules.maxLength ||
|
||||
validateOnChange === true ||
|
||||
(validateOnChange !== false && (formSubmited || validated))
|
||||
) {
|
||||
this.validate();
|
||||
} else if (validateOnChange === false) {
|
||||
this.model?.reset();
|
||||
}
|
||||
this.checkValidate();
|
||||
}
|
||||
|
||||
handleBlur(e: any) {
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
mapTree
|
||||
} from '../utils/helper';
|
||||
import {ServiceStore} from './service';
|
||||
import {filter, isVisible, resolveVariableAndFilter} from '../utils';
|
||||
|
||||
export const AppStore = ServiceStore.named('AppStore')
|
||||
.props({
|
||||
@ -21,7 +22,7 @@ export const AppStore = ServiceStore.named('AppStore')
|
||||
get navigations(): Array<NavigationObject> {
|
||||
if (Array.isArray(self.pages)) {
|
||||
return mapTree(self.pages, item => {
|
||||
let visible = item.visible;
|
||||
let visible = isVisible(item, self.data);
|
||||
|
||||
if (
|
||||
visible !== false &&
|
||||
@ -38,7 +39,12 @@ export const AppStore = ServiceStore.named('AppStore')
|
||||
path: item.path,
|
||||
children: item.children,
|
||||
className: item.className,
|
||||
visible
|
||||
visible,
|
||||
badge:
|
||||
typeof item.badge === 'string'
|
||||
? filter(item.badge, self.data)
|
||||
: item.badge,
|
||||
badgeClassName: filter(item.badgeClassName, self.data)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -147,9 +147,15 @@ export const ComboStore = iRendererStore
|
||||
});
|
||||
|
||||
self.forms.forEach(form =>
|
||||
form.items.forEach(
|
||||
item => item.unique && item.syncOptions(undefined, form.data)
|
||||
)
|
||||
form.items.forEach(item => {
|
||||
if (item.unique) {
|
||||
item.syncOptions(undefined, form.data);
|
||||
|
||||
if (item.errors.length) {
|
||||
item.validate(item.tmpValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {saveAs} from 'file-saver';
|
||||
import {filter} from 'amis-core';
|
||||
import {types, flow, getEnv, isAlive, Instance} from 'mobx-state-tree';
|
||||
import {IRendererStore} from './index';
|
||||
import {ServiceStore} from './service';
|
||||
@ -17,6 +16,7 @@ import pick from 'lodash/pick';
|
||||
import {resolveVariableAndFilter} from '../utils/tpl-builtin';
|
||||
import {normalizeApiResponseData} from '../utils/api';
|
||||
import {matchSorter} from 'match-sorter';
|
||||
import {filter} from '../utils/tpl';
|
||||
|
||||
class ServerError extends Error {
|
||||
type = 'ServerError';
|
||||
@ -289,14 +289,16 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
items = result.items || result.rows;
|
||||
}
|
||||
|
||||
// 如果不按照 items 格式返回,就拿第一个数组当成 items
|
||||
if (!Array.isArray(items)) {
|
||||
// 如果不按照 items 格式返回,就拿第一个数组当成 items
|
||||
for (const key of Object.keys(result)) {
|
||||
if (result.hasOwnProperty(key) && Array.isArray(result[key])) {
|
||||
items = result[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (items == null) {
|
||||
items = [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
|
@ -17,7 +17,8 @@ import {
|
||||
isEmpty,
|
||||
mapObject,
|
||||
keyToPath,
|
||||
isObject
|
||||
isObject,
|
||||
ValidateError
|
||||
} from '../utils/helper';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import flatten from 'lodash/flatten';
|
||||
@ -92,7 +93,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
/** 获取InputGroup的子元素 */
|
||||
get inputGroupItems() {
|
||||
const formItems: Record<string, IFormItemStore[]> = {};
|
||||
const children = self.children.concat();
|
||||
const children: Array<any> = this.directItems.concat();
|
||||
|
||||
while (children.length) {
|
||||
const current = children.shift();
|
||||
@ -526,37 +527,20 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
fn?: (values: object) => Promise<any>,
|
||||
hooks?: Array<() => Promise<any>>,
|
||||
failedMessage?: string,
|
||||
validateErrCb?: () => void
|
||||
validateErrCb?: () => void,
|
||||
throwErrors?: boolean
|
||||
) => Promise<any> = flow(function* submit(
|
||||
fn: any,
|
||||
hooks?: Array<() => Promise<any>>,
|
||||
failedMessage?: string,
|
||||
validateErrCb?: () => void
|
||||
validateErrCb?: () => void,
|
||||
throwErrors?: boolean
|
||||
) {
|
||||
self.submited = true;
|
||||
self.submiting = true;
|
||||
|
||||
try {
|
||||
let valid = yield validate(hooks);
|
||||
|
||||
// 如果不是valid,而且有包含不是remote的报错的表单项时,不可提交
|
||||
if (
|
||||
(!valid &&
|
||||
self.items.some(item =>
|
||||
item.errorData.some(e => e.tag !== 'remote')
|
||||
)) ||
|
||||
self.restError.length
|
||||
) {
|
||||
let msg = failedMessage ?? self.__('Form.validateFailed');
|
||||
let dispatcher: any = validateErrCb && validateErrCb();
|
||||
if (dispatcher?.then) {
|
||||
dispatcher = yield dispatcher;
|
||||
}
|
||||
if (!dispatcher?.prevented) {
|
||||
msg && toastValidateError(msg);
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
yield validate(hooks, undefined, true, failedMessage, validateErrCb);
|
||||
|
||||
if (fn) {
|
||||
const diff = difference(self.data, self.pristine);
|
||||
@ -581,10 +565,16 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
|
||||
const validate: (
|
||||
hooks?: Array<() => Promise<any>>,
|
||||
forceValidate?: boolean
|
||||
forceValidate?: boolean,
|
||||
throwErrors?: boolean,
|
||||
failedMessage?: string,
|
||||
validateErrCb?: () => void
|
||||
) => Promise<boolean> = flow(function* validate(
|
||||
hooks?: Array<() => Promise<any>>,
|
||||
forceValidate?: boolean
|
||||
forceValidate?: boolean,
|
||||
throwErrors?: boolean,
|
||||
failedMessage?: string,
|
||||
validateErrCb?: () => void
|
||||
) {
|
||||
self.validated = true;
|
||||
const items = self.directItems.concat();
|
||||
@ -632,6 +622,32 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.valid) {
|
||||
// 如果不是valid,而且有包含不是remote的报错的表单项时,不可提交
|
||||
if (
|
||||
self.items.some(item =>
|
||||
item.errorData.some(e => e.tag !== 'remote')
|
||||
) ||
|
||||
self.restError.length
|
||||
) {
|
||||
let msg = failedMessage ?? self.__('Form.validateFailed');
|
||||
let dispatcher: any = validateErrCb && validateErrCb();
|
||||
if (dispatcher?.then) {
|
||||
dispatcher = yield dispatcher;
|
||||
}
|
||||
if (!dispatcher?.prevented) {
|
||||
msg && toastValidateError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (throwErrors) {
|
||||
throw new ValidateError(
|
||||
failedMessage || self.__('Form.validateFailed'),
|
||||
self.errors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return self.valid;
|
||||
});
|
||||
|
||||
|
@ -100,6 +100,7 @@ export const ListStore = iRendererStore
|
||||
draggable: false,
|
||||
dragging: false,
|
||||
multiple: true,
|
||||
strictMode: false,
|
||||
selectable: false,
|
||||
itemCheckableOn: '',
|
||||
itemDraggableOn: '',
|
||||
@ -167,6 +168,7 @@ export const ListStore = iRendererStore
|
||||
config.selectable === void 0 || (self.selectable = config.selectable);
|
||||
config.draggable === void 0 || (self.draggable = config.draggable);
|
||||
config.multiple === void 0 || (self.multiple = config.multiple);
|
||||
config.strictMode === void 0 || (self.strictMode = config.strictMode);
|
||||
config.hideCheckToggler === void 0 ||
|
||||
(self.hideCheckToggler = config.hideCheckToggler);
|
||||
|
||||
@ -212,11 +214,13 @@ export const ListStore = iRendererStore
|
||||
if (~selected.indexOf(item.pristine)) {
|
||||
self.selectedItems.push(item);
|
||||
} else if (
|
||||
find(
|
||||
selected,
|
||||
a =>
|
||||
a[valueField || 'value'] == item.pristine[valueField || 'value']
|
||||
)
|
||||
find(selected, a => {
|
||||
const selectValue = a[valueField || 'value'];
|
||||
const itemValue = item.pristine[valueField || 'value'];
|
||||
return self.strictMode
|
||||
? selectValue === itemValue
|
||||
: selectValue == itemValue;
|
||||
})
|
||||
) {
|
||||
self.selectedItems.push(item);
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ import {
|
||||
difference,
|
||||
immutableExtends,
|
||||
extendObject,
|
||||
hasVisibleExpression
|
||||
hasVisibleExpression,
|
||||
sortArray
|
||||
} from '../utils/helper';
|
||||
import {evalExpression} from '../utils/tpl';
|
||||
import {IFormStore} from './form';
|
||||
@ -1353,6 +1354,19 @@ export const TableStore = iRendererStore
|
||||
self.orderDir = key ? direction : '';
|
||||
}
|
||||
|
||||
function changeOrder(key: string, direction: 'asc' | 'desc' | '') {
|
||||
setOrderByInfo(key, direction);
|
||||
const dir = /desc/i.test(self.orderDir) ? -1 : 1;
|
||||
self.rows.replace(
|
||||
sortArray(
|
||||
self.rows.concat(),
|
||||
self.orderBy,
|
||||
dir,
|
||||
(item, field) => item.data[field]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
self.rows.forEach(item => item.reset());
|
||||
let rows = self.rows.concat();
|
||||
@ -1490,6 +1504,7 @@ export const TableStore = iRendererStore
|
||||
collapseAllAtDepth,
|
||||
clear,
|
||||
setOrderByInfo,
|
||||
changeOrder,
|
||||
reset,
|
||||
toggleDragging,
|
||||
stopDragging,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {attachmentAdpator} from 'amis-core';
|
||||
import omit from 'lodash/omit';
|
||||
import {Api, ApiObject, EventTrack, fetcherResult, Payload} from '../types';
|
||||
import {fetcherConfig} from '../factory';
|
||||
@ -696,11 +695,20 @@ export function isApiOutdated(
|
||||
}
|
||||
|
||||
nextApi = normalizeApi(nextApi);
|
||||
prevApi = (prevApi ? normalizeApi(prevApi) : prevApi) as ApiObject;
|
||||
|
||||
if (nextApi.autoRefresh === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// api 本身有变化
|
||||
if ((prevApi && prevApi.url !== nextApi.url) || !prevApi) {
|
||||
return !!(
|
||||
isValidApi(nextApi.url) &&
|
||||
(!nextApi.sendOn || evalExpression(nextApi.sendOn, nextData))
|
||||
);
|
||||
}
|
||||
|
||||
const trackExpression = nextApi.trackExpression ?? nextApi.url;
|
||||
if (typeof trackExpression !== 'string' || !~trackExpression.indexOf('$')) {
|
||||
return false;
|
||||
@ -708,24 +716,18 @@ export function isApiOutdated(
|
||||
|
||||
let isModified = false;
|
||||
|
||||
if (prevApi) {
|
||||
prevApi = normalizeApi(prevApi);
|
||||
|
||||
if (nextApi.trackExpression || prevApi.trackExpression) {
|
||||
isModified =
|
||||
tokenize(prevApi.trackExpression || '', prevData) !==
|
||||
tokenize(nextApi.trackExpression || '', nextData);
|
||||
} else {
|
||||
prevApi = buildApi(prevApi as Api, prevData as object, {
|
||||
ignoreData: true
|
||||
});
|
||||
nextApi = buildApi(nextApi as Api, nextData as object, {
|
||||
ignoreData: true
|
||||
});
|
||||
isModified = prevApi.url !== nextApi.url;
|
||||
}
|
||||
if (nextApi.trackExpression || prevApi.trackExpression) {
|
||||
isModified =
|
||||
tokenize(prevApi.trackExpression || '', prevData) !==
|
||||
tokenize(nextApi.trackExpression || '', nextData);
|
||||
} else {
|
||||
isModified = true;
|
||||
prevApi = buildApi(prevApi as Api, prevData as object, {
|
||||
ignoreData: true
|
||||
});
|
||||
nextApi = buildApi(nextApi as Api, nextData as object, {
|
||||
ignoreData: true
|
||||
});
|
||||
isModified = prevApi.url !== nextApi.url;
|
||||
}
|
||||
|
||||
return !!(
|
||||
@ -736,10 +738,25 @@ export function isApiOutdated(
|
||||
}
|
||||
|
||||
export function isValidApi(api: string) {
|
||||
return (
|
||||
api &&
|
||||
/^(?:(https?|wss?|taf):\/\/[^\/]+)?(\/?[^\s\/\?]*){1,}(\?.*)?$/.test(api)
|
||||
);
|
||||
if (!api || typeof api !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const idx = api.indexOf('://');
|
||||
|
||||
// 不允许直接相对路径写 api
|
||||
// 不允许 :// 结尾
|
||||
if ((!~idx && api[0] !== '/') || (~idx && idx + 3 === api.length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 不补一个协议,URL 判断为 false
|
||||
api = (~idx ? '' : 'schema://domain') + api;
|
||||
new URL(api);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isEffectiveApi(
|
||||
|
67
packages/amis-core/src/utils/arraySlice.ts
Normal file
67
packages/amis-core/src/utils/arraySlice.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 使用字符串语法来切片数组,参考了 python 中的语法
|
||||
* 支持的语法有:
|
||||
* * 取单个值 1,2,3
|
||||
* * 取范围 3:10
|
||||
* * 组合 1,2,3:10
|
||||
* * 范围是负数 :-1,代表除了最后一个元素的所有元素
|
||||
*/
|
||||
import {toJS, isObservableArray} from 'mobx';
|
||||
|
||||
export function arraySlice(array: any[], slice: string) {
|
||||
if (typeof slice !== 'string') {
|
||||
return array;
|
||||
}
|
||||
if (isObservableArray(array)) {
|
||||
array = toJS(array);
|
||||
}
|
||||
|
||||
slice = slice.trim();
|
||||
if (!slice || !Array.isArray(array)) {
|
||||
return array;
|
||||
}
|
||||
const parts = slice.split(',');
|
||||
const ret: any[] = [];
|
||||
const arrayLength = array.length;
|
||||
if (!arrayLength) {
|
||||
return array;
|
||||
}
|
||||
for (const part of parts) {
|
||||
// 普通的场景
|
||||
if (part.indexOf(':') === -1) {
|
||||
const index = parseInt(part, 10);
|
||||
if (!isNaN(index) && index < arrayLength) {
|
||||
ret.push(array[index]);
|
||||
}
|
||||
} else {
|
||||
const [start, end] = part.split(':');
|
||||
let startIndex = parseInt(start || '0', 10);
|
||||
if (isNaN(startIndex) || startIndex < 0) {
|
||||
startIndex = 0;
|
||||
}
|
||||
// 大于就没意义了
|
||||
if (startIndex >= arrayLength) {
|
||||
continue;
|
||||
}
|
||||
let endIndex = parseInt(end, 10);
|
||||
if (isNaN(endIndex)) {
|
||||
endIndex = arrayLength;
|
||||
}
|
||||
// 负数就从后面开始取
|
||||
if (endIndex < 0) {
|
||||
endIndex = arrayLength + endIndex;
|
||||
}
|
||||
// 小于没有意义
|
||||
if (endIndex < startIndex) {
|
||||
continue;
|
||||
}
|
||||
if (endIndex > arrayLength) {
|
||||
endIndex = arrayLength;
|
||||
}
|
||||
|
||||
ret.push(...array.slice(startIndex, endIndex));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
@ -99,19 +99,21 @@ export function dataMapping(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Object.keys(to).forEach(key => {
|
||||
const value = to[key];
|
||||
let keys: Array<string>;
|
||||
const objectKeys = Object.keys(to);
|
||||
// 如果存在 '&' 作为 key,则特殊处理
|
||||
// 就是无论这个 key 的位置在什么地方始终都是当放在最前面
|
||||
const idx = objectKeys.indexOf('&');
|
||||
if (~idx) {
|
||||
const value = to['&'];
|
||||
objectKeys.splice(idx, 1);
|
||||
|
||||
if (typeof ignoreFunction === 'function' && ignoreFunction(key, value)) {
|
||||
// 如果被ignore,不做数据映射处理。
|
||||
setVariable(ret, key, value, convertKeyToPath);
|
||||
} else if (key === '&' && value === '$$') {
|
||||
if (value === '$$') {
|
||||
ret = {
|
||||
...ret,
|
||||
...from
|
||||
};
|
||||
} else if (key === '&') {
|
||||
} else {
|
||||
let keys: Array<string>;
|
||||
const v =
|
||||
isPlainObject(value) &&
|
||||
(keys = Object.keys(value)) &&
|
||||
@ -142,9 +144,22 @@ export function dataMapping(
|
||||
...v
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objectKeys.forEach(key => {
|
||||
const value = to[key];
|
||||
|
||||
if (typeof ignoreFunction === 'function' && ignoreFunction(key, value)) {
|
||||
// 如果被ignore,不做数据映射处理。
|
||||
setVariable(ret, key, value, convertKeyToPath);
|
||||
} else if (value === '$$') {
|
||||
setVariable(ret, key, from, convertKeyToPath);
|
||||
} else if (value && value[0] === '$') {
|
||||
} else if (
|
||||
typeof value === 'string' &&
|
||||
value.length > 0 &&
|
||||
value[0] === '$'
|
||||
) {
|
||||
const v = resolveMapping(value, from, undefined, ignoreIfNotMatch);
|
||||
setVariable(ret, key, v, convertKeyToPath);
|
||||
|
||||
|
@ -279,6 +279,10 @@ export function getStyleNumber(element: HTMLElement, styleName: string) {
|
||||
|
||||
/** 根据关键字高亮显示文本内容 */
|
||||
export function renderTextByKeyword(rendererText: string, curKeyword: string) {
|
||||
if (!rendererText || typeof rendererText !== 'string') {
|
||||
return rendererText;
|
||||
}
|
||||
|
||||
if (curKeyword && ~rendererText.indexOf(curKeyword)) {
|
||||
const keywordStartIndex = rendererText.indexOf(curKeyword);
|
||||
const keywordEndIndex = keywordStartIndex + curKeyword.length;
|
||||
|
@ -1273,12 +1273,13 @@ export const bulkBindFunctions = function <
|
||||
export function sortArray<T extends any>(
|
||||
items: Array<T>,
|
||||
field: string,
|
||||
dir: -1 | 1
|
||||
dir: -1 | 1,
|
||||
fieldGetter?: (item: T, field: string) => any
|
||||
): Array<T> {
|
||||
return items.sort((a: any, b: any) => {
|
||||
let ret: number;
|
||||
const a1 = a[field];
|
||||
const b1 = b[field];
|
||||
const a1 = fieldGetter ? fieldGetter(a, field) : a[field];
|
||||
const b1 = fieldGetter ? fieldGetter(b, field) : b[field];
|
||||
|
||||
if (typeof a1 === 'number' && typeof b1 === 'number') {
|
||||
ret = a1 < b1 ? -1 : a1 === b1 ? 0 : 1;
|
||||
@ -1310,11 +1311,12 @@ export function qsstringify(
|
||||
},
|
||||
keepEmptyArray?: boolean
|
||||
) {
|
||||
// qs会保留空字符串。fix: Combo模式的空数组,无法清空。改为存为空字符串;只转换一层
|
||||
keepEmptyArray &&
|
||||
Object.keys(data).forEach((key: any) => {
|
||||
Array.isArray(data[key]) && !data[key].length && (data[key] = '');
|
||||
});
|
||||
// qs会保留空字符串。fix: Combo模式的空数组,无法清空。改为存为空字符串;
|
||||
if (keepEmptyArray) {
|
||||
data = JSONValueMap(data, value =>
|
||||
Array.isArray(value) && !value.length ? '' : value
|
||||
);
|
||||
}
|
||||
return qs.stringify(data, options);
|
||||
}
|
||||
|
||||
@ -1406,7 +1408,7 @@ export function chainEvents(props: any, schema: any) {
|
||||
ret[key] = chainFunctions(schema[key], props[key]);
|
||||
}
|
||||
} else {
|
||||
ret[key] = props[key];
|
||||
ret[key] = props[key] ?? schema[key];
|
||||
}
|
||||
});
|
||||
|
||||
@ -1487,6 +1489,21 @@ export function loadStyle(href: string) {
|
||||
|
||||
export class SkipOperation extends Error {}
|
||||
|
||||
export class ValidateError extends Error {
|
||||
name: 'ValidateError';
|
||||
detail: {[propName: string]: Array<string> | string};
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
error: {[propName: string]: Array<string> | string}
|
||||
) {
|
||||
super();
|
||||
this.name = 'ValidateError';
|
||||
this.message = message;
|
||||
this.detail = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否有循环引用,来自 https://stackoverflow.com/a/34909127
|
||||
* @param obj
|
||||
@ -1586,8 +1603,13 @@ export function getPropValue<
|
||||
name?: string;
|
||||
data?: any;
|
||||
defaultValue?: any;
|
||||
canAccessSuperData?: boolean;
|
||||
}
|
||||
>(props: T, getter?: (props: T) => any, canAccessSuper?: boolean) {
|
||||
>(
|
||||
props: T,
|
||||
getter?: (props: T) => any,
|
||||
canAccessSuper = props.canAccessSuperData
|
||||
) {
|
||||
const {name, value, data, defaultValue} = props;
|
||||
return (
|
||||
value ??
|
||||
@ -1741,6 +1763,73 @@ export function JSONTraverse(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 每层都会执行,返回新的对象,新对象不会递归下去
|
||||
* @param json
|
||||
* @param mapper
|
||||
* @returns
|
||||
*/
|
||||
export function JSONValueMap(
|
||||
json: any,
|
||||
mapper: (
|
||||
value: any,
|
||||
key: string | number,
|
||||
host: Object,
|
||||
stack: Array<Object>
|
||||
) => any,
|
||||
stack: Array<Object> = []
|
||||
) {
|
||||
if (!isPlainObject(json) && !Array.isArray(json)) {
|
||||
return json;
|
||||
}
|
||||
|
||||
const iterator = (
|
||||
origin: any,
|
||||
key: number | string,
|
||||
host: any,
|
||||
stack: Array<any> = []
|
||||
) => {
|
||||
let maped: any = mapper(origin, key, host, stack);
|
||||
|
||||
if (maped === origin && (isPlainObject(origin) || Array.isArray(origin))) {
|
||||
return JSONValueMap(origin, mapper, 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;
|
||||
});
|
||||
return flag ? mapped : json;
|
||||
}
|
||||
|
||||
let flag = 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;
|
||||
toUpdate[key] = result;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return flag
|
||||
? {
|
||||
...json,
|
||||
...toUpdate
|
||||
}
|
||||
: json;
|
||||
}
|
||||
|
||||
export function convertArrayValueToMoment(
|
||||
value: number[],
|
||||
types: string[],
|
||||
|
@ -54,6 +54,8 @@ export * from './toNumber';
|
||||
export * from './decodeEntity';
|
||||
export * from './style-helper';
|
||||
export * from './resolveCondition';
|
||||
export * from './arraySlice';
|
||||
export * from './math';
|
||||
|
||||
import animation from './Animation';
|
||||
|
||||
|
42
packages/amis-core/src/utils/math.ts
Normal file
42
packages/amis-core/src/utils/math.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export function safeAdd(arg1: number, arg2: number) {
|
||||
let digits1, digits2, maxDigits;
|
||||
try {
|
||||
digits1 = arg1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
digits1 = 0;
|
||||
}
|
||||
try {
|
||||
digits2 = arg2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
digits2 = 0;
|
||||
}
|
||||
maxDigits = Math.pow(10, Math.max(digits1, digits2));
|
||||
return (arg1 * maxDigits + arg2 * maxDigits) / maxDigits;
|
||||
}
|
||||
|
||||
//减
|
||||
export function safeSub(arg1: number, arg2: number) {
|
||||
let digits1, digits2, maxDigits;
|
||||
try {
|
||||
digits1 = arg1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
digits1 = 0;
|
||||
}
|
||||
try {
|
||||
digits2 = arg2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
digits2 = 0;
|
||||
}
|
||||
maxDigits = Math.pow(10, Math.max(digits1, digits2));
|
||||
return (arg1 * maxDigits - arg2 * maxDigits) / maxDigits;
|
||||
}
|
||||
|
||||
export function numberFormatter(num: number | string, precision: number = 0) {
|
||||
const ZERO = 0;
|
||||
const number = +num;
|
||||
if (typeof number === 'number' && !isNaN(number)) {
|
||||
const regexp = precision ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(\d{3})+$)/g;
|
||||
return number.toFixed(precision).replace(regexp, '$1,');
|
||||
}
|
||||
return ZERO.toFixed(precision);
|
||||
}
|
@ -9,13 +9,21 @@ export const normalizeLink = (to: string, location = window.location) => {
|
||||
|
||||
const idx = to.indexOf('?');
|
||||
const idx2 = to.indexOf('#');
|
||||
let pathname = ~idx
|
||||
? to.substring(0, idx)
|
||||
: ~idx2
|
||||
? to.substring(0, idx2)
|
||||
: to;
|
||||
let search = ~idx ? to.substring(idx, ~idx2 ? idx2 : undefined) : '';
|
||||
let hash = ~idx2 ? to.substring(idx2) : location.hash;
|
||||
let pathname = to;
|
||||
let search = '';
|
||||
let hash = location.hash;
|
||||
// host?a=a#b 的情况
|
||||
if (idx < idx2) {
|
||||
pathname = ~idx ? to.substring(0, idx) : ~idx2 ? to.substring(0, idx2) : to;
|
||||
hash = ~idx2 ? to.substring(idx2) : location.hash;
|
||||
search = ~idx ? to.substring(idx, ~idx2 ? idx2 : undefined) : '';
|
||||
}
|
||||
// host#b?a=a 的情况
|
||||
else if (idx > idx2) {
|
||||
pathname = ~idx2 ? to.substring(0, idx2) : ~idx ? to.substring(0, idx) : to;
|
||||
hash = ~idx2 ? to.substring(idx2, ~idx ? idx : undefined) : location.hash;
|
||||
search = ~idx ? to.substring(idx) : '';
|
||||
}
|
||||
|
||||
if (!pathname) {
|
||||
pathname = location.pathname;
|
||||
@ -33,5 +41,6 @@ export const normalizeLink = (to: string, location = window.location) => {
|
||||
pathname = paths.concat(pathname).join('/');
|
||||
}
|
||||
|
||||
return pathname + search + hash;
|
||||
const rest = idx < idx2 ? search + hash : hash + search;
|
||||
return pathname + rest;
|
||||
};
|
||||
|
@ -61,6 +61,18 @@ export function createObjectFromChain(chain: Array<object>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 向最近一层插入新链
|
||||
* @param obj
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
export function injectObjectChain(obj: any, value: any) {
|
||||
const chain = extractObjectChain(obj);
|
||||
chain.splice(chain.length - 1, 0, value);
|
||||
return createObjectFromChain(chain);
|
||||
}
|
||||
|
||||
export function cloneObject(target: any, persistOwnProps: boolean = true) {
|
||||
const obj =
|
||||
target && target.__super
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {ListenerAction, ListenerContext, runActions} from '../actions/Action';
|
||||
import {RendererProps} from '../factory';
|
||||
import {IScopedContext} from '../Scoped';
|
||||
import {createObject} from './object';
|
||||
import {createObject, extendObject} from './object';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
export interface debounceConfig {
|
||||
@ -65,7 +65,7 @@ export function createRendererEvent<T extends RendererEventContext>(
|
||||
context: T
|
||||
): RendererEvent<T> {
|
||||
const rendererEvent = {
|
||||
context,
|
||||
context: extendObject({pristineData: context.data}, context),
|
||||
type,
|
||||
prevented: false,
|
||||
stoped: false,
|
||||
@ -81,6 +81,10 @@ export function createRendererEvent<T extends RendererEventContext>(
|
||||
return rendererEvent.context.data;
|
||||
},
|
||||
|
||||
get pristineData() {
|
||||
return rendererEvent.context.pristineData;
|
||||
},
|
||||
|
||||
setData(data: any) {
|
||||
rendererEvent.context.data = data;
|
||||
}
|
||||
@ -128,10 +132,13 @@ export const bindEvent = (renderer: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
return (eventName?: string) => {
|
||||
// eventName用来避免过滤广播事件
|
||||
rendererEventListeners = rendererEventListeners.filter(
|
||||
(item: RendererEventListener) => item.renderer !== renderer
|
||||
(item: RendererEventListener) =>
|
||||
item.renderer !== renderer && eventName !== undefined
|
||||
? item.type !== eventName
|
||||
: true
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -147,7 +154,7 @@ export async function dispatchEvent(
|
||||
data: any,
|
||||
broadcast?: RendererEvent<any>
|
||||
): Promise<RendererEvent<any> | void> {
|
||||
let unbindEvent: (() => void) | null | undefined = null;
|
||||
let unbindEvent: ((eventName?: string) => void) | null | undefined = null;
|
||||
const eventName = typeof e === 'string' ? e : e.type;
|
||||
|
||||
renderer?.props?.env?.beforeDispatchEvent?.(
|
||||
@ -183,6 +190,7 @@ export async function dispatchEvent(
|
||||
data,
|
||||
scoped
|
||||
});
|
||||
|
||||
// 过滤&排序
|
||||
const listeners = rendererEventListeners
|
||||
.filter(
|
||||
@ -198,7 +206,7 @@ export async function dispatchEvent(
|
||||
const checkExecuted = () => {
|
||||
executedCount++;
|
||||
if (executedCount === listeners.length) {
|
||||
unbindEvent?.();
|
||||
unbindEvent?.(eventName);
|
||||
}
|
||||
};
|
||||
for (let listener of listeners) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor-core",
|
||||
"version": "5.4.7",
|
||||
"version": "5.5.0",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
45
packages/amis-editor-core/src/component/AsyncLayer.tsx
Normal file
45
packages/amis-editor-core/src/component/AsyncLayer.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file AsyncLayer.tsx
|
||||
* @desc 异步加载层
|
||||
*/
|
||||
import React from 'react';
|
||||
import {Spinner} from 'amis';
|
||||
|
||||
export interface asyncLayerOptions {
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const makeAsyncLayer = (
|
||||
schemaBuilderFn: () => Promise<any>,
|
||||
options?: asyncLayerOptions
|
||||
) => {
|
||||
const {fallback} = options || {};
|
||||
const LazyComponent = React.lazy(async () => {
|
||||
const schemaFormRender = await schemaBuilderFn();
|
||||
|
||||
return {
|
||||
default: (...props: any[]) => <>{schemaFormRender(...props)}</>
|
||||
};
|
||||
});
|
||||
|
||||
return (props: any) => (
|
||||
<React.Suspense
|
||||
fallback={
|
||||
fallback && React.isValidElement(fallback) ? (
|
||||
fallback
|
||||
) : (
|
||||
<Spinner
|
||||
show
|
||||
overlay
|
||||
size="sm"
|
||||
tip="配置面板加载中"
|
||||
tipPlacement="bottom"
|
||||
className="flex"
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<LazyComponent {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
};
|
@ -1291,6 +1291,8 @@ export class EditorManager {
|
||||
}
|
||||
|
||||
store.changeValue(value, diff);
|
||||
|
||||
this.trigger('after-update', context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,10 @@
|
||||
/**
|
||||
* @file 定义插件的 interface,以及提供一个 BasePlugin 基类,把一些通用的方法放在这。
|
||||
*/
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
import {RegionWrapperProps} from './component/RegionWrapper';
|
||||
import {makeAsyncLayer} from './component/AsyncLayer';
|
||||
import {EditorManager} from './manager';
|
||||
import {EditorStoreType} from './store/editor';
|
||||
import {EditorNodeType} from './store/node';
|
||||
@ -13,7 +16,7 @@ import find from 'lodash/find';
|
||||
import type {RendererConfig} from 'amis-core';
|
||||
import type {MenuDivider, MenuItem} from 'amis-ui/lib/components/ContextMenu';
|
||||
import type {BaseSchema, SchemaCollection} from 'amis';
|
||||
import {DSFieldGroup} from './builder/DSBuilder';
|
||||
import type {asyncLayerOptions} from './component/AsyncLayer';
|
||||
|
||||
/**
|
||||
* 区域的定义,容器渲染器都需要定义区域信息。
|
||||
@ -800,6 +803,11 @@ export interface PluginInterface
|
||||
*/
|
||||
panelJustify?: boolean;
|
||||
|
||||
/**
|
||||
* 配置面板内容区是否开启异步加载
|
||||
*/
|
||||
async?: {enable: boolean} & asyncLayerOptions;
|
||||
|
||||
/**
|
||||
* 有数据域的容器,可以为子组件提供读取的字段绑定页面
|
||||
*/
|
||||
@ -1045,17 +1053,34 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
icon: plugin.panelIcon || plugin.icon || 'fa fa-cog',
|
||||
pluginIcon: plugin.pluginIcon,
|
||||
title: plugin.panelTitle || '设置',
|
||||
render: this.manager.makeSchemaFormRender({
|
||||
definitions: plugin.panelDefinitions,
|
||||
submitOnChange: plugin.panelSubmitOnChange,
|
||||
api: plugin.panelApi,
|
||||
body: body,
|
||||
controls: plugin.panelControlsCreator
|
||||
? plugin.panelControlsCreator(context)
|
||||
: plugin.panelControls!,
|
||||
justify: plugin.panelJustify,
|
||||
panelById: store.activeId
|
||||
})
|
||||
render:
|
||||
typeof plugin.async === 'object' && plugin.async?.enable === true
|
||||
? makeAsyncLayer(async () => {
|
||||
const panelBody = await body;
|
||||
|
||||
return this.manager.makeSchemaFormRender({
|
||||
definitions: plugin.panelDefinitions,
|
||||
submitOnChange: plugin.panelSubmitOnChange,
|
||||
api: plugin.panelApi,
|
||||
body: panelBody,
|
||||
controls: plugin.panelControlsCreator
|
||||
? plugin.panelControlsCreator(context)
|
||||
: plugin.panelControls!,
|
||||
justify: plugin.panelJustify,
|
||||
panelById: store.activeId
|
||||
});
|
||||
}, omit(plugin.async, 'enable'))
|
||||
: this.manager.makeSchemaFormRender({
|
||||
definitions: plugin.panelDefinitions,
|
||||
submitOnChange: plugin.panelSubmitOnChange,
|
||||
api: plugin.panelApi,
|
||||
body: body,
|
||||
controls: plugin.panelControlsCreator
|
||||
? plugin.panelControlsCreator(context)
|
||||
: plugin.panelControls!,
|
||||
justify: plugin.panelJustify,
|
||||
panelById: store.activeId
|
||||
})
|
||||
});
|
||||
} else if (
|
||||
context.info.plugin === this &&
|
||||
|
@ -1214,7 +1214,7 @@ export const updateComponentContext = (variables: any[]) => {
|
||||
...child,
|
||||
label:
|
||||
index === 0
|
||||
? `当前数据域${child.label ? '(' + child.label + ')' : ''}`
|
||||
? `当前层${child.label ? '(' + child.label + ')' : ''}`
|
||||
: `上${index}层${child.label ? '(' + child.label + ')' : ''}`
|
||||
}))
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor",
|
||||
"version": "5.4.7",
|
||||
"version": "5.5.0",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
@ -117,7 +117,7 @@ import './plugin/Divider'; // 分隔线
|
||||
import './plugin/CodeView'; // 代码高亮
|
||||
import './plugin/Markdown';
|
||||
import './plugin/Collapse'; // 折叠器
|
||||
// import './plugin/OfficeViewer'; // 文档预览
|
||||
import './plugin/OfficeViewer'; // 文档预览
|
||||
import './plugin/Log'; // 日志
|
||||
|
||||
// 其他
|
||||
|
@ -1,6 +1,11 @@
|
||||
import {getI18nEnabled, registerEditorPlugin} from 'amis-editor-core';
|
||||
import {
|
||||
RendererPluginEvent,
|
||||
getI18nEnabled,
|
||||
registerEditorPlugin
|
||||
} from 'amis-editor-core';
|
||||
import {BasePlugin, RegionConfig, BaseEventContext} from 'amis-editor-core';
|
||||
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
|
||||
import {getEventControlConfig} from '../renderer/event-control/helper';
|
||||
|
||||
export class CollapsePlugin extends BasePlugin {
|
||||
static id = 'CollapsePlugin';
|
||||
@ -35,6 +40,31 @@ export class CollapsePlugin extends BasePlugin {
|
||||
|
||||
panelJustify = true;
|
||||
|
||||
events: RendererPluginEvent[] = [
|
||||
{
|
||||
eventName: 'change',
|
||||
eventLabel: '折叠状态改变',
|
||||
description: '折叠器折叠状态改变时触发',
|
||||
dataSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
title: '数据',
|
||||
properties: {
|
||||
collapsed: {
|
||||
type: 'boolean',
|
||||
title: '折叠器状态'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
const i18nEnabled = getI18nEnabled();
|
||||
return getSchemaTpl('tabs', [
|
||||
@ -121,6 +151,16 @@ export class CollapsePlugin extends BasePlugin {
|
||||
]
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
title: '事件',
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getSchemaTpl('eventControl', {
|
||||
name: 'onEvent',
|
||||
...getEventControlConfig(this.manager, context)
|
||||
})
|
||||
]
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
@ -1,9 +1,14 @@
|
||||
import {getI18nEnabled, registerEditorPlugin} from 'amis-editor-core';
|
||||
import {
|
||||
RendererPluginEvent,
|
||||
getI18nEnabled,
|
||||
registerEditorPlugin
|
||||
} from 'amis-editor-core';
|
||||
import {BasePlugin, RegionConfig, BaseEventContext} from 'amis-editor-core';
|
||||
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
|
||||
|
||||
import {tipedLabel} from 'amis-editor-core';
|
||||
import {isObject} from 'amis-editor-core';
|
||||
import {getEventControlConfig} from '../renderer/event-control/helper';
|
||||
|
||||
export class CollapseGroupPlugin extends BasePlugin {
|
||||
static id = 'CollapseGroupPlugin';
|
||||
@ -56,6 +61,39 @@ export class CollapseGroupPlugin extends BasePlugin {
|
||||
...this.scaffold
|
||||
};
|
||||
|
||||
events: RendererPluginEvent[] = [
|
||||
{
|
||||
eventName: 'change',
|
||||
eventLabel: '折叠状态改变',
|
||||
description: '折叠面板折叠状态改变时触发',
|
||||
dataSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
title: '数据',
|
||||
type: 'object',
|
||||
properties: {
|
||||
activeKeys: {
|
||||
type: 'array',
|
||||
title: '当前展开的索引列表'
|
||||
},
|
||||
collapseId: {
|
||||
type: 'string',
|
||||
title: '折叠器索引'
|
||||
},
|
||||
collapsed: {
|
||||
type: 'boolean',
|
||||
title: '折叠器状态'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
activeKeyData: any = [];
|
||||
panelTitle = '折叠面板';
|
||||
|
||||
@ -235,6 +273,16 @@ export class CollapseGroupPlugin extends BasePlugin {
|
||||
isFormItem: false
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
title: '事件',
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getSchemaTpl('eventControl', {
|
||||
name: 'onEvent',
|
||||
...getEventControlConfig(this.manager, context)
|
||||
})
|
||||
]
|
||||
}
|
||||
])
|
||||
];
|
||||
|
@ -966,7 +966,8 @@ export class FormPlugin extends BasePlugin {
|
||||
? await current.info.plugin.buildDataSchemas(current, region)
|
||||
: {
|
||||
type: 'string',
|
||||
title: schema.label || schema.name,
|
||||
title:
|
||||
typeof schema.label === 'string' ? schema.label : schema.name,
|
||||
originalValue: schema.value // 记录原始值,循环引用检测需要
|
||||
};
|
||||
}
|
||||
|
@ -185,6 +185,7 @@ export class PickerControlPlugin extends BasePlugin {
|
||||
]
|
||||
},
|
||||
|
||||
getSchemaTpl('strictMode'),
|
||||
getSchemaTpl('multiple'),
|
||||
getSchemaTpl('joinValues'),
|
||||
getSchemaTpl('delimiter'),
|
||||
|
@ -13,7 +13,7 @@ export class OfficeViewerPlugin extends BasePlugin {
|
||||
name = '文档预览';
|
||||
isBaseComponent = true;
|
||||
description = 'Office 文档预览';
|
||||
docLink = '/amis/zh-CN/components/OfficeViewer';
|
||||
docLink = '/amis/zh-CN/components/office-viewer';
|
||||
tags = ['展示'];
|
||||
icon = 'fa fa-file-word';
|
||||
pluginIcon = 'officeViewer-plugin';
|
||||
@ -176,7 +176,9 @@ export class OfficeViewerPlugin extends BasePlugin {
|
||||
},
|
||||
{
|
||||
title: '外观',
|
||||
className: 'p-none'
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
getSchemaTpl('style:classNames', {isFormItem: false})
|
||||
])
|
||||
}
|
||||
])
|
||||
];
|
||||
|
@ -111,56 +111,63 @@ export default class APIAdaptorControl extends React.Component<
|
||||
const lastParams =
|
||||
typeof mergeParams === 'function' ? mergeParams(params) : params;
|
||||
|
||||
return render('api-adaptor-control-editor', [
|
||||
{
|
||||
type: 'container',
|
||||
className: 'ae-AdaptorControl-func-header',
|
||||
body: [
|
||||
'<span class="mtk6">function </span>',
|
||||
'<span class="mtk1 bracket-highlighting-0">(</span>',
|
||||
...lastParams
|
||||
.map(({label, tip}, index) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
level: 'link',
|
||||
label,
|
||||
className: 'ae-AdaptorControl-func-arg',
|
||||
...(tip ? {tooltip: this.genTooltipProps(tip)} : {})
|
||||
},
|
||||
...(index === lastParams.length - 1
|
||||
? []
|
||||
: ['<span class="mtk1">, </span>'])
|
||||
];
|
||||
})
|
||||
.flat(),
|
||||
'<span class="mtk1 bracket-highlighting-0">) {</span>'
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
mode: 'normal',
|
||||
name: '__editor_' + name,
|
||||
type: 'js-editor',
|
||||
className: 'ae-AdaptorControl-func-editor',
|
||||
allowFullscreen,
|
||||
value,
|
||||
placeholder: editorPlaceholder || '',
|
||||
onChange: (value: any) => {
|
||||
this.onChange(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'container',
|
||||
body: '<span class="mtk1 bracket-highlighting-0">}</span>',
|
||||
className: 'ae-AdaptorControl-func-footer'
|
||||
},
|
||||
{
|
||||
type: 'container',
|
||||
className: 'cxd-Form-description',
|
||||
body: editorDesc
|
||||
}
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
{render('api-adaptor-control-editor/0', {
|
||||
type: 'container',
|
||||
className: 'ae-AdaptorControl-func-header',
|
||||
body: [
|
||||
'<span class="mtk6">function </span>',
|
||||
'<span class="mtk1 bracket-highlighting-0">(</span>',
|
||||
...lastParams
|
||||
.map(({label, tip}, index) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
level: 'link',
|
||||
label,
|
||||
className: 'ae-AdaptorControl-func-arg',
|
||||
...(tip ? {tooltip: this.genTooltipProps(tip)} : {})
|
||||
},
|
||||
...(index === lastParams.length - 1
|
||||
? []
|
||||
: ['<span class="mtk1">, </span>'])
|
||||
];
|
||||
})
|
||||
.flat(),
|
||||
'<span class="mtk1 bracket-highlighting-0">) {</span>'
|
||||
]
|
||||
})}
|
||||
|
||||
{render(
|
||||
'api-adaptor-control-editor/1',
|
||||
{
|
||||
label: '',
|
||||
name: '__whatever_name_adpator',
|
||||
placeholder: editorPlaceholder || '',
|
||||
mode: 'normal',
|
||||
type: 'js-editor',
|
||||
className: 'ae-AdaptorControl-func-editor',
|
||||
allowFullscreen
|
||||
},
|
||||
{
|
||||
value,
|
||||
onChange: this.onChange
|
||||
}
|
||||
)}
|
||||
|
||||
{render('api-adaptor-control-editor/2', {
|
||||
type: 'container',
|
||||
body: '<span class="mtk1 bracket-highlighting-0">}</span>',
|
||||
className: 'ae-AdaptorControl-func-footer'
|
||||
})}
|
||||
{render('api-adaptor-control-editor/3', {
|
||||
type: 'container',
|
||||
className: 'cxd-Form-description',
|
||||
body: editorDesc
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderSwitch() {
|
||||
|
@ -411,11 +411,7 @@ export default class APIControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -158,11 +158,7 @@ export default class MapSourceControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -197,11 +197,7 @@ export default class NavSourceControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -513,11 +513,7 @@ export default class OptionControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -360,11 +360,7 @@ export default class TimelineItemControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -189,11 +189,7 @@ function BaseOptionControl(Cmpt: React.JSXElementConstructor<any>) {
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -192,11 +192,7 @@ export default class TreeOptionControl extends React.Component<
|
||||
tooltip: labelRemark,
|
||||
className: cx(`Form-lableRemark`, labelRemark?.className),
|
||||
useMobileUI,
|
||||
container: popOverContainer
|
||||
? popOverContainer
|
||||
: env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: undefined
|
||||
container: popOverContainer || env.getModalContainer
|
||||
})
|
||||
: null}
|
||||
</label>
|
||||
|
@ -12,8 +12,8 @@ export const BASE_ACTION_PROPS = [
|
||||
'__actionDesc',
|
||||
'preventDefault',
|
||||
'stopPropagation',
|
||||
'expression',
|
||||
'outputVar'
|
||||
'expression'
|
||||
// 'outputVar'
|
||||
];
|
||||
|
||||
export default class CmptActionSelect extends React.Component<RendererProps> {
|
||||
|
@ -485,7 +485,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'args',
|
||||
name: 'dialog',
|
||||
label: '弹框内容',
|
||||
mode: 'horizontal',
|
||||
required: true,
|
||||
@ -690,9 +690,9 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
actionLabel: '发送请求',
|
||||
actionType: 'ajax',
|
||||
description: '配置并发送API请求',
|
||||
innerArgs: ['api', 'options'],
|
||||
// innerArgs: ['api', 'options'],
|
||||
descDetail: (info: any) => {
|
||||
let apiInfo = info?.args?.api;
|
||||
let apiInfo = info?.api ?? info?.args?.api;
|
||||
if (typeof apiInfo === 'string') {
|
||||
apiInfo = normalizeApi(apiInfo);
|
||||
}
|
||||
@ -711,43 +711,72 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
type: 'wrapper',
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getArgsWrapper(
|
||||
[
|
||||
getSchemaTpl('apiControl', {
|
||||
name: 'api',
|
||||
label: '配置请求',
|
||||
mode: 'horizontal',
|
||||
size: 'lg',
|
||||
inputClassName: 'm-b-none',
|
||||
renderLabel: true,
|
||||
required: true
|
||||
}),
|
||||
// getArgsWrapper(
|
||||
// [
|
||||
// getSchemaTpl('apiControl', {
|
||||
// name: 'api',
|
||||
// label: '配置请求',
|
||||
// mode: 'horizontal',
|
||||
// size: 'lg',
|
||||
// inputClassName: 'm-b-none',
|
||||
// renderLabel: true,
|
||||
// required: true
|
||||
// }),
|
||||
// {
|
||||
// name: 'options',
|
||||
// type: 'combo',
|
||||
// label: tipedLabel(
|
||||
// '静默请求',
|
||||
// '开启后,服务请求将以静默模式发送,即不会弹出成功或报错提示。'
|
||||
// ),
|
||||
// mode: 'horizontal',
|
||||
// items: [
|
||||
// {
|
||||
// type: 'switch',
|
||||
// name: 'silent',
|
||||
// label: false,
|
||||
// onText: '开启',
|
||||
// offText: '关闭',
|
||||
// mode: 'horizontal',
|
||||
// pipeIn: defaultValue(false)
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// false,
|
||||
// {
|
||||
// className: 'action-apiControl'
|
||||
// }
|
||||
// ),
|
||||
getSchemaTpl('apiControl', {
|
||||
name: 'api',
|
||||
label: '配置请求',
|
||||
mode: 'horizontal',
|
||||
size: 'lg',
|
||||
inputClassName: 'm-b-none',
|
||||
renderLabel: true,
|
||||
required: true
|
||||
}),
|
||||
{
|
||||
name: 'options',
|
||||
type: 'combo',
|
||||
label: tipedLabel(
|
||||
'静默请求',
|
||||
'开启后,服务请求将以静默模式发送,即不会弹出成功或报错提示。'
|
||||
),
|
||||
mode: 'horizontal',
|
||||
items: [
|
||||
{
|
||||
name: 'options',
|
||||
type: 'combo',
|
||||
label: tipedLabel(
|
||||
'静默请求',
|
||||
'开启后,服务请求将以静默模式发送,即不会弹出成功或报错提示。'
|
||||
),
|
||||
type: 'switch',
|
||||
name: 'silent',
|
||||
label: false,
|
||||
onText: '开启',
|
||||
offText: '关闭',
|
||||
mode: 'horizontal',
|
||||
items: [
|
||||
{
|
||||
type: 'switch',
|
||||
name: 'silent',
|
||||
label: false,
|
||||
onText: '开启',
|
||||
offText: '关闭',
|
||||
mode: 'horizontal',
|
||||
pipeIn: defaultValue(false)
|
||||
}
|
||||
]
|
||||
pipeIn: defaultValue(false)
|
||||
}
|
||||
],
|
||||
false,
|
||||
{
|
||||
className: 'action-apiControl'
|
||||
}
|
||||
),
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'outputVar',
|
||||
type: 'input-text',
|
||||
@ -787,26 +816,35 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
|
||||
actionLabel: '下载文件',
|
||||
actionType: 'download',
|
||||
description: '触发下载文件',
|
||||
innerArgs: ['api'],
|
||||
// innerArgs: ['api'],
|
||||
schema: {
|
||||
type: 'wrapper',
|
||||
style: {padding: '0'},
|
||||
body: [
|
||||
getArgsWrapper(
|
||||
getSchemaTpl('apiControl', {
|
||||
name: 'api',
|
||||
label: '配置请求',
|
||||
mode: 'horizontal',
|
||||
inputClassName: 'm-b-none',
|
||||
size: 'lg',
|
||||
renderLabel: true,
|
||||
required: true
|
||||
}),
|
||||
false,
|
||||
{
|
||||
className: 'action-apiControl'
|
||||
}
|
||||
)
|
||||
// getArgsWrapper(
|
||||
// getSchemaTpl('apiControl', {
|
||||
// name: 'api',
|
||||
// label: '配置请求',
|
||||
// mode: 'horizontal',
|
||||
// inputClassName: 'm-b-none',
|
||||
// size: 'lg',
|
||||
// renderLabel: true,
|
||||
// required: true
|
||||
// }),
|
||||
// false,
|
||||
// {
|
||||
// className: 'action-apiControl'
|
||||
// }
|
||||
// )
|
||||
getSchemaTpl('apiControl', {
|
||||
name: 'api',
|
||||
label: '配置请求',
|
||||
mode: 'horizontal',
|
||||
inputClassName: 'm-b-none',
|
||||
size: 'lg',
|
||||
renderLabel: true,
|
||||
required: true
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -2738,11 +2776,13 @@ export const getEventControlConfig = (
|
||||
config.__actionExpression = action.args?.value;
|
||||
}
|
||||
|
||||
if (
|
||||
action.actionType === 'ajax' &&
|
||||
typeof action?.args?.api === 'string'
|
||||
) {
|
||||
action.args.api = normalizeApi(action?.args?.api);
|
||||
if (['ajax', 'download'].includes(action.actionType)) {
|
||||
config.api = action.api ?? action?.args?.api;
|
||||
config.options = action.options ?? action?.args?.options;
|
||||
if (typeof action?.api === 'string') {
|
||||
config.api = normalizeApi(action?.api);
|
||||
}
|
||||
delete config.args;
|
||||
}
|
||||
|
||||
// 获取动作专有配置参数
|
||||
|
@ -491,8 +491,12 @@ export class EventControl extends React.Component<
|
||||
}
|
||||
|
||||
buildEventDataSchema(data: any, manager: EditorManager) {
|
||||
const {actionTree, pluginActions, commonActions, allComponents} =
|
||||
this.props;
|
||||
const {
|
||||
actionTree,
|
||||
actions: pluginActions,
|
||||
commonActions,
|
||||
allComponents
|
||||
} = this.props;
|
||||
const {events, onEvent} = this.state;
|
||||
|
||||
const eventConfig = events.find(
|
||||
@ -564,7 +568,7 @@ export class EventControl extends React.Component<
|
||||
properties: {
|
||||
...jsonSchema.properties?.data?.properties,
|
||||
[action.outputVar!]: {
|
||||
...actionSchema[0],
|
||||
...(Array.isArray(actionSchema) && (actionSchema[0] || {})),
|
||||
title: `${action.outputVar}(${actionLabel}动作出参)`
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +140,25 @@ setSchemaTpl('multiple', (schema: any = {}) => {
|
||||
};
|
||||
});
|
||||
|
||||
setSchemaTpl('strictMode', {
|
||||
type: 'switch',
|
||||
label: '严格模式',
|
||||
name: 'strictMode',
|
||||
value: false,
|
||||
mode: 'horizontal',
|
||||
horizontal: {
|
||||
justify: true,
|
||||
left: 8
|
||||
},
|
||||
inputClassName: 'is-inline ',
|
||||
labelRemark: {
|
||||
trigger: ['hover', 'focus'],
|
||||
setting: true,
|
||||
title: '',
|
||||
content: '启用严格模式将采用值严格相等比较'
|
||||
}
|
||||
});
|
||||
|
||||
setSchemaTpl('checkAllLabel', {
|
||||
type: 'input-text',
|
||||
name: 'checkAllLabel',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-formula",
|
||||
"version": "3.1.5",
|
||||
"version": "3.3.0-beta.4",
|
||||
"description": "负责 amis 里面的表达式实现,内置公式,编辑器等",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -81,7 +81,6 @@
|
||||
"browserslist": "IE >= 11",
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"cobertura"
|
||||
|
@ -3,7 +3,7 @@
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"version": "3.1.5",
|
||||
"version": "3.3.0-beta.4",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "npm run clean-dist && NODE_ENV=production rollup -c ",
|
||||
@ -35,8 +35,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/mini-decimal": "^1.0.1",
|
||||
"amis-core": "^3.1.1",
|
||||
"amis-formula": "^3.1.1",
|
||||
"amis-core": "^3.3.0-beta.4",
|
||||
"amis-formula": "^3.3.0-beta.4",
|
||||
"classnames": "2.3.1",
|
||||
"codemirror": "^5.63.0",
|
||||
"downshift": "6.1.12",
|
||||
@ -108,7 +108,6 @@
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"cobertura"
|
||||
|
@ -1,4 +1,5 @@
|
||||
:root {
|
||||
:root,
|
||||
.AMISCSSWrapper {
|
||||
--button-default-default-top-border-color: var(--colors-neutral-line-8);
|
||||
--button-default-default-top-border-style: var(--borders-style-2);
|
||||
--button-default-default-top-border-width: var(--borders-width-2);
|
||||
|
@ -5,7 +5,8 @@ $remFactor: 16px;
|
||||
/* 此处放置需要override的变量,因为部分变量已经在variables.scss中定义 */
|
||||
$Table-strip-bg: transparent;
|
||||
|
||||
:root {
|
||||
:root,
|
||||
.AMISCSSWrapper {
|
||||
--white: var(--colors-neutral-text-11);
|
||||
--primary: var(--colors-brand-5);
|
||||
--primary-onHover: var(--colors-brand-6);
|
||||
|
@ -198,6 +198,7 @@
|
||||
width: px2rem(10px);
|
||||
height: px2rem(10px);
|
||||
top: 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -543,6 +543,10 @@
|
||||
margin-bottom: 0;
|
||||
font-size: var(--fontSizeLg);
|
||||
|
||||
& + .#{$ns}Form-item-controlBox {
|
||||
max-width: calc(100% - 28%);
|
||||
}
|
||||
|
||||
> span {
|
||||
line-height: px2rem(32px);
|
||||
display: inline-block;
|
||||
@ -677,6 +681,9 @@
|
||||
|
||||
.#{$ns}Form-item-controlBox {
|
||||
flex: 1;
|
||||
max-width: -moz-available;
|
||||
max-width: -webkit-fill-available;
|
||||
max-width: fill-available;
|
||||
}
|
||||
|
||||
.#{$ns}Form-static {
|
||||
|
@ -445,6 +445,7 @@
|
||||
width: px2rem(10px);
|
||||
height: px2rem(10px);
|
||||
top: 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,16 +314,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-sugs {
|
||||
position: absolute;
|
||||
&-popover {
|
||||
margin-top: px2rem(4px);
|
||||
background: var(--Form-select-menu-bg);
|
||||
color: var(--Form-select-menu-color);
|
||||
border-radius: px2rem(2px);
|
||||
box-shadow: var(--menu-box-shadow);
|
||||
left: px2rem(-1px);
|
||||
right: px2rem(-1px);
|
||||
top: calc(100% + #{px2rem(4px)});
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&-sugs {
|
||||
max-height: px2rem(300px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -147,9 +147,9 @@
|
||||
|
||||
&:hover {
|
||||
.#{$ns}Tree {
|
||||
&-itemLabel-item:not(.is-mobile) {
|
||||
background-color: var(--Tree-item-onHover-bg-pure);
|
||||
}
|
||||
// &-itemLabel-item:not(.is-mobile) {
|
||||
// background-color: var(--Tree-item-onHover-bg-pure);
|
||||
// }
|
||||
|
||||
&-item-icons {
|
||||
visibility: visible;
|
||||
@ -163,6 +163,10 @@
|
||||
}
|
||||
|
||||
&-item {
|
||||
&:hover {
|
||||
background-color: var(--Tree-item-onHover-bg-pure);
|
||||
}
|
||||
|
||||
.is-checked {
|
||||
border-radius: var(--Tree-item-onChekced-bg-borderRadius);
|
||||
.#{$ns}Tree {
|
||||
@ -211,7 +215,7 @@
|
||||
}
|
||||
|
||||
&-itemInput {
|
||||
padding-left: var(--Tree-itemArrowWidth);
|
||||
// padding-left: var(--Tree-itemArrowWidth);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
|
@ -49,6 +49,7 @@ export interface CalendarMobileProps extends ThemeProps, LocaleProps {
|
||||
};
|
||||
};
|
||||
defaultDate?: moment.Moment;
|
||||
isEndDate?: boolean;
|
||||
}
|
||||
|
||||
export interface CalendarMobileState {
|
||||
@ -574,7 +575,8 @@ export class CalendarMobile extends React.Component<
|
||||
viewMode = 'days',
|
||||
close,
|
||||
defaultDate,
|
||||
showViewMode
|
||||
showViewMode,
|
||||
isEndDate
|
||||
} = this.props;
|
||||
const __ = this.props.translate;
|
||||
|
||||
@ -654,6 +656,7 @@ export class CalendarMobile extends React.Component<
|
||||
hideHeader={true}
|
||||
updateOn={viewMode}
|
||||
key={'calendar' + index}
|
||||
isEndDate={isEndDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -671,7 +674,8 @@ export class CalendarMobile extends React.Component<
|
||||
close,
|
||||
timeConstraints,
|
||||
defaultDate,
|
||||
isDatePicker
|
||||
isDatePicker,
|
||||
isEndDate
|
||||
} = this.props;
|
||||
const __ = this.props.translate;
|
||||
|
||||
@ -705,6 +709,7 @@ export class CalendarMobile extends React.Component<
|
||||
})}
|
||||
timeConstraints={timeConstraints}
|
||||
isValidDate={this.checkIsValidDate}
|
||||
isEndDate={isEndDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -110,9 +110,10 @@ export class Collapse extends React.Component<CollapseProps, CollapseState> {
|
||||
if (props.disabled || props.collapsable === false) {
|
||||
return;
|
||||
}
|
||||
props.onCollapse && props.onCollapse(props, !this.state.collapsed);
|
||||
const newCollapsed = !this.state.collapsed;
|
||||
props.onCollapse?.(props, newCollapsed);
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed
|
||||
collapsed: newCollapsed
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,15 @@ export interface CollapseGroupProps {
|
||||
classPrefix: string;
|
||||
children?: React.ReactNode | Array<React.ReactNode>;
|
||||
useMobileUI?: boolean;
|
||||
onCollapseChange?: (
|
||||
activeKeys: Array<string | number>,
|
||||
collapseId: string | number,
|
||||
collapsed: boolean
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface CollapseGroupState {
|
||||
activeKey: Array<string | number | never>;
|
||||
activeKeys: Array<string | number | never>;
|
||||
}
|
||||
|
||||
class CollapseGroup extends React.Component<
|
||||
@ -73,38 +78,43 @@ class CollapseGroup extends React.Component<
|
||||
|
||||
if (isInit) {
|
||||
this.state = {
|
||||
activeKey: curActiveKey.map((key: number | string) => String(key))
|
||||
activeKeys: curActiveKey.map((key: number | string) => String(key))
|
||||
};
|
||||
} else {
|
||||
this.setState({
|
||||
activeKey: curActiveKey.map((key: number | string) => String(key))
|
||||
activeKeys: curActiveKey.map((key: number | string) => String(key))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
collapseChange(collapseId: string, collapsed: boolean) {
|
||||
let activeKey = this.state.activeKey.concat();
|
||||
let activeKeys = this.state.activeKeys.concat();
|
||||
if (!collapsed) {
|
||||
// 开启状态
|
||||
if (this.props.accordion) {
|
||||
activeKey = [];
|
||||
activeKeys = [];
|
||||
} else {
|
||||
for (let i = 0; i < activeKey.length; i++) {
|
||||
if (activeKey[i] === collapseId) {
|
||||
activeKey.splice(i, 1); // 剔除开启状态
|
||||
for (let i = 0; i < activeKeys.length; i++) {
|
||||
if (activeKeys[i] === collapseId) {
|
||||
activeKeys.splice(i, 1); // 剔除开启状态
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.props.accordion) {
|
||||
activeKey = [collapseId as string];
|
||||
activeKeys = [collapseId as string];
|
||||
} else {
|
||||
activeKey.push(collapseId as string);
|
||||
activeKeys.push(collapseId as string);
|
||||
}
|
||||
}
|
||||
this.props.onCollapseChange?.(
|
||||
activeKeys,
|
||||
collapseId,
|
||||
activeKeys.indexOf(collapseId) === -1
|
||||
);
|
||||
this.setState({
|
||||
activeKey
|
||||
activeKeys
|
||||
});
|
||||
}
|
||||
|
||||
@ -118,7 +128,7 @@ class CollapseGroup extends React.Component<
|
||||
|
||||
const collapseId = props.propKey || String(index);
|
||||
// 判断是否折叠
|
||||
const collapsed = this.state.activeKey.indexOf(collapseId) === -1;
|
||||
const collapsed = this.state.activeKeys.indexOf(collapseId) === -1;
|
||||
|
||||
return React.cloneElement(child as any, {
|
||||
...props,
|
||||
|
@ -310,6 +310,9 @@ export interface DateProps extends LocaleProps, ThemeProps {
|
||||
onBlur?: Function;
|
||||
onRef?: any;
|
||||
data?: any;
|
||||
|
||||
// 是否为结束时间
|
||||
isEndDate?: boolean;
|
||||
}
|
||||
|
||||
export interface DatePickerState {
|
||||
@ -708,6 +711,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
clearable,
|
||||
shortcuts,
|
||||
utc,
|
||||
isEndDate,
|
||||
overlayPlacement,
|
||||
locale,
|
||||
format,
|
||||
@ -749,6 +753,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
viewMode === 'quarters' || viewMode === 'months' ? 'years' : 'months'
|
||||
}
|
||||
timeConstraints={timeConstraints}
|
||||
isEndDate={isEndDate}
|
||||
/>
|
||||
);
|
||||
const CalendarMobileTitle = (
|
||||
@ -815,6 +820,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
onScheduleClick={onScheduleClick}
|
||||
embed={embed}
|
||||
useMobileUI={useMobileUI}
|
||||
isEndDate={isEndDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -902,6 +908,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
useMobileUI={useMobileUI}
|
||||
isEndDate={isEndDate}
|
||||
// utc={utc}
|
||||
/>
|
||||
</PopOver>
|
||||
|
@ -158,7 +158,7 @@ class DropDownSelection extends BaseSelection<
|
||||
>
|
||||
{!isMobile() ? (
|
||||
<span className={cx('DropDownSelection-caret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
<Icon icon="right-arrow-bold" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
</ResultBox>
|
||||
|
@ -18,6 +18,7 @@ import {themeable} from 'amis-core';
|
||||
import {autobind, camel} from 'amis-core';
|
||||
import {stripNumber} from 'amis-core';
|
||||
import {isMobile} from 'amis-core';
|
||||
import {safeAdd, safeSub} from 'amis-core';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {Icon} from './icons';
|
||||
|
||||
@ -389,9 +390,9 @@ export class Range extends React.Component<RangeItemProps, any> {
|
||||
let result = 0;
|
||||
// 余数 >= 步长一半 -> 向上取
|
||||
// 余数 < 步长一半 -> 向下取
|
||||
const _value = surplus >= step / 2 ? value : value - step;
|
||||
const _value = surplus >= step / 2 ? value : safeSub(value, step);
|
||||
while (result <= _value) {
|
||||
result += step;
|
||||
result = safeAdd(result, step);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -251,6 +251,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
maxTagCount,
|
||||
overflowTagPopover,
|
||||
showArrow,
|
||||
popOverContainer,
|
||||
...rest
|
||||
} = this.props;
|
||||
const isFocused = this.state.isFocused;
|
||||
|
@ -55,6 +55,7 @@ export default class TinymceEditor extends React.Component<TinymceEditorProps> {
|
||||
};
|
||||
config?: any;
|
||||
editor?: any;
|
||||
editorInitialized?: boolean = false;
|
||||
currentContent?: string;
|
||||
|
||||
elementRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
|
||||
@ -136,6 +137,8 @@ export default class TinymceEditor extends React.Component<TinymceEditorProps> {
|
||||
help: {title: 'Help', items: 'help'}
|
||||
},
|
||||
paste_data_images: true,
|
||||
// 很诡异的问题,video 会被复制放在光标上,直接用样式隐藏先
|
||||
content_style: '[data-mce-bogus] video {display:none;}',
|
||||
...this.props.config,
|
||||
target: this.elementRef.current,
|
||||
readOnly: this.props.disabled,
|
||||
@ -144,6 +147,7 @@ export default class TinymceEditor extends React.Component<TinymceEditorProps> {
|
||||
this.editor = editor;
|
||||
|
||||
editor.on('init', (e: Event) => {
|
||||
this.editorInitialized = true;
|
||||
this.initEditor(e, editor);
|
||||
});
|
||||
}
|
||||
@ -159,7 +163,7 @@ export default class TinymceEditor extends React.Component<TinymceEditorProps> {
|
||||
props.model !== prevProps.model &&
|
||||
props.model !== this.currentContent
|
||||
) {
|
||||
this.editor?.setContent(props.model || '');
|
||||
this.editorInitialized && this.editor?.setContent(props.model || '');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -619,10 +619,11 @@ export class TreeSelector extends React.Component<
|
||||
const result = [] as Option[];
|
||||
|
||||
for (let option of this.state.flattenedOptions) {
|
||||
result.push(option);
|
||||
if (option === parent) {
|
||||
result.push({...option, isAdding: true});
|
||||
} else {
|
||||
result.push(option);
|
||||
const insert = {isAdding: true};
|
||||
this.levels.set(insert, (this.levels.get(option) || 0) + 1);
|
||||
result.push(insert);
|
||||
}
|
||||
}
|
||||
this.setState({flattenedOptions: result});
|
||||
@ -888,12 +889,12 @@ export class TreeSelector extends React.Component<
|
||||
if (!isVisible(item)) {
|
||||
return;
|
||||
}
|
||||
this.levels.set(item, level);
|
||||
if (paths.length === 0) {
|
||||
// 父节点
|
||||
flattenedOptions.push(item);
|
||||
} else if (this.isUnfolded(parent)) {
|
||||
this.relations.set(item, parent);
|
||||
this.levels.set(item, level);
|
||||
// 父节点是展开的状态
|
||||
flattenedOptions.push(item);
|
||||
}
|
||||
@ -1125,7 +1126,9 @@ export class TreeSelector extends React.Component<
|
||||
if (isEditing && editingItem === item) {
|
||||
body = this.renderInput(checkbox);
|
||||
} else if (item.isAdding) {
|
||||
body = this.renderInput(checkbox);
|
||||
body = this.renderInput(
|
||||
<span className={cx('Tree-itemArrowPlaceholder')} />
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div
|
||||
@ -1275,8 +1278,7 @@ export class TreeSelector extends React.Component<
|
||||
})}
|
||||
style={{
|
||||
...style,
|
||||
left: `calc(${level} * var(--Tree-indent))`,
|
||||
width: `calc(100% - ${level} * var(--Tree-indent))`
|
||||
paddingLeft: `calc(${level} * var(--Tree-indent))`
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
|
@ -238,7 +238,11 @@ export function withRemoteConfig<P = any>(
|
||||
store.data,
|
||||
'| raw'
|
||||
),
|
||||
() => this.syncConfig()
|
||||
() => this.syncConfig(),
|
||||
// 当nav配置source: "${amisStore.app.portalNavs}"时,切换页面就会触发source更新
|
||||
// 因此这里增加这个配置 数据源完全不相等情况下再执行loadConfig
|
||||
// 否则数据源重置 保存不了展开状态 就会始终是手风琴模式了
|
||||
{equals: comparer.structural}
|
||||
)
|
||||
);
|
||||
} else if (env && isEffectiveApi(source, data)) {
|
||||
@ -254,11 +258,7 @@ export function withRemoteConfig<P = any>(
|
||||
ignoreData: true
|
||||
}).url;
|
||||
},
|
||||
() => this.loadConfig(),
|
||||
// 当nav配置source: "${amisStore.app.portalNavs}"时,切换页面就会触发source更新
|
||||
// 因此这里增加这个配置 数据源完全不相等情况下再执行loadConfig
|
||||
// 否则数据源重置 保存不了展开状态 就会始终是手风琴模式了
|
||||
{equals: comparer.structural}
|
||||
() => this.loadConfig()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -472,6 +472,11 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
|
||||
});
|
||||
// 最多展示3个
|
||||
showSchedule = showSchedule.slice(0, 3);
|
||||
|
||||
const locale = this.props.viewDate.localeData();
|
||||
// 以周几作为一周的开始,0表示周日,1表示周一
|
||||
const firstDayOfWeek = locale.firstDayOfWeek();
|
||||
|
||||
const scheduleDiv = showSchedule.map((item: any, index: number) => {
|
||||
let diffDays = moment(item.endTime).diff(
|
||||
moment(item.startTime),
|
||||
@ -486,9 +491,9 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
|
||||
/* 前面的计算结果是闭区间,所以最终结果要补足1 */
|
||||
diffDays += 1;
|
||||
|
||||
const width =
|
||||
item.width ||
|
||||
Math.min(diffDays, 7 - moment(item.startTime).weekday());
|
||||
const endWidth =
|
||||
7 - (moment(item.startTime).weekday() - firstDayOfWeek + 1);
|
||||
const width = item.width || Math.min(diffDays, endWidth) || 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -127,7 +127,7 @@ export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className={cx('CBGroup-fieldCaret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
<Icon icon="right-arrow-bold" className="icon" />
|
||||
</span>
|
||||
</ResultBox>
|
||||
</div>
|
||||
|
@ -260,7 +260,7 @@ export class ConditionGroup extends React.Component<
|
||||
{body ? (
|
||||
body.map((item, index) => (
|
||||
<GroupOrItem
|
||||
draggable={value!.children!.length > 1}
|
||||
draggable={draggable && value!.children!.length > 1}
|
||||
onDragStart={onDragStart}
|
||||
config={config}
|
||||
key={item.id}
|
||||
|
@ -248,7 +248,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
>
|
||||
{!isMobile() ? (
|
||||
<span className={cx('CBGroup-operatorCaret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
<Icon icon="right-arrow-bold" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
</ResultBox>
|
||||
|
@ -37,6 +37,7 @@ export interface ConditionBuilderProps extends ThemeProps, LocaleProps {
|
||||
onChange: (value?: ConditionGroupValue) => void;
|
||||
config?: ConditionBuilderConfig;
|
||||
disabled?: boolean;
|
||||
draggable?: boolean;
|
||||
searchable?: boolean;
|
||||
fieldClassName?: string;
|
||||
formula?: FormulaPickerProps;
|
||||
@ -67,6 +68,9 @@ export class QueryBuilder extends React.Component<
|
||||
|
||||
@autobind
|
||||
handleDragStart(e: React.DragEvent) {
|
||||
const {draggable = true} = this.props;
|
||||
// draggable为false时不可拖拽
|
||||
if (!draggable) return;
|
||||
const target = e.currentTarget;
|
||||
const item = target.closest('[data-id]') as HTMLElement;
|
||||
this.dragTarget = item;
|
||||
@ -250,6 +254,7 @@ export class QueryBuilder extends React.Component<
|
||||
showANDOR,
|
||||
data,
|
||||
disabled,
|
||||
draggable = true,
|
||||
searchable,
|
||||
builderMode,
|
||||
formula,
|
||||
@ -291,6 +296,7 @@ export class QueryBuilder extends React.Component<
|
||||
showNot={showNot}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
draggable={draggable}
|
||||
searchable={searchable}
|
||||
formula={formula}
|
||||
renderEtrValue={renderEtrValue}
|
||||
|
@ -80,8 +80,8 @@ export function FuncList(props: FuncListProps) {
|
||||
className={cx('FormulaEditor-FuncList-item', {
|
||||
'is-active': item.name === activeFunc?.name
|
||||
})}
|
||||
onClick={() => setActiveFunc(item)}
|
||||
onDoubleClick={() => props.onSelect?.(item)}
|
||||
onMouseEnter={() => setActiveFunc(item)}
|
||||
onClick={() => props.onSelect?.(item)}
|
||||
key={item.name}
|
||||
>
|
||||
{item.name}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user