mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 表单项增加展示态 (#5391)
* feat: 表单项增加展示态 包括: 多选、单选、下拉列表、按钮选择、标签选择、时间选择类组件、进度条选择、时间范围选择类组件、列表选择器、数字输入框、评分、矩阵输入框、文本输入器、combo、inputKv、inputKvs、树选择器、级联选择器、穿梭器、城市选择器 * docs: 表单和表单项支持静态展示 * feat: 表单和表单项支持静态展示 - 微调 Co-authored-by: jinye <jinye@baidu.com>
This commit is contained in:
parent
fd02fcd7f4
commit
13b0fdf04d
@ -305,6 +305,181 @@ order: 1
|
||||
|
||||
> `visible`和`hidden`,`visibleOn`和`hiddenOn`除了判断逻辑相反以外,没有任何区别
|
||||
|
||||
### 配置静态展示
|
||||
|
||||
##### 静态配置
|
||||
|
||||
通过配置`"static": true`来将表单项以静态形式展示
|
||||
可以在[示例页](../../../examples/form/switchDisplay)查看支持静态展示的表单项的展示方式
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"label": "静态",
|
||||
"name": "text1",
|
||||
"value": "text1的值",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"type": "input-text",
|
||||
"label": "输入态",
|
||||
"name": "text2",
|
||||
"value": "text2的值"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 通过条件配置静态/输入态
|
||||
|
||||
也可以通过[表达式](../../../docs/concepts/expression)配置`staticOn`,来实现在某个条件下将当前表单项状态的的自动切换.
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-number",
|
||||
"label": "数量",
|
||||
"name": "number",
|
||||
"value": 0,
|
||||
"description": "调整数量大小查看效果吧!"
|
||||
},
|
||||
{
|
||||
"type": "input-text",
|
||||
"label": "文本",
|
||||
"name": "text",
|
||||
"staticOn": "this.number > 1",
|
||||
"value": "text value",
|
||||
"description": "当数量大于1的时候,该文本框会变成静态"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 自定义展示态的展示方式
|
||||
|
||||
通过配置`staticSchema`,可以自定义静态展示时的展示方式
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "var3",
|
||||
"label": "自定义展示态schema",
|
||||
"value": "表单项value",
|
||||
"static": true,
|
||||
"staticSchema": [
|
||||
"自定义前缀 | ",
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "${var3}"
|
||||
},
|
||||
" | 自定义后缀",
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 限制选择器类组件的展示数量
|
||||
|
||||
下拉选择器、多选框等组件,当选项过多静态展示时,若全部展示会占用页面很多空间,所以默认进行了限制(10个)
|
||||
可以通过配置`staticSchema.limit`,可以自定义静态展示时的数量
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"body": [
|
||||
{
|
||||
"type": "input-tag",
|
||||
"name": "tags",
|
||||
"label": "自定义展示数量",
|
||||
"value": "1,2,3,4,5,6,7,8",
|
||||
"options": [
|
||||
{"label": "选项1", "value": "选项1"},
|
||||
{"label": "选项2", "value": "选项2"},
|
||||
{"label": "选项3", "value": "选项3"},
|
||||
{"label": "选项4", "value": "选项4"},
|
||||
{"label": "选项5", "value": "选项5"},
|
||||
{"label": "选项6", "value": "选项6"},
|
||||
{"label": "选项7", "value": "选项7"},
|
||||
{"label": "选项8", "value": "选项8"}
|
||||
],
|
||||
"static": true,
|
||||
"staticSchema": {
|
||||
"limit": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 通过事件动作切换表单项状态
|
||||
|
||||
也支持使用 事件动作 切换表单项的 输入态和展示态(静态),也可以使用动作对整个表单进行状态切换
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "单个表单项状态切换",
|
||||
"mode": "horizontal",
|
||||
"labelWidth": 150,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"id": "formItemSwitch",
|
||||
"name": "var1",
|
||||
"label": "使用事件动作状态切换",
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
"type": 'button-toolbar',
|
||||
"name": 'button-toolbar',
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "输入态",
|
||||
"level": "primary",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "nonstatic",
|
||||
"componentId": "formItemSwitch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"label": "展示态",
|
||||
"level": "primary",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "static",
|
||||
"componentId": "formItemSwitch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"className": 'show'
|
||||
},
|
||||
],
|
||||
"actions": []
|
||||
}
|
||||
```
|
||||
|
||||
## 表单项值
|
||||
|
||||
表单项值,即表单项通过用户交互发生变化后,更新表单数据域中同`name`变量值.
|
||||
@ -1344,3 +1519,59 @@ fillMapping 配置 支持变量取值和表达式;
|
||||
| autoFill.size | `string` | | showSuggestion 为 true 时,参照录入 mode 为 dialog 时,可设置大小 |
|
||||
| autoFill.columns | `Array<Column>` | | showSuggestion 为 true 时,数据展示列配置 |
|
||||
| autoFill.filter | [SchemaNode](../../docs/types/schemanode) | | showSuggestion 为 true 时,数据查询过滤条件 |
|
||||
| static | `boolean` | | 当前表单项是否是静态展示,目前支持静[支持静态展示的表单项](#支持静态展示的表单项) |
|
||||
| staticClassName | `string` | | 静态展示时的类名 |
|
||||
| staticLabelClassName | `string` | | 静态展示时的Label的类名 |
|
||||
| staticInputClassName | `string` | | 静态展示时的value的类名 |
|
||||
| staticSchema | `string`|`Array`|[SchemaNode](../../docs/types/schemanode) | | 自定义静态展示方式 |
|
||||
| staticSchema.limit | `number` | 10 | select、checkboxes等选择类组件多选时展示态展示的数量 |
|
||||
|
||||
## 支持静态展示的表单项
|
||||
|
||||
可以在[示例页](../../../examples/form/switchDisplay)查看支持静态展示的表单项的展示方式
|
||||
|
||||
- form 表单
|
||||
- button-group-select 按钮点选
|
||||
- chained-select 链式下拉框
|
||||
- chart-radios 图表单选框
|
||||
- checkbox 勾选框
|
||||
- checkboxes 复选框
|
||||
- combo 组合
|
||||
- input-kv 键值对
|
||||
- input-array 数组输入框
|
||||
- input-city 城市选择器
|
||||
- input-color 颜色选择器
|
||||
- input-date 日期选择器
|
||||
- input-date-range 日期范围选择器
|
||||
- input-datetime-range 日期时间选择器
|
||||
- input-time-range 时间范围选择器
|
||||
- input-group 输入框组合
|
||||
- input-month-range 月份范围
|
||||
- input-number 数字输入
|
||||
- input-quarter-range 季度范围
|
||||
- input-range 滑块
|
||||
- input-rating 评分
|
||||
- input-tag 标签选择器
|
||||
- input-text 输入框
|
||||
- input-password 密码输入框
|
||||
- input-email 邮箱输入框
|
||||
- input-url url输入框
|
||||
- native-date native日期选择器
|
||||
- native-time native时间选择器
|
||||
- native-number native数字输入
|
||||
- input-tree 树形选择器
|
||||
- input-year-range 年份范围
|
||||
- list-select 列表选择器
|
||||
- location-picker 地理位置
|
||||
- matrix-checkboxes 矩阵勾选
|
||||
- nested-select 级联选择器
|
||||
- radios 单选框
|
||||
- select 下拉框
|
||||
- multi-select 多选下拉框
|
||||
- switch 开关
|
||||
- tabs-transfer 组合穿梭器
|
||||
- tabs-transfer-picker 组合穿梭选择器
|
||||
- textarea 多行输入框
|
||||
- transfer 穿梭器
|
||||
- transfer-picker 穿梭选择器
|
||||
- tree-select 属性选择器
|
||||
|
@ -568,6 +568,144 @@ Form 默认会在底部渲染一个提交按钮,用于执行表单的提交行
|
||||
|
||||
如果表单项较多导致表单过长,而不方便操作底部的按钮栏,可以配置`"affixFooter": true`属性,将底部按钮栏固定在浏览器底部
|
||||
|
||||
## 表单静态展示
|
||||
|
||||
在一些场景,表单提交后需要将填写的内容静态展示
|
||||
|
||||
### 设置初始状态
|
||||
|
||||
通过配置`static: true`将整个表单设置为静态展示,单个表单项也支持此配置
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "表单状态切换",
|
||||
"mode": "horizontal",
|
||||
"labelWidth": 150,
|
||||
"id": "allFormSwitch",
|
||||
"static": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "var1",
|
||||
"label": "输入框",
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
"type": "input-color",
|
||||
"name": "var2",
|
||||
"label": "颜色选择",
|
||||
"value": "#F0F"
|
||||
},
|
||||
{
|
||||
"type": "switch",
|
||||
"name": "switch",
|
||||
"label": "开关",
|
||||
"option": "开关说明",
|
||||
"value": true
|
||||
}
|
||||
],
|
||||
"actions": []
|
||||
}
|
||||
```
|
||||
|
||||
### 切换输入态和展示态
|
||||
|
||||
也支持使用[动作](#动作表)切换表单的 输入态和展示态(静态),也可以使用动作对单个表单项进行状态切换
|
||||
可以在[示例页](../../../examples/form/switchDisplay)查看表单项的静态展示方式
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "表单状态切换",
|
||||
"mode": "horizontal",
|
||||
"labelWidth": 150,
|
||||
"id": "allFormSwitch",
|
||||
"static": true,
|
||||
"data": {
|
||||
"isStatic": false
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
"name": "var1",
|
||||
"label": "输入框",
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
"type": "input-color",
|
||||
"name": "var2",
|
||||
"label": "颜色选择",
|
||||
"value": "#F0F"
|
||||
},
|
||||
{
|
||||
"type": "switch",
|
||||
"name": "switch",
|
||||
"label": "开关",
|
||||
"option": "开关说明",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": 'button-toolbar',
|
||||
"name": 'button-toolbar',
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "提交",
|
||||
"level": "primary",
|
||||
"visibleOn": "${!isStatic}",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "allFormSwitch",
|
||||
"args": {
|
||||
"value": {
|
||||
"isStatic": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"actionType": "static",
|
||||
"componentId": "allFormSwitch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"label": "编辑",
|
||||
"level": "primary",
|
||||
"visibleOn": "${isStatic}",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "allFormSwitch",
|
||||
"args": {
|
||||
"value": {
|
||||
"isStatic": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"actionType": "nonstatic",
|
||||
"componentId": "allFormSwitch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
"actions": []
|
||||
}
|
||||
```
|
||||
|
||||
## 表单项数据初始化
|
||||
|
||||
表单可以通过配置`initApi`,实现表单初始化时请求接口,用于展示数据或初始化表单项。
|
||||
@ -1297,7 +1435,9 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
| trimValues | `boolean` | `false` | trim 当前表单项的每一个值 |
|
||||
| promptPageLeave | `boolean` | `false` | form 还没保存,即将离开页面前是否弹框确认。 |
|
||||
| columnCount | `number` | 0 | 表单项显示为几列 |
|
||||
| inheritData | `booelan` | `true` | 默认表单是采用数据链的形式创建个自己的数据域,表单提交的时候只会发送自己这个数据域的数据,如果希望共用上层数据域可以设置这个属性为 false,这样上层数据域的数据不需要在表单中用隐藏域或者显式映射才能发送了。 |
|
||||
| inheritData | `boolean` | `true` | 默认表单是采用数据链的形式创建个自己的数据域,表单提交的时候只会发送自己这个数据域的数据,如果希望共用上层数据域可以设置这个属性为 false,这样上层数据域的数据不需要在表单中用隐藏域或者显式映射才能发送了。 |
|
||||
| static | `boolean` | | 整个表单静态方式展示,详情请查看[示例页](../../../examples/form/switchDisplay) |
|
||||
| staticClassName | `string` | | 表单静态展示时使用的类名 |
|
||||
|
||||
## 事件表
|
||||
|
||||
@ -1324,5 +1464,7 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
| reset | - | 重置表单 |
|
||||
| clear | - | 清空表单 |
|
||||
| validate | - | 校验表单 |
|
||||
| reload | - | 刷新(重新加载) |
|
||||
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
|
||||
| reload | - | 刷新(重新加载) |
|
||||
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
|
||||
| static | - | 表单切换为静态展示 |
|
||||
| nonstatic | - | 表单切换为普通输入态 |
|
||||
|
@ -175,7 +175,7 @@ export const examples = [
|
||||
},
|
||||
|
||||
{
|
||||
label: '编辑态、展示态切换',
|
||||
label: '输入态、展示态切换',
|
||||
path: '/examples/form/switchDisplay',
|
||||
component: makeSchemaRenderer(SwitchFormDisplay)
|
||||
},
|
||||
|
@ -1,5 +1,13 @@
|
||||
const renderOptions = (
|
||||
count = 3,
|
||||
render = (index) => ({label: `选项${index}`, value: index})
|
||||
) => [...Array(count)].map((item, index) => render(index));
|
||||
|
||||
const renderSelectValues = (count) =>
|
||||
[...Array(count)].map((item, index) => index).join(',');
|
||||
|
||||
export default {
|
||||
"title": "表单及表单项切换编辑态展示态",
|
||||
"title": "表单及表单项切换输入态展示态",
|
||||
"data": {
|
||||
"id": 1
|
||||
},
|
||||
@ -10,6 +18,9 @@ export default {
|
||||
"mode": "horizontal",
|
||||
"labelWidth": 150,
|
||||
"id": "allFormSwitch",
|
||||
"data": {
|
||||
"isStatic": false
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "input-text",
|
||||
@ -27,61 +38,46 @@ export default {
|
||||
"type": "switch",
|
||||
"name": "switch",
|
||||
"label": "开关",
|
||||
"option": "开关说明",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": "checkboxes",
|
||||
"name": "checkboxes",
|
||||
"label": "多选框",
|
||||
"value": "1,2",
|
||||
"value": renderSelectValues(12),
|
||||
"multiple": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "选项1",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "选项2",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "选项3",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
"options": renderOptions(12)
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "type",
|
||||
"label": "下拉单选",
|
||||
"type": "input-tag",
|
||||
"name": "select11",
|
||||
"label": "标签选择",
|
||||
"inline": true,
|
||||
"value": 1,
|
||||
"options": [
|
||||
{
|
||||
"label": "选项1",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "选项2",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "内容很长很长的选项,内容很长很长的选项",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
"value": renderSelectValues(12),
|
||||
"options": renderOptions(12)
|
||||
},
|
||||
{
|
||||
type: 'button-toolbar',
|
||||
name: 'button-toolbar',
|
||||
buttons: [
|
||||
"type": 'button-toolbar',
|
||||
"name": 'button-toolbar',
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "提交",
|
||||
"level": "primary",
|
||||
"visibleOn": "${!isStatic}",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "allFormSwitch",
|
||||
"args": {
|
||||
"value": {
|
||||
"isStatic": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"actionType": "static",
|
||||
"componentId": "allFormSwitch"
|
||||
@ -94,9 +90,19 @@ export default {
|
||||
"type": "button",
|
||||
"label": "编辑",
|
||||
"level": "primary",
|
||||
"visibleOn": "${isStatic}",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "setValue",
|
||||
"componentId": "allFormSwitch",
|
||||
"args": {
|
||||
"value": {
|
||||
"isStatic": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"actionType": "nonstatic",
|
||||
"componentId": "allFormSwitch"
|
||||
@ -106,7 +112,7 @@ export default {
|
||||
}
|
||||
}
|
||||
],
|
||||
className: 'show'
|
||||
"className": 'show'
|
||||
},
|
||||
],
|
||||
"actions": []
|
||||
@ -126,12 +132,12 @@ export default {
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
type: 'button-toolbar',
|
||||
name: 'button-toolbar',
|
||||
buttons: [
|
||||
"type": 'button-toolbar',
|
||||
"name": 'button-toolbar',
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "编辑态",
|
||||
"label": "输入态",
|
||||
"level": "primary",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
@ -160,7 +166,7 @@ export default {
|
||||
}
|
||||
}
|
||||
],
|
||||
className: 'show'
|
||||
"className": 'show'
|
||||
},
|
||||
],
|
||||
"actions": []
|
||||
@ -175,7 +181,7 @@ export default {
|
||||
"type": "input-text",
|
||||
"id": "formItemInputText",
|
||||
"name": "var1",
|
||||
"label": "编辑态<br />不设置<br />或static: false",
|
||||
"label": "输入态<br />不设置<br />或static: false",
|
||||
"value": "text",
|
||||
"static": false,
|
||||
"desc": "使用staticOn 支持表达式控制,用法类似"
|
||||
@ -201,7 +207,6 @@ export default {
|
||||
"name": "var3",
|
||||
"label": "自定义展示态schema",
|
||||
"value": "表单项value",
|
||||
"desc": "通过\\${name}可获取到当前值",
|
||||
"static": true,
|
||||
"staticSchema": [
|
||||
"自定义前缀 | ",
|
||||
@ -223,7 +228,7 @@ export default {
|
||||
"autoFocus": true,
|
||||
"panel": false,
|
||||
"debug": false,
|
||||
"title": "目前支持编辑态展示态切换的表单项",
|
||||
"title": "目前支持输入态展示态切换的表单项",
|
||||
"labelWidth": 150,
|
||||
"staticClassName": "now-is-static",
|
||||
"body": [
|
||||
@ -233,7 +238,7 @@ export default {
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "切换为编辑态",
|
||||
"label": "切换为输入态",
|
||||
"level": "primary",
|
||||
"visibleOn": "${static}",
|
||||
"onEvent": {
|
||||
@ -471,21 +476,8 @@ export default {
|
||||
"label": "List",
|
||||
"desc": "可多选",
|
||||
"multiple": true,
|
||||
"value": "1,2",
|
||||
"options": [
|
||||
{
|
||||
"label": "选项 A",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "选项 B",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "选项 C",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
"value": renderSelectValues(2),
|
||||
"options": renderOptions(3)
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
@ -496,24 +488,12 @@ export default {
|
||||
"label": "List",
|
||||
"imageClassName": "thumb-lg",
|
||||
"desc": "支持放张图片",
|
||||
"value": 1,
|
||||
"options": [
|
||||
{
|
||||
"image": "/examples/static/photo/3893101144.jpg",
|
||||
"value": 1,
|
||||
"label": "图片1"
|
||||
},
|
||||
{
|
||||
"image": "/examples/static/photo/3893101144.jpg",
|
||||
"value": 2,
|
||||
"label": "图片2"
|
||||
},
|
||||
{
|
||||
"image": "/examples/static/photo/3893101144.jpg",
|
||||
"value": 3,
|
||||
"label": "图片3"
|
||||
}
|
||||
]
|
||||
"value": renderSelectValues(2),
|
||||
"options": renderOptions(3, (index) => ({
|
||||
"image": "/examples/static/photo/3893101144.jpg",
|
||||
"value": index,
|
||||
"label": `图片${index}`
|
||||
}))
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
@ -523,22 +503,12 @@ export default {
|
||||
"name": "list5",
|
||||
"label": "List",
|
||||
"desc": "支持文字排版",
|
||||
"multiple": true,
|
||||
"value": 1,
|
||||
"options": [
|
||||
{
|
||||
"value": 1,
|
||||
"body": "<div class=\"m-l-sm m-r-sm m-b-sm m-t-xs\">\n <div class=\"text-md p-b-xs b-inherit b-b m-b-xs\">套餐:C01</div>\n <div class=\"text-sm\">CPU:22核</div>\n <div class=\"text-sm\">内存:10GB</div>\n <div class=\"text-sm\">SSD盘:1024GB</div>\n </div>"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"body": "<div class=\"m-l-sm m-r-sm m-b-sm m-t-xs\">\n <div class=\"text-md p-b-xs b-inherit b-b m-b-xs\">套餐:C02</div>\n <div class=\"text-sm\">CPU:23核</div>\n <div class=\"text-sm\">内存:11GB</div>\n <div class=\"text-sm\">SSD盘:1025GB</div>\n </div>"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"disabled": true,
|
||||
"body": "<div class=\"m-l-sm m-r-sm m-b-sm m-t-xs\">\n <div class=\"text-md p-b-xs b-inherit b-b m-b-xs\">套餐:C03</div>\n <div class=\"text-sm\">CPU:24核</div>\n <div class=\"text-sm\">内存:12GB</div>\n <div class=\"text-sm\">SSD盘:1026GB</div>\n </div>"
|
||||
}
|
||||
]
|
||||
"options": renderOptions(3, (index) => ({
|
||||
"value": index,
|
||||
"body": "<div class=\"m-l-sm m-r-sm m-b-sm m-t-xs\">\n <div class=\"text-md p-b-xs b-inherit b-b m-b-xs\">套餐:C01</div>\n <div class=\"text-sm\">CPU:22核</div>\n <div class=\"text-sm\">内存:10GB</div>\n <div class=\"text-sm\">SSD盘:1024GB</div>\n </div>"
|
||||
}))
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
@ -589,7 +559,7 @@ export default {
|
||||
"type": "checkbox",
|
||||
"name": "checkbox",
|
||||
"label": "勾选框",
|
||||
"options": "勾选说明",
|
||||
"option": "同意协议",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
@ -755,9 +725,6 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "input-group",
|
||||
"label": "各种组合",
|
||||
@ -819,7 +786,10 @@ export default {
|
||||
"type": "textarea",
|
||||
"name": "textarea",
|
||||
"label": "多行文本",
|
||||
"value": "这是一段多行文本文字\n第二行内容"
|
||||
"value": "这是一段多行文本文字\n第二行内容\n2222\n333",
|
||||
"staticSchema": {
|
||||
"limit": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
@ -836,6 +806,332 @@ export default {
|
||||
"inline": true,
|
||||
"value": "A,B,C"
|
||||
},
|
||||
{
|
||||
"label": '组合穿梭器',
|
||||
"type": 'tabs-transfer',
|
||||
"name": 'tabs-transfer',
|
||||
"sortable": true,
|
||||
"selectMode": 'tree',
|
||||
"id": 'tab-transfer-receiver',
|
||||
"resetValue": 'zhugeliang',
|
||||
"value": "zhugeliang,caocao",
|
||||
"enableNodePath": true,
|
||||
"options": [
|
||||
{
|
||||
"label": '成员',
|
||||
"selectMode": 'tree',
|
||||
"searchable": true,
|
||||
"children": [
|
||||
{
|
||||
"label": '法师',
|
||||
"children": [
|
||||
{
|
||||
"label": '诸葛亮',
|
||||
"value": 'zhugeliang'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '战士',
|
||||
"children": [
|
||||
{
|
||||
"label": '曹操',
|
||||
"value": 'caocao'
|
||||
},
|
||||
{
|
||||
"label": '钟无艳',
|
||||
"value": 'zhongwuyan'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '打野',
|
||||
"children": [
|
||||
{
|
||||
"label": '李白',
|
||||
"value": 'libai'
|
||||
},
|
||||
{
|
||||
"label": '韩信',
|
||||
"value": 'hanxin'
|
||||
},
|
||||
{
|
||||
"label": '云中君',
|
||||
"value": 'yunzhongjun'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '用户',
|
||||
"selectMode": 'chained',
|
||||
"children": [
|
||||
{
|
||||
"label": '法师',
|
||||
"children": [
|
||||
{
|
||||
"label": '诸葛亮',
|
||||
"value": 'zhugeliang2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '战士',
|
||||
"children": [
|
||||
{
|
||||
"label": '曹操',
|
||||
"value": 'caocao2'
|
||||
},
|
||||
{
|
||||
"label": '钟无艳',
|
||||
"value": 'zhongwuyan2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '打野',
|
||||
"children": [
|
||||
{
|
||||
"label": '李白',
|
||||
"value": 'libai2'
|
||||
},
|
||||
{
|
||||
"label": '韩信',
|
||||
"value": 'hanxin2'
|
||||
},
|
||||
{
|
||||
"label": '云中君',
|
||||
"value": 'yunzhongjun2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": 'toast',
|
||||
"args": {
|
||||
"msgType": 'info',
|
||||
"msg": '${event.data.value|json}'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": '穿梭器picker',
|
||||
"type": 'transfer-picker',
|
||||
"name": 'transfer-picker',
|
||||
"id": 'transfer-picker-receiver',
|
||||
"resetValue": 'zhugeliang',
|
||||
"sortable": true,
|
||||
"selectMode": 'tree',
|
||||
"searchable": true,
|
||||
"value": "zhugeliang,zhongwuyan",
|
||||
"enableNodePath": true,
|
||||
"options": [
|
||||
{
|
||||
"label": '法师',
|
||||
"children": [
|
||||
{
|
||||
"label": '诸葛亮',
|
||||
"value": 'zhugeliang'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '战士',
|
||||
"children": [
|
||||
{
|
||||
"label": '曹操',
|
||||
"value": 'caocao'
|
||||
},
|
||||
{
|
||||
"label": '钟无艳',
|
||||
"value": 'zhongwuyan'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '打野',
|
||||
"children": [
|
||||
{
|
||||
"label": '李白',
|
||||
"value": 'libai'
|
||||
},
|
||||
{
|
||||
"label": '韩信',
|
||||
"value": 'hanxin'
|
||||
},
|
||||
{
|
||||
"label": '云中君',
|
||||
"value": 'yunzhongjun'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": 'toast',
|
||||
"args": {
|
||||
"msgType": 'info',
|
||||
"msg": '${event.data.value|json}'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": '组合穿梭器picker',
|
||||
"type": 'tabs-transfer-picker',
|
||||
"name": 'tabs-transfer-picker',
|
||||
"id": 'tabs-transfer-picker-receiver',
|
||||
"resetValue": 'zhugeliang',
|
||||
"value": "caocao,zhongwuyan",
|
||||
"sortable": true,
|
||||
"selectMode": 'tree',
|
||||
"pickerSize": 'md',
|
||||
"menuTpl":
|
||||
"<div class='flex justify-between'><span>${label}</span>${email ? `<div class='text-muted m-r-xs text-sm text-right'>${email}<br />${phone}</div>`: ''}</div>",
|
||||
"valueTpl": '${label}(${value})',
|
||||
"options": [
|
||||
{
|
||||
"label": '成员',
|
||||
"selectMode": 'tree',
|
||||
"searchable": true,
|
||||
"children": [
|
||||
{
|
||||
"label": '法师',
|
||||
"children": [
|
||||
{
|
||||
"label": '诸葛亮',
|
||||
"value": 'zhugeliang',
|
||||
"email": 'zhugeliang@timi.com',
|
||||
"phone": 13111111111
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '战士',
|
||||
"children": [
|
||||
{
|
||||
"label": '曹操',
|
||||
"value": 'caocao',
|
||||
"email": 'caocao@timi.com',
|
||||
"phone": 13111111111
|
||||
},
|
||||
{
|
||||
"label": '钟无艳',
|
||||
"value": 'zhongwuyan',
|
||||
"email": 'zhongwuyan@timi.com',
|
||||
"phone": 13111111111
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '打野',
|
||||
"children": [
|
||||
{
|
||||
"label": '李白',
|
||||
"value": 'libai',
|
||||
"email": 'libai@timi.com',
|
||||
"phone": 13111111111
|
||||
},
|
||||
{
|
||||
"label": '韩信',
|
||||
"value": 'hanxin',
|
||||
"email": 'hanxin@timi.com',
|
||||
"phone": 13111111111
|
||||
},
|
||||
{
|
||||
"label": '云中君',
|
||||
"value": 'yunzhongjun',
|
||||
"email": 'yunzhongjun@timi.com',
|
||||
"phone": 13111111111
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '角色',
|
||||
"selectMode": 'list',
|
||||
"children": [
|
||||
{
|
||||
"label": '角色 1',
|
||||
"value": 'role1'
|
||||
},
|
||||
{
|
||||
"label": '角色 2',
|
||||
"value": 'role2'
|
||||
},
|
||||
{
|
||||
"label": '角色 3',
|
||||
"value": 'role3'
|
||||
},
|
||||
{
|
||||
"label": '角色 4',
|
||||
"value": 'role4'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '部门',
|
||||
"selectMode": 'tree',
|
||||
"children": [
|
||||
{
|
||||
"label": '总部',
|
||||
"value": 'dep0',
|
||||
"children": [
|
||||
{
|
||||
"label": '部门 1',
|
||||
"value": 'dep1',
|
||||
"children": [
|
||||
{
|
||||
"label": '部门 4',
|
||||
"value": 'dep4'
|
||||
},
|
||||
{
|
||||
"label": '部门 5',
|
||||
"value": 'dep5'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": '部门 2',
|
||||
"value": 'dep2'
|
||||
},
|
||||
{
|
||||
"label": '部门 3',
|
||||
"value": 'dep3'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"onEvent": {
|
||||
"change": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": 'toast',
|
||||
"args": {
|
||||
"msgType": 'info',
|
||||
"msg": '${event.data.value|json}'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
@ -872,9 +1168,6 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "input-tree",
|
||||
"name": "trees",
|
||||
@ -906,9 +1199,6 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "tree-select",
|
||||
"name": "selecttree",
|
||||
@ -939,13 +1229,11 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "tree-select",
|
||||
"name": "selecttrees",
|
||||
"label": "树多选选择器",
|
||||
"enableNodePath": true,
|
||||
"multiple": true,
|
||||
"value": "1-2,5",
|
||||
"options": [
|
||||
@ -973,6 +1261,9 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "nested-select",
|
||||
"name": "nestedSelect",
|
||||
@ -1268,7 +1559,11 @@ export default {
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
"name": "select3",
|
||||
"type": "chained-select",
|
||||
"label": "链式下拉选择器",
|
||||
"source": "/api/mock2/options/chainedOptions?waitSeconds=1&parentId=$parentId&level=$level&maxLevel=4",
|
||||
"value": "a,b"
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
@ -1488,7 +1783,56 @@ export default {
|
||||
"type": "input-range",
|
||||
"name": "range",
|
||||
"label": "范围",
|
||||
"value": "50"
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"name": "city",
|
||||
"type": "input-city",
|
||||
"label": "城市",
|
||||
"searchable": true,
|
||||
"value": 210727
|
||||
},
|
||||
{
|
||||
"type": "location-picker",
|
||||
"label": "地理位置",
|
||||
"name": "location",
|
||||
"ak": "LiZT5dVbGTsPI91tFGcOlSpe5FDehpf7",
|
||||
"label": "地址"
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "chart-radios",
|
||||
"label": "图表单选框",
|
||||
"name": "main",
|
||||
"chartValueField": "num",
|
||||
"value": "a",
|
||||
"options": [
|
||||
{
|
||||
"label": "A",
|
||||
"num": 100,
|
||||
"value": "a"
|
||||
},
|
||||
{
|
||||
"label": "B",
|
||||
"num": 120,
|
||||
"value": "b"
|
||||
},
|
||||
{
|
||||
"label": "C",
|
||||
"num": 30,
|
||||
"value": "c"
|
||||
},
|
||||
{
|
||||
"label": "D",
|
||||
"num": 40,
|
||||
"value": "d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "button-toolbar",
|
||||
@ -1496,7 +1840,7 @@ export default {
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "切换为编辑态",
|
||||
"label": "切换为输入态",
|
||||
"level": "primary",
|
||||
"visibleOn": "${static}",
|
||||
"onEvent": {
|
||||
@ -1525,6 +1869,20 @@ export default {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"label": "清空",
|
||||
"onEvent": {
|
||||
"click": {
|
||||
"actions": [
|
||||
{
|
||||
"actionType": "clear",
|
||||
"componentId": "myform"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ import {
|
||||
ActionObject,
|
||||
Payload,
|
||||
ClassName,
|
||||
BaseApiObject
|
||||
BaseApiObject,
|
||||
SchemaExpression,
|
||||
SchemaClassName
|
||||
} from '../types';
|
||||
import {filter, evalExpression} from '../utils/tpl';
|
||||
import getExprProperties from '../utils/filter-schema';
|
||||
@ -334,7 +336,9 @@ export interface FormSchemaBase {
|
||||
/**
|
||||
* 展示态时的className
|
||||
*/
|
||||
staticClassName?: string;
|
||||
static?: boolean;
|
||||
staticOn?: SchemaExpression;
|
||||
staticClassName?: SchemaClassName;
|
||||
}
|
||||
|
||||
export type FormGroup = FormSchemaBase & {
|
||||
@ -1529,7 +1533,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
formLabelWidth: labelWidth,
|
||||
controlWidth,
|
||||
disabled: disabled || (control as Schema).disabled || form.loading,
|
||||
static: (control as Schema).static ?? isStatic,
|
||||
static: (control as Schema).static || isStatic,
|
||||
btnDisabled: disabled || form.loading || form.validating,
|
||||
onAction: this.handleAction,
|
||||
onQuery: this.handleQuery,
|
||||
@ -1601,9 +1605,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
`Form`,
|
||||
`Form--${mode || 'normal'}`,
|
||||
columnCount ? `Form--column Form--column-${columnCount}` : null,
|
||||
className,
|
||||
isStatic ? 'Form--isStatic' : null,
|
||||
staticClassName && isStatic ? staticClassName : null
|
||||
staticClassName && isStatic ? staticClassName : className,
|
||||
isStatic ? 'Form--isStatic' : null
|
||||
)}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
noValidate
|
||||
@ -1704,9 +1707,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
affixFooter,
|
||||
lazyLoad,
|
||||
translate: __,
|
||||
footer,
|
||||
static: isStatic = false,
|
||||
formStore
|
||||
footer
|
||||
} = this.props;
|
||||
|
||||
let body: JSX.Element = this.renderBody();
|
||||
|
@ -25,7 +25,9 @@ import {
|
||||
BaseApiObject,
|
||||
BaseSchemaWithoutType,
|
||||
ClassName,
|
||||
Schema
|
||||
Schema,
|
||||
SchemaClassName,
|
||||
SchemaExpression
|
||||
} from '../types';
|
||||
import {filter} from '../utils/tpl';
|
||||
import {HocStoreFactory} from '../WithStore';
|
||||
@ -466,10 +468,12 @@ export interface FormItemProps extends RendererProps {
|
||||
showErrorMsg?: boolean;
|
||||
// 展示态 相关
|
||||
static?: boolean;
|
||||
staticOn?: SchemaExpression;
|
||||
staticPlaceholder?: string;
|
||||
staticClassName?: string;
|
||||
staticLabelClassName?: string;
|
||||
staticValueClassName?: string;
|
||||
staticClassName?: SchemaClassName;
|
||||
staticLabelClassName?: SchemaClassName;
|
||||
staticInputClassName?: SchemaClassName;
|
||||
staticSchema?: any;
|
||||
}
|
||||
|
||||
// 下发下去的属性
|
||||
@ -499,6 +503,16 @@ export interface FormItemConfig extends FormItemBasicConfig {
|
||||
component: FormControlComponent;
|
||||
}
|
||||
|
||||
const getItemLabelClassName = (props: FormItemProps) => {
|
||||
const {staticLabelClassName, labelClassName} = props;
|
||||
return props.static && staticLabelClassName ? staticLabelClassName : labelClassName;
|
||||
};
|
||||
|
||||
const getItemInputClassName = (props: FormItemProps) => {
|
||||
const {staticInputClassName, inputClassName} = props;
|
||||
return props.static && staticInputClassName ? staticInputClassName : inputClassName;
|
||||
};
|
||||
|
||||
export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
reaction: Array<() => void> = [];
|
||||
lastSearchTerm: any;
|
||||
@ -844,7 +858,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
|
||||
renderControl(): JSX.Element | null {
|
||||
const {
|
||||
inputClassName,
|
||||
formItem: model,
|
||||
classnames: cx,
|
||||
children,
|
||||
@ -882,7 +895,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
controlSize !== 'full'
|
||||
},
|
||||
model?.errClassNames,
|
||||
inputClassName
|
||||
getItemInputClassName(this.props)
|
||||
)
|
||||
});
|
||||
}
|
||||
@ -908,7 +921,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
captionClassName,
|
||||
desc,
|
||||
label,
|
||||
labelClassName,
|
||||
render,
|
||||
required,
|
||||
caption,
|
||||
@ -924,9 +936,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
useMobileUI,
|
||||
translate: __,
|
||||
static: isStatic,
|
||||
staticClassName,
|
||||
staticLabelClassName,
|
||||
staticValueClassName
|
||||
staticClassName
|
||||
} = props;
|
||||
|
||||
// 强制不渲染 label 的话
|
||||
@ -946,13 +956,12 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
data-role="form-item"
|
||||
className={cx(
|
||||
`Form-item Form-item--horizontal`,
|
||||
className,
|
||||
isStatic && staticClassName ? staticClassName : className,
|
||||
{
|
||||
'Form-item--horizontal-justify': horizontal.justify,
|
||||
[`is-error`]: model && !model.valid,
|
||||
[`is-required`]: required
|
||||
},
|
||||
isStatic && staticClassName,
|
||||
model?.errClassNames
|
||||
)}
|
||||
>
|
||||
@ -969,8 +978,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
[`Form-itemColumn--${left}`]: !horizontal.leftFixed,
|
||||
'Form-label--left': labelAlign === 'left'
|
||||
},
|
||||
labelClassName,
|
||||
isStatic && staticLabelClassName,
|
||||
getItemLabelClassName(props)
|
||||
)}
|
||||
style={labelWidth != null ? {width: labelWidth} : undefined}
|
||||
>
|
||||
@ -1003,9 +1011,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cx(`Form-value`,
|
||||
isStatic && staticValueClassName,
|
||||
{
|
||||
className={cx(`Form-value`,{
|
||||
// [`Form-itemColumn--offset${getWidthRate(horizontal.offset)}`]: !label && label !== false,
|
||||
[`Form-itemColumn--${right}`]:
|
||||
!horizontal.leftFixed && !!right && right !== 12 - left
|
||||
@ -1069,7 +1075,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
desc,
|
||||
description,
|
||||
label,
|
||||
labelClassName,
|
||||
render,
|
||||
required,
|
||||
caption,
|
||||
@ -1085,9 +1090,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
data,
|
||||
showErrorMsg,
|
||||
useMobileUI,
|
||||
translate: __,
|
||||
static: isStatic,
|
||||
staticLabelClassName
|
||||
translate: __
|
||||
} = props;
|
||||
|
||||
description = description || desc;
|
||||
@ -1106,7 +1109,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
)}
|
||||
>
|
||||
{label && renderLabel !== false ? (
|
||||
<label className={cx(`Form-label`, labelClassName, isStatic && staticLabelClassName)}>
|
||||
<label className={cx(`Form-label`, getItemLabelClassName(props))}>
|
||||
<span>
|
||||
{label
|
||||
? render(
|
||||
@ -1190,7 +1193,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
desc,
|
||||
description,
|
||||
label,
|
||||
labelClassName,
|
||||
render,
|
||||
required,
|
||||
caption,
|
||||
@ -1206,9 +1208,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
data,
|
||||
showErrorMsg,
|
||||
useMobileUI,
|
||||
static: isStatic,
|
||||
staticLabelClassName,
|
||||
staticValueClassName,
|
||||
translate: __
|
||||
} = props;
|
||||
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||
@ -1229,7 +1228,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
>
|
||||
{label && renderLabel !== false ? (
|
||||
<label
|
||||
className={cx(`Form-label`, labelClassName, isStatic && staticLabelClassName)}
|
||||
className={cx(`Form-label`, getItemLabelClassName(props))}
|
||||
style={labelWidth != null ? {width: labelWidth} : undefined}
|
||||
>
|
||||
<span>
|
||||
@ -1260,7 +1259,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
<div className={cx(`Form-value`, isStatic && staticValueClassName)}>
|
||||
<div className={cx(`Form-value`)}>
|
||||
{renderControl()}
|
||||
|
||||
{caption
|
||||
@ -1318,7 +1317,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
desc,
|
||||
description,
|
||||
label,
|
||||
labelClassName,
|
||||
render,
|
||||
required,
|
||||
caption,
|
||||
@ -1334,8 +1332,6 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
data,
|
||||
showErrorMsg,
|
||||
useMobileUI,
|
||||
static: isStatic,
|
||||
staticLabelClassName,
|
||||
translate: __
|
||||
} = props;
|
||||
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||
@ -1357,7 +1353,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
<div className={cx('Form-rowInner')}>
|
||||
{label && renderLabel !== false ? (
|
||||
<label
|
||||
className={cx(`Form-label`, labelClassName, isStatic && staticLabelClassName)}
|
||||
className={cx(`Form-label`, getItemLabelClassName(props))}
|
||||
style={labelWidth != null ? {width: labelWidth} : undefined}
|
||||
>
|
||||
<span>
|
||||
@ -1643,7 +1639,6 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
|
||||
renderControl() {
|
||||
const {
|
||||
inputClassName,
|
||||
formItem: model,
|
||||
classnames: cx,
|
||||
children,
|
||||
@ -1686,7 +1681,7 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
controlSize !== 'full'
|
||||
},
|
||||
model?.errClassNames,
|
||||
inputClassName
|
||||
getItemInputClassName(this.props)
|
||||
)}
|
||||
></Control>
|
||||
{isOpened ? this.buildSchema() : null}
|
||||
|
@ -1,4 +1,5 @@
|
||||
.#{$ns}ListControl {
|
||||
.#{$ns}ListControl,
|
||||
.#{$ns}ListControl-static {
|
||||
&-items {
|
||||
display: block;
|
||||
margin: calc(var(--ListControl-gutterWidth) / -2);
|
||||
@ -24,13 +25,39 @@
|
||||
max-width: calc(#{px2rem(200px)} + 2 * var(--ListControl-item-paddingX));
|
||||
border-radius: var(--ListControl-item-borderRadius);
|
||||
|
||||
&:not(.is-disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.b-inherit {
|
||||
border-color: var(--ListControl-item-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-itemImage {
|
||||
margin: calc(var(--ListControl-item-paddingY) * -1)
|
||||
calc(var(--ListControl-item-paddingX) * -1);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-itemLabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-itemImage + &-itemLabel {
|
||||
margin-top: var(--ListControl-item-paddingY);
|
||||
}
|
||||
|
||||
&-placeholder {
|
||||
color: var(--Form-input-placeholderColor);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}ListControl {
|
||||
&-item {
|
||||
&:not(.is-disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@include hover {
|
||||
background: var(--ListControl-item-onHover-bg);
|
||||
@ -97,38 +124,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-itemImage {
|
||||
margin: calc(var(--ListControl-item-paddingY) * -1)
|
||||
calc(var(--ListControl-item-paddingX) * -1);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-itemLabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-itemImage + &-itemLabel {
|
||||
margin-top: var(--ListControl-item-paddingY);
|
||||
}
|
||||
|
||||
&-placeholder {
|
||||
color: var(--Form-input-placeholderColor);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Form-static {
|
||||
.#{$ns}ListControl-item.is-disabled {
|
||||
opacity: 1;
|
||||
background: var(--ListControl-item-onActive-bg);
|
||||
border-color: var(--ListControl-item-onDisabled-borderColor);
|
||||
color: var(--ListControl-item-color);
|
||||
&::before, &::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -129,6 +129,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Form-static {
|
||||
.#{$ns}Switch-option {
|
||||
vertical-align: initial;
|
||||
color: var(--text--muted-color);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}SwitchControl {
|
||||
padding-top: calc((var(--Form-input-height) - var(--Switch-height)) / 2);
|
||||
|
||||
|
@ -64,3 +64,11 @@
|
||||
top: var(--Form-input-paddingY);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Form-static {
|
||||
.#{$ns}TextareaControl > textarea {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
resize: none;
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ interface MapPickerProps {
|
||||
lng: number;
|
||||
city?: string;
|
||||
};
|
||||
onChange: (value: any) => void;
|
||||
onChange?: (value: any) => void;
|
||||
}
|
||||
|
||||
interface LocationItem {
|
||||
@ -288,7 +288,7 @@ export class BaiduMapPicker extends React.Component<
|
||||
if (this.props.coordinatesType == 'gcj02') {
|
||||
this.covertPoint(point, COORDINATES_BD09, COORDINATES_GCJ02).then(
|
||||
(convertedPoint: any) => {
|
||||
this.props?.onChange({
|
||||
(typeof this.props?.onChange === 'function') && this.props.onChange({
|
||||
address: loc.address.trim() || loc.title,
|
||||
lat: convertedPoint.lat,
|
||||
lng: convertedPoint.lng,
|
||||
@ -297,7 +297,7 @@ export class BaiduMapPicker extends React.Component<
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.props?.onChange({
|
||||
(typeof this.props?.onChange === 'function') && this.props?.onChange({
|
||||
address: loc.address.trim() || loc.title,
|
||||
lat: loc.lat,
|
||||
lng: loc.lng,
|
||||
|
153
packages/amis-ui/src/components/MultilineText.tsx
Normal file
153
packages/amis-ui/src/components/MultilineText.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import React from 'react';
|
||||
import {anyChanged, autobind, localeable, LocaleProps} from 'amis-core';
|
||||
import {themeable, ThemeProps} from 'amis-core';
|
||||
import Button from './Button';
|
||||
|
||||
export interface MultilineTextProps extends ThemeProps, LocaleProps {
|
||||
/**
|
||||
* 最大行数
|
||||
*/
|
||||
maxRows?: number;
|
||||
|
||||
/**
|
||||
* 展示文本
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* 展开按钮文本
|
||||
*/
|
||||
expendButtonText?: string;
|
||||
|
||||
/**
|
||||
* 收起按钮文本
|
||||
*/
|
||||
collapseButtonText?: string;
|
||||
}
|
||||
|
||||
export interface MultilineTextState {
|
||||
isExpend: boolean;
|
||||
showBtn: boolean;
|
||||
}
|
||||
|
||||
export class MultilineText extends React.Component<MultilineTextProps, MultilineTextState> {
|
||||
static defaultProps = {
|
||||
maxRows: 5,
|
||||
expendButtonText: '展开',
|
||||
collapseButtonText: '收起'
|
||||
};
|
||||
|
||||
state = {
|
||||
isExpend: false,
|
||||
showBtn: false
|
||||
};
|
||||
|
||||
ref?: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: MultilineTextProps) {
|
||||
super(props);
|
||||
this.ref = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.ref && this.ref.current) {
|
||||
if (this.ref.current.scrollHeight > this.ref.current.clientHeight) {
|
||||
this.setState({
|
||||
showBtn: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Readonly<MultilineTextProps>, nextState: Readonly<MultilineTextState>, nextContext: any): boolean {
|
||||
if (
|
||||
anyChanged(
|
||||
['text', 'maxRows', 'expendButtonText', 'collapseButtonText', 'className'],
|
||||
this.props,
|
||||
nextProps
|
||||
)
|
||||
|| anyChanged(['isExpend', 'showBtn'], this.state, nextState)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps: any, oldState: any) {
|
||||
const {text, maxRows} = this.props;
|
||||
if (text !== oldProps.text || maxRows !== oldProps) {
|
||||
if (this.ref && this.ref.current) {
|
||||
this.setState({
|
||||
showBtn: this.ref.current.scrollHeight > this.ref.current.clientHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@autobind
|
||||
toggleExpend() {
|
||||
this.setState({
|
||||
isExpend: !this.state.isExpend
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
text,
|
||||
classnames: cx,
|
||||
maxRows = 5,
|
||||
expendButtonText,
|
||||
collapseButtonText
|
||||
} = this.props;
|
||||
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
showBtn,
|
||||
isExpend
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
`MultilineText`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* 用于计算高度 */}
|
||||
<div
|
||||
ref={this.ref}
|
||||
className={cx('white-space-pre-line', 'overflow-hidden')}
|
||||
style={{
|
||||
height: `${maxRows * 20}px`,
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
zIndex: -99
|
||||
}}
|
||||
>{text}</div>
|
||||
{/* 用于展示 */}
|
||||
<div
|
||||
className={cx('white-space-pre-line', 'overflow-hidden')}
|
||||
style={{
|
||||
height: (showBtn && !isExpend) ? `${maxRows * 20}px` : 'auto'
|
||||
}}
|
||||
>{text}</div>
|
||||
{showBtn &&
|
||||
<div className="text-right">
|
||||
<Button
|
||||
className="mt-1"
|
||||
level="link"
|
||||
onClick={this.toggleExpend}
|
||||
>{!isExpend ? expendButtonText : collapseButtonText}</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default themeable(localeable(MultilineText));
|
@ -108,6 +108,8 @@ import Steps, {StepStatus} from './Steps';
|
||||
import Tag from './Tag';
|
||||
import Timeline from './Timeline';
|
||||
import ImageGallery from './ImageGallery';
|
||||
import BaiduMapPicker from './BaiduMapPicker';
|
||||
import MultilineText from './MultilineText';
|
||||
|
||||
export {
|
||||
NotFound,
|
||||
@ -220,5 +222,7 @@ export {
|
||||
StepStatus,
|
||||
Tag,
|
||||
Timeline,
|
||||
ImageGallery
|
||||
ImageGallery,
|
||||
BaiduMapPicker,
|
||||
MultilineText
|
||||
};
|
||||
|
@ -128,6 +128,10 @@ import {
|
||||
SchemaExpression
|
||||
} from 'amis-core';
|
||||
import type {FormSchemaBase} from 'amis-core/lib/renderers/Form';
|
||||
import {WordsSchema} from './renderers/Words';
|
||||
import {MultilineTextSchema} from './renderers/MultilineText';
|
||||
import {DateRangeSchema} from './renderers/DateRange';
|
||||
import {PasswordSchema} from './renderers/Password';
|
||||
|
||||
// 每加个类型,这补充一下。
|
||||
export type SchemaType =
|
||||
@ -163,6 +167,7 @@ export type SchemaType =
|
||||
| 'static-time' // 这个几个跟表单项同名,再form下面用必须带前缀 static-
|
||||
| 'month'
|
||||
| 'static-month' // 这个几个跟表单项同名,再form下面用必须带前缀 static-
|
||||
| 'date-range'
|
||||
| 'dialog'
|
||||
| 'spinner'
|
||||
| 'divider'
|
||||
@ -347,6 +352,10 @@ export type SchemaType =
|
||||
| 'grid-nav'
|
||||
| 'users-select'
|
||||
| 'tag'
|
||||
| 'tags'
|
||||
| 'words'
|
||||
| 'password'
|
||||
| 'multiline-text'
|
||||
|
||||
// 原生 input 类型
|
||||
| 'native-date'
|
||||
@ -479,7 +488,11 @@ export type SchemaObject =
|
||||
| TabsTransferPickerControlSchema
|
||||
| TreeControlSchema
|
||||
| TreeSelectControlSchema
|
||||
| UserSelectControlSchema;
|
||||
| UserSelectControlSchema
|
||||
| DateRangeSchema
|
||||
| MultilineTextSchema
|
||||
| PasswordSchema
|
||||
| WordsSchema;
|
||||
|
||||
export type SchemaCollection =
|
||||
| SchemaObject
|
||||
|
@ -144,6 +144,10 @@ import './renderers/GridNav';
|
||||
import './renderers/TooltipWrapper';
|
||||
import './renderers/Tag';
|
||||
import './renderers/Table2/index';
|
||||
import './renderers/Words';
|
||||
import './renderers/Password';
|
||||
import './renderers/DateRange';
|
||||
import './renderers/MultilineText';
|
||||
|
||||
import './compat';
|
||||
import './schemaExtend';
|
||||
|
98
packages/amis/src/renderers/DateRange.tsx
Normal file
98
packages/amis/src/renderers/DateRange.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
import {Renderer, RendererProps} from 'amis-core';
|
||||
import moment from 'moment';
|
||||
import {BaseSchema} from '../Schema';
|
||||
import {getPropValue} from 'amis-core';
|
||||
|
||||
/**
|
||||
* DateRange 展示渲染器。
|
||||
*/
|
||||
export interface DateRangeSchema extends BaseSchema {
|
||||
/**
|
||||
* 指定为日期展示类型
|
||||
*/
|
||||
type: 'date-range';
|
||||
|
||||
/**
|
||||
* 值的时间格式,参考 moment 中的格式说明。
|
||||
*/
|
||||
valueFormat?: string;
|
||||
|
||||
/**
|
||||
* 展示的时间格式,参考 moment 中的格式说明。
|
||||
*/
|
||||
format?: string;
|
||||
|
||||
/**
|
||||
* 分割符
|
||||
*/
|
||||
delimiter?: string;
|
||||
|
||||
/**
|
||||
* 连接符
|
||||
*/
|
||||
connector?: string;
|
||||
}
|
||||
|
||||
export interface DateRangeProps
|
||||
extends RendererProps,
|
||||
Omit<DateRangeSchema, 'type' | 'className'> {}
|
||||
|
||||
export class DateRangeField extends React.Component<DateRangeProps, Object> {
|
||||
refreshInterval: ReturnType<typeof setTimeout>;
|
||||
|
||||
static defaultProps: Pick<
|
||||
DateRangeProps,
|
||||
'valueFormat'| 'format' | 'connector'
|
||||
> = {
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'X',
|
||||
connector: '~'
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
delimiter = ',',
|
||||
connector = '~',
|
||||
value,
|
||||
valueFormat,
|
||||
format = 'YYYY-MM-DD',
|
||||
classnames: cx,
|
||||
className
|
||||
} = this.props;
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = value.split(delimiter);
|
||||
}
|
||||
let [startTime = '', endTime = ''] = value;
|
||||
|
||||
if (valueFormat) {
|
||||
startTime = moment(startTime, valueFormat);
|
||||
endTime = moment(endTime, valueFormat);
|
||||
}
|
||||
else {
|
||||
startTime = moment(startTime * 1000);
|
||||
endTime = moment(endTime * 1000);
|
||||
}
|
||||
|
||||
startTime = startTime.isValid() ? startTime.format(format) : '';
|
||||
endTime = endTime.isValid() ? endTime.format(format) : '';
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cx('DateRangeField', className)}
|
||||
>
|
||||
{[startTime, endTime].join(` ${connector} `)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
type: 'date-range'
|
||||
})
|
||||
export class DateRangeFieldRenderer extends DateRangeField {};
|
@ -8,6 +8,7 @@ import type {Option} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {getLevelFromClassName, autobind, isEmpty} from 'amis-core';
|
||||
import {ButtonGroupSchema} from '../ButtonGroup';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* 按钮组控件。
|
||||
@ -67,6 +68,7 @@ export default class ButtonGroupControl extends React.Component<
|
||||
reload && reload();
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render(props = this.props) {
|
||||
const {
|
||||
render,
|
||||
|
@ -12,6 +12,8 @@ import {isEffectiveApi} from 'amis-core';
|
||||
import {isMobile, createObject} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
import find from 'lodash/find';
|
||||
|
||||
/**
|
||||
* 链式下拉框
|
||||
@ -257,6 +259,49 @@ export default class ChainedSelectControl extends React.Component<
|
||||
reload && reload();
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
const {
|
||||
options = [],
|
||||
labelField = 'label',
|
||||
valueField = 'value',
|
||||
classPrefix,
|
||||
classnames: cx,
|
||||
className,
|
||||
value,
|
||||
delimiter
|
||||
} = this.props;
|
||||
|
||||
const allOptions = [
|
||||
{options, visible: true},
|
||||
...(this.state.stack || [])
|
||||
];
|
||||
const valueArr = Array.isArray(value)
|
||||
? value.concat()
|
||||
: value && typeof value === 'string'
|
||||
? value.split(delimiter || ',')
|
||||
: [];
|
||||
|
||||
if (valueArr?.length > 0) {
|
||||
displayValue = valueArr
|
||||
.map((value: any, index) => {
|
||||
const {options, visible} = allOptions[index] || {};
|
||||
if (visible === false) {
|
||||
return null;
|
||||
}
|
||||
if (!options || !options.length) {
|
||||
return value;
|
||||
}
|
||||
const selectedOption = find(options, (o) => value === o[valueField]) || {};
|
||||
return selectedOption[labelField] ?? value;
|
||||
})
|
||||
.filter(v => v != null)
|
||||
.join(' > ');
|
||||
}
|
||||
|
||||
return <div className={cx(`${classPrefix}SelectStaticControl`, className)}>{displayValue}</div>;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
options,
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from 'amis-core';
|
||||
import {autobind} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* 图表 Radio 单选框。
|
||||
@ -46,6 +47,10 @@ export default class ChartRadiosControl extends React.Component<
|
||||
}
|
||||
|
||||
highlight(index: number = this.highlightIndex) {
|
||||
if (this.props.static) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlightIndex = index;
|
||||
|
||||
if (!this.chart || this.prevIndex === index) {
|
||||
@ -80,7 +85,8 @@ export default class ChartRadiosControl extends React.Component<
|
||||
this.prevIndex = index;
|
||||
}
|
||||
|
||||
compoonentDidMount() {
|
||||
componentDidMount() {
|
||||
// to do 初始化有值的情况暂时无法生效
|
||||
if (this.props.selectedOptions.length) {
|
||||
this.highlight(this.props.options.indexOf(this.props.selectedOptions[0]));
|
||||
}
|
||||
@ -92,6 +98,30 @@ export default class ChartRadiosControl extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
this.prevIndex = -1;
|
||||
this.highlightIndex = -1;
|
||||
|
||||
const {
|
||||
options = [],
|
||||
selectedOptions,
|
||||
labelField = 'label',
|
||||
valueField = 'value',
|
||||
chartValueField
|
||||
} = this.props;
|
||||
if (options.length && selectedOptions.length) {
|
||||
const count = options.reduce((all, cur) => {
|
||||
return all + cur[chartValueField || valueField]
|
||||
}, 0);
|
||||
if (count > 0) {
|
||||
const percent = (+selectedOptions[0][chartValueField || valueField] / count * 100).toFixed(2);
|
||||
displayValue = `${selectedOptions[0][labelField]}:${percent}%`;
|
||||
}
|
||||
}
|
||||
return <>{displayValue}</>;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {options, labelField, chartValueField, valueField, render} =
|
||||
this.props;
|
||||
|
@ -6,6 +6,7 @@ import {withBadge, BadgeObject} from 'amis-ui';
|
||||
import {autobind, createObject} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {BaseSchema, FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
export interface SchemaMap {
|
||||
checkbox: CheckboxControlSchema;
|
||||
@ -89,6 +90,37 @@ export default class CheckboxControl extends React.Component<
|
||||
onChange && onChange(eventData);
|
||||
}
|
||||
|
||||
renderStatic() {
|
||||
const {
|
||||
value,
|
||||
trueValue,
|
||||
falseValue,
|
||||
option,
|
||||
render,
|
||||
partial,
|
||||
optionType,
|
||||
checked,
|
||||
labelClassName
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
inline
|
||||
value={value || ''}
|
||||
trueValue={trueValue}
|
||||
falseValue={falseValue}
|
||||
disabled={true}
|
||||
partial={partial}
|
||||
optionType={optionType}
|
||||
checked={checked}
|
||||
labelClassName={labelClassName}
|
||||
>
|
||||
{option ? render('option', option) : null}
|
||||
</Checkbox>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import type {ActionObject, Api, OptionsControlProps, Option} from 'amis-core';
|
||||
import {Checkbox, Icon} from 'amis-ui';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* 复选框
|
||||
@ -322,6 +323,7 @@ export default class CheckboxesControl extends React.Component<
|
||||
return result;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -1009,6 +1009,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
conditions,
|
||||
changeImmediately,
|
||||
addBtnText,
|
||||
static: isStatic,
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
@ -1164,6 +1165,10 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
renderDelBtn(value: any, index: number) {
|
||||
if (this.props.static) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
@ -1257,6 +1262,10 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
renderAddBtn() {
|
||||
if (this.props.static) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
@ -1355,7 +1364,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
placeholder,
|
||||
translate: __,
|
||||
itemClassName,
|
||||
itemsWrapperClassName
|
||||
itemsWrapperClassName,
|
||||
static: isStatic
|
||||
} = this.props;
|
||||
|
||||
let items = this.props.items;
|
||||
@ -1372,7 +1382,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
multiLine ? `Combo--ver` : `Combo--hor`,
|
||||
noBorder ? `Combo--noBorder` : '',
|
||||
disabled ? 'is-disabled' : '',
|
||||
!disabled && draggable && Array.isArray(value) && value.length > 1
|
||||
!isStatic && !disabled && draggable && Array.isArray(value) && value.length > 1
|
||||
? 'is-draggable'
|
||||
: ''
|
||||
)}
|
||||
@ -1405,7 +1415,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
className={cx(`Combo-item`, itemClassName)}
|
||||
key={this.keys[index] || (this.keys[index] = guid())}
|
||||
>
|
||||
{!disabled && draggable && thelist.length > 1 ? (
|
||||
{!isStatic && !disabled && draggable && thelist.length > 1 ? (
|
||||
<div className={cx('Combo-itemDrager')}>
|
||||
<a
|
||||
key="drag"
|
||||
@ -1479,7 +1489,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
<div className={cx(`Combo-placeholder`)}>{__(placeholder)}</div>
|
||||
) : null}
|
||||
</div>
|
||||
{!disabled ? (
|
||||
{!isStatic && !disabled ? (
|
||||
<div className={cx(`Combo-toolbar`)}>
|
||||
{this.renderAddBtn()}
|
||||
{draggable ? (
|
||||
@ -1584,16 +1594,39 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
);
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
// 如有 staticSchema 会被拦截渲染schema, 不会走到这里
|
||||
return this.props.render(
|
||||
'static-input-kv',
|
||||
{
|
||||
type: 'json'
|
||||
},
|
||||
this.props
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
type,
|
||||
formInited,
|
||||
multiple,
|
||||
className,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
disabled
|
||||
static: isStatic,
|
||||
staticSchema
|
||||
} = this.props;
|
||||
|
||||
// 静态展示时
|
||||
// 当有staticSchema 或 type = input-kv | input-kvs
|
||||
// 才拦截处理,其他情况交给子表单项处理即可
|
||||
if (
|
||||
isStatic
|
||||
&& (staticSchema || ['input-kv', 'input-kvs'].includes(type))
|
||||
) {
|
||||
return this.renderStatic();
|
||||
}
|
||||
|
||||
return formInited || typeof formInited === 'undefined' ? (
|
||||
<div className={cx(`ComboControl`, className)}>
|
||||
{multiple ? this.renderMultipe() : this.renderSingle()}
|
||||
|
@ -9,6 +9,7 @@ import {ActionObject} from 'amis-core';
|
||||
import {Option} from 'amis-core';
|
||||
import {localeable, LocaleProps} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* City 城市选择框。
|
||||
@ -71,6 +72,21 @@ export interface CityPickerProps
|
||||
useMobileUI?: boolean;
|
||||
}
|
||||
|
||||
export interface CityDb {
|
||||
province: Array<string>;
|
||||
city: {
|
||||
[propName: number]: Array<number>;
|
||||
};
|
||||
district: {
|
||||
[propName: number]:
|
||||
| {
|
||||
[propName: number]: Array<number>;
|
||||
}
|
||||
| Array<number>;
|
||||
};
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export interface CityPickerState {
|
||||
code: number;
|
||||
province: string;
|
||||
@ -80,21 +96,76 @@ export interface CityPickerState {
|
||||
district: string;
|
||||
districtCode: number;
|
||||
street: string;
|
||||
db?: CityDb;
|
||||
}
|
||||
|
||||
db?: {
|
||||
province: Array<string>;
|
||||
city: {
|
||||
[propName: number]: Array<number>;
|
||||
};
|
||||
district: {
|
||||
[propName: number]:
|
||||
| {
|
||||
[propName: number]: Array<number>;
|
||||
}
|
||||
| Array<number>;
|
||||
};
|
||||
[propName: string]: any;
|
||||
const getCityFromCode = ({
|
||||
value,
|
||||
db,
|
||||
delimiter = ','
|
||||
}:{
|
||||
value: any;
|
||||
db?: CityDb;
|
||||
delimiter?: string;
|
||||
}) => {
|
||||
const result = {
|
||||
code: 0,
|
||||
province: '',
|
||||
provinceCode: 0,
|
||||
city: '',
|
||||
cityCode: 0,
|
||||
district: '',
|
||||
districtCode: 0,
|
||||
street: ''
|
||||
};
|
||||
|
||||
if (!db || !value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let code =
|
||||
(value && value.code) ||
|
||||
(typeof value === 'number' && value) ||
|
||||
(typeof value === 'string' && /(\d{6})/.test(value) && RegExp.$1);
|
||||
|
||||
if (code && db[code]) {
|
||||
code = parseInt(code, 10);
|
||||
result.code = code;
|
||||
|
||||
const provinceCode = code - (code % 10000);
|
||||
if (db[provinceCode]) {
|
||||
result.provinceCode = provinceCode;
|
||||
result.province = db[provinceCode];
|
||||
}
|
||||
|
||||
const cityCode = code - (code % 100);
|
||||
if (cityCode !== provinceCode && db[cityCode]) {
|
||||
result.cityCode = cityCode;
|
||||
result.city = db[cityCode];
|
||||
} else if (~db.city[provinceCode]?.indexOf(code)) {
|
||||
result.cityCode = code;
|
||||
result.city = db[code];
|
||||
}
|
||||
|
||||
if (code % 100) {
|
||||
result.district = db[code];
|
||||
result.districtCode = code;
|
||||
}
|
||||
} else if (value) {
|
||||
// todo 模糊查找
|
||||
}
|
||||
|
||||
if (value && value.street) {
|
||||
result.street = value.street;
|
||||
} else if (typeof value === 'string' && ~value.indexOf(delimiter)) {
|
||||
result.street = value.slice(value.indexOf(delimiter) + delimiter.length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const loadDb = (callback: (db: any) => void): void => {
|
||||
import('amis-ui/lib/components/CityDB').then(callback);
|
||||
}
|
||||
|
||||
export class CityPicker extends React.Component<
|
||||
@ -139,7 +210,7 @@ export class CityPicker extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
import('amis-ui/lib/components/CityDB').then(db => {
|
||||
loadDb(db => {
|
||||
this.setState(
|
||||
{
|
||||
db: {
|
||||
@ -272,56 +343,11 @@ export class CityPicker extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
const state = {
|
||||
code: 0,
|
||||
province: '',
|
||||
provinceCode: 0,
|
||||
city: '',
|
||||
cityCode: 0,
|
||||
district: '',
|
||||
districtCode: 0,
|
||||
street: ''
|
||||
};
|
||||
|
||||
let code =
|
||||
(value && value.code) ||
|
||||
(typeof value === 'number' && value) ||
|
||||
(typeof value === 'string' && /(\d{6})/.test(value) && RegExp.$1);
|
||||
|
||||
if (code && db[code]) {
|
||||
code = parseInt(code, 10);
|
||||
state.code = code;
|
||||
|
||||
const provinceCode = code - (code % 10000);
|
||||
if (db[provinceCode]) {
|
||||
state.provinceCode = provinceCode;
|
||||
state.province = db[provinceCode];
|
||||
}
|
||||
|
||||
const cityCode = code - (code % 100);
|
||||
if (cityCode !== provinceCode && db[cityCode]) {
|
||||
state.cityCode = cityCode;
|
||||
state.city = db[cityCode];
|
||||
} else if (~db.city[provinceCode]?.indexOf(code)) {
|
||||
state.cityCode = code;
|
||||
state.city = db[code];
|
||||
}
|
||||
|
||||
if (code % 100) {
|
||||
state.district = db[code];
|
||||
state.districtCode = code;
|
||||
}
|
||||
} else if (value) {
|
||||
// todo 模糊查找
|
||||
}
|
||||
|
||||
if (value && value.street) {
|
||||
state.street = value.street;
|
||||
} else if (typeof value === 'string' && ~value.indexOf(delimiter)) {
|
||||
state.street = value.slice(value.indexOf(delimiter) + delimiter.length);
|
||||
}
|
||||
|
||||
this.setState(state);
|
||||
this.setState(getCityFromCode({
|
||||
value,
|
||||
delimiter,
|
||||
db
|
||||
}));
|
||||
}
|
||||
|
||||
@autobind
|
||||
@ -462,6 +488,10 @@ export interface LocationControlProps extends FormControlProps {
|
||||
allowStreet?: boolean;
|
||||
}
|
||||
export class LocationControl extends React.Component<LocationControlProps> {
|
||||
state = {
|
||||
db: null
|
||||
}
|
||||
|
||||
@autobind
|
||||
doAction(action: ActionObject, data: object, throwErrors: boolean) {
|
||||
const {resetValue, onChange} = this.props;
|
||||
@ -492,6 +522,51 @@ export class LocationControl extends React.Component<LocationControlProps> {
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '') {
|
||||
const {value, delimiter} = this.props;
|
||||
if (!this.state.db) {
|
||||
loadDb(db => {
|
||||
this.setState(
|
||||
{
|
||||
db: {
|
||||
...db.default,
|
||||
province: db.province as any,
|
||||
city: db.city,
|
||||
district: db.district
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
return <Spinner size='sm' />;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return <>{displayValue}</>;
|
||||
}
|
||||
|
||||
const {
|
||||
province,
|
||||
city,
|
||||
district,
|
||||
street
|
||||
} = getCityFromCode({
|
||||
value,
|
||||
delimiter,
|
||||
db: this.state.db
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
[province, city, district, street]
|
||||
.filter(v => !!v)
|
||||
.join(delimiter)
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
|
@ -5,7 +5,7 @@ import {FormItem, FormControlProps} from 'amis-core';
|
||||
import type {PresetColor} from 'amis-ui';
|
||||
import {isMobile} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import renderStaticHoc from './StaticHoc';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
// todo amis-ui 里面组件直接改成按需加载
|
||||
export const ColorPicker = React.lazy(
|
||||
@ -71,15 +71,7 @@ export default class ColorControl extends React.PureComponent<
|
||||
open: false
|
||||
};
|
||||
|
||||
@renderStaticHoc()
|
||||
renderStatic() {
|
||||
return this.props.render(
|
||||
'static-color',
|
||||
{type: 'color'},
|
||||
this.props
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
@ -93,25 +85,21 @@ export default class ColorControl extends React.PureComponent<
|
||||
const mobileUI = useMobileUI && isMobile();
|
||||
return (
|
||||
<div className={cx(`${ns}ColorControl`, className)}>
|
||||
{
|
||||
isStatic
|
||||
? this.renderStatic()
|
||||
: <Suspense fallback={<div>...</div>}>
|
||||
<ColorPicker
|
||||
classPrefix={ns}
|
||||
{...rest}
|
||||
useMobileUI={useMobileUI}
|
||||
popOverContainer={
|
||||
mobileUI && env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: mobileUI
|
||||
? undefined
|
||||
: rest.popOverContainer
|
||||
}
|
||||
value={value || ''}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
<Suspense fallback={<div>...</div>}>
|
||||
<ColorPicker
|
||||
classPrefix={ns}
|
||||
{...rest}
|
||||
useMobileUI={useMobileUI}
|
||||
popOverContainer={
|
||||
mobileUI && env && env.getModalContainer
|
||||
? env.getModalContainer
|
||||
: mobileUI
|
||||
? undefined
|
||||
: rest.popOverContainer
|
||||
}
|
||||
value={value || ''}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {DatePicker} from 'amis-ui';
|
||||
import {FormBaseControlSchema, SchemaObject} from '../../Schema';
|
||||
import {createObject, anyChanged, isMobile, autobind} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
export interface InputDateBaseControlSchema extends FormBaseControlSchema {
|
||||
/**
|
||||
@ -455,6 +456,7 @@ export default class DateControl extends React.PureComponent<
|
||||
this.props.onChange(nextValue);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
let {
|
||||
className,
|
||||
|
@ -8,6 +8,7 @@ import {isMobile, createObject, autobind} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import type {ShortCuts} from 'amis-ui/lib/components/DatePicker';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* DateRange 日期范围控件
|
||||
@ -232,6 +233,7 @@ export default class DateRangeControl extends React.Component<DateRangeProps> {
|
||||
this.props.onChange(nextValue);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -4,6 +4,7 @@ import cx from 'classnames';
|
||||
import {filterDate, parseDuration} from 'amis-core';
|
||||
import InputDateRange, {DateRangeControlSchema} from './InputDateRange';
|
||||
import {DateRangePicker} from 'amis-ui';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* MonthRange 月范围控件
|
||||
@ -16,6 +17,7 @@ export interface MonthRangeControlSchema
|
||||
}
|
||||
|
||||
export default class MonthRangeControl extends InputDateRange {
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
ActionObject
|
||||
} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* 数字输入框
|
||||
@ -296,7 +297,18 @@ export default class NumberControl extends React.Component<
|
||||
}
|
||||
this.input.focus();
|
||||
}
|
||||
render(): JSX.Element {
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
const {unit, value} = this.props;
|
||||
const finalValue =
|
||||
unit && value && typeof value === 'string'
|
||||
? value.replace(unit, '')
|
||||
: value;
|
||||
return <>{finalValue || displayValue}</>;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
classPrefix: ns,
|
||||
|
@ -4,6 +4,7 @@ import cx from 'classnames';
|
||||
import {filterDate, parseDuration} from 'amis-core';
|
||||
import InputDateRange, {DateRangeControlSchema} from './InputDateRange';
|
||||
import {DateRangePicker} from 'amis-ui';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
/**
|
||||
* QuarterRange 季度范围控件
|
||||
* 文档:https://baidu.gitee.io/amis/docs/components/form/input-quarter-range
|
||||
@ -14,6 +15,7 @@ export interface QuarterRangeControlSchema
|
||||
}
|
||||
|
||||
export default class QuarterRangeControl extends InputDateRange {
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -13,6 +13,7 @@ import {autobind, createObject} from 'amis-core';
|
||||
import {filter} from 'amis-core';
|
||||
import {FormBaseControlSchema, SchemaObject} from '../../Schema';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Range
|
||||
@ -599,6 +600,7 @@ export default class RangeControl extends React.PureComponent<
|
||||
: value;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {value} = this.state;
|
||||
const props: RangeItemProps = {
|
||||
|
@ -5,6 +5,7 @@ import {ActionObject} from 'amis-core';
|
||||
import {Rating} from 'amis-ui';
|
||||
import type {textPositionType} from 'amis-ui/lib/components/Rating';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Rating
|
||||
@ -117,6 +118,43 @@ export default class RatingControl extends React.Component<RatingProps, any> {
|
||||
onChange?.(value);
|
||||
}
|
||||
|
||||
renderStatic() {
|
||||
const {
|
||||
className,
|
||||
value,
|
||||
count,
|
||||
half,
|
||||
char,
|
||||
inactiveColor,
|
||||
colors,
|
||||
texts,
|
||||
charClassName,
|
||||
textClassName,
|
||||
textPosition,
|
||||
classnames: cx
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx('RatingControl', className)}>
|
||||
<Rating
|
||||
classnames={cx}
|
||||
value={value}
|
||||
disabled={true}
|
||||
count={count}
|
||||
half={half}
|
||||
char={char}
|
||||
inactiveColor={inactiveColor}
|
||||
colors={colors}
|
||||
texts={texts}
|
||||
charClassName={charClassName}
|
||||
textClassName={textClassName}
|
||||
textPosition={textPosition}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -2,14 +2,12 @@ import React from 'react';
|
||||
import {
|
||||
OptionsControl,
|
||||
OptionsControlProps,
|
||||
Option,
|
||||
FormOptionsControl
|
||||
Option
|
||||
} from 'amis-core';
|
||||
import Downshift from 'downshift';
|
||||
import find from 'lodash/find';
|
||||
import isInteger from 'lodash/isInteger';
|
||||
import unionWith from 'lodash/unionWith';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {ResultBox} from 'amis-ui';
|
||||
import {autobind, filterTree, createObject} from 'amis-core';
|
||||
@ -19,6 +17,7 @@ import {PopOver} from 'amis-core';
|
||||
import {ListMenu} from 'amis-ui';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Tag 输入框
|
||||
@ -425,6 +424,7 @@ export default class TagControl extends React.PureComponent<
|
||||
return max != null && isInteger(max) && selectedOptions.length >= max;
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -19,7 +19,7 @@ import {ActionSchema} from '../Action';
|
||||
import {FormOptionsSchema, SchemaApi} from '../../Schema';
|
||||
import {generateIcon} from 'amis-core';
|
||||
import {rendererEventDispatcher, bindRendererEvent} from 'amis-core';
|
||||
import renderStaticHoc from './StaticHoc';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
import type {Option} from 'amis-core';
|
||||
import type {ListenerAction} from 'amis-core';
|
||||
@ -892,27 +892,11 @@ export default class TextControl extends React.PureComponent<
|
||||
);
|
||||
}
|
||||
|
||||
@renderStaticHoc()
|
||||
renderStatic(displayValue = '-'): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
this.props.type === 'input-password'
|
||||
? '********'
|
||||
: displayValue
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
renderBody(body: JSX.Element) {
|
||||
const {
|
||||
classnames: cx,
|
||||
className,
|
||||
classPrefix: ns,
|
||||
options,
|
||||
source,
|
||||
autoComplete,
|
||||
addOn: addOnRaw,
|
||||
render,
|
||||
data,
|
||||
@ -929,12 +913,6 @@ export default class TextControl extends React.PureComponent<
|
||||
}
|
||||
: addOnRaw;
|
||||
|
||||
let input = isStatic
|
||||
? this.renderStatic()
|
||||
: autoComplete !== false && (source || options?.length || autoComplete)
|
||||
? this.renderSugestMode()
|
||||
: this.renderNormal();
|
||||
|
||||
const iconElement = generateIcon(cx, addOn?.icon, 'Icon');
|
||||
|
||||
let addOnDom = addOn && !isStatic ? (
|
||||
@ -954,23 +932,42 @@ export default class TextControl extends React.PureComponent<
|
||||
) : null;
|
||||
|
||||
if (inputOnly) {
|
||||
return input;
|
||||
return body;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(className, `${ns}TextControl`, {
|
||||
const classNames = !isStatic
|
||||
? cx(className, `${ns}TextControl`, {
|
||||
[`${ns}TextControl--withAddOn`]: !!addOnDom,
|
||||
'is-focused': this.state.isFocused,
|
||||
'is-disabled': disabled
|
||||
})}
|
||||
>
|
||||
})
|
||||
: cx(`${ns}TextControl`, {
|
||||
[`${ns}TextControl--withAddOn`]: !!addOnDom
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{addOn && addOn.position === 'left' ? addOnDom : null}
|
||||
{input}
|
||||
{body}
|
||||
{addOn && addOn.position !== 'left' ? addOnDom : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render(): JSX.Element {
|
||||
const {
|
||||
options,
|
||||
source,
|
||||
autoComplete,
|
||||
} = this.props;
|
||||
|
||||
let input = autoComplete !== false && (source || options?.length || autoComplete)
|
||||
? this.renderSugestMode()
|
||||
: this.renderNormal();
|
||||
|
||||
return this.renderBody(input);
|
||||
}
|
||||
}
|
||||
|
||||
export function mapItemIndex(
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
} from 'amis-core';
|
||||
import {Spinner} from 'amis-ui';
|
||||
import {FormOptionsSchema, SchemaApi} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Tree 下拉选择框。
|
||||
@ -179,6 +180,7 @@ export default class TreeControl extends React.Component<TreeProps> {
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -4,6 +4,7 @@ import cx from 'classnames';
|
||||
import {filterDate, parseDuration} from 'amis-core';
|
||||
import InputDateRange, {DateRangeControlSchema} from './InputDateRange';
|
||||
import {DateRangePicker} from 'amis-ui';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* YearRange 年份范围控件
|
||||
@ -15,6 +16,7 @@ export interface YearRangeControlSchema
|
||||
}
|
||||
|
||||
export default class YearRangeControl extends InputDateRange {
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
SchemaClassName,
|
||||
SchemaCollection
|
||||
} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* List 复选框
|
||||
@ -94,6 +95,78 @@ export default class ListControl extends React.Component<ListProps, any> {
|
||||
reload && reload();
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
const {
|
||||
itemSchema,
|
||||
labelField,
|
||||
valueField,
|
||||
imageClassName,
|
||||
itemClassName,
|
||||
selectedOptions,
|
||||
classnames: cx,
|
||||
render,
|
||||
data
|
||||
} = this.props;
|
||||
|
||||
if (!selectedOptions.length) {
|
||||
return displayValue;
|
||||
}
|
||||
|
||||
const itemRender = (option: Option, key: number) => {
|
||||
let label = option[labelField || 'label'];
|
||||
label = label || `选项${key + 1}`;
|
||||
if (itemSchema || option.body || option.image) {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={cx(
|
||||
'ListControl-static-item',
|
||||
itemClassName
|
||||
)}
|
||||
>
|
||||
{itemSchema
|
||||
? render(`${key}/body`, itemSchema, {
|
||||
data: createObject(data, option)
|
||||
})
|
||||
: option.body
|
||||
? render(`${key}/body`, option.body)
|
||||
: [(option.image
|
||||
? <div key="image"
|
||||
className={cx('ListControl-itemImage', imageClassName)}
|
||||
>
|
||||
<img src={option.image} alt={label} />
|
||||
</div>
|
||||
: null
|
||||
),
|
||||
(
|
||||
<div key="label"
|
||||
className={cx('ListControl-itemLabel')}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
)
|
||||
]
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={cx(`ListControl-static-item`)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={cx('StaticList')}>
|
||||
{selectedOptions.map(itemRender)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
render,
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import {themeable, ClassNamesFn, ThemeProps} from 'amis-core';
|
||||
import {themeable, ClassNamesFn, ThemeProps, Overlay, PopOver, autobind} from 'amis-core';
|
||||
import {FormItem, FormBaseControl, FormControlProps} from 'amis-core';
|
||||
import {LocationPicker} from 'amis-ui';
|
||||
import {LocationPicker, Alert2, BaiduMapPicker, Icon} from 'amis-ui';
|
||||
import {filter} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
/**
|
||||
* Location 选点组件
|
||||
* 文档:https://baidu.gitee.io/amis/docs/components/form/location
|
||||
@ -41,7 +42,89 @@ export class LocationControl extends React.Component<LocationControlProps> {
|
||||
vendor: 'baidu',
|
||||
coordinatesType: 'bd09'
|
||||
};
|
||||
domRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
state = {
|
||||
isOpened: false
|
||||
};
|
||||
|
||||
@autobind
|
||||
close() {
|
||||
this.setState({
|
||||
isOpened: false
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
open() {
|
||||
this.setState({
|
||||
isOpened: true
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleClick() {
|
||||
this.state.isOpened ? this.close() : this.open();
|
||||
}
|
||||
|
||||
@autobind
|
||||
getParent() {
|
||||
return this.domRef.current?.parentElement;
|
||||
}
|
||||
|
||||
@autobind
|
||||
getTarget() {
|
||||
return this.domRef.current;
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
const {
|
||||
classnames: cx,
|
||||
value,
|
||||
vendor,
|
||||
ak,
|
||||
coordinatesType,
|
||||
popOverContainer,
|
||||
} = this.props;
|
||||
const __ = this.props.translate;
|
||||
|
||||
if (!value) {
|
||||
return <>{displayValue}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={this.props.classnames('LocationControl')} ref={this.domRef}>
|
||||
<span>{value.address}</span>
|
||||
<a className={cx('LocationPicker-toggler', 'ml-1')} onClick={this.handleClick}>
|
||||
<Icon icon="location" className="icon" />
|
||||
</a>
|
||||
<Overlay
|
||||
target={this.getTarget}
|
||||
container={popOverContainer || this.getParent}
|
||||
rootClose={false}
|
||||
show={this.state.isOpened}
|
||||
>
|
||||
<PopOver
|
||||
className={cx('LocationPicker-popover')}
|
||||
onHide={this.close}
|
||||
overlay
|
||||
style={{width: this.getTarget()?.offsetWidth}}
|
||||
>
|
||||
{vendor === 'baidu' ? (
|
||||
<BaiduMapPicker
|
||||
ak={ak}
|
||||
value={value}
|
||||
coordinatesType={coordinatesType}
|
||||
/>
|
||||
) : (
|
||||
<Alert2>{__('${vendor} 地图控件不支持', {vendor})}</Alert2>
|
||||
)}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
return (
|
||||
<div className={this.props.classnames('LocationControl')}>
|
||||
|
@ -10,6 +10,7 @@ import {Checkbox, Spinner} from 'amis-ui';
|
||||
import {autobind, setVariable, createObject} from 'amis-core';
|
||||
import {ApiObject, ActionObject} from 'amis-core';
|
||||
import {FormBaseControlSchema, SchemaApi} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Matrix 选择控件。适合做权限勾选。
|
||||
@ -286,7 +287,7 @@ export default class MatrixCheckbox extends React.Component<
|
||||
this.props.onChange(value.concat());
|
||||
}
|
||||
|
||||
renderInput() {
|
||||
renderInput(forceDisabled = false) {
|
||||
const {columns, rows} = this.state;
|
||||
const {rowLabel, disabled, classnames: cx, multiple} = this.props;
|
||||
|
||||
@ -321,7 +322,7 @@ export default class MatrixCheckbox extends React.Component<
|
||||
<td key={x} className="text-center">
|
||||
<Checkbox
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
disabled={disabled}
|
||||
disabled={forceDisabled || disabled}
|
||||
checked={
|
||||
!!(value[x] && value[x][y] && value[x][y].checked)
|
||||
}
|
||||
@ -340,9 +341,22 @@ export default class MatrixCheckbox extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '-') {
|
||||
const {className, render, classnames: cx} = this.props;
|
||||
const {error} = this.state;
|
||||
return (
|
||||
<div key="input" className={cx('MatrixControl', className || '')}>
|
||||
{error
|
||||
? displayValue
|
||||
: this.renderInput(true)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {className, render, classnames: cx} = this.props;
|
||||
|
||||
const {error, loading} = this.state;
|
||||
|
||||
return (
|
||||
|
@ -30,6 +30,7 @@ import {RootClose} from 'amis-core';
|
||||
import {Cascader} from 'amis-ui';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Nested Select
|
||||
@ -862,6 +863,7 @@ export default class NestedSelectControl extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import {autobind, isEmpty, createObject} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Radio 单选框。
|
||||
@ -87,6 +88,7 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
|
||||
reload && reload();
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -20,6 +20,7 @@ import {TransferDropDown} from 'amis-ui';
|
||||
|
||||
import type {SchemaClassName} from '../../Schema';
|
||||
import type {TooltipObject} from 'amis-ui/lib/components/TooltipWrapper';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Select 下拉选择框。
|
||||
@ -398,6 +399,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
let {
|
||||
autoComplete,
|
||||
|
@ -1,23 +1,72 @@
|
||||
import React from "react";
|
||||
import {getPropValue, FormControlProps} from "amis-core";
|
||||
|
||||
export interface renderStaticHocProps {
|
||||
/**
|
||||
* 展示态时,是否去掉paddingY
|
||||
* 大多数都需要,个别表单项不需要
|
||||
*/
|
||||
staticNoPaddingY?: boolean;
|
||||
function renderCommonStatic(props: any, defaultValue: string) {
|
||||
const {
|
||||
type,
|
||||
render,
|
||||
staticSchema
|
||||
} = props;
|
||||
const staticProps = {
|
||||
...props,
|
||||
...staticSchema
|
||||
};
|
||||
|
||||
switch(type) {
|
||||
case 'select':
|
||||
case 'checkboxes':
|
||||
case 'button-group-select':
|
||||
case 'input-tree':
|
||||
case 'tree-select':
|
||||
case 'nested-select':
|
||||
case 'cascader-select':
|
||||
case 'radios':
|
||||
case 'multi-select':
|
||||
case 'transfer':
|
||||
case 'transfer-picker':
|
||||
case 'tabs-transfer':
|
||||
case 'tabs-transfer-picker':
|
||||
return render('static-select', {type: 'words'}, staticProps);
|
||||
|
||||
case 'input-date':
|
||||
case 'input-datetime':
|
||||
case 'input-time':
|
||||
case 'input-month':
|
||||
case 'input-quarter':
|
||||
case 'input-year':
|
||||
return renderStaticDateTypes(staticProps);
|
||||
|
||||
case 'input-date-range':
|
||||
case 'input-datetime-range':
|
||||
case 'input-time-range':
|
||||
case 'input-month-range':
|
||||
case 'input-quarter-range':
|
||||
case 'input-year-range':
|
||||
return render('static-input-date-range', {type: 'date-range'}, {
|
||||
...props,
|
||||
valueFormat: props.format,
|
||||
format: props.inputFormat,
|
||||
...staticSchema
|
||||
});
|
||||
|
||||
case 'input-password':
|
||||
return render('static-input-password', {type: 'password'}, staticProps);
|
||||
|
||||
case 'input-color':
|
||||
return render('static-color', {type: 'color'}, staticProps);
|
||||
|
||||
case 'input-tag':
|
||||
return render('static-input-tag', {type: 'tags'}, staticProps);
|
||||
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单项目类成员展示态装饰器
|
||||
* 表单项类成员render支持静态展示装饰器
|
||||
*/
|
||||
export default function renderStaticHoc<T extends FormControlProps>(
|
||||
hocProps:renderStaticHocProps = {
|
||||
staticNoPaddingY: false
|
||||
}
|
||||
) {
|
||||
const {staticNoPaddingY} = hocProps;
|
||||
export function supportStatic<T extends FormControlProps>() {
|
||||
return function (
|
||||
target: any,
|
||||
name: string,
|
||||
@ -26,27 +75,57 @@ export default function renderStaticHoc<T extends FormControlProps>(
|
||||
const original = descriptor.value;
|
||||
descriptor.value = function (...args: any[]) {
|
||||
const props = (this as TypedPropertyDescriptor<any> & {props: T}).props;
|
||||
const {
|
||||
staticSchema,
|
||||
render,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
staticPlaceholder = '-'
|
||||
} = props;
|
||||
if (props.static) {
|
||||
const {
|
||||
render,
|
||||
staticSchema,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
className,
|
||||
staticPlaceholder = '-'
|
||||
} = props;
|
||||
|
||||
let body;
|
||||
|
||||
const displayValue = getPropValue(props) || staticPlaceholder;
|
||||
const body = staticSchema
|
||||
// 外部传入自定义展示态schema
|
||||
? render('form-static-schema', staticSchema, props)
|
||||
// 预置展示态
|
||||
: original.apply(this, [...args, displayValue]);
|
||||
const displayValue = getPropValue(props);
|
||||
if (!displayValue) {
|
||||
body = staticPlaceholder;
|
||||
} else {
|
||||
// 自定义了schema并且有type
|
||||
if (staticSchema && (
|
||||
staticSchema.type
|
||||
|| Array.isArray(staticSchema)
|
||||
|| typeof staticSchema === 'string'
|
||||
|| typeof staticSchema === 'number'
|
||||
)) {
|
||||
body = render('form-static-schema', staticSchema, props);
|
||||
} else if (target.renderStatic) {
|
||||
// 特殊组件
|
||||
body = target.renderStatic.apply(this, [...args, displayValue]);
|
||||
} else {
|
||||
// 可复用组件
|
||||
body = renderCommonStatic(props, displayValue);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={cx(`${ns}Form-static`, {
|
||||
'is-noPaddingY-static': staticNoPaddingY
|
||||
})}>
|
||||
{body}
|
||||
</div>
|
||||
return <div className={cx(`${ns}Form-static`, className)}>{body}</div>
|
||||
}
|
||||
|
||||
return original.apply(this, args);
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
function renderStaticDateTypes(props: any) {
|
||||
const {render, type, inputFormat, timeFormat, format, value} = props;
|
||||
return render(
|
||||
'static-input-date',
|
||||
{
|
||||
type: 'date',
|
||||
value,
|
||||
format: type === 'time' && timeFormat ? timeFormat : inputFormat,
|
||||
valueFormat: format
|
||||
}
|
||||
);
|
||||
}
|
@ -5,7 +5,7 @@ import {createObject, autobind, isObject} from 'amis-core';
|
||||
import {generateIcon} from 'amis-core';
|
||||
import {IconSchema} from '../Icon';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import renderStaticHoc from './StaticHoc';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Switch
|
||||
@ -79,19 +79,50 @@ export default class SwitchControl extends React.Component<SwitchProps, any> {
|
||||
onChange && onChange(checked);
|
||||
}
|
||||
|
||||
@renderStaticHoc({
|
||||
staticNoPaddingY: true
|
||||
})
|
||||
getResult() {
|
||||
const {
|
||||
classnames: cx,
|
||||
onText,
|
||||
offText,
|
||||
} = this.props;
|
||||
const on = isObject(onText)
|
||||
? generateIcon(cx, onText.icon, 'Switch-icon')
|
||||
: onText;
|
||||
const off = isObject(offText)
|
||||
? generateIcon(cx, offText.icon, 'Switch-icon')
|
||||
: offText;
|
||||
return {on, off};
|
||||
}
|
||||
|
||||
renderBody(children: any) {
|
||||
const {
|
||||
classnames: cx,
|
||||
option,
|
||||
optionAtLeft
|
||||
} = this.props;
|
||||
|
||||
const Option = <span className={cx('Switch-option')}>{option}</span>;
|
||||
return (
|
||||
<>
|
||||
{optionAtLeft ? Option : null}
|
||||
{children}
|
||||
{optionAtLeft ? null : Option}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderStatic() {
|
||||
const {
|
||||
value,
|
||||
trueValue,
|
||||
on = '开',
|
||||
off = '关'
|
||||
} = this.props;
|
||||
return <>{value === trueValue ? on : off}</>;
|
||||
|
||||
const {on = '开', off = '关'} = this.getResult();
|
||||
const body = <span>{value === trueValue ? on : off}</span>;
|
||||
return this.renderBody(body);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
size,
|
||||
@ -101,46 +132,26 @@ export default class SwitchControl extends React.Component<SwitchProps, any> {
|
||||
value,
|
||||
trueValue,
|
||||
falseValue,
|
||||
onText,
|
||||
offText,
|
||||
option,
|
||||
onChange,
|
||||
disabled,
|
||||
optionAtLeft,
|
||||
static: isStatic
|
||||
} = this.props;
|
||||
|
||||
const on = isObject(onText)
|
||||
? generateIcon(cx, onText.icon, 'Switch-icon')
|
||||
: onText;
|
||||
const off = isObject(offText)
|
||||
? generateIcon(cx, offText.icon, 'Switch-icon')
|
||||
: offText;
|
||||
const {on, off} = this.getResult();
|
||||
|
||||
return (
|
||||
<div className={cx(`SwitchControl`, className)}>
|
||||
{optionAtLeft ? (
|
||||
<span className={cx('Switch-option')}>{option}</span>
|
||||
) : null}
|
||||
|
||||
{
|
||||
isStatic
|
||||
? this.renderStatic()
|
||||
: <Switch
|
||||
classPrefix={ns}
|
||||
value={value}
|
||||
trueValue={trueValue}
|
||||
falseValue={falseValue}
|
||||
onText={on}
|
||||
offText={off}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}
|
||||
size={size as any}
|
||||
/>
|
||||
}
|
||||
|
||||
{optionAtLeft ? null : (
|
||||
<span className={cx('Switch-option')}>{option}</span>
|
||||
{this.renderBody(
|
||||
<Switch
|
||||
classPrefix={ns}
|
||||
value={value}
|
||||
trueValue={trueValue}
|
||||
falseValue={falseValue}
|
||||
onText={on}
|
||||
offText={off}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}
|
||||
size={size as any}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import {Selection as BaseSelection} from 'amis-ui';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* TabsTransfer
|
||||
@ -268,6 +269,7 @@ export class TabsTransferRenderer extends BaseTabsTransferRenderer<TabsTransferP
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -8,6 +8,7 @@ import {autobind, createObject} from 'amis-core';
|
||||
import {Selection as BaseSelection} from 'amis-ui';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* TabsTransferPicker 穿梭器的弹框形态
|
||||
@ -80,6 +81,7 @@ export class TabsTransferPickerRenderer extends BaseTabsTransferRenderer<TabsTra
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -3,11 +3,12 @@ import {FormItem, FormControlProps, FormBaseControl} from 'amis-core';
|
||||
|
||||
import {Textarea} from 'amis-ui';
|
||||
|
||||
import {autobind, ucFirst} from 'amis-core';
|
||||
import {autobind} from 'amis-core';
|
||||
|
||||
import {bindRendererEvent} from 'amis-core';
|
||||
import type {ListenerAction} from 'amis-core';
|
||||
import {FormBaseControlSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* TextArea 多行文本输入框。
|
||||
@ -149,9 +150,22 @@ export default class TextAreaControl extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
renderStatic(displayValue = '') {
|
||||
const {
|
||||
render,
|
||||
staticSchema = {}
|
||||
} = this.props;
|
||||
|
||||
return render('static-textarea', {
|
||||
type: 'multiline-text',
|
||||
text: displayValue,
|
||||
maxRows: staticSchema.limit || 5
|
||||
}, staticSchema);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {...rest} = this.props;
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
{...rest}
|
||||
|
@ -26,6 +26,7 @@ import {Selection as BaseSelection} from 'amis-ui';
|
||||
import {ResultList} from 'amis-ui';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Transfer
|
||||
@ -384,6 +385,7 @@ export class BaseTransferRenderer<
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
let {
|
||||
className,
|
||||
|
@ -5,6 +5,7 @@ import {BaseTransferRenderer, TransferControlSchema} from './Transfer';
|
||||
import {TransferPicker} from 'amis-ui';
|
||||
import {autobind} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* TransferPicker 穿梭器的弹框形态
|
||||
@ -58,6 +59,7 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
|
||||
}
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
@ -23,6 +23,7 @@ import {findDOMNode} from 'react-dom';
|
||||
import {normalizeOptions} from 'amis-core';
|
||||
import {ActionObject} from 'amis-core';
|
||||
import {FormOptionsSchema} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Tree 下拉选择框。
|
||||
@ -598,6 +599,7 @@ export default class TreeSelectControl extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
@supportStatic()
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
|
51
packages/amis/src/renderers/MultilineText.tsx
Normal file
51
packages/amis/src/renderers/MultilineText.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file MultilineText
|
||||
*/
|
||||
import React from 'react';
|
||||
import {Renderer, RendererProps, resolveVariableAndFilter} from 'amis-core';
|
||||
import {BaseSchema} from '../Schema';
|
||||
import {MultilineText} from 'amis-ui';
|
||||
|
||||
/**
|
||||
* MultilineText
|
||||
*/
|
||||
export interface MultilineTextSchema extends BaseSchema {
|
||||
type: 'multiline-text';
|
||||
|
||||
/**
|
||||
* 文本
|
||||
*/
|
||||
text?: string;
|
||||
|
||||
/**
|
||||
* 最大行数
|
||||
*/
|
||||
maxRows?: number;
|
||||
|
||||
/**
|
||||
* 展开按钮文本
|
||||
*/
|
||||
expendButtonText?: string;
|
||||
|
||||
/**
|
||||
* 收起按钮文本
|
||||
*/
|
||||
collapseButtonText?: string;
|
||||
}
|
||||
|
||||
export interface MultilineTextProps
|
||||
extends RendererProps,
|
||||
Omit<MultilineTextSchema, 'type' | 'className'> {}
|
||||
|
||||
export class MultilineTextField extends React.Component<MultilineTextProps, object> {
|
||||
render() {
|
||||
const {data, text: originText} = this.props;
|
||||
const text = resolveVariableAndFilter(originText, data, '| raw');
|
||||
return <MultilineText {...this.props} text={text} />;
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
type: 'multiline-text'
|
||||
})
|
||||
export class MultilineTextFieldRenderer extends MultilineTextField {}
|
60
packages/amis/src/renderers/Password.tsx
Normal file
60
packages/amis/src/renderers/Password.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file Password
|
||||
*/
|
||||
import React from 'react';
|
||||
import {autobind, Renderer, RendererProps} from 'amis-core';
|
||||
import {BaseSchema} from '../Schema';
|
||||
import {Icon} from 'amis-ui';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*/
|
||||
export interface PasswordSchema extends BaseSchema {
|
||||
type: 'password';
|
||||
|
||||
/**
|
||||
* 打码模式的文本
|
||||
*/
|
||||
mosaicText?: string;
|
||||
}
|
||||
|
||||
export interface PasswordProps
|
||||
extends RendererProps,
|
||||
Omit<PasswordSchema, 'type' | 'className'> {}
|
||||
|
||||
export class PasswordField extends React.Component<PasswordProps, object> {
|
||||
state = {
|
||||
visible: false
|
||||
};
|
||||
|
||||
@autobind
|
||||
toggleVisible() {
|
||||
this.setState({
|
||||
visible: !this.state.visible
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
classnames: cx,
|
||||
className,
|
||||
mosaicText = '********',
|
||||
value
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span className={cx('Password-field', className)}>
|
||||
{this.state.visible ? value : mosaicText}
|
||||
{this.state.visible
|
||||
? <Icon icon="view" className="icon" onClick={this.toggleVisible} />
|
||||
: <Icon icon="invisible" className="icon" onClick={this.toggleVisible} />
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
type: 'password'
|
||||
})
|
||||
export class PasswordFieldRenderer extends PasswordField {}
|
260
packages/amis/src/renderers/Words.tsx
Normal file
260
packages/amis/src/renderers/Words.tsx
Normal file
@ -0,0 +1,260 @@
|
||||
/**
|
||||
* @file Words
|
||||
*/
|
||||
import React, {Fragment} from 'react';
|
||||
import {autobind, Renderer, RendererProps, Option, getTreeAncestors, resolveVariableAndFilter} from 'amis-core';
|
||||
import {BaseSchema} from '../Schema';
|
||||
import {PlainObject} from '../types';
|
||||
import {Tag} from 'amis-ui';
|
||||
|
||||
type Words = string | string[];
|
||||
/**
|
||||
* Words
|
||||
*/
|
||||
export interface WordsSchema extends BaseSchema {
|
||||
type: 'words';
|
||||
|
||||
/**
|
||||
* 展示限制, 为0时也无限制
|
||||
*/
|
||||
limit?: number;
|
||||
|
||||
/**
|
||||
* 展示文字
|
||||
*/
|
||||
expendButtonText?: string;
|
||||
|
||||
/**
|
||||
* 展示文字
|
||||
*/
|
||||
expendButton?: PlainObject;
|
||||
|
||||
/**
|
||||
* 收起文字
|
||||
*/
|
||||
collapseButtonText?: string;
|
||||
|
||||
/**
|
||||
* 展示文字
|
||||
*/
|
||||
collapseButton?: PlainObject;
|
||||
|
||||
/**
|
||||
* tags数据
|
||||
*/
|
||||
words: Words;
|
||||
|
||||
/**
|
||||
* useTag 当数据是数组时,是否使用tag的方式展示
|
||||
*/
|
||||
inTag?: boolean | PlainObject;
|
||||
|
||||
/**
|
||||
* 分割符
|
||||
*/
|
||||
delimiter?: string | JSX.Element;
|
||||
}
|
||||
|
||||
export interface WordsProps
|
||||
extends RendererProps,
|
||||
Omit<WordsSchema, 'type' | 'className'> {}
|
||||
|
||||
function getLabel(item: Option, index: number, {
|
||||
type,
|
||||
labelField = 'label',
|
||||
options = [],
|
||||
enableNodePath,
|
||||
hideNodePathLabel,
|
||||
pathSeparator = '/',
|
||||
}: any): string {
|
||||
if (enableNodePath
|
||||
|| (type === 'nested-select' && !hideNodePathLabel)
|
||||
){
|
||||
// 将所有祖先节点也展现出来
|
||||
const ancestors = getTreeAncestors(options, item, true);
|
||||
return `${
|
||||
ancestors
|
||||
? ancestors.map(item => `${item[labelField || 'label']}`).join(` ${pathSeparator} `)
|
||||
: item[labelField || 'label']
|
||||
}`;
|
||||
}
|
||||
|
||||
return item[labelField] || `选项${index}`;
|
||||
}
|
||||
|
||||
export class WordsField extends React.Component<WordsProps, object> {
|
||||
static defaultProps: Partial<WordsProps> = {
|
||||
inTag: false
|
||||
};
|
||||
|
||||
state = {
|
||||
isExpend: false
|
||||
};
|
||||
|
||||
@autobind
|
||||
toggleExpend() {
|
||||
this.setState({
|
||||
isExpend: !this.state.isExpend
|
||||
});
|
||||
}
|
||||
|
||||
getLimit(words: Words) {
|
||||
const {limit} = this.props;
|
||||
return limit ?? (Array.isArray(words) ? 10 : 200);
|
||||
}
|
||||
|
||||
renderContent(words: Words) {
|
||||
const {
|
||||
delimiter,
|
||||
inTag
|
||||
} = this.props;
|
||||
|
||||
// 纯文字展示
|
||||
if (!Array.isArray(words)) {
|
||||
return words;
|
||||
}
|
||||
|
||||
// 不使用tag时,默认用 逗号连接
|
||||
if (!inTag) {
|
||||
return words.map((item, key) => {
|
||||
return <Fragment key={key}>
|
||||
{item}
|
||||
{delimiter ? delimiter : ', '}
|
||||
</Fragment>
|
||||
})
|
||||
}
|
||||
|
||||
return words.map((label, key) => (
|
||||
// 使用tag展示时,默认不使用连接符
|
||||
<Tag
|
||||
key={key}
|
||||
label={label}
|
||||
className={'mb-1'}
|
||||
{...typeof inTag === 'object' ? inTag : undefined}
|
||||
></Tag>
|
||||
));
|
||||
}
|
||||
|
||||
renderAll(words: Words, hasBtn = false) {
|
||||
const {
|
||||
collapseButtonText = '收起',
|
||||
collapseButton,
|
||||
render
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderContent(words)}
|
||||
{!hasBtn ? null :
|
||||
render('collapseBtn', {
|
||||
type: 'button',
|
||||
level: 'link',
|
||||
className: 'ml-1'
|
||||
}, {
|
||||
onClick: this.toggleExpend,
|
||||
...collapseButton,
|
||||
label: collapseButtonText
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderPart(words: Words) {
|
||||
const {
|
||||
expendButtonText = '展开',
|
||||
expendButton,
|
||||
render
|
||||
} = this.props;
|
||||
|
||||
const limit = this.getLimit(words);
|
||||
let partContent = Array.isArray(words)
|
||||
? words.slice(0, limit)
|
||||
: words.toString().slice(0, limit);
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderContent(partContent)}
|
||||
...
|
||||
{render('collapseBtn', {
|
||||
type: 'button',
|
||||
level: 'link',
|
||||
className: 'ml-1'
|
||||
}, {
|
||||
onClick: this.toggleExpend,
|
||||
...expendButton,
|
||||
label: expendButtonText
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
getWords() {
|
||||
const {
|
||||
selectedOptions = [],
|
||||
words: oldWords,
|
||||
data
|
||||
} = this.props;
|
||||
|
||||
let words;
|
||||
if (typeof oldWords === 'string') {
|
||||
words = resolveVariableAndFilter(oldWords, data, '| raw');
|
||||
}
|
||||
|
||||
if (words) {
|
||||
return words;
|
||||
}
|
||||
|
||||
if (selectedOptions?.length > 0) {
|
||||
return selectedOptions
|
||||
.map((option: Option, index: number) => getLabel(option, index, this.props));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
classnames: cx,
|
||||
className
|
||||
} = this.props;
|
||||
|
||||
const words = this.getWords();
|
||||
|
||||
if (!words) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const limit = this.getLimit(words);
|
||||
let body;
|
||||
if (
|
||||
!limit
|
||||
|| (Array.isArray(words) && words.length <= limit)
|
||||
|| (!Array.isArray(words) && words.toString().length <= limit)
|
||||
) {
|
||||
// 渲染全部,且无展开收起按钮
|
||||
body = this.renderAll(words);
|
||||
}
|
||||
else {
|
||||
body = this.state.isExpend
|
||||
? this.renderAll(words, true)
|
||||
: this.renderPart(words);
|
||||
}
|
||||
|
||||
return <div className={cx('Words-field', className)}>{body}</div>
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
type: 'words'
|
||||
})
|
||||
export class WordsRenderer extends WordsField {}
|
||||
|
||||
@Renderer({
|
||||
type: 'tags'
|
||||
})
|
||||
export class TagsRenderer extends WordsField {
|
||||
static defaultProps: Partial<WordsProps> = {
|
||||
inTag: true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user