Merge remote-tracking branch 'upstream/master' into feat/ajaxActionArgs

This commit is contained in:
pianruijie 2023-07-03 11:27:37 +08:00
commit f50682f612
142 changed files with 5545 additions and 2414 deletions

View File

@ -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

24
.gitpod.yml Normal file
View 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

View File

@ -7,7 +7,7 @@
[文档(国外)](https://baidu.github.io/amis/) |
[可视化编辑器](https://aisuda.github.io/amis-editor-demo/) |
[amis-admin](https://github.com/aisuda/amis-admin) |
[爱速搭](https://baidu.gitee.io/aisuda-docs/)
[爱速搭](https://aisuda.bce.baidu.com/aisuda-docs/)
</div>

View File

@ -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}` 取值。 |

View File

@ -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"
}
}
]
}
}
}
```

View File

@ -1881,7 +1881,7 @@ crud 组件支持通过配置`headerToolbar`和`footerToolbar`属性,实现在
}
```
### 通过 api 导出 CSV
#### 通过 api 导出 CSV
> 1.4.0 及以上版本
@ -1932,6 +1932,61 @@ crud 组件支持通过配置`headerToolbar`和`footerToolbar`属性,实现在
}
```
#### 自定义导出 CSV 的文件名
> 1.4.0 及以上版本
`export-csv` 可以单独配置 `api` 实现导出全量功能,这个 api 的返回结果和 CRUD 类似
```schema: scope="body"
{
"type": "crud",
"syncLocation": false,
"api": "/api/mock2/sample",
"data": {
"name": "123"
},
"headerToolbar": [
{
"type": "export-csv",
"label": "自定义导出 CSV",
"api": "/api/mock2/sample",
"filename": "自定义文件名${name}"
}
],
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "engine",
"label": "Rendering engine"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "platform",
"label": "Platform(s)"
},
{
"name": "version",
"label": "Engine version"
},
{
"name": "grade",
"label": "CSS grade",
"type": "mapping",
"map": {
"*": "<span class=\"label label-info\">${grade}</span>"
}
}
]
}
```
### 导出 Excel
在`headerToolbar`或者`footerToolbar`数组中添加`export-excel`字符串,可以实现点击下载 Excel 的功能,和导出 CSV 一样只包括当前分页的数据,但它们有明显区别:

View File

@ -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及以上版本` |

View File

@ -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 秒 |
## 事件表

View File

@ -12,6 +12,8 @@ order: 61
> 1.10.0 及以上版本
这个组件可以基于 JSON Schema 生成表单项,方便对接类似 OpenAPI/Swagger Specification 的接口规范,可基于接口定义自动生成 amis 表单项。
> 此组件还在实验阶段,很多 json-schema 属性没有对应实现,使用前请先确认你要的功能满足了需求
基于 json-schema 定义生成表单输入项。

View File

@ -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"
}
]
}
]
}
}
```

View File

@ -722,6 +722,46 @@ order: 68
}
```
## title自定义
> 3.2.0 及以上版本
通过配置 tabs 数组中 title 为 schema就能自定义 title 的显示。
```schema: scope="body"
{
"type": "tabs",
"addBtnText": "新增Tab",
"showTip": true,
"tabs": [
{
"title": {
"type": "container",
"body": [
{
"type": "tpl",
"tpl": "这里是容器内容区"
},
{
"type": "icon",
"icon": "cloud"
}
]
},
"closable": true,
"tab": "Content 1",
"tip": "容器内容区提示"
},
{
"title": "Tab 2",
"tab": "Content 2"
}
]
}
```
## 配置超出折叠
通过配置 `collapseOnExceed` 可以用来实现超出折叠,额外还能通过 `collapseBtnLabel` 配置折叠按钮的文字
@ -807,7 +847,7 @@ order: 68
| source | `string` | | tabs 关联数据,关联后可以重复生成选项卡 |
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏 |
| toolbarClassName | `string` | | tabs 中工具栏的类名 |
| tabs[x].title | `string` | | Tab 标题 |
| tabs[x].title | `string` \| [SchemaNode](../types/schemanode) | | Tab 标题,当是 [SchemaNode](../types/schemanode) 时,该 title 不支持 editable 为 true 的双击编辑 |
| tabs[x].icon | `icon` | | Tab 的图标 |
| tabs[x].iconPosition | `left` / `right` | `left` | Tab 的图标位置 |
| tabs[x].tab | [SchemaNode](../types/schemanode) | | 内容区 |
@ -815,6 +855,7 @@ order: 68
| tabs[x].reload | `boolean` | | 设置以后内容每次都会重新渲染,对于 crud 的重新拉取很有用 |
| tabs[x].unmountOnExit | `boolean` | | 每次退出都会销毁当前 tab 栏内容 |
| tabs[x].className | `string` | `"bg-white b-l b-r b-b wrapper-md"` | Tab 区域样式 |
| tabs[x].tip | `string` | | `3.2.0及以上版本支持` Tab 提示,当开启 `showTip` 时生效,作为 Tab 在 hover 时的提示显示,可不配置,如不设置,`tabs[x].title` 作为提示显示 |
| tabs[x].closable | `boolean` | false | 是否支持删除,优先级高于组件的 `closable` |
| tabs[x].disabled | `boolean` | false | 是否禁用 |
| mountOnEnter | `boolean` | false | 只有在点中 tab 的时候才渲染 |
@ -825,7 +866,7 @@ order: 68
| draggable | `boolean` | false | 是否支持拖拽 |
| showTip | `boolean` | false | 是否支持提示 |
| showTipClassName | `string` | `'' ` | 提示的类 |
| editable | `boolean` | false | 收否可编辑标签名 |
| editable | `boolean` | false | 是否可编辑标签名。当 `tabs[x].title` 为 [SchemaNode](../types/schemanode) 时,双击编辑 Tab 的 title 显示空的内容 |
| scrollable | `boolean` | true | 是否导航支持内容溢出滚动。(属性废弃) |
| sidePosition | `left` / `right` | `left` | `sidebar` 模式下,标签栏位置 |
| collapseOnExceed | `number` | | 当 tabs 超出多少个时开始折叠 |

View File

@ -414,7 +414,7 @@ amis 从 3.2.0 版本开始针对[具备数据域的组件](#具备数据域的
1. `trackExpression` 配置成 `"none"` 也就是说不追踪任何数据。
2. `trackExpression` 配置成 `"${xxxVariable}"` 这样 xxxVariable 变化了更新当前组件的数据链。
关于 `trackExpression` 的语法,请查看表达式篇章,可以监听多个变量比如: `"${xxx1},${xxx2}"`,还可以写表单时`"${ xxx ? xxx : yyy}"`
关于 `trackExpression` 的语法,请查看表达式篇章,可以监听多个变量比如: `"${xxx1},${xxx2}"`,还可以写表达式`"${ xxx ? xxx : yyy}"`
amis 内部是通过运算这个表达式的结果来判断。所以表达式中千万不要用随机函数,或者用当前时间等,否则每次都会更新数据链。另外如果变量是数组,或者对象,会转成统一的字符串 `[object Array]` 或者 `[object Object]` 这个其实会影响检测的,所以建议转成 json 字符串如。 `${xxxObject | json}`。还有就是既然是监控上层数据,表达式中不要写当前层数据变量,是取不到的。

View File

@ -377,57 +377,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 +438,7 @@ run action ajax
}
```
**动作属性args**
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
**动作属性**
| 属性名 | 类型 | 默认值 | 说明 |
| ------ | ----------------------- | ------ | --------------------------------------------------------- |
@ -466,63 +462,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 +549,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 +591,7 @@ run action ajax
}
```
**动作属性args**
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
**动作属性**
| 属性名 | 类型 | 默认值 | 说明 |
| ------ | ----------------------- | ------ | --------------------------------------------------------- |
@ -626,63 +614,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 +683,13 @@ run action ajax
| ----------- | -------- | ------ | --------------- |
| componentId | `string` | - | 指定抽屉组件 id |
### 打开对话框
### 打开确认弹窗
通过配置`actionType: 'alert'`或`actionType: 'confirm'`打开不同对话框,该动作分别需实现 env.alert: (msg: string) => void 和 env.confirm: (msg: string, title?: string) => boolean | Promise&lt;boolean&gt;
通过配置`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&lt;boolean&gt;
```schema
{
@ -762,10 +708,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 +727,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 +1730,7 @@ run action ajax
}
```
**动作属性args**
> `< 2.3.2 及以下版本`,以下属性与 args 同级。
**动作属性**
| 属性名 | 类型 | 默认值 | 说明 |
| ------ | ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
@ -1802,9 +1858,9 @@ run action ajax
> 注意:直接调用`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
{

View File

@ -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

View File

@ -453,6 +453,7 @@
overflow: auto;
word-break: normal;
/* word-break: keep-all; */
border-left: 1px solid #ddd;
}
.markdown-body table:not(.table) th {
@ -477,6 +478,33 @@ body.dark .markdown-body table:not(.table) tr:nth-child(2n) {
background: none;
}
.markdown-body table td:first-child,
.markdown-body table th:first-child {
position: sticky;
left: 0;
z-index: 10;
background-color: #fff;
border-left: 0px !important;
}
.markdown-body table td:first-child::after,
.markdown-body table th:first-child::after {
box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.1);
position: absolute;
top: 0;
right: 0;
bottom: -1px;
width: 30px;
transform: translate(100%);
transition: box-shadow 0.3s;
content: '';
pointer-events: none;
}
.markdown-body table tr:nth-child(2n) td:first-child {
background-color: #f8f8f8;
}
/* modified by zhangjun08
* 更改文档中的图片展示样式
*/

View File

@ -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')
@ -534,7 +537,12 @@ 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/**'],
@ -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'],

View File

@ -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(/\/$/, '');
}

View File

@ -94,7 +94,6 @@
"jest": {
"verbose": true,
"testEnvironment": "jsdom",
"collectCoverage": true,
"coverageReporters": [
"text",
"cobertura"

View File

@ -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);

View File

@ -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}
/>
);
}

View File

@ -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'];
@ -239,14 +239,6 @@ export const runAction = async (
false
);
}
let stopPropagation = false;
if (actionConfig.stopPropagation) {
stopPropagation = await evalExpressionWithConditionBuilder(
actionConfig.stopPropagation,
mergeData,
false
);
}
let key = {
componentId: dataMapping(actionConfig.componentId, mergeData),
@ -288,7 +280,7 @@ export const runAction = async (
console.group?.(`run action ${actionConfig.actionType}`);
console.debug(`[${actionConfig.actionType}] action args, data`, args, data);
let stoped = false;
let stopped = false;
const actionResult = await actionInstrance.run(
{
...actionConfig,
@ -302,7 +294,16 @@ export const runAction = async (
);
// 二次确认弹窗如果取消,则终止后续动作
if (actionConfig?.actionType === 'confirmDialog' && !actionResult) {
stoped = true;
stopped = true;
}
let stopPropagation = false;
if (actionConfig.stopPropagation) {
stopPropagation = await evalExpressionWithConditionBuilder(
actionConfig.stopPropagation,
mergeData,
false
);
}
console.debug(`[${actionConfig.actionType}] action end event`, event);
console.groupEnd?.();
@ -310,5 +311,5 @@ export const runAction = async (
// 阻止原有动作执行
preventDefault && event.preventDefault();
// 阻止后续动作执行
(stopPropagation || stoped) && event.stopPropagation();
(stopPropagation || stopped) && event.stopPropagation();
};

View File

@ -44,12 +44,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';

View File

@ -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;
}
}

View File

@ -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

View File

@ -661,7 +661,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 +810,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(
@ -1132,7 +1133,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)) {
@ -1258,7 +1259,7 @@ export default class Form extends React.Component<FormProps, object> {
this.validate(true);
} 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 +1327,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;
}
}

View File

@ -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) {

View File

@ -9,6 +9,7 @@ import {
mapTree
} from '../utils/helper';
import {ServiceStore} from './service';
import {isVisible} 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 &&

View File

@ -16,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';
@ -288,8 +289,10 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
items = result.items || result.rows;
}
// 如果不按照 items 格式返回,就拿第一个数组当成 items
if (!Array.isArray(items)) {
if (items == null) {
items = [];
} else if (!Array.isArray(items)) {
// 如果不按照 items 格式返回,就拿第一个数组当成 items
for (const key of Object.keys(result)) {
if (result.hasOwnProperty(key) && Array.isArray(result[key])) {
items = result[key];
@ -585,9 +588,17 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
};
const exportAsCSV = async (
options: {loadDataOnce?: boolean; api?: Api; data?: any} = {}
options: {
loadDataOnce?: boolean;
api?: Api;
data?: any;
filename?: string;
} = {}
) => {
let items = options.loadDataOnce ? self.data.itemsRaw : self.data.items;
const filename = options.filename
? filter(options.filename, options.data, '| raw')
: 'data';
if (options.api) {
const env = getEnv(self);
@ -626,7 +637,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
type: 'text/plain;charset=utf-8'
}
);
saveAs(blob, 'data.csv');
saveAs(blob, `${filename}.csv`);
}
});
};

View File

@ -92,7 +92,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();

View File

@ -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,

View File

@ -382,6 +382,7 @@ export type FunctionPropertyNames<T> = {
export type JSONSchema = JSONSchema7 & {
group?: string; // 分组
typeLabel?: string; // 类型说明
};
// export type Omit<T, K extends keyof T & any> = Pick<T, Exclude<keyof T, K>>;

View File

@ -1,6 +1,15 @@
import {JSONSchema} from '../types';
import {guid, keyToPath, mapTree} from './helper';
export const DATASCHEMA_TYPE_MAP: {[type: string]: string} = {
boolean: '布尔',
integer: '整数',
number: '数字',
string: '文本',
array: '数组',
object: '对象'
};
export class DataScope {
// 指向父级
parent?: DataScope;
@ -213,7 +222,10 @@ export class DataScope {
value: schema.title === '成员' ? '' : path.value,
path: schema.title === '成员' ? '' : path.label,
type: schema.type,
tag: schema.type,
tag:
schema.typeLabel ??
DATASCHEMA_TYPE_MAP[schema.type as string] ??
schema.type,
description: schema.description,
isMember,
disabled: schema.title === '成员'

View File

@ -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,6 +144,15 @@ 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] === '$') {

View File

@ -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;

View File

@ -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];
}
});
@ -1741,6 +1743,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[],

View File

@ -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;
};

View File

@ -1,6 +1,6 @@
{
"name": "amis-editor-core",
"version": "5.4.1",
"version": "5.4.7",
"description": "amis 可视化编辑器",
"main": "lib/index.js",
"module": "esm/index.js",

View File

@ -15,7 +15,7 @@ import IFrameBridge from './IFrameBridge';
import {isAlive} from 'mobx-state-tree';
import {findTree} from 'amis-core';
import BackTop from './base/BackTop';
import {RendererConfig} from 'amis-core';
import type {RendererConfig} from 'amis-core';
export interface PreviewProps {
// isEditorEnabled?: (

View File

@ -267,5 +267,8 @@ extendLocale('en-US', {
'b71e8739cb9bcba8057a8fa8b59512da': '<Column>',
'34d240d1ded259c32f7fee21e99c5671': 'Preview/Edit',
'6722089d251c1e4aabe9d58c26a2208a': 'Turn preview mode on/off',
'107542a61cdccbb2981ad0c8bafc3440': 'Replace component'
'107542a61cdccbb2981ad0c8bafc3440': 'Replace component',
'813a5158d9f7171d20e7df340c5b48f9': 'Component Context',
'd2655fdad2c9f8b6f6d7050d65226428':
"Exception registering plugin '{{@1}}', there is already a plugin with the same name:"
});

View File

@ -239,5 +239,7 @@ extendLocale('zh-CN', {
'b71e8739cb9bcba8057a8fa8b59512da': '<列>',
'34d240d1ded259c32f7fee21e99c5671': '预览/编辑',
'6722089d251c1e4aabe9d58c26a2208a': '开启/关闭预览模式',
'107542a61cdccbb2981ad0c8bafc3440': '替换组件'
'107542a61cdccbb2981ad0c8bafc3440': '替换组件',
'813a5158d9f7171d20e7df340c5b48f9': '组件上下文',
'd2655fdad2c9f8b6f6d7050d65226428': '注册插件「{{@1}}」异常,已存在同名插件:'
});

View File

@ -11,13 +11,13 @@ const tpls: {
export function getSchemaTpl(
name: string,
patch?: object,
rendererSchema?: any
options?: object
): any {
const tpl = tpls[name] || {};
let schema = null;
if (typeof tpl === 'function') {
schema = tpl(patch, rendererSchema);
schema = tpl(patch, options);
} else {
schema = patch
? {

View File

@ -11,6 +11,8 @@ import type {Schema} from 'amis';
import type {SchemaObject} from 'amis';
import {assign, cloneDeep} from 'lodash';
import {getGlobalData} from 'amis-theme-editor-helper';
import {isExpression, resolveVariableAndFilter} from 'amis-core';
import type {VariableItem} from 'amis-ui';
const {
guid,
@ -958,7 +960,9 @@ export function jsonToJsonSchema(
title: titleBuilder?.(type, key) || key,
...(type === 'object'
? jsonToJsonSchema(value, titleBuilder, maxDepth - 1)
: {items: jsonToJsonSchema(value[0], titleBuilder, maxDepth - 1)})
: typeof value[0] === 'object'
? {items: jsonToJsonSchema(value[0], titleBuilder, maxDepth - 1)}
: {})
};
} else {
jsonschema.properties[key] = {
@ -1130,3 +1134,90 @@ export function deleteThemeConfigData(data: any) {
return schemaData;
}
/**
* amis数据域中取变量数据
* @param node
* @param manager
* @returns
*/
export async function resolveVariablesFromScope(node: any, manager: any) {
await manager?.getContextSchemas(node);
// 获取当前组件内相关变量,如表单、增删改查
const dataPropsAsOptions: VariableItem[] = updateComponentContext(
(await manager?.dataSchema?.getDataPropsAsOptions()) ?? []
);
const variables: VariableItem[] =
manager?.variableManager?.getVariableFormulaOptions() || [];
return [...dataPropsAsOptions, ...variables].filter(
(item: any) => item.children?.length
);
}
/**
* props & amis数据域 variables
* @param that this
**/
export async function getVariables(that: any) {
let variablesArr: any[] = [];
const {variables, requiredDataPropsVariables} = that.props;
if (!variables || requiredDataPropsVariables) {
// 从amis数据域中取变量数据
const {node, manager} = that.props.formProps || that.props;
let vars = await resolveVariablesFromScope(node, manager);
if (Array.isArray(vars)) {
if (!that.isUnmount) {
variablesArr = vars;
}
}
}
if (variables) {
if (Array.isArray(variables)) {
variablesArr = [...variables, ...variablesArr];
} else if (typeof variables === 'function') {
variablesArr = [...variables(that), ...variablesArr];
} else if (isExpression(variables)) {
variablesArr = [
...resolveVariableAndFilter(
that.props.variables as any,
that.props.data,
'| raw'
),
...variablesArr
];
}
}
// 如果存在应用语言类型,则进行翻译
if (that.appLocale && that.appCorpusData) {
return translateSchema(variablesArr, that.appCorpusData);
}
return variablesArr;
}
/**
* label为带层级说明
* @param variables
* @returns
*/
export const updateComponentContext = (variables: any[]) => {
const items = [...variables];
const idx = items.findIndex(item => item.label === '组件上下文');
if (~idx) {
items.splice(idx, 1, {
...items[idx],
children: items[idx].children.map((child: any, index: number) => ({
...child,
label:
index === 0
? `当前层${child.label ? '(' + child.label + ')' : ''}`
: `${index}${child.label ? '(' + child.label + ')' : ''}`
}))
});
}
return items;
};

View File

@ -7,7 +7,14 @@ import sortBy from 'lodash/sortBy';
import cloneDeep from 'lodash/cloneDeep';
import reverse from 'lodash/reverse';
import pick from 'lodash/pick';
import {JSONSchema, DataSchema, mapTree, findTree, eachTree} from 'amis-core';
import {
JSONSchema,
DataSchema,
mapTree,
findTree,
eachTree,
DATASCHEMA_TYPE_MAP
} from 'amis-core';
import type {Option} from 'amis-core';
export interface VariableGroup {
@ -169,7 +176,7 @@ export class VariableManager {
const children = mapTree(varScope.getDataPropsAsOptions(), item => ({
...item,
/** tag默认会被赋值为description这里得替换回来 */
tag: item.type
tag: DATASCHEMA_TYPE_MAP[item.type] ?? item.type
}));
if (varScope.tag) {
@ -228,11 +235,11 @@ export class VariableManager {
getPageVariablesOptions() {
let options: Option[] = [];
const pageScope = this.dataSchema?.root.children?.filter(
item => item.tag === '页面变量'
)[0];
if (pageScope) {
options = pageScope.getDataPropsAsOptions();
const rootScope = this.dataSchema?.root;
if (rootScope) {
options = rootScope
.getDataPropsAsOptions()
.filter((item: any) => ['__query', '__page'].includes(item.value));
}
eachTree(options, item => {
if (item.type === 'array') {

View File

@ -160,6 +160,33 @@ const schemas = [
}
}
}
},
{
type: 'object',
properties: {
__query: {
title: '页面入参',
type: 'object',
required: [],
properties: {
name: {
type: 'string',
title: '用户名'
}
}
},
__page: {
title: '页面变量',
type: 'object',
required: [],
properties: {
num: {
type: 'number',
title: '数量'
}
}
}
}
}
];
@ -632,6 +659,11 @@ export default class AMisSchemaEditor extends React.Component<any, any> {
replaceText
} as any
}
ctx={{
__page: {
num: 2
}
}}
/>
);
}

View File

@ -1,6 +1,6 @@
{
"name": "amis-editor",
"version": "5.4.1",
"version": "5.4.7",
"description": "amis 可视化编辑器",
"main": "lib/index.js",
"module": "esm/index.js",

View File

@ -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'; // 日志
// 其他

View File

@ -4067,5 +4067,60 @@ extendLocale('en-US', {
'800dfdd90200bd47bb4bb83def4fea56': 'today',
'a6a93b404bc039cded728683af5d625d': 'Shortcut Key Name',
'快去添加事件,让你的产品动起来吧':
'Quickly add events and get your product moving'
'Quickly add events and get your product moving',
'50f198f07fc820a4911d1c97a0ceb8c2': 'context',
'6142a89066ca7dd6a1ce9493462c5aca': 'Selected row records',
'21bd0846bd8aa2296c597a1c1ff8e1a7': 'No row records selected',
'8f98291c9fa89c0bfce463c0a2eaf97c': 'Column Name',
'c35c1a13309c6f9da9837857517e65fc': 'Sort Values',
'e125986c2ba6783c4297ffe5405cc8bc': 'Filter Values',
'caafbcb52c70ad0bbbbf127ee7b08b89': 'Search Values',
'64ef585f778c9d1b010e86b032398ab6': 'Sorted records',
'db9df54392e408520ca12c6a56113b5a': 'Current displayed column configuration',
'bf2a4fd8ecd654982e2d466f10f54d3f': 'Current row record',
'85f1708454f409855d552f702ac27b19':
'The current data field can be used to read the corresponding value through the field name',
'e48d65cda774019d9a6677354bc781f2': 'Selected value',
'10d23d1f68ee1facb03b1f86678aa2ba': 'Status value',
'ed85c3f659acc9e89bcf6f0dbaa02a62': 'Current code content',
'e26f6832d586f9e73d2361573bf5273f': 'The value of the combination item',
'91190195405845950230616929d852cf': 'Deleted index',
'1b5cf3e354142cc1cdd6f56b6afaba49':
'The current form data can be read from the corresponding value through the field name',
'b06216eac0df52f6072a8adb095f72b7': 'Current City',
'a610ef4a4fbe8f0c8b756162acfb6186': 'current date',
'05606badb4b14ffd3c38c278fb0f3c9f': 'Current time range',
'de3ad0cd57153f799f7538dd1e4fd441': 'Excel parsed data',
'6d829f061ed82a688f2669c54dd83301': 'current value',
'bc0689a4c353e9c95c5b7fc5aa49b59f': 'Current slider value',
'9b0c6dee9b5f48734c37901d4a430b71': 'Current score',
'd84017fa76584f7475e26f79767df28d': 'Add Index',
'9d776ddd9dd2d8d85ea225df9c27e929': 'Current label value',
'4ed30a5be1b6680e6cc9fec0965d0f4f': 'Selected labels',
'e01315f74dee36831d93a117cbc47c8f': 'Label List',
'd5c135b5a4aed5dc39ef846a6f502d4f': 'Current text content',
'2eda8e3f67e2c6e02e63d27978530ec2': 'Changing node values',
'b4e54cb84d448952a4aa1a17ceaa6ad3': 'Selected row records',
'b2a18e08b0b0e0fd7e80554b89244aa0': 'Option List',
'c46f27dcf45a345993f1cbb63380fa98': 'Activated Index',
'f7daf85b4501d9d2aa048f85618b3f1f': 'Current text content',
'e5369f1a5e8d2b3e64eeb627e69c4e9b': 'Selected node values',
'28387ec7d7fd160541e7901d9f0a900d': 'The currently selected value',
'94935dfa6c9b908515a593956ee7d07c': 'Selected row records',
'f10b94a4ac77878be53fad599a761928': 'No row records selected',
'6c200daeb748ecce2c730d01837d3508': 'Initial tab',
'bd749c7a75af1236325d8d669e9bc5fc':
'The tab activated during component initialization has higher priority than the activated tab and cannot respond to contextual data. When configuring the hash for the tab, use hash, otherwise use index values, and support obtaining variables, such as<code>tab ${id}</code>,<code> ${id}</code>',
'8b4de52c23ad472b9ece9e30d8750c48': 'Initial default activated tab',
'7806807651c37e4467f9d2fc1c18eb2a': 'Active Tabs',
'8a59e0a5705fea1751d77a97b7bf5d8d':
'By default, a certain tab is displayed, which can respond to contextual data. When configuring a hash for a tab, use hash. Otherwise, use index values, and support obtaining variables, such as<code>tab ${id}</code>,<code> ${id}</code>',
'27e0d57c4412bcb89e6aaeeb1e5935fe': 'Default activated tab',
'8d1903162d2a50d6321819c3fcc1f2f6': '{{@1}} ({{@2}} action input)',
'd5fb02425d3b8586d8d7b98971d63e68': 'Event Action',
'5e3406cb54f255dc1be5edbaa6f87389': 'Arrange by',
'60e237a1b5e9a4cc3633898d527d5a38': 'Label width',
'813a5158d9f7171d20e7df340c5b48f9': 'Component Context',
'8b3fd9147a07d27ad95c0ba2594fb67a': 'Current data field {{@1}}',
'0383d6f467ed0dd89860a7b8cc793ce9': 'Upper {{@1}} layer {{@2}}'
});

View File

@ -3644,5 +3644,61 @@ extendLocale('zh-CN', {
'4622a4ce221f9b79aa3396cc461adc75': '清空组件数据',
'800dfdd90200bd47bb4bb83def4fea56': '今天',
'a6a93b404bc039cded728683af5d625d': '快捷键名称',
'快去添加事件,让你的产品动起来吧': '快去添加事件,让你的产品动起来吧'
'快去添加事件,让你的产品动起来吧': '快去添加事件,让你的产品动起来吧',
'50f198f07fc820a4911d1c97a0ceb8c2': '上下文',
'6142a89066ca7dd6a1ce9493462c5aca': '已选择行记录',
'21bd0846bd8aa2296c597a1c1ff8e1a7': '未选择行记录',
'8f98291c9fa89c0bfce463c0a2eaf97c': '列名',
'c35c1a13309c6f9da9837857517e65fc': '排序值',
'e125986c2ba6783c4297ffe5405cc8bc': '筛选值',
'caafbcb52c70ad0bbbbf127ee7b08b89': '搜索值',
'64ef585f778c9d1b010e86b032398ab6': '已排序记录',
'db9df54392e408520ca12c6a56113b5a': '当前显示的列配置',
'bf2a4fd8ecd654982e2d466f10f54d3f': '当前行记录',
'85f1708454f409855d552f702ac27b19': '当前数据域,可以通过.字段名读取对应的值',
'e48d65cda774019d9a6677354bc781f2': '选中的值',
'10d23d1f68ee1facb03b1f86678aa2ba': '状态值',
'ed85c3f659acc9e89bcf6f0dbaa02a62': '当前代码内容',
'e26f6832d586f9e73d2361573bf5273f': '组合项的值',
'91190195405845950230616929d852cf': '被删除的索引',
'1b5cf3e354142cc1cdd6f56b6afaba49':
'当前表单数据,可以通过.字段名读取对应的值',
'b06216eac0df52f6072a8adb095f72b7': '当前城市',
'a610ef4a4fbe8f0c8b756162acfb6186': '当前日期',
'05606badb4b14ffd3c38c278fb0f3c9f': '当前时间范围',
'de3ad0cd57153f799f7538dd1e4fd441': 'excel解析后的数据',
'6d829f061ed82a688f2669c54dd83301': '当前数值',
'bc0689a4c353e9c95c5b7fc5aa49b59f': '当前滑块值',
'9b0c6dee9b5f48734c37901d4a430b71': '当前分值',
'd84017fa76584f7475e26f79767df28d': '新增索引',
'9d776ddd9dd2d8d85ea225df9c27e929': '当前标签值',
'4ed30a5be1b6680e6cc9fec0965d0f4f': '选中的标签',
'e01315f74dee36831d93a117cbc47c8f': '标签列表',
'd5c135b5a4aed5dc39ef846a6f502d4f': '当前文本内容',
'2eda8e3f67e2c6e02e63d27978530ec2': '变化的节点值',
'b4e54cb84d448952a4aa1a17ceaa6ad3': '选中的行记录',
'b2a18e08b0b0e0fd7e80554b89244aa0': '选项列表',
'c46f27dcf45a345993f1cbb63380fa98': '激活的索引',
'f7daf85b4501d9d2aa048f85618b3f1f': '当前的文本内容',
'e5369f1a5e8d2b3e64eeb627e69c4e9b': '选中的节点值',
'28387ec7d7fd160541e7901d9f0a900d': '当前选中的值',
'94935dfa6c9b908515a593956ee7d07c': '已选行记录',
'f10b94a4ac77878be53fad599a761928': '未选行记录',
'6c200daeb748ecce2c730d01837d3508': '初始选项卡',
'bd749c7a75af1236325d8d669e9bc5fc':
'组件初始化时激活的选项卡优先级高于激活的选项卡不可响应上下文数据选项卡配置hash时使用hash否则使用索引值支持获取变量<code>tab\\${id}</code>、<code>\\${id}</code>',
'8b4de52c23ad472b9ece9e30d8750c48': '初始默认激活的选项卡',
'7806807651c37e4467f9d2fc1c18eb2a': '激活的选项卡',
'8a59e0a5705fea1751d77a97b7bf5d8d':
'默认显示某个选项卡可响应上下文数据选项卡配置hash时使用hash否则使用索引值支持获取变量<code>tab\\${id}</code>、<code>\\${id}</code>',
'27e0d57c4412bcb89e6aaeeb1e5935fe': '默认激活的选项卡',
'e83caa4c6ef82543603609c916cd804d':
'可基于 JavaScript 语言直接录入发送适配器的函数体,在该函数体内,您可以对 <span style="color: #108CEE">api</span> 进行处理或者返回新的内容,最后需要 <span style="color: #108CEE">return</span> <span style="color: #108CEE">api</span>。<br><br/>\n 函数体内可访问的变量如下:<br/>\n &nbsp;1. <span style="color: #108CEE">api</span>接口的schema配置对象<br/>\n &nbsp;2. <span style="color: #108CEE">api.data</span>:请求数据<br/>\n &nbsp;3. <span style="color: #108CEE">api.query</span>:请求查询参数<br/>\n &nbsp;4. <span style="color: #108CEE">api.headers</span>:请求头<br/>\n &nbsp;5. <span style="color: #108CEE">api.url</span>:请求地址<br/>',
'8d1903162d2a50d6321819c3fcc1f2f6': '{{@1}}({{@2}}动作出参)',
'd5fb02425d3b8586d8d7b98971d63e68': '事件动作',
'5e3406cb54f255dc1be5edbaa6f87389': '排列方式',
'60e237a1b5e9a4cc3633898d527d5a38': '标签宽度',
'813a5158d9f7171d20e7df340c5b48f9': '组件上下文',
'8b3fd9147a07d27ad95c0ba2594fb67a': '当前数据域{{@1}}',
'0383d6f467ed0dd89860a7b8cc793ce9': '上{{@1}}层{{@2}}'
});

View File

@ -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)
})
]
}
]);
};

View File

@ -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 | number',
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)
})
]
}
])
];

View File

@ -236,7 +236,7 @@ export class ContainerPlugin extends LayoutBasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
}
]);

View File

@ -8,6 +8,7 @@ import {
} from 'amis-editor-core';
import {getSchemaTpl, defaultValue} from 'amis-editor-core';
import {getEventControlConfig} from '../../renderer/event-control/helper';
import {ValidatorTag} from '../../validator';
export class ButtonGroupControlPlugin extends BasePlugin {
static id = 'ButtonGroupControlPlugin';
@ -137,7 +138,8 @@ export class ButtonGroupControlPlugin extends BasePlugin {
},
getSchemaTpl('status', {
isFormItem: true
})
}),
getSchemaTpl('validation', {tag: ValidatorTag.MultiSelect})
])
]
},

View File

@ -148,14 +148,14 @@ export class CheckboxControlPlugin extends BasePlugin {
needDeleteProps: ['option'],
label: '默认勾选',
rendererWrapper: true, // 浅色线框包裹一下,增加边界感
valueType: 'boolean',
pipeIn: (value: any, data: any) => {
return value === (data?.data?.trueValue ?? true);
},
pipeOut: (value: any, origin: any, data: any) => {
const {trueValue = true, falseValue = false} = data;
return value ? trueValue : falseValue;
}
valueType: 'boolean'
// pipeIn: (value: any, data: any) => {
// return value === (data?.data?.trueValue ?? true);
// },
// pipeOut: (value: any, origin: any, data: any) => {
// const {trueValue = true, falseValue = false} = data;
// return value ? trueValue : falseValue;
// }
}),
getSchemaTpl('labelRemark'),
getSchemaTpl('remark'),

View File

@ -372,7 +372,7 @@ export class GridPlugin extends BasePlugin {
getSchemaTpl('subFormItemMode'),
getSchemaTpl('subFormHorizontalMode'),
getSchemaTpl('subFormHorizontal'),
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
]
}
@ -550,7 +550,7 @@ export class GridPlugin extends BasePlugin {
title: '外观',
body: [
getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
]
}

View File

@ -77,7 +77,7 @@ export class IFramePlugin extends BasePlugin {
})
]
},
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
]
}

View File

@ -212,7 +212,7 @@ export class FlexPluginBase extends LayoutBasePlugin {
title: '外观',
className: 'p-none',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
}
])

View File

@ -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';

View File

@ -292,7 +292,7 @@ export class PagePlugin extends BasePlugin {
className: 'p-none',
body: [
getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', ['layout'])
...getSchemaTpl('theme:common', {exclude: ['layout']})
])
]
},

View File

@ -208,7 +208,14 @@ export class TagPlugin extends BasePlugin {
label: '状态',
value: 'status'
}
]
],
onChange: (value: any, origin: any, item: any, form: any) => {
if (value !== 'status') {
form.setValues({
icon: undefined
});
}
}
},
getSchemaTpl('icon', {
visibleOn: 'data.displayMode === "status"',

View File

@ -240,7 +240,10 @@ export class TplPlugin extends BasePlugin {
{
title: '外观',
body: getSchemaTpl('collapseGroup', [
...getSchemaTpl('theme:common', ['layout'], ['font'])
...getSchemaTpl('theme:common', {
exclude: ['layout'],
include: ['font']
})
])
},
{

View File

@ -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&nbsp;</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">,&nbsp;</span>'])
];
})
.flat(),
'<span class="mtk1 bracket-highlighting-0">)&nbsp;{</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&nbsp;</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">,&nbsp;</span>'])
];
})
.flat(),
'<span class="mtk1 bracket-highlighting-0">)&nbsp;{</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() {
@ -397,9 +404,8 @@ setSchemaTpl('apiRequestAdaptor', {
&nbsp;1. <span style="color: #108CEE">api</span>schema配置对象<br/>
&nbsp;2. <span style="color: #108CEE">api.data</span><br/>
&nbsp;3. <span style="color: #108CEE">api.query</span><br/>
&nbsp;4. <span style="color: #108CEE">api.body</span>POST/PUT/PATCH<br/>
&nbsp;5. <span style="color: #108CEE">api.headers</span><br/>
&nbsp;6. <span style="color: #108CEE">api.url</span><br/>`
&nbsp;4. <span style="color: #108CEE">api.headers</span><br/>
&nbsp;5. <span style="color: #108CEE">api.url</span><br/>`
),
name: 'requestAdaptor',
type: 'ae-apiAdaptorControl',

View File

@ -8,9 +8,9 @@ import cx from 'classnames';
import {FormItem, Button, Icon, PickerContainer} from 'amis';
import {FormulaEditor} from 'amis-ui';
import type {VariableItem} from 'amis-ui';
import {getVariables} from './textarea-formula/utils';
import {renderFormulaValue} from './FormulaControl';
import {reaction} from 'mobx';
import {getVariables} from 'amis-editor-core';
interface ExpressionFormulaControlProps extends FormControlProps {
/**
@ -74,10 +74,6 @@ export default class ExpressionFormulaControl extends React.Component<
async () => {
this.appLocale = editorStore?.appLocale;
this.appCorpusData = editorStore?.appCorpusData;
const variablesArr = await getVariables(this);
this.setState({
variables: variablesArr
});
}
);
}

View File

@ -3,16 +3,12 @@
*/
import React from 'react';
import debounce from 'lodash/debounce';
import uniqBy from 'lodash/uniqBy';
import isNumber from 'lodash/isNumber';
import isBoolean from 'lodash/isBoolean';
import isPlainObject from 'lodash/isPlainObject';
import isArray from 'lodash/isArray';
import isString from 'lodash/isString';
import isEqual from 'lodash/isEqual';
import omit from 'lodash/omit';
import last from 'lodash/last';
import cx from 'classnames';
import {
FormItem,
@ -34,11 +30,11 @@ import type {
VariableItem,
FuncGroup
} from 'amis-ui/lib/components/formula/Editor';
import {dataMapping, FormControlProps} from 'amis-core';
import {FormControlProps} from 'amis-core';
import type {BaseEventContext} from 'amis-editor-core';
import {EditorManager} from 'amis-editor-core';
import {reaction} from 'mobx';
import {getVariables} from './textarea-formula/utils';
import {getVariables} from 'amis-editor-core';
export enum FormulaDateType {
NotDate, // 不是时间类
@ -191,10 +187,6 @@ export default class FormulaControl extends React.Component<
async () => {
this.appLocale = editorStore?.appLocale;
this.appCorpusData = editorStore?.appCorpusData;
const variablesArr = await getVariables(this);
this.setState({
variables: variablesArr
});
}
);
}

View File

@ -10,11 +10,11 @@ import type {VariableItem, CodeMirror} from 'amis-ui';
import {Icon, Button, FormItem, TooltipWrapper} from 'amis';
import {autobind, FormControlProps} from 'amis-core';
import {FormulaPlugin, editorFactory} from './textarea-formula/plugin';
import {getVariables} from './textarea-formula/utils';
import {renderFormulaValue} from './FormulaControl';
import FormulaPicker, {
CustomFormulaPickerProps
} from './textarea-formula/FormulaPicker';
import {getVariables} from 'amis-editor-core';
export interface TplFormulaControlProps extends FormControlProps {
/**
@ -112,10 +112,6 @@ export class TplFormulaControl extends React.Component<
async () => {
this.appLocale = editorStore?.appLocale;
this.appCorpusData = editorStore?.appCorpusData;
const variablesArr = await getVariables(this);
this.setState({
variables: variablesArr
});
}
);

View File

@ -485,7 +485,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
)
},
{
name: 'args',
name: 'dialog',
label: '弹框内容',
mode: 'horizontal',
required: true,

View File

@ -41,7 +41,7 @@ export * from './helper';
import {i18n as _i18n} from 'i18n-runtime';
import type {VariableItem} from 'amis-ui/lib/components/formula/Editor';
import {reaction} from 'mobx';
import {updateComponentContext} from '../../util';
import {updateComponentContext} from 'amis-editor-core';
interface EventControlProps extends FormControlProps {
actions: PluginActions; // 组件的动作列表
@ -502,9 +502,14 @@ export class EventControl extends React.Component<
// 收集当前事件动作出参
let actions = onEvent[data.actionData!.eventKey].actions;
// 编辑的时候只能拿到当前动作前面动作的事件变量
// 编辑的时候只能拿到当前动作前面动作的事件变量以及当前动作事件
if (data.type === 'update') {
actions = actions.slice(0, data.actionData!.actionIndex);
actions = actions.slice(
0,
data.actionData!.actionIndex !== undefined
? data.actionData!.actionIndex + 1
: 0
);
}
let jsonSchema = {...(eventConfig?.dataSchema?.[0] ?? {})};

View File

@ -11,9 +11,9 @@ import type {VariableItem, CodeMirror} from 'amis-ui';
import {FormulaPlugin, editorFactory} from './plugin';
import FormulaPicker, {CustomFormulaPickerProps} from './FormulaPicker';
import {getVariables} from './utils';
import {reaction} from 'mobx';
import {renderFormulaValue} from '../FormulaControl';
import {getVariables} from 'amis-editor-core';
export interface AdditionalMenuClickOpts {
/**
@ -163,10 +163,6 @@ export class TextareaFormulaControl extends React.Component<
async () => {
this.appLocale = editorStore?.appLocale;
this.appCorpusData = editorStore?.appCorpusData;
const variablesArr = await getVariables(this);
this.setState({
variables: variablesArr
});
}
);

View File

@ -1,67 +0,0 @@
import {isExpression, resolveVariableAndFilter} from 'amis-core';
import {translateSchema} from 'amis-editor-core';
import type {VariableItem} from 'amis-ui/lib/components/formula/Editor';
import {updateComponentContext} from '../../util';
/**
* amis数据域中取变量数据
* @param node
* @param manager
* @returns
*/
export async function resolveVariablesFromScope(node: any, manager: any) {
await manager?.getContextSchemas(node);
// 获取当前组件内相关变量,如表单、增删改查
const dataPropsAsOptions: VariableItem[] = updateComponentContext(
(await manager?.dataSchema?.getDataPropsAsOptions()) ?? []
);
const variables: VariableItem[] =
manager?.variableManager?.getVariableFormulaOptions() || [];
return [...dataPropsAsOptions, ...variables].filter(
(item: any) => item.children?.length
);
}
/**
* props & amis数据域 variables
* @param that this
**/
export async function getVariables(that: any) {
let variablesArr: any[] = [];
const {variables, requiredDataPropsVariables} = that.props;
if (!variables || requiredDataPropsVariables) {
// 从amis数据域中取变量数据
const {node, manager} = that.props.formProps || that.props;
let vars = await resolveVariablesFromScope(node, manager);
if (Array.isArray(vars)) {
if (!that.isUnmount) {
variablesArr = vars;
}
}
}
if (variables) {
if (Array.isArray(variables)) {
variablesArr = [...variables, ...variablesArr];
} else if (typeof variables === 'function') {
variablesArr = [...variables(that), ...variablesArr];
} else if (isExpression(variables)) {
variablesArr = [
...resolveVariableAndFilter(
that.props.variables as any,
that.props.data,
'| raw'
),
...variablesArr
];
}
}
// 如果存在应用语言类型,则进行翻译
if (that.appLocale && that.appCorpusData) {
return translateSchema(variablesArr, that.appCorpusData);
}
return variablesArr;
}

View File

@ -360,7 +360,9 @@ setSchemaTpl(
return {
type: 'collapse-group',
activeKey: collapseGroupBody.map(panel => panel.title),
activeKey: collapseGroupBody
.filter(item => item && !item.collapsed)
.map(panel => panel.title),
expandIconPosition: 'right',
expandIcon: {
type: 'icon',

View File

@ -24,6 +24,7 @@ setSchemaTpl('leftFixed', {
type: 'button-group-select',
visibleOn: 'data.horizontal && data.horizontal.leftFixed',
label: '宽度',
size: 'xs',
options: [
{
label: '小',
@ -58,13 +59,31 @@ setSchemaTpl('leftRate', {
}
});
setSchemaTpl('labelAlign', {
name: 'labelAlign',
type: 'button-group-select',
visibleOn: 'data.horizontal && data.horizontal.leftFixed',
label: '排列方式',
size: 'xs',
options: [
{
label: '左对齐',
value: 'left'
},
{
label: '右对齐',
value: 'right'
}
]
});
setSchemaTpl(
'horizontal',
(config: {visibleOn: string; [propName: string]: any}) => {
return [
{
type: 'select',
label: '标题宽度',
label: '标宽度',
name: 'horizontal',
options: [
{label: '继承', value: 'formHorizontal'},
@ -93,13 +112,15 @@ setSchemaTpl(
visibleOn: 'this.mode == "horizontal" && this.label !== false',
...(isObject(config) ? config : {})
},
{
type: 'container',
className: 'ae-ExtendMore mb-3',
getSchemaTpl('layout:wrapper-contanier', {
visibleOn:
'this.mode == "horizontal" && this.horizontal && this.label !== false',
body: [getSchemaTpl('leftFixed'), getSchemaTpl('leftRate')]
}
body: [
getSchemaTpl('leftFixed'),
getSchemaTpl('leftRate'),
getSchemaTpl('labelAlign')
]
})
];
}
);

View File

@ -593,7 +593,13 @@ setSchemaTpl('theme:size', (option: any = {}) => {
setSchemaTpl(
'theme:common',
(exclude: string[] | string, include: string[]) => {
(option: {
exclude: string[] | string;
include: string[];
collapsed?: boolean;
}) => {
let {exclude, include, collapsed} = option || {};
const curCollapsed = collapsed ?? false; // 默认都展开
// key统一转换成Kebab caseeg: boxShadow => bos-shadow
exclude = (
exclude ? (Array.isArray(exclude) ? exclude : [exclude]) : []
@ -631,6 +637,7 @@ setSchemaTpl(
{
header: '布局',
key: 'layout',
collapsed: curCollapsed,
body: [
{
type: 'style-display',
@ -641,10 +648,12 @@ setSchemaTpl(
},
{
title: '自定义样式',
collapsed: curCollapsed,
body: styles
},
{
title: '样式源码',
collapsed: curCollapsed,
body: [
{
type: 'theme-cssCode',

View File

@ -42,26 +42,3 @@ export const schemaToArray = (value: any) => {
export const schemaArrayFormat = (value: any) => {
return value && Array.isArray(value) && value.length === 1 ? value[0] : value;
};
/**
* label为带层级说明
* @param variables
* @returns
*/
export const updateComponentContext = (variables: any[]) => {
const items = [...variables];
const idx = items.findIndex(item => item.label === '组件上下文');
if (~idx) {
items.splice(idx, 1, {
...items[idx],
children: items[idx].children.map((child: any, index: number) => ({
...child,
label:
index === 0
? `当前数据域${child.label ? '(' + child.label + ')' : ''}`
: `${index}${child.label ? '(' + child.label + ')' : ''}`
}))
});
}
return items;
};

View File

@ -4,72 +4,74 @@
用法:`IF(condition, consequent, alternate)`
* `condition:expression` 条件表达式.
* `condition:expression` 条件表达式。例如:语文成绩>80
* `consequent:any` 条件判断通过的返回结果
* `alternate:any` 条件判断不通过的返回结果
返回:`any` 根据条件返回不同的结果
示例IF(A, B, C)
如果满足条件condition则返回consequent否则返回alternate支持多层嵌套IF函数。
如果满足条件A则返回B否则返回C支持多层嵌套IF函数。
也可以用表达式如A ? B : C
等价于直接用JS表达式如condition ? consequent : alternate。
### AND
用法:`AND(expression1, expression2, ...expressionN)`
* `conditions:...expression` 条件表达式.
* `conditions:...expression` 条件表达式,多个用逗号隔开。例如:语文成绩>80, 数学成绩>80
返回:`boolean`
条件全部符合,返回 true否则返回 false
条件全部符合,返回 true否则返回 false
示例AND(语文成绩>80, 数学成绩>80)
示例AND(语文成绩>80, 数学成绩>80)
语文成绩和数学成绩都大于 80则返回 true否则返回 false
语文成绩和数学成绩都大于 80则返回 true否则返回 false
也可以直接用表达式如:语文成绩>80 && 数学成绩>80
等价于直接用JS表达式如:语文成绩>80 && 数学成绩>80
### OR
用法:`OR(expression1, expression2, ...expressionN)`
* `conditions:...expression` 条件表达式.
* `conditions:...expression` 条件表达式,多个用逗号隔开。例如:语文成绩>80, 数学成绩>80
返回:`boolean`
条件任意一个满足条件,返回 true否则返回 false
条件任意一个满足条件,返回 true否则返回 false
示例OR(语文成绩>80, 数学成绩>80)
示例OR(语文成绩>80, 数学成绩>80)
语文成绩和数学成绩任意一个大于 80则返回 true否则返回 false
语文成绩和数学成绩任意一个大于 80则返回 true否则返回 false
也可以直接用表达式如:语文成绩>80 || 数学成绩>80
等价于直接用JS表达式如:语文成绩>80 || 数学成绩>80
### XOR
用法:`XOR(condition1, condition2)`
用法:`XOR(condition1, condition2, ...expressionN)`
* `condition1:expression` 条件表达式1
* `condition2:expression` 条件表达式2
* `condition:...expression` 条件表达式,多个用逗号隔开。例如:语文成绩>80, 数学成绩>80
返回:`boolean`
异或处理,多个表达式组中存在奇数个真时认为真。
示例XOR(语文成绩 > 80, 数学成绩 > 80, 英语成绩 > 80)
三门成绩中有一门或者三门大于 80则返回 true否则返回 false。
### IFS
用法:`IFS(condition1, result1, condition2, result2,...conditionN, resultN)`
* `args:...any` 条件,返回值集合
* `condition:...expression` 条件表达式
* `result:...any` 返回值
返回:`any` 第一个满足条件的结果,没有命中的返回 false。
判断函数集合,相当于多个 else if 合并成一个。
示例IFS(语文成绩 > 80, "优秀", 语文成绩 > 60, "良", "继续努力")
示例IFS(语文成绩 > 80, "优秀", 语文成绩 > 60, "良", "继续努力")
如果语文成绩大于 80则返回优秀否则判断大于 60 分,则返回良,否则返回继续努力。
@ -83,7 +85,7 @@
返回:`number` 传入数值的绝对值
返回传入数字的绝对值
返回传入数字的绝对值
### MAX
@ -93,7 +95,7 @@
返回:`number` 所有传入值中最大的那个
获取最大值,如果只有一个参数且是数组,则计算这个数组内的值
获取最大值,如果只有一个参数且是数组,则计算这个数组内的值
### MIN
@ -103,7 +105,7 @@
返回:`number` 所有传入值中最小的那个
获取最小值,如果只有一个参数且是数组,则计算这个数组内的值
获取最小值,如果只有一个参数且是数组,则计算这个数组内的值
### SUM
@ -113,7 +115,7 @@
返回:`number` 所有传入数值的总和
求和,如果只有一个参数且是数组,则计算这个数组内的值
求和,如果只有一个参数且是数组,则计算这个数组内的值
### INT
@ -123,7 +125,7 @@
返回:`number` 数值对应的整形
将数值向下取整为最接近的整数
将数值向下取整为最接近的整数
### MOD
@ -134,20 +136,20 @@
返回:`number` 两数相除的余数
返回两数相除的余数,参数 number 是被除数divisor 是除数
返回两数相除的余数,参数 number 是被除数divisor 是除数
### PI
用法:`PI()`
圆周率 3.1415...
圆周率 3.1415...
### ROUND
用法:`ROUND(num[, numDigits = 2])`
* `num:number` 要处理的数字
* `numDigits:number` 小数位数
* `numDigits:number` 小数位数默认为2
返回:`number` 传入数值四舍五入后的结果
@ -158,7 +160,7 @@
用法:`FLOOR(num[, numDigits=2])`
* `num:number` 要处理的数字
* `numDigits:number` 小数位数
* `numDigits:number` 小数位数默认为2
返回:`number` 传入数值向下取整后的结果
@ -169,7 +171,7 @@
用法:`CEIL(num[, numDigits=2])`
* `num:number` 要处理的数字
* `numDigits:number` 小数位数
* `numDigits:number` 小数位数默认为2
返回:`number` 传入数值向上取整后的结果
@ -193,7 +195,7 @@
返回:`number` 所有数值的平均值
返回所有参数的平均值,如果只有一个参数且是数组,则计算这个数组内的值
返回所有参数的平均值,如果只有一个参数且是数组,则计算这个数组内的值
### DEVSQ
@ -203,7 +205,7 @@
返回:`number` 所有数值的平均值
返回数据点与数据均值点之差(数据偏差)的平方和,如果只有一个参数且是数组,则计算这个数组内的值
返回数据点与数据均值点之差(数据偏差)的平方和,如果只有一个参数且是数组,则计算这个数组内的值
### AVEDEV
@ -213,7 +215,7 @@
返回:`number` 所有数值的平均值
数据点到其算术平均值的绝对偏差的平均值
数据点到其算术平均值的绝对偏差的平均值
### HARMEAN
@ -223,7 +225,7 @@
返回:`number` 所有数值的平均值
数据点的调和平均值,如果只有一个参数且是数组,则计算这个数组内的值
数据点的调和平均值,如果只有一个参数且是数组,则计算这个数组内的值
### LARGE
@ -234,7 +236,7 @@
返回:`number` 所有数值的平均值
数据集中第 k 个最大值
数据集中第 k 个最大值
### UPPERMONEY
@ -244,7 +246,7 @@
返回:`string` 数值中文大写字符
将数值转为中文大写金额
将数值转为中文大写金额
### RAND
@ -252,9 +254,9 @@
返回大于等于 0 且小于 1 的均匀分布随机实数。每一次触发计算都会变化。
示例:`RAND()*100`
示例:`RAND()*100`
返回 0-100 之间的随机数
返回 0-100 之间的随机数
### LAST
@ -264,7 +266,7 @@
返回:`any` 最后一个值
取数据最后一个
取数据最后一个
## 文本函数
@ -298,7 +300,7 @@
返回:`number` 长度
计算文本的长度
计算文本的长度
### LENGTH
@ -308,7 +310,7 @@
返回:`Array<number>` 长度集合
计算文本集合中所有文本的长度
计算文本集合中所有文本的长度
### ISEMPTY
@ -318,7 +320,7 @@
返回:`boolean` 判断结果
判断文本是否为空
判断文本是否为空
### CONCATENATE
@ -328,7 +330,7 @@
返回:`string` 连接后的文本
将多个传入值连接成文本
将多个传入值连接成文本
### CHAR
@ -340,7 +342,7 @@
返回计算机字符集的数字代码所对应的字符。
`CHAR(97)` 等价于 "a"
示例:`CHAR(97)` 等价于 "a"。
### LOWER
@ -350,7 +352,7 @@
返回:`string` 结果文本
将传入文本转成小写
将传入文本转成小写
### UPPER
@ -360,7 +362,7 @@
返回:`string` 结果文本
将传入文本转成大写
将传入文本转成大写
### UPPERFIRST
@ -370,7 +372,7 @@
返回:`string` 结果文本
将传入文本首字母转成大写
将传入文本首字母转成大写
### PADSTART
@ -382,11 +384,11 @@
返回:`string` 结果文本
向前补齐文本长度
向前补齐文本长度
示例 `PADSTART("6", 2, "0")`
示例 `PADSTART("6", 2, "0")`
返回 `06`
返回 `06`
### CAPITALIZE
@ -396,11 +398,11 @@
返回:`string` 结果文本
将文本转成标题
将文本转成标题
示例 `CAPITALIZE("star")`
示例 `CAPITALIZE("star")`
返回 `Star`
返回 `Star`
### ESCAPE
@ -410,11 +412,11 @@
返回:`string` 结果文本
对文本进行 HTML 转义
对文本进行 HTML 转义
示例 `ESCAPE("<star>&")`
示例 `ESCAPE("<star>&")`
返回 `&lt;start&gt;&amp;`
返回 `&lt;start&gt;&amp;`
### TRUNCATE
@ -425,11 +427,11 @@
返回:`string` 结果文本
对文本长度进行截断
对文本长度进行截断
示例 `TRUNCATE("amis.baidu.com", 6)`
示例 `TRUNCATE("amis.baidu.com", 6)`
返回 `amis...`
返回 `amis...`
### BEFORELAST
@ -440,7 +442,7 @@
返回:`string` 判断结果
取在某个分隔符之前的所有字符串
取在某个分隔符之前的所有字符串
### SPLIT
@ -451,11 +453,11 @@
返回:`Array<string>` 文本集
将文本根据指定片段分割成数组
将文本根据指定片段分割成数组
示例:`SPLIT("a,b,c", ",")`
示例:`SPLIT("a,b,c", ",")`
返回 `["a", "b", "c"]`
返回 `["a", "b", "c"]`
### TRIM
@ -465,7 +467,7 @@
返回:`string` 处理后的文本
将文本去除前后空格
将文本去除前后空格
### STRIPTAG
@ -475,11 +477,11 @@
返回:`string` 处理后的文本
去除文本中的 HTML 标签
去除文本中的 HTML 标签
示例:`STRIPTAG("<b>amis</b>")`
示例:`STRIPTAG("<b>amis</b>")`
返回:`amis`
返回:`amis`
### LINEBREAK
@ -489,11 +491,11 @@
返回:`string` 处理后的文本
将字符串中的换行转成 HTML `<br>`,用于简单换行的场景
将字符串中的换行转成 HTML `<br>`,用于简单换行的场景
示例:`LINEBREAK("\n")`
示例:`LINEBREAK("\n")`
返回:`<br/>`
返回:`<br/>`
### STARTSWITH
@ -504,7 +506,7 @@
返回:`string` 判断结果
判断字符串(text)是否以特定字符串(startString)开始,是则返回 True否则返回 False
判断字符串(text)是否以特定字符串(startString)开始,是则返回 true否则返回 false。
### ENDSWITH
@ -515,7 +517,7 @@
返回:`string` 判断结果
判断字符串(text)是否以特定字符串(endString)结束,是则返回 True否则返回 False
判断字符串(text)是否以特定字符串(endString)结束,是则返回 true否则返回 false。
### CONTAINS
@ -526,7 +528,7 @@
返回:`string` 判断结果
判断参数 1 中的文本是否包含参数 2 中的文本。
判断参数 1 中的文本是否包含参数 2 中的文本,是则返回 true否则返回 false
### REPLACE
@ -550,7 +552,7 @@
返回:`number` 命中的位置
对文本进行搜索,返回命中的位置
对文本进行搜索,返回命中的位置
### MID
@ -562,7 +564,7 @@
返回:`number` 命中的位置
返回文本字符串中从指定位置开始的特定数目的字符
返回文本字符串中从指定位置开始的特定数目的字符
### BASENAME
@ -572,11 +574,11 @@
返回:`string` 文件名
返回路径中的文件名
返回路径中的文件名
示例:`/home/amis/a.json`
示例:`/home/amis/a.json`
返回a.json`
返回a.json`
## 日期函数
@ -586,25 +588,25 @@
创建日期对象,可以通过特定格式的字符串,或者数值。
需要注意的是其中月份的数值是从0开始的也就是说,
如果是12月份你应该传入数值11。
需要注意的是其中月份的数值是从0开始的
如果是12月份你应该传入数值11。
### TIMESTAMP
用法:`TIMESTAMP(date, 'x')`
用法:`TIMESTAMP(date[, format = "X"])`
* `date:date` 日期对象
* `format:string` 时间戳格式,带毫秒传入 'x'。默认为 'X' 不带毫秒的。
返回:`number` 时间戳
返回时间的时间戳
返回时间的时间戳
### TODAY
用法:`TODAY()`
返回今天的日期
返回今天的日期
### NOW
@ -621,11 +623,11 @@
返回:`number` 星期几的数字标识
获取日期的星期几
获取日期的星期几
示例
示例
WEEKDAY('2023-02-27') 得到 1
WEEKDAY('2023-02-27') 得到 1
### WEEK
@ -636,11 +638,11 @@ WEEKDAY('2023-02-27') 得到 1
返回:`number` 星期几的数字标识
获取年份的星期,即第几周
获取年份的星期,即第几周
示例
示例
WEEK('2023-03-05') 得到 10
WEEK('2023-03-05') 得到 10
### DATETOSTR
@ -651,14 +653,14 @@ WEEK('2023-03-05') 得到 10
返回:`string` 日期字符串
对日期、日期字符串、时间戳进行格式化
对日期、日期字符串、时间戳进行格式化
示例
示例
DATETOSTR('12/25/2022', 'YYYY-MM-DD') 得到 '2022.12.25'
DATETOSTR(1676563200, 'YYYY.MM.DD') 得到 '2023.02.17'
DATETOSTR(1676563200000, 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATETOSTR(DATE('2021-12-21'), 'YYYY.MM.DD hh:mm:ss') 得到 '2021.12.21 08:00:00'
DATETOSTR('12/25/2022', 'YYYY-MM-DD') 得到 '2022.12.25'
DATETOSTR(1676563200, 'YYYY.MM.DD') 得到 '2023.02.17'
DATETOSTR(1676563200000, 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATETOSTR(DATE('2021-12-21'), 'YYYY.MM.DD hh:mm:ss') 得到 '2021.12.21 08:00:00'
### DATERANGESPLIT
@ -671,16 +673,16 @@ DATETOSTR(DATE('2021-12-21'), 'YYYY.MM.DD hh:mm:ss') 得到 '2021.12.21 08:00:00
返回:`string` 日期字符串
获取日期范围字符串中的开始时间、结束时间
获取日期范围字符串中的开始时间、结束时间
示例:
DATERANGESPLIT('1676563200, 1676735999') 得到 [1676563200, 1676735999]
DATERANGESPLIT('1676563200, 1676735999', undefined , 'YYYY.MM.DD hh:mm:ss') 得到 [2023.02.17 12:00:00, 2023.02.18 11:59:59]
DATERANGESPLIT('1676563200, 1676735999', 0 , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATERANGESPLIT('1676563200, 1676735999', 'start' , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATERANGESPLIT('1676563200, 1676735999', 1 , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.18 11:59:59'
DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.18 11:59:59'
DATERANGESPLIT('1676563200, 1676735999') 得到 [1676563200, 1676735999]
DATERANGESPLIT('1676563200, 1676735999', undefined , 'YYYY.MM.DD hh:mm:ss') 得到 [2023.02.17 12:00:00, 2023.02.18 11:59:59]
DATERANGESPLIT('1676563200, 1676735999', 0 , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATERANGESPLIT('1676563200, 1676735999', 'start' , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.17 12:00:00'
DATERANGESPLIT('1676563200, 1676735999', 1 , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.18 11:59:59'
DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '2023.02.18 11:59:59'
### STARTOF
@ -692,7 +694,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`date` 新的日期对象
返回日期的指定范围的开端
返回日期的指定范围的开端
### ENDOF
@ -704,7 +706,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`date` 新的日期对象
返回日期的指定范围的末尾
返回日期的指定范围的末尾
### YEAR
@ -714,7 +716,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回日期的年份
返回日期的年份
### MONTH
@ -734,7 +736,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回日期的天
返回日期的天
### HOUR
@ -744,7 +746,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回日期的小时
返回日期的小时
### MINUTE
@ -754,7 +756,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回日期的分
返回日期的分
### SECOND
@ -764,7 +766,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回日期的秒
返回日期的秒
### YEARS
@ -775,7 +777,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回两个日期相差多少年
返回两个日期相差多少年
### MINUTES
@ -786,7 +788,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回两个日期相差多少分钟
返回两个日期相差多少分钟
### DAYS
@ -797,7 +799,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回两个日期相差多少天
返回两个日期相差多少天
### HOURS
@ -808,7 +810,7 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`number` 数值
返回两个日期相差多少小时
返回两个日期相差多少小时
### DATEMODIFY
@ -820,11 +822,11 @@ DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') 得到 '
返回:`date` 日期对象
修改日期,对日期进行加减天、月份、年等操作
修改日期,对日期进行加减天、月份、年等操作
示例:
DATEMODIFY(A, -2, 'month')
DATEMODIFY(A, -2, 'month')
对日期 A 进行往前减2月的操作。
@ -851,7 +853,7 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 判断结果
判断两个日期,是否第一个日期在第二个日期的前面
判断两个日期,是否第一个日期在第二个日期的前面,是则返回 true否则返回 false。
### ISAFTER
@ -863,7 +865,7 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 判断结果
判断两个日期,是否第一个日期在第二个日期的后面
判断两个日期,是否第一个日期在第二个日期的后面,是则返回 true否则返回 false。
### BETWEENRANGE
@ -876,9 +878,9 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 判断结果
判断日期是否在指定范围内
判断日期是否在指定范围内,是则返回 true否则返回 false。
示例BETWEENRANGE('2021/12/6', ['2021/12/5','2021/12/7'])
示例BETWEENRANGE('2021/12/6', ['2021/12/5','2021/12/7'])
### ISSAMEORBEFORE
@ -890,7 +892,7 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 判断结果
判断两个日期,是否第一个日期在第二个日期的前面或者相等
判断两个日期,是否第一个日期在第二个日期的前面或者相等,是则返回 true否则返回 false。
### ISSAMEORAFTER
@ -902,7 +904,7 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 判断结果
判断两个日期,是否第一个日期在第二个日期的后面或者相等
判断两个日期,是否第一个日期在第二个日期的后面或者相等,是则返回 true否则返回 false。
## 数组
@ -914,7 +916,7 @@ DATEMODIFY(A, -2, 'month')
返回:`boolean` 结果
返回数组的长度
返回数组的长度
### ARRAYMAP
@ -953,7 +955,7 @@ DATEMODIFY(A, -2, 'month')
示例:
ARRAYFINDINDEX([0, 2, false], item => item === 2) 得到 1
ARRAYFINDINDEX([0, 2, false], item => item === 2) 得到 1
### ARRAYFIND
@ -969,7 +971,7 @@ ARRAYFINDINDEX([0, 2, false], item => item === 2) 得到 1
示例:
ARRAYFIND([0, 2, false], item => item === 2) 得到 2
ARRAYFIND([0, 2, false], item => item === 2) 得到 2
### ARRAYSOME
@ -981,11 +983,11 @@ ARRAYFIND([0, 2, false], item => item === 2) 得到 2
返回:`boolean` 结果
数据做数据遍历判断,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
判断第二个箭头函数是否存在返回为 true 的成员。
判断第二个箭头函数是否存在返回为 true 的成员,是则返回 true否则返回 false
示例:
ARRAYSOME([0, 2, false], item => item === 2) 得到 true
ARRAYSOME([0, 2, false], item => item === 2) 得到 true
### ARRAYEVERY
@ -997,7 +999,7 @@ ARRAYSOME([0, 2, false], item => item === 2) 得到 true
返回:`boolean` 结果
数据做数据遍历判断,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
判断第二个箭头函数返回是否都为 true。
判断第二个箭头函数返回是否都为 true,是则返回 true否则返回 false
示例:
@ -1012,11 +1014,11 @@ ARRAYEVERY([0, 2, false], item => item === 2) 得到 false
返回:`any` 结果
判断数据中是否存在指定元素
判断数据中是否存在指定元素
示例:
ARRAYINCLUDES([0, 2, false], 2) 得到 true
ARRAYINCLUDES([0, 2, false], 2) 得到 true
### COMPACT
@ -1026,11 +1028,11 @@ ARRAYINCLUDES([0, 2, false], 2) 得到 true
返回:`Array<any>` 结果
数组过滤掉 false、null、0 和 ""
数组过滤掉 false、null、0 和 ""
示例:
COMPACT([0, 1, false, 2, '', 3]) 得到 [1, 2, 3]
COMPACT([0, 1, false, 2, '', 3]) 得到 [1, 2, 3]
### JOIN
@ -1041,11 +1043,11 @@ COMPACT([0, 1, false, 2, '', 3]) 得到 [1, 2, 3]
返回:`String` 结果
数组转成字符串
数组转成字符串
示例:
JOIN(['a', 'b', 'c'], '=') 得到 'a=b=c'
JOIN(['a', 'b', 'c'], '=') 得到 'a=b=c'
### CONCAT
@ -1055,11 +1057,11 @@ JOIN(['a', 'b', 'c'], '=') 得到 'a=b=c'
返回:`Array<any>` 结果
数组合并
数组合并
示例:
CONCAT(['a', 'b', 'c'], ['1'], ['3']) 得到 ['a', 'b', 'c', '1', '3']
CONCAT(['a', 'b', 'c'], ['1'], ['3']) 得到 ['a', 'b', 'c', '1', '3']
### UNIQ
@ -1070,11 +1072,11 @@ CONCAT(['a', 'b', 'c'], ['1'], ['3']) 得到 ['a', 'b', 'c', '1', '3']
返回:`Array<any>` 结果
数组去重第二个参数「field」可指定根据该字段去重
数组去重第二个参数「field」可指定根据该字段去重
示例:
UNIQ([{a: '1'}, {b: '2'}, {a: '1'}] 'id')
UNIQ([{a: '1'}, {b: '2'}, {a: '1'}] 'id')
## 编码
@ -1086,11 +1088,11 @@ UNIQ([{a: '1'}, {b: '2'}, {a: '1'}] 'id')
返回:`string` 结果
将JS对象转换成JSON字符串
将JS对象转换成JSON字符串
示例:
ENCODEJSON({name: 'amis'}) 得到 '{"name":"amis"}'
ENCODEJSON({name: 'amis'}) 得到 '{"name":"amis"}'
### DECODEJSON
@ -1100,11 +1102,11 @@ ENCODEJSON({name: 'amis'}) 得到 '{"name":"amis"}'
返回:`object` 结果
解析JSON编码数据返回JS对象
解析JSON编码数据返回JS对象
示例:
DECODEJSON('{\"name\": "amis"}') 得到 {name: 'amis'}
DECODEJSON('{\"name\": "amis"}') 得到 {name: 'amis'}
## 其他
@ -1118,15 +1120,15 @@ DECODEJSON('{\"name\": "amis"}') 得到 {name: 'amis'}
返回:`any` 结果
根据对象或者数组的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代
根据对象或者数组的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代
示例:
GET([0, 2, {name: 'amis', age: 18}], 1) 得到 2
GET([0, 2, {name: 'amis', age: 18}], '2.name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr[0].name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr.0.name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr.1.name', 'not-found') 得到 'not-found'
GET([0, 2, {name: 'amis', age: 18}], 1) 得到 2
GET([0, 2, {name: 'amis', age: 18}], '2.name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr[0].name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr.0.name') 得到 'amis'
GET({arr: [{name: 'amis', age: 18}]}, 'arr.1.name', 'not-found') 得到 'not-found'
### ISTYPE

File diff suppressed because it is too large Load Diff

View File

@ -498,14 +498,12 @@ export class Evaluator {
}
/**
* IF(A, B, C)
* conditionconsequentalternateIF函数
*
* ABCIF函数
*
* A ? B : C
* JS表达式如condition ? consequent : alternate
*
* @example IF(condition, consequent, alternate)
* @param {expression} condition - .
* @param {expression} condition >80
* @param {any} consequent
* @param {any} alternate
* @namespace
@ -517,16 +515,16 @@ export class Evaluator {
}
/**
* true false
* true false
*
* AND(>80, >80)
* AND(>80, >80)
*
* 80 true false
* 80 true false
*
* >80 && >80
* JS表达式>80 && >80
*
* @example AND(expression1, expression2, ...expressionN)
* @param {...expression} conditions - .
* @param {...expression} conditions >80, >80
* @namespace
*
* @returns {boolean}
@ -536,16 +534,16 @@ export class Evaluator {
}
/**
* true false
* true false
*
* OR(>80, >80)
* OR(>80, >80)
*
* 80 true false
* 80 true false
*
* >80 || >80
* JS表达式>80 || >80
*
* @example OR(expression1, expression2, ...expressionN)
* @param {...expression} conditions - .
* @param {...expression} conditions >80, >80
* @namespace
*
* @returns {boolean}
@ -557,9 +555,12 @@ export class Evaluator {
/**
*
*
* @example XOR(condition1, condition2)
* @param {expression} condition1 - 1
* @param {expression} condition2 - 2
* XOR( > 80, > 80, > 80)
*
* 80 true false
*
* @example XOR(condition1, condition2, ...expressionN)
* @param {...expression} condition >80, >80
* @namespace
*
* @returns {boolean}
@ -571,12 +572,13 @@ export class Evaluator {
/**
* else if
*
* IFS( > 80, "优秀", > 60, "良", "继续努力")
* IFS( > 80, "优秀", > 60, "良", "继续努力")
*
* 80 60
*
* @example IFS(condition1, result1, condition2, result2,...conditionN, resultN)
* @param {...any} args -
* @param {...expression} condition
* @param {...any} result
* @namespace
* @returns {any} false
*/
@ -597,7 +599,7 @@ export class Evaluator {
}
/**
*
*
*
* @example ABS(num)
* @param {number} num -
@ -611,7 +613,7 @@ export class Evaluator {
}
/**
*
*
*
* @example MAX(num1, num2, ...numN)
* @param {...number} num -
@ -628,7 +630,7 @@ export class Evaluator {
}
/**
*
*
*
* @example MIN(num1, num2, ...numN)
* @param {...number} num -
@ -645,7 +647,7 @@ export class Evaluator {
}
/**
*
*
*
* @example SUM(num1, num2, ...numN)
* @param {...number} num -
@ -659,7 +661,7 @@ export class Evaluator {
}
/**
*
*
*
* @example INT(num)
* @param {number} num -
@ -672,7 +674,7 @@ export class Evaluator {
}
/**
* number divisor
* number divisor
*
* @example MOD(num, divisor)
* @param {number} num -
@ -686,7 +688,7 @@ export class Evaluator {
}
/**
* 3.1415...
* 3.1415...
*
* @example PI()
* @namespace
@ -702,7 +704,7 @@ export class Evaluator {
*
* @example ROUND(num[, numDigits = 2])
* @param {number} num -
* @param {number} numDigits -
* @param {number} numDigits - 2
* @namespace
*
* @returns {number}
@ -725,7 +727,7 @@ export class Evaluator {
*
* @example FLOOR(num[, numDigits=2])
* @param {number} num -
* @param {number} numDigits -
* @param {number} numDigits - 2
* @namespace
*
* @returns {number}
@ -748,7 +750,7 @@ export class Evaluator {
*
* @example CEIL(num[, numDigits=2])
* @param {number} num -
* @param {number} numDigits -
* @param {number} numDigits - 2
* @namespace
*
* @returns {number}
@ -780,7 +782,7 @@ export class Evaluator {
}
/**
*
*
*
* @example AVG(num1, num2, ...numN)
* @param {...number} num -
@ -799,7 +801,7 @@ export class Evaluator {
}
/**
*
*
*
* @example DEVSQ(num1, num2, ...numN)
* @param {...number} num -
@ -824,7 +826,7 @@ export class Evaluator {
}
/**
*
*
*
* @example AVEDEV(num1, num2, ...numN)
* @param {...number} num -
@ -851,7 +853,7 @@ export class Evaluator {
}
/**
*
*
*
* @example HARMEAN(num1, num2, ...numN)
* @param {...number} num -
@ -876,7 +878,7 @@ export class Evaluator {
}
/**
* k
* k
*
* @example LARGE(array, k)
* @param {array} nums -
@ -899,7 +901,7 @@ export class Evaluator {
}
/**
*
*
*
* @example UPPERMONEY(num)
* @param {number} num -
@ -950,9 +952,9 @@ export class Evaluator {
/**
* 0 1
*
* `RAND()*100`
* `RAND()*100`
*
* 0-100
* 0-100
*
* @example RAND()
* @namespace
@ -964,7 +966,7 @@ export class Evaluator {
}
/**
*
*
*
* @example LAST(array)
* @param {...number} arr -
@ -1017,7 +1019,7 @@ export class Evaluator {
}
/**
*
*
*
* @example LEN(text)
* @param {string} text -
@ -1031,7 +1033,7 @@ export class Evaluator {
}
/**
*
*
*
* @example LENGTH(textArr)
* @param {string[]} textArr -
@ -1044,7 +1046,7 @@ export class Evaluator {
}
/**
*
*
*
* @example ISEMPTY(text)
* @param {string} text -
@ -1057,7 +1059,7 @@ export class Evaluator {
}
/**
*
*
*
* @example CONCATENATE(text1, text2, ...textN)
* @param {...string} text -
@ -1072,7 +1074,7 @@ export class Evaluator {
/**
*
*
* `CHAR(97)` "a"
* `CHAR(97)` "a"
*
* @example CHAR(code)
* @param {number} code -
@ -1085,7 +1087,7 @@ export class Evaluator {
}
/**
*
*
*
* @example LOWER(text)
* @param {string} text -
@ -1099,7 +1101,7 @@ export class Evaluator {
}
/**
*
*
*
* @example UPPER(text)
* @param {string} text -
@ -1113,7 +1115,7 @@ export class Evaluator {
}
/**
*
*
*
* @example UPPERFIRST(text)
* @param {string} text -
@ -1127,11 +1129,11 @@ export class Evaluator {
}
/**
*
*
*
* `PADSTART("6", 2, "0")`
* `PADSTART("6", 2, "0")`
*
* `06`
* `06`
*
* @example PADSTART(text)
* @param {string} text -
@ -1147,11 +1149,11 @@ export class Evaluator {
}
/**
*
*
*
* `CAPITALIZE("star")`
* `CAPITALIZE("star")`
*
* `Star`
* `Star`
*
* @example CAPITALIZE(text)
* @param {string} text -
@ -1165,11 +1167,11 @@ export class Evaluator {
}
/**
* HTML
* HTML
*
* `ESCAPE("<star>&")`
* `ESCAPE("<star>&")`
*
* `&lt;start&gt;&amp;`
* `&lt;start&gt;&amp;`
*
* @example ESCAPE(text)
* @param {string} text -
@ -1183,11 +1185,11 @@ export class Evaluator {
}
/**
*
*
*
* `TRUNCATE("amis.baidu.com", 6)`
* `TRUNCATE("amis.baidu.com", 6)`
*
* `amis...`
* `amis...`
*
* @example TRUNCATE(text, 6)
* @param {string} text -
@ -1202,7 +1204,7 @@ export class Evaluator {
}
/**
*
*
*
* @example BEFORELAST(text, '.')
* @param {string} text -
@ -1217,11 +1219,11 @@ export class Evaluator {
}
/**
*
*
*
* `SPLIT("a,b,c", ",")`
* `SPLIT("a,b,c", ",")`
*
* `["a", "b", "c"]`
* `["a", "b", "c"]`
*
* @example SPLIT(text, ',')
* @param {string} text -
@ -1236,7 +1238,7 @@ export class Evaluator {
}
/**
*
*
*
* @example TRIM(text)
* @param {string} text -
@ -1250,11 +1252,11 @@ export class Evaluator {
}
/**
* HTML
* HTML
*
* `STRIPTAG("<b>amis</b>")`
* `STRIPTAG("<b>amis</b>")`
*
* `amis`
* `amis`
*
* @example STRIPTAG(text)
* @param {string} text -
@ -1268,11 +1270,11 @@ export class Evaluator {
}
/**
* HTML `<br>`
* HTML `<br>`
*
* `LINEBREAK("\n")`
* `LINEBREAK("\n")`
*
* `<br/>`
* `<br/>`
*
* @example LINEBREAK(text)
* @param {string} text -
@ -1286,7 +1288,7 @@ export class Evaluator {
}
/**
* (text)(startString) True False
* (text)(startString) true false
*
* @example STARTSWITH(text, '片段')
* @param {string} text -
@ -1305,7 +1307,7 @@ export class Evaluator {
}
/**
* (text)(endString) True False
* (text)(endString) true false
*
* @example ENDSWITH(text, '片段')
* @param {string} text -
@ -1324,7 +1326,7 @@ export class Evaluator {
}
/**
* 1 2
* 1 2 true false
*
* @example CONTAINS(text, searchText)
* @param {string} text -
@ -1374,7 +1376,7 @@ export class Evaluator {
}
/**
*
*
*
* @example SEARCH(text, search, 0)
* @param {string} text -
@ -1397,7 +1399,7 @@ export class Evaluator {
}
/**
*
*
*
* @example MID(text, from, len)
* @param {string} text -
@ -1413,11 +1415,11 @@ export class Evaluator {
}
/**
*
*
*
* `/home/amis/a.json`
* `/home/amis/a.json`
*
* a.json`
* a.json`
*
* @example BASENAME(text)
* @param {string} text -
@ -1435,8 +1437,8 @@ export class Evaluator {
/**
*
*
* 0
* 1211
* 0
* 1211
*
* @example DATE(2021, 11, 6, 8, 20, 0)
* @example DATE('2021-12-06 08:20:00')
@ -1460,10 +1462,9 @@ export class Evaluator {
}
/**
*
*
*
* @example TIMESTAMP(date[, format = "X"])
* @example TIMESTAMP(date, 'x')
* @namespace
* @param {date} date
* @param {string} format 'x' 'X'
@ -1478,7 +1479,7 @@ export class Evaluator {
}
/**
*
*
*
* @example TODAY()
* @namespace
@ -1502,11 +1503,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
*
* WEEKDAY('2023-02-27') 1
* WEEKDAY('2023-02-27') 1
*
* @example WEEKDAY(date)
* @namespace
@ -1521,11 +1522,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
*
* WEEK('2023-03-05') 10
* WEEK('2023-03-05') 10
*
* @example WEEK(date)
* @namespace
@ -1540,14 +1541,14 @@ export class Evaluator {
}
/**
*
*
*
*
*
*
* DATETOSTR('12/25/2022', 'YYYY-MM-DD') '2022.12.25'
* DATETOSTR(1676563200, 'YYYY.MM.DD') '2023.02.17'
* DATETOSTR(1676563200000, 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATETOSTR(DATE('2021-12-21'), 'YYYY.MM.DD hh:mm:ss') '2021.12.21 08:00:00'
* DATETOSTR('12/25/2022', 'YYYY-MM-DD') '2022.12.25'
* DATETOSTR(1676563200, 'YYYY.MM.DD') '2023.02.17'
* DATETOSTR(1676563200000, 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATETOSTR(DATE('2021-12-21'), 'YYYY.MM.DD hh:mm:ss') '2021.12.21 08:00:00'
*
* @example DATETOSTR(date, 'YYYY-MM-DD')
* @namespace
@ -1565,16 +1566,16 @@ export class Evaluator {
}
/**
*
*
*
*
*
* DATERANGESPLIT('1676563200, 1676735999') [1676563200, 1676735999]
* DATERANGESPLIT('1676563200, 1676735999', undefined , 'YYYY.MM.DD hh:mm:ss') [2023.02.17 12:00:00, 2023.02.18 11:59:59]
* DATERANGESPLIT('1676563200, 1676735999', 0 , 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATERANGESPLIT('1676563200, 1676735999', 'start' , 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATERANGESPLIT('1676563200, 1676735999', 1 , 'YYYY.MM.DD hh:mm:ss') '2023.02.18 11:59:59'
* DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') '2023.02.18 11:59:59'
* DATERANGESPLIT('1676563200, 1676735999') [1676563200, 1676735999]
* DATERANGESPLIT('1676563200, 1676735999', undefined , 'YYYY.MM.DD hh:mm:ss') [2023.02.17 12:00:00, 2023.02.18 11:59:59]
* DATERANGESPLIT('1676563200, 1676735999', 0 , 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATERANGESPLIT('1676563200, 1676735999', 'start' , 'YYYY.MM.DD hh:mm:ss') '2023.02.17 12:00:00'
* DATERANGESPLIT('1676563200, 1676735999', 1 , 'YYYY.MM.DD hh:mm:ss') '2023.02.18 11:59:59'
* DATERANGESPLIT('1676563200, 1676735999', 'end' , 'YYYY.MM.DD hh:mm:ss') '2023.02.18 11:59:59'
*
* @example DATERANGESPLIT(date, 'YYYY-MM-DD')
* @namespace
@ -1615,7 +1616,7 @@ export class Evaluator {
}
/**
*
*
*
* @namespace
* @example STARTOF(date[unit = "day"])
@ -1630,7 +1631,8 @@ export class Evaluator {
}
/**
*
*
*
* @namespace
* @example ENDOF(date[unit = "day"])
* @param {date} date
@ -1672,7 +1674,8 @@ export class Evaluator {
}
/**
*
*
*
* @namespace
* @example YEAR(date)
* @param {date} date
@ -1697,7 +1700,8 @@ export class Evaluator {
}
/**
*
*
*
* @namespace
* @example DAY(date)
* @param {date} date
@ -1709,7 +1713,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} date
* @namespace
* @example HOUR(date)
@ -1721,7 +1726,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} date
* @namespace
* @example MINUTE(date)
@ -1733,7 +1739,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} date
* @namespace
* @example SECOND(date)
@ -1745,7 +1752,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} endDate
* @param {date} startDate
* @namespace
@ -1759,7 +1767,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} endDate
* @param {date} startDate
* @namespace
@ -1773,7 +1782,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} endDate
* @param {date} startDate
* @namespace
@ -1787,7 +1797,8 @@ export class Evaluator {
}
/**
*
*
*
* @param {date} endDate
* @param {date} startDate
* @namespace
@ -1801,11 +1812,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
* DATEMODIFY(A, -2, 'month')
* DATEMODIFY(A, -2, 'month')
*
* A 2
*
@ -1837,7 +1848,7 @@ export class Evaluator {
}
/**
*
* true false
*
* @param {date} a
* @param {date} b
@ -1853,7 +1864,7 @@ export class Evaluator {
}
/**
*
* true false
*
* @param {date} a
* @param {date} b
@ -1869,9 +1880,9 @@ export class Evaluator {
}
/**
*
* true false
*
* BETWEENRANGE('2021/12/6', ['2021/12/5','2021/12/7'])
* BETWEENRANGE('2021/12/6', ['2021/12/5','2021/12/7'])
*
* @param {any} date
* @param {any[]} daterange
@ -1897,7 +1908,7 @@ export class Evaluator {
}
/**
*
* true false
*
* @param {date} a
* @param {date} b
@ -1913,7 +1924,7 @@ export class Evaluator {
}
/**
*
* true false
*
* @param {date} a
* @param {date} b
@ -1929,7 +1940,7 @@ export class Evaluator {
}
/**
*
*
*
* @param {Array<any>} arr
* @namespace
@ -1985,7 +1996,7 @@ export class Evaluator {
*
*
*
* ARRAYFINDINDEX([0, 2, false], item => item === 2) 1
* ARRAYFINDINDEX([0, 2, false], item => item === 2) 1
*
* @param {Array<any>} arr
* @param {Function<any>} iterator
@ -2009,7 +2020,7 @@ export class Evaluator {
*
*
*
* ARRAYFIND([0, 2, false], item => item === 2) 2
* ARRAYFIND([0, 2, false], item => item === 2) 2
*
* @param {Array<any>} arr
* @param {Function<any>} iterator
@ -2029,11 +2040,11 @@ export class Evaluator {
/**
* 使
* true
* true true false
*
*
*
* ARRAYSOME([0, 2, false], item => item === 2) true
* ARRAYSOME([0, 2, false], item => item === 2) true
*
* @param {Array<any>} arr
* @param {Function<any>} iterator
@ -2053,7 +2064,7 @@ export class Evaluator {
/**
* 使
* true
* true true false
*
*
*
@ -2076,11 +2087,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
* ARRAYINCLUDES([0, 2, false], 2) true
* ARRAYINCLUDES([0, 2, false], 2) true
*
* @param {Array<any>} arr
* @param {any} item
@ -2093,11 +2104,11 @@ export class Evaluator {
}
/**
* falsenull0 ""
* falsenull0 ""
*
*
*
* COMPACT([0, 1, false, 2, '', 3]) [1, 2, 3]
* COMPACT([0, 1, false, 2, '', 3]) [1, 2, 3]
*
* @param {Array<any>} arr
* @namespace
@ -2120,11 +2131,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
* JOIN(['a', 'b', 'c'], '=') 'a=b=c'
* JOIN(['a', 'b', 'c'], '=') 'a=b=c'
*
* @param {Array<any>} arr
* @param { String} separator
@ -2141,11 +2152,11 @@ export class Evaluator {
}
/**
*
*
*
*
*
* CONCAT(['a', 'b', 'c'], ['1'], ['3']) ['a', 'b', 'c', '1', '3']
* CONCAT(['a', 'b', 'c'], ['1'], ['3']) ['a', 'b', 'c', '1', '3']
*
* @param {Array<any>} arr
* @namespace
@ -2160,11 +2171,11 @@ export class Evaluator {
}
/**
* field
* field
*
*
*
* UNIQ([{a: '1'}, {b: '2'}, {a: '1'}] 'id')
* UNIQ([{a: '1'}, {b: '2'}, {a: '1'}] 'id')
*
* @param {Array<any>} arr
* @param {string} field
@ -2178,11 +2189,11 @@ export class Evaluator {
}
/**
* JS对象转换成JSON字符串
* JS对象转换成JSON字符串
*
*
*
* ENCODEJSON({name: 'amis'}) '{"name":"amis"}'
* ENCODEJSON({name: 'amis'}) '{"name":"amis"}'
*
* @param {object} obj JS对象
* @namespace
@ -2194,11 +2205,11 @@ export class Evaluator {
}
/**
* JSON编码数据JS对象
* JSON编码数据JS对象
*
*
*
* DECODEJSON('{\"name\": "amis"}') {name: 'amis'}
* DECODEJSON('{\"name\": "amis"}') {name: 'amis'}
*
* @param {string} str
* @namespace
@ -2210,15 +2221,15 @@ export class Evaluator {
}
/**
* path路径获取值 value undefined defaultValue
* path路径获取值 value undefined defaultValue
*
*
*
* GET([0, 2, {name: 'amis', age: 18}], 1) 2
* GET([0, 2, {name: 'amis', age: 18}], '2.name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr[0].name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr.0.name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr.1.name', 'not-found') 'not-found'
* GET([0, 2, {name: 'amis', age: 18}], 1) 2
* GET([0, 2, {name: 'amis', age: 18}], '2.name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr[0].name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr.0.name') 'amis'
* GET({arr: [{name: 'amis', age: 18}]}, 'arr.1.name', 'not-found') 'not-found'
*
* @param {any} obj
* @param {string} path

View File

@ -2,6 +2,7 @@
position: relative;
display: inline-flex;
vertical-align: middle;
flex-wrap: wrap;
> .#{$ns}Button {
position: relative;

View File

@ -198,6 +198,7 @@
width: px2rem(10px);
height: px2rem(10px);
top: 0;
transform: rotate(90deg);
}
}

View File

@ -196,7 +196,7 @@
&-sublist {
position: relative;
margin: 0 0 0 px2rem(35px);
margin: 0 0 0 px2rem(30px);
display: none;
}
@ -249,7 +249,7 @@
}
.#{$ns}TreeSelection-itemLabel {
margin-left: var(--gap-xs);
// margin-left: var(--gap-xs);
}
}
@ -445,10 +445,11 @@
width: px2rem(10px);
height: px2rem(10px);
top: 0;
transform: rotate(90deg);
}
}
&-input.is-active &-caret {
transform: rotate(180deg);
}
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>
);

View File

@ -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
});
}

View File

@ -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,

View File

@ -23,6 +23,7 @@ import {isMobile, ucFirst} from 'amis-core';
import CalendarMobile from './CalendarMobile';
import Input from './Input';
import type {PlainObject} from 'amis-core';
import type {RendererEnv} from 'amis-core';
const availableShortcuts: {[propName: string]: any} = {
now: {
@ -295,6 +296,7 @@ export interface DateProps extends LocaleProps, ThemeProps {
className?: string;
}>;
scheduleClassNames?: Array<string>;
env?: RendererEnv;
largeMode?: boolean;
todayActiveStyle?: React.CSSProperties;
onScheduleClick?: (scheduleData: any) => void;
@ -308,6 +310,9 @@ export interface DateProps extends LocaleProps, ThemeProps {
onBlur?: Function;
onRef?: any;
data?: any;
// 是否为结束时间
isEndDate?: boolean;
}
export interface DatePickerState {
@ -706,6 +711,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
clearable,
shortcuts,
utc,
isEndDate,
overlayPlacement,
locale,
format,
@ -720,7 +726,8 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
todayActiveStyle,
onScheduleClick,
mobileCalendarMode,
label
label,
env
} = this.props;
const __ = this.props.translate;
@ -746,6 +753,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
viewMode === 'quarters' || viewMode === 'months' ? 'years' : 'months'
}
timeConstraints={timeConstraints}
isEndDate={isEndDate}
/>
);
const CalendarMobileTitle = (
@ -806,11 +814,13 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
maxDate={maxDate}
// utc={utc}
schedules={schedulesData}
env={env}
largeMode={largeMode}
todayActiveStyle={todayActiveStyle}
onScheduleClick={onScheduleClick}
embed={embed}
useMobileUI={useMobileUI}
isEndDate={isEndDate}
/>
</div>
);
@ -898,6 +908,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
minDate={minDate}
maxDate={maxDate}
useMobileUI={useMobileUI}
isEndDate={isEndDate}
// utc={utc}
/>
</PopOver>

View File

@ -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>

View File

@ -251,6 +251,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
maxTagCount,
overflowTagPopover,
showArrow,
popOverContainer,
...rest
} = this.props;
const isFocused = this.state.isFocused;

View File

@ -7,7 +7,7 @@
import React from 'react';
import {ClassName, localeable, LocaleProps, Schema} from 'amis-core';
import Transition, {ENTERED, ENTERING} from 'react-transition-group/Transition';
import {themeable, ThemeProps} from 'amis-core';
import {themeable, ThemeProps, noop} from 'amis-core';
import {uncontrollable} from 'amis-core';
import {generateIcon, isObjectShallowModified} from 'amis-core';
import {autobind, guid} from 'amis-core';
@ -47,6 +47,7 @@ export interface TabProps extends ThemeProps {
eventKey: string | number;
prevKey?: string | number;
nextKey?: string | number;
tip?: string;
tab?: Schema;
className?: string;
activeKey?: string | number;
@ -137,10 +138,10 @@ class TabComponent extends React.PureComponent<TabProps> {
'Tabs-pane',
className
)}
onTouchStart={swipeable && mobileUI && this.onTouchStart}
onTouchMove={swipeable && mobileUI && this.onTouchMove}
onTouchEnd={swipeable && mobileUI && this.onTouchEnd}
onTouchCancel={swipeable && mobileUI && this.onTouchEnd}
onTouchStart={swipeable && mobileUI ? this.onTouchStart : noop}
onTouchMove={swipeable && mobileUI ? this.onTouchMove : noop}
onTouchEnd={swipeable && mobileUI ? this.onTouchEnd : noop}
onTouchCancel={swipeable && mobileUI ? this.onTouchEnd : noop}
>
{children}
</div>
@ -601,6 +602,7 @@ export class Tabs extends React.Component<TabsProps, any> {
toolbar,
tabClassName,
closable: tabClosable,
tip,
hash
} = child.props;
@ -660,13 +662,15 @@ export class Tabs extends React.Component<TabsProps, any> {
key={this.generateTabKey(hash, eventKey, index)}
onClick={() => (disabled ? '' : this.handleSelect(eventKey))}
onDoubleClick={() => {
editable && this.handleStartEdit(index, title);
editable &&
typeof title === 'string' &&
this.handleStartEdit(index, title);
}}
>
{showTip ? (
<TooltipWrapper
placement="top"
tooltip={title}
tooltip={tip ?? (typeof title === 'string' ? title : '')}
trigger="hover"
tooltipClassName={showTipClassName}
>

View File

@ -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}

View File

@ -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()
)
);
}

View File

@ -15,6 +15,7 @@ import {PickerOption} from '../PickerColumn';
import 'moment/locale/zh-cn';
import 'moment/locale/de';
import {isMobile} from 'amis-core';
import type {RendererEnv} from 'amis-core';
export type DateType =
| 'year'
@ -91,6 +92,7 @@ interface BaseDatePickerProps {
content: string | React.ReactElement;
color?: string;
}>;
env?: RendererEnv;
largeMode?: boolean;
todayActiveStyle?: React.CSSProperties;
onScheduleClick?: (scheduleData: any) => void;
@ -420,7 +422,8 @@ class BaseDatePicker extends React.Component<
'updateOn',
'useMobileUI',
'showToolbar',
'embed'
'embed',
'env'
].forEach(key => (props[key] = (this.props as any)[key]));
return props;

View File

@ -17,6 +17,7 @@ import {
convertArrayValueToMoment,
isMobile
} from 'amis-core';
import type {RendererEnv} from 'amis-core';
import Picker from '../Picker';
import {PickerOption} from '../PickerColumn';
import {DateType} from './Calendar';
@ -73,6 +74,7 @@ interface CustomDaysViewProps extends LocaleProps {
content: any;
className?: string;
}>;
env?: RendererEnv;
largeMode?: boolean;
todayActiveStyle?: React.CSSProperties;
onScheduleClick?: (scheduleData: any) => void;
@ -356,9 +358,17 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
this.props.onClose && this.props.onClose();
};
curfilterHtml = (content: any) => {
const {env} = this.props;
if (env?.filterHtml) {
return env.filterHtml(content);
}
return content;
};
renderDay = (props: any, currentDate: moment.Moment) => {
const {todayActiveStyle} = props; /** 只有today才会传入这个属性 */
const {classnames: cx, translate: __} = this.props;
const {classnames: cx, translate: __, env} = this.props;
const injectedProps = omit(props, ['todayActiveStyle']);
/** 某些情况下需要用inline style覆盖动态class需要hack important的样式 */
const todayDomRef = (node: HTMLSpanElement | null) => {
@ -462,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),
@ -476,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
@ -493,9 +508,12 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
this.props.onScheduleClick(scheduleData)
}
>
<div className={cx('ScheduleCalendar-text-overflow')}>
{item.content}
</div>
<div
className={cx('ScheduleCalendar-text-overflow')}
dangerouslySetInnerHTML={{
__html: this.curfilterHtml(item.content)
}}
></div>
</div>
);
});

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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}

View File

@ -196,7 +196,7 @@ export class FormulaEditor extends React.Component<
eachTree(variables, item => {
if (item.value) {
const key = item.value;
varMap[key] = item.label;
varMap[key] = item.path ?? item.label;
}
});
const vars = Object.keys(varMap)
@ -228,7 +228,7 @@ export class FormulaEditor extends React.Component<
if (reg.test(encodeHtml)) {
html = encodeHtml.replace(
REPLACE_KEY,
`<span class="c-field">${varMap[v]}</span>`
`<span class="c-field">${v}</span>`
);
} else {
html = encodeHtml.replace(REPLACE_KEY, v);
@ -243,7 +243,7 @@ export class FormulaEditor extends React.Component<
componentDidMount(): void {
const {variables} = this.props;
this.normalizeVariables(variables);
this.normalizeVariables(variables as VariableItem[]);
}
componentDidUpdate(
@ -252,7 +252,7 @@ export class FormulaEditor extends React.Component<
snapshot?: any
): void {
if (prevProps.variables !== this.props.variables) {
this.normalizeVariables(this.props.variables);
this.normalizeVariables(this.props.variables as VariableItem[]);
}
}

View File

@ -1,4 +1,8 @@
import {uncontrollable} from 'amis-core';
import {
isExpression,
resolveVariableAndFilterForAsync,
uncontrollable
} from 'amis-core';
import React from 'react';
import {
FormulaEditor,
@ -23,7 +27,8 @@ import Modal from '../Modal';
import PopUp from '../PopUp';
import {isMobile} from 'amis-core';
export interface FormulaPickerProps extends FormulaEditorProps {
export interface FormulaPickerProps
extends Omit<FormulaEditorProps, 'variables'> {
// 新的属性?
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
@ -124,7 +129,13 @@ export interface FormulaPickerProps extends FormulaEditorProps {
onRef?: (node: any) => void;
popOverContainer?: any;
useMobileUI?: boolean;
variables?:
| Array<VariableItem>
| string
| ((props: any) => Array<VariableItem>);
}
export interface FormulaPickerState {
@ -155,7 +166,8 @@ export class FormulaPicker extends React.Component<
isOpened: false,
value: this.props.value!,
editorValue: this.value2EditorValue(this.props),
isError: false
isError: false,
variables: Array.isArray(props.variables) ? props.variables : []
};
}
@ -254,6 +266,19 @@ export class FormulaPicker extends React.Component<
@autobind
async handleClick() {
const {variables, data} = this.props;
if (typeof variables === 'function') {
const list = await variables(this.props);
this.setState({variables: list});
} else if (typeof variables === 'string' && isExpression(variables)) {
const result = await resolveVariableAndFilterForAsync(
variables,
data,
'|raw'
);
this.setState({variables: result});
}
const state = {
...(await this.props.onPickerOpen?.(this.props)),
editorValue: this.value2EditorValue(this.props),
@ -328,7 +353,6 @@ export class FormulaPicker extends React.Component<
icon,
title,
clearable,
variables,
functions,
children,
variableMode,
@ -405,7 +429,7 @@ export class FormulaPicker extends React.Component<
? void 0
: FormulaEditor.highlightValue(
value,
variables!,
this.state.variables!,
this.props.evalMode
)
}
@ -448,7 +472,7 @@ export class FormulaPicker extends React.Component<
? void 0
: FormulaEditor.highlightValue(
value,
variables!,
this.state.variables!,
this.props.evalMode
)
}
@ -485,7 +509,7 @@ export class FormulaPicker extends React.Component<
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
variables={this.state.variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}
@ -516,7 +540,7 @@ export class FormulaPicker extends React.Component<
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
variables={this.state.variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}

View File

@ -207,7 +207,7 @@ export class FormulaPlugin {
eachTree(variables, item => {
if (item.value) {
varMap[item.value] = item.path ?? '';
varMap[item.value] = item.path ?? item.label;
}
});

View File

@ -64,7 +64,8 @@ export class MenuItem extends React.Component<MenuItemProps> {
'attribute',
'onMouseEnter',
'onMouseLeave',
'onClick'
'onClick',
'className'
];
/** 检查icon参数值是否为文件路径 */

View File

@ -209,17 +209,19 @@ export class SubMenu extends React.Component<SubMenuProps> {
}
render() {
const {popupClassName, classnames: cx, hidden} = this.props;
const {popupClassName, classnames: cx, hidden, className} = this.props;
const isDarkTheme = this.context.themeColor === 'dark';
return hidden ? null : (
<RcSubMenu
{...pick(this.props, this.internalProps)}
className={cx('Nav-Menu-submenu', {
['Nav-Menu-submenu-dark']: isDarkTheme
})}
className={cx(
'Nav-Menu-submenu',
{
['Nav-Menu-submenu-dark']: isDarkTheme
},
className
)}
popupClassName={cx(
'Nav-Menu-submenu-popup',
{
['Nav-Menu-submenu-popup-dark']: isDarkTheme
},

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