mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 添加 tabs-transfer-picker 组件 (#2972)
This commit is contained in:
parent
019dac8040
commit
dd8229a938
126
docs/zh-CN/components/form/tabs-transfer-picker.md
Normal file
126
docs/zh-CN/components/form/tabs-transfer-picker.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
title: TabsTransferPicker 穿梭选择器
|
||||||
|
description:
|
||||||
|
type: 0
|
||||||
|
group: null
|
||||||
|
menuName: TabsTransferPicker 穿梭选择器
|
||||||
|
icon:
|
||||||
|
---
|
||||||
|
|
||||||
|
在[TabsTransfer 组合穿梭器](./tabs-transfer)的基础上扩充了弹窗选择模式,展示值用的是简单的 input 框,但是编辑的操作是弹窗个穿梭框来完成。
|
||||||
|
|
||||||
|
适合用来做复杂选人组件。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"label": "组合穿梭器",
|
||||||
|
"type": "tabs-transfer-picker",
|
||||||
|
"name": "a",
|
||||||
|
"sortable": true,
|
||||||
|
"selectMode": "tree",
|
||||||
|
"searchable": true,
|
||||||
|
"pickerSize": "md",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "成员",
|
||||||
|
"selectMode": "tree",
|
||||||
|
"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": "zhugeliang"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "战士",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"label": "曹操",
|
||||||
|
"value": "caocao"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "钟无艳",
|
||||||
|
"value": "zhongwuyan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "打野",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"label": "李白",
|
||||||
|
"value": "libai"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "韩信",
|
||||||
|
"value": "hanxin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "云中君",
|
||||||
|
"value": "yunzhongjun"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 属性表
|
||||||
|
|
||||||
|
更多配置请参考[TabsTransfer 组合穿梭器](./tabs-transfer)。
|
@ -23,92 +23,41 @@ icon:
|
|||||||
"searchable": true,
|
"searchable": true,
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"label": "成员",
|
"label": "法师",
|
||||||
"selectMode": "tree",
|
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"label": "法师",
|
"label": "诸葛亮",
|
||||||
"children": [
|
"value": "zhugeliang"
|
||||||
{
|
|
||||||
"label": "诸葛亮",
|
|
||||||
"value": "zhugeliang"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "战士",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"label": "曹操",
|
|
||||||
"value": "caocao"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "钟无艳",
|
|
||||||
"value": "zhongwuyan"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "打野",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"label": "李白",
|
|
||||||
"value": "libai"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "韩信",
|
|
||||||
"value": "hanxin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "云中君",
|
|
||||||
"value": "yunzhongjun"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "用户",
|
"label": "战士",
|
||||||
"selectMode": "chained",
|
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"label": "法师",
|
"label": "曹操",
|
||||||
"children": [
|
"value": "caocao"
|
||||||
{
|
|
||||||
"label": "诸葛亮",
|
|
||||||
"value": "zhugeliang2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "战士",
|
"label": "钟无艳",
|
||||||
"children": [
|
"value": "zhongwuyan"
|
||||||
{
|
}
|
||||||
"label": "曹操",
|
]
|
||||||
"value": "caocao2"
|
},
|
||||||
},
|
{
|
||||||
{
|
"label": "打野",
|
||||||
"label": "钟无艳",
|
"children": [
|
||||||
"value": "zhongwuyan2"
|
{
|
||||||
}
|
"label": "李白",
|
||||||
]
|
"value": "libai"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "打野",
|
"label": "韩信",
|
||||||
"children": [
|
"value": "hanxin"
|
||||||
{
|
},
|
||||||
"label": "李白",
|
{
|
||||||
"value": "libai2"
|
"label": "云中君",
|
||||||
},
|
"value": "yunzhongjun"
|
||||||
{
|
|
||||||
"label": "韩信",
|
|
||||||
"value": "hanxin2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "云中君",
|
|
||||||
"value": "yunzhongjun2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ order: 60
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 配置工具栏
|
## 配置工具栏
|
||||||
|
|
||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
@ -106,7 +107,7 @@ order: 60
|
|||||||
|
|
||||||
## 隐藏头部
|
## 隐藏头部
|
||||||
|
|
||||||
去掉头部,默认只展示内容tab第一项的内容
|
去掉头部,默认只展示内容 tab 第一项的内容
|
||||||
|
|
||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
{
|
{
|
||||||
@ -121,9 +122,9 @@ order: 60
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 设置style
|
## 设置 style
|
||||||
|
|
||||||
默认tabs只有一项的时候没有选中状态
|
默认 tabs 只有一项的时候没有选中状态
|
||||||
|
|
||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
{
|
{
|
||||||
@ -140,8 +141,6 @@ order: 60
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 去掉分隔线
|
## 去掉分隔线
|
||||||
|
|
||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
@ -157,9 +156,9 @@ order: 60
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## source动态数据
|
## source 动态数据
|
||||||
|
|
||||||
配置 source 属性,根据某个数据来动态生成。具体使用参考Tabs选项卡组件
|
配置 source 属性,根据某个数据来动态生成。具体使用参考 Tabs 选项卡组件
|
||||||
|
|
||||||
## 图标
|
## 图标
|
||||||
|
|
||||||
@ -215,26 +214,26 @@ order: 60
|
|||||||
|
|
||||||
## 属性表
|
## 属性表
|
||||||
|
|
||||||
| 属性名 | 类型 | 默认值 | 说明 |
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
| --------------------- | --------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ |
|
| --------------------- | ------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||||
| type | `string` | `"portlet"` | 指定为 Portlet 渲染器 |
|
| type | `string` | `"portlet"` | 指定为 Portlet 渲染器 |
|
||||||
| className | `string` | | 外层 Dom 的类名 |
|
| className | `string` | | 外层 Dom 的类名 |
|
||||||
| tabsClassName | `string` | | Tabs Dom 的类名 |
|
| tabsClassName | `string` | | Tabs Dom 的类名 |
|
||||||
| contentClassName | `string` | | Tabs content Dom 的类名 |
|
| contentClassName | `string` | | Tabs content Dom 的类名 |
|
||||||
| tabs | `Array` | | tabs 内容 |
|
| tabs | `Array` | | tabs 内容 |
|
||||||
| source | `Object` | | tabs 关联数据,关联后可以重复生成选项卡 |
|
| source | `Object` | | tabs 关联数据,关联后可以重复生成选项卡 |
|
||||||
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,不随tab切换而变化 |
|
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,不随 tab 切换而变化 |
|
||||||
| style | `string \| Object` | | 自定义样式|
|
| style | `string \| Object` | | 自定义样式 |
|
||||||
| description | [模板](../../docs/concepts/template)| | 标题右侧信息 |
|
| description | [模板](../../docs/concepts/template) | | 标题右侧信息 |
|
||||||
| hideHeader | `boolean` | false | 隐藏头部 |
|
| hideHeader | `boolean` | false | 隐藏头部 |
|
||||||
| divider | `boolean` | false | 去掉分隔线 |
|
| divider | `boolean` | false | 去掉分隔线 |
|
||||||
| tabs[x].title | `string` | | Tab 标题 |
|
| tabs[x].title | `string` | | Tab 标题 |
|
||||||
| tabs[x].icon | `icon` | | Tab 的图标 |
|
| tabs[x].icon | `icon` | | Tab 的图标 |
|
||||||
| tabs[x].tab | [SchemaNode](../types/schemanode) | | 内容区 |
|
| tabs[x].tab | [SchemaNode](../types/schemanode) | | 内容区 |
|
||||||
| tabs[x].toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,随tab切换而变化
|
| tabs[x].toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,随 tab 切换而变化 |
|
||||||
| tabs[x].reload | `boolean` | | 设置以后内容每次都会重新渲染,对于 crud 的重新拉取很有用 |
|
| tabs[x].reload | `boolean` | | 设置以后内容每次都会重新渲染,对于 crud 的重新拉取很有用 |
|
||||||
| tabs[x].unmountOnExit | `boolean` | | 每次退出都会销毁当前 tab 栏内容 |
|
| tabs[x].unmountOnExit | `boolean` | | 每次退出都会销毁当前 tab 栏内容 |
|
||||||
| tabs[x].className | `string` | `"bg-white b-l b-r b-b wrapper-md"` | Tab 区域样式 |
|
| tabs[x].className | `string` | `"bg-white b-l b-r b-b wrapper-md"` | Tab 区域样式 |
|
||||||
| mountOnEnter | `boolean` | false | 只有在点中 tab 的时候才渲染 |
|
| mountOnEnter | `boolean` | false | 只有在点中 tab 的时候才渲染 |
|
||||||
| unmountOnExit | `boolean` | false | 切换 tab 的时候销毁 |
|
| unmountOnExit | `boolean` | false | 切换 tab 的时候销毁 |
|
||||||
| scrollable | `boolean` | false | 是否导航支持内容溢出滚动,`vertical`和`chrome`模式下不支持该属性;`chrome`模式默认压缩标签 |
|
| scrollable | `boolean` | false | 是否导航支持内容溢出滚动,`vertical`和`chrome`模式下不支持该属性;`chrome`模式默认压缩标签 |
|
||||||
|
@ -658,6 +658,14 @@ export const components = [
|
|||||||
makeMarkdownRenderer
|
makeMarkdownRenderer
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'TransferPicker 穿梭选择器',
|
||||||
|
path: '/zh-CN/components/form/transfer-picker',
|
||||||
|
getComponent: () =>
|
||||||
|
import('../../docs/zh-CN/components/form/transfer-picker.md').then(
|
||||||
|
makeMarkdownRenderer
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'TabsTransfer 组合穿梭器',
|
label: 'TabsTransfer 组合穿梭器',
|
||||||
path: '/zh-CN/components/form/tabs-transfer',
|
path: '/zh-CN/components/form/tabs-transfer',
|
||||||
@ -667,12 +675,12 @@ export const components = [
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'TransferPicker 穿梭选择器',
|
label: 'TabsTransferPicker 组合穿梭选择器',
|
||||||
path: '/zh-CN/components/form/transfer-picker',
|
path: '/zh-CN/components/form/tabs-transfer-picker',
|
||||||
getComponent: () =>
|
getComponent: () =>
|
||||||
import('../../docs/zh-CN/components/form/transfer-picker.md').then(
|
import(
|
||||||
makeMarkdownRenderer
|
'../../docs/zh-CN/components/form/tabs-transfer-picker.md'
|
||||||
)
|
).then(makeMarkdownRenderer)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'InputTree 树形选择框',
|
label: 'InputTree 树形选择框',
|
||||||
|
@ -111,6 +111,7 @@ import {TreeSelectControlSchema} from './renderers/Form/TreeSelect';
|
|||||||
import {UUIDControlSchema} from './renderers/Form/UUID';
|
import {UUIDControlSchema} from './renderers/Form/UUID';
|
||||||
import {FormControlSchema} from './renderers/Form/Control';
|
import {FormControlSchema} from './renderers/Form/Control';
|
||||||
import {TransferPickerControlSchema} from './renderers/Form/TransferPicker';
|
import {TransferPickerControlSchema} from './renderers/Form/TransferPicker';
|
||||||
|
import {TabsTransferPickerControlSchema} from './renderers/Form/TabsTransferPicker';
|
||||||
|
|
||||||
// 每加个类型,这补充一下。
|
// 每加个类型,这补充一下。
|
||||||
export type SchemaType =
|
export type SchemaType =
|
||||||
@ -311,6 +312,7 @@ export type SchemaType =
|
|||||||
| 'textarea'
|
| 'textarea'
|
||||||
| 'transfer'
|
| 'transfer'
|
||||||
| 'transfer-picker'
|
| 'transfer-picker'
|
||||||
|
| 'tabs-transfer-picker'
|
||||||
| 'input-tree'
|
| 'input-tree'
|
||||||
| 'tree-select'
|
| 'tree-select'
|
||||||
| 'table-view'
|
| 'table-view'
|
||||||
@ -435,6 +437,7 @@ export type SchemaObject =
|
|||||||
| TextareaControlSchema
|
| TextareaControlSchema
|
||||||
| TransferControlSchema
|
| TransferControlSchema
|
||||||
| TransferPickerControlSchema
|
| TransferPickerControlSchema
|
||||||
|
| TabsTransferPickerControlSchema
|
||||||
| TreeControlSchema
|
| TreeControlSchema
|
||||||
| TreeSelectControlSchema;
|
| TreeSelectControlSchema;
|
||||||
|
|
||||||
|
85
src/components/TabsTransferPicker.tsx
Normal file
85
src/components/TabsTransferPicker.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {localeable} from '../locale';
|
||||||
|
import {themeable} from '../theme';
|
||||||
|
import {uncontrollable} from 'uncontrollable';
|
||||||
|
import React from 'react';
|
||||||
|
import ResultBox from './ResultBox';
|
||||||
|
import {Icon} from './icons';
|
||||||
|
import PickerContainer from './PickerContainer';
|
||||||
|
import {autobind} from '../utils/helper';
|
||||||
|
import TabsTransfer, {TabsTransferProps} from './TabsTransfer';
|
||||||
|
|
||||||
|
export interface TabsTransferPickerProps
|
||||||
|
extends Omit<TabsTransferProps, 'itemRender'> {
|
||||||
|
// 新的属性?
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TransferPicker extends React.Component<TabsTransferPickerProps> {
|
||||||
|
@autobind
|
||||||
|
handleClose() {
|
||||||
|
this.setState({
|
||||||
|
inputValue: '',
|
||||||
|
searchResult: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleConfirm(value: any) {
|
||||||
|
this.props.onChange?.(value);
|
||||||
|
this.handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
value,
|
||||||
|
translate: __,
|
||||||
|
disabled,
|
||||||
|
className,
|
||||||
|
onChange,
|
||||||
|
size,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PickerContainer
|
||||||
|
title={__('Select.placeholder')}
|
||||||
|
popOverRender={({onClose, value, onChange}) => {
|
||||||
|
return <TabsTransfer {...rest} value={value} onChange={onChange} />;
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
onConfirm={this.handleConfirm}
|
||||||
|
onCancel={this.handleClose}
|
||||||
|
size={size}
|
||||||
|
>
|
||||||
|
{({onClick, isOpened}) => (
|
||||||
|
<ResultBox
|
||||||
|
className={cx(
|
||||||
|
'TransferPicker',
|
||||||
|
className,
|
||||||
|
isOpened ? 'is-active' : ''
|
||||||
|
)}
|
||||||
|
allowInput={false}
|
||||||
|
result={value}
|
||||||
|
onResultChange={onChange}
|
||||||
|
onResultClick={onClick}
|
||||||
|
placeholder={__('Select.placeholder')}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<span className={cx('TransferPicker-icon')}>
|
||||||
|
<Icon icon="pencil" className="icon" />
|
||||||
|
</span>
|
||||||
|
</ResultBox>
|
||||||
|
)}
|
||||||
|
</PickerContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default themeable(
|
||||||
|
localeable(
|
||||||
|
uncontrollable(TransferPicker, {
|
||||||
|
value: 'onChange'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
@ -1,46 +1,19 @@
|
|||||||
import {localeable} from '../locale';
|
import {localeable} from '../locale';
|
||||||
import {themeable} from '../theme';
|
import {themeable} from '../theme';
|
||||||
import {Transfer, TransferProps} from './Transfer';
|
import Transfer, {TransferProps} from './Transfer';
|
||||||
import {uncontrollable} from 'uncontrollable';
|
import {uncontrollable} from 'uncontrollable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ResultBox from './ResultBox';
|
import ResultBox from './ResultBox';
|
||||||
import {Icon} from './icons';
|
import {Icon} from './icons';
|
||||||
import PickerContainer from './PickerContainer';
|
import PickerContainer from './PickerContainer';
|
||||||
import InputBox from './InputBox';
|
import {autobind} from '../utils/helper';
|
||||||
import {BaseSelection} from './Selection';
|
|
||||||
import {autobind, flattenTree} from '../utils/helper';
|
|
||||||
import ResultList from './ResultList';
|
|
||||||
import {Options} from './Select';
|
|
||||||
|
|
||||||
export interface TransferPickerProps extends TransferProps {
|
export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
|
||||||
// 新的属性?
|
// 新的属性?
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransferPicker extends Transfer<TransferPickerProps> {
|
export class TransferPicker extends React.Component<TransferPickerProps> {
|
||||||
handlePickerToggleAll(value: any, onChange: (value: any) => void) {
|
|
||||||
const {options, option2value} = this.props;
|
|
||||||
let valueArray = BaseSelection.value2array(value, options, option2value);
|
|
||||||
const availableOptions = flattenTree(options).filter(
|
|
||||||
(option, index, list) =>
|
|
||||||
!option.disabled &&
|
|
||||||
option.value !== void 0 &&
|
|
||||||
list.indexOf(option) === index
|
|
||||||
);
|
|
||||||
|
|
||||||
if (valueArray.length < availableOptions.length) {
|
|
||||||
valueArray = availableOptions;
|
|
||||||
} else {
|
|
||||||
valueArray = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let newValue: string | Options = option2value
|
|
||||||
? valueArray.map(item => option2value(item))
|
|
||||||
: valueArray;
|
|
||||||
|
|
||||||
onChange && onChange(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -63,92 +36,15 @@ export class TransferPicker extends Transfer<TransferPickerProps> {
|
|||||||
disabled,
|
disabled,
|
||||||
className,
|
className,
|
||||||
onChange,
|
onChange,
|
||||||
onSearch,
|
size,
|
||||||
options,
|
...rest
|
||||||
option2value,
|
|
||||||
inline,
|
|
||||||
showArrow,
|
|
||||||
resultTitle,
|
|
||||||
statistics,
|
|
||||||
sortable,
|
|
||||||
resultItemRender,
|
|
||||||
size
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PickerContainer
|
<PickerContainer
|
||||||
title={__('Select.placeholder')}
|
title={__('Select.placeholder')}
|
||||||
popOverRender={({onClose, value, onChange}) => {
|
popOverRender={({onClose, value, onChange}) => {
|
||||||
this.valueArray = BaseSelection.value2array(
|
return <Transfer {...rest} value={value} onChange={onChange} />;
|
||||||
value,
|
|
||||||
options,
|
|
||||||
option2value
|
|
||||||
);
|
|
||||||
this.availableOptions = flattenTree(options).filter(
|
|
||||||
(option, index, list) =>
|
|
||||||
!option.disabled &&
|
|
||||||
option.value !== void 0 &&
|
|
||||||
list.indexOf(option) === index
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'Transfer',
|
|
||||||
className,
|
|
||||||
inline ? 'Transfer--inline' : ''
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className={cx('Transfer-select')}>
|
|
||||||
{this.renderSelect({
|
|
||||||
...this.props,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onToggleAll: () =>
|
|
||||||
this.handlePickerToggleAll(value, onChange)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className={cx('Transfer-mid')}>
|
|
||||||
{showArrow /*todo 需要改成确认模式,即:点了按钮才到右边 */ ? (
|
|
||||||
<div className={cx('Transfer-arrow')}>
|
|
||||||
<Icon icon="right-arrow" className="icon" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className={cx('Transfer-result')}>
|
|
||||||
<div className={cx('Transfer-title')}>
|
|
||||||
<span>
|
|
||||||
{__(resultTitle || 'Transfer.selectd')}
|
|
||||||
{statistics !== false ? (
|
|
||||||
<span>
|
|
||||||
({this.valueArray.length}/
|
|
||||||
{this.availableOptions.length})
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
onClick={this.clearAll}
|
|
||||||
className={cx(
|
|
||||||
'Transfer-clearAll',
|
|
||||||
disabled || !this.valueArray.length ? 'is-disabled' : ''
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{__('clear')}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ResultList
|
|
||||||
className={cx('Transfer-selections')}
|
|
||||||
sortable={sortable}
|
|
||||||
disabled={disabled}
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
placeholder={__('Transfer.selectFromLeft')}
|
|
||||||
itemRender={resultItemRender}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
value={value}
|
value={value}
|
||||||
onConfirm={this.handleConfirm}
|
onConfirm={this.handleConfirm}
|
||||||
|
@ -118,6 +118,7 @@ import './renderers/Form/IconPicker';
|
|||||||
import './renderers/Form/Formula';
|
import './renderers/Form/Formula';
|
||||||
import './renderers/Form/FieldSet';
|
import './renderers/Form/FieldSet';
|
||||||
import './renderers/Form/TabsTransfer';
|
import './renderers/Form/TabsTransfer';
|
||||||
|
import './renderers/Form/TabsTransferPicker';
|
||||||
import './renderers/Form/Group';
|
import './renderers/Form/Group';
|
||||||
import './renderers/Form/InputGroup';
|
import './renderers/Form/InputGroup';
|
||||||
import './renderers/Grid';
|
import './renderers/Grid';
|
||||||
|
123
src/renderers/Form/TabsTransferPicker.tsx
Normal file
123
src/renderers/Form/TabsTransferPicker.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import {
|
||||||
|
OptionsControlProps,
|
||||||
|
OptionsControl,
|
||||||
|
FormOptionsControl
|
||||||
|
} from './Options';
|
||||||
|
import React from 'react';
|
||||||
|
import {Api} from '../../types';
|
||||||
|
import Spinner from '../../components/Spinner';
|
||||||
|
import {BaseTransferRenderer} from './Transfer';
|
||||||
|
import TabsTransfer from '../../components/TabsTransfer';
|
||||||
|
import {SchemaApi} from '../../Schema';
|
||||||
|
import TransferPicker from '../../components/TransferPicker';
|
||||||
|
import TabsTransferPicker from '../../components/TabsTransferPicker';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabsTransferPicker 穿梭器的弹框形态
|
||||||
|
* 文档:https://baidu.gitee.io/amis/docs/components/form/tabs-transfer-picker
|
||||||
|
*/
|
||||||
|
export interface TabsTransferPickerControlSchema extends FormOptionsControl {
|
||||||
|
type: 'tabs-transfer-picker';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示剪头
|
||||||
|
*/
|
||||||
|
showArrow?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可排序?
|
||||||
|
*/
|
||||||
|
sortable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索结果展示模式
|
||||||
|
*/
|
||||||
|
searchResultMode?: 'table' | 'list' | 'tree' | 'chained';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可搜索?
|
||||||
|
*/
|
||||||
|
searchable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索 API
|
||||||
|
*/
|
||||||
|
searchApi?: SchemaApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 左侧的标题文字
|
||||||
|
*/
|
||||||
|
selectTitle?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 右侧结果的标题文字
|
||||||
|
*/
|
||||||
|
resultTitle?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗大小
|
||||||
|
*/
|
||||||
|
pickerSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabsTransferProps
|
||||||
|
extends OptionsControlProps,
|
||||||
|
Omit<
|
||||||
|
TabsTransferPickerControlSchema,
|
||||||
|
| 'type'
|
||||||
|
| 'options'
|
||||||
|
| 'inputClassName'
|
||||||
|
| 'className'
|
||||||
|
| 'descriptionClassName'
|
||||||
|
> {}
|
||||||
|
|
||||||
|
@OptionsControl({
|
||||||
|
type: 'tabs-transfer-picker'
|
||||||
|
})
|
||||||
|
export class TabsTransferPickerRenderer extends BaseTransferRenderer<TabsTransferProps> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
classnames: cx,
|
||||||
|
options,
|
||||||
|
selectedOptions,
|
||||||
|
sortable,
|
||||||
|
loading,
|
||||||
|
searchable,
|
||||||
|
searchResultMode,
|
||||||
|
showArrow,
|
||||||
|
deferLoad,
|
||||||
|
disabled,
|
||||||
|
selectTitle,
|
||||||
|
resultTitle,
|
||||||
|
pickerSize,
|
||||||
|
columns,
|
||||||
|
leftMode,
|
||||||
|
leftOptions
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('TabsTransferControl', className)}>
|
||||||
|
<TabsTransferPicker
|
||||||
|
value={selectedOptions}
|
||||||
|
disabled={disabled}
|
||||||
|
options={options}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
option2value={this.option2value}
|
||||||
|
sortable={sortable}
|
||||||
|
searchResultMode={searchResultMode}
|
||||||
|
onSearch={searchable ? this.handleSearch : undefined}
|
||||||
|
showArrow={showArrow}
|
||||||
|
onDeferLoad={deferLoad}
|
||||||
|
selectTitle={selectTitle}
|
||||||
|
resultTitle={resultTitle}
|
||||||
|
size={pickerSize}
|
||||||
|
leftMode={leftMode}
|
||||||
|
leftOptions={leftOptions}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Spinner overlay key="info" show={loading} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -89,7 +89,10 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
|
|||||||
disabled,
|
disabled,
|
||||||
selectTitle,
|
selectTitle,
|
||||||
resultTitle,
|
resultTitle,
|
||||||
pickerSize
|
pickerSize,
|
||||||
|
columns,
|
||||||
|
leftMode,
|
||||||
|
leftOptions
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -108,6 +111,9 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
|
|||||||
selectTitle={selectTitle}
|
selectTitle={selectTitle}
|
||||||
resultTitle={resultTitle}
|
resultTitle={resultTitle}
|
||||||
size={pickerSize}
|
size={pickerSize}
|
||||||
|
columns={columns}
|
||||||
|
leftMode={leftMode}
|
||||||
|
leftOptions={leftOptions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Spinner overlay key="info" show={loading} />
|
<Spinner overlay key="info" show={loading} />
|
||||||
|
@ -6,15 +6,21 @@ import {Renderer, RendererProps} from '../factory';
|
|||||||
import {resolveVariable} from '../utils/tpl-builtin';
|
import {resolveVariable} from '../utils/tpl-builtin';
|
||||||
import {str2AsyncFunction} from '../utils/api';
|
import {str2AsyncFunction} from '../utils/api';
|
||||||
import {
|
import {
|
||||||
isVisible,
|
isVisible,
|
||||||
autobind,
|
autobind,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isObject,
|
isObject,
|
||||||
createObject
|
createObject
|
||||||
} from '../utils/helper';
|
} from '../utils/helper';
|
||||||
|
|
||||||
import {filter} from '../utils/tpl';
|
import {filter} from '../utils/tpl';
|
||||||
import {SchemaTpl, SchemaClassName, BaseSchema, SchemaCollection, SchemaIcon} from '../Schema';
|
import {
|
||||||
|
SchemaTpl,
|
||||||
|
SchemaClassName,
|
||||||
|
BaseSchema,
|
||||||
|
SchemaCollection,
|
||||||
|
SchemaIcon
|
||||||
|
} from '../Schema';
|
||||||
|
|
||||||
import {ActionSchema} from './Action';
|
import {ActionSchema} from './Action';
|
||||||
|
|
||||||
@ -23,314 +29,298 @@ import {ActionSchema} from './Action';
|
|||||||
* 文档:https://baidu.gitee.io/amis/docs/components/portlet
|
* 文档:https://baidu.gitee.io/amis/docs/components/portlet
|
||||||
*/
|
*/
|
||||||
export interface PortletTabSchema extends Omit<BaseSchema, 'type'> {
|
export interface PortletTabSchema extends Omit<BaseSchema, 'type'> {
|
||||||
/**
|
/**
|
||||||
* Tab 标题
|
* Tab 标题
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内容
|
* 内容
|
||||||
* @deprecated 用 body 属性
|
* @deprecated 用 body 属性
|
||||||
*/
|
*/
|
||||||
tab?: SchemaCollection;
|
tab?: SchemaCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以在右侧配置点其他功能按钮,随着tab切换而切换
|
* 可以在右侧配置点其他功能按钮,随着tab切换而切换
|
||||||
*/
|
*/
|
||||||
toolbar?: Array<ActionSchema>;
|
toolbar?: Array<ActionSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内容
|
* 内容
|
||||||
*/
|
*/
|
||||||
body?: SchemaCollection;
|
body?: SchemaCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按钮图标
|
* 按钮图标
|
||||||
*/
|
*/
|
||||||
icon?: SchemaIcon;
|
icon?: SchemaIcon;
|
||||||
|
|
||||||
iconPosition?: 'left' | 'right';
|
iconPosition?: 'left' | 'right';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置以后内容每次都会重新渲染
|
* 设置以后内容每次都会重新渲染
|
||||||
*/
|
*/
|
||||||
reload?: boolean;
|
reload?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点开时才加载卡片内容
|
* 点开时才加载卡片内容
|
||||||
*/
|
*/
|
||||||
mountOnEnter?: boolean;
|
mountOnEnter?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卡片隐藏就销毁卡片节点。
|
* 卡片隐藏就销毁卡片节点。
|
||||||
*/
|
*/
|
||||||
unmountOnExit?: boolean;
|
unmountOnExit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortletSchema extends Omit<BaseSchema, 'type'> {
|
export interface PortletSchema extends Omit<BaseSchema, 'type'> {
|
||||||
/**
|
/**
|
||||||
* 指定为 portlet 类型
|
* 指定为 portlet 类型
|
||||||
*/
|
*/
|
||||||
type: 'portlet';
|
type: 'portlet';
|
||||||
|
|
||||||
tabs: Array<PortletTabSchema>;
|
tabs: Array<PortletTabSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关联已有数据,选项卡直接根据目标数据重复。
|
* 关联已有数据,选项卡直接根据目标数据重复。
|
||||||
*/
|
*/
|
||||||
source?: string;
|
source?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类名
|
* 类名
|
||||||
*/
|
*/
|
||||||
tabsClassName?: SchemaClassName;
|
tabsClassName?: SchemaClassName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 展示形式
|
* 展示形式
|
||||||
*/
|
*/
|
||||||
tabsMode?: '' | 'line' | 'card' | 'radio' | 'vertical' | 'tiled';
|
tabsMode?: '' | 'line' | 'card' | 'radio' | 'vertical' | 'tiled';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内容类名
|
* 内容类名
|
||||||
*/
|
*/
|
||||||
contentClassName?: SchemaClassName;
|
contentClassName?: SchemaClassName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 链接外层类名
|
* 链接外层类名
|
||||||
*/
|
*/
|
||||||
linksClassName?: SchemaClassName;
|
linksClassName?: SchemaClassName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卡片是否只有在点开的时候加载?
|
* 卡片是否只有在点开的时候加载?
|
||||||
*/
|
*/
|
||||||
mountOnEnter?: boolean;
|
mountOnEnter?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卡片隐藏的时候是否销毁卡片内容
|
* 卡片隐藏的时候是否销毁卡片内容
|
||||||
*/
|
*/
|
||||||
unmountOnExit?: boolean;
|
unmountOnExit?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以在右侧配置点其他功能按钮。不会随着tab切换
|
* 可以在右侧配置点其他功能按钮。不会随着tab切换
|
||||||
*/
|
*/
|
||||||
toolbar?: Array<ActionSchema>;
|
toolbar?: Array<ActionSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否支持溢出滚动
|
* 是否支持溢出滚动
|
||||||
*/
|
*/
|
||||||
scrollable?: boolean;
|
scrollable?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* header和内容是否展示分割线
|
* header和内容是否展示分割线
|
||||||
*/
|
*/
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标题右侧的描述
|
* 标题右侧的描述
|
||||||
*/
|
*/
|
||||||
description?: SchemaTpl;
|
description?: SchemaTpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 隐藏头部
|
* 隐藏头部
|
||||||
*/
|
*/
|
||||||
hideHeader?: boolean;
|
hideHeader?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义样式
|
* 自定义样式
|
||||||
*/
|
*/
|
||||||
style?: string | {
|
style?:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortletProps
|
export interface PortletProps
|
||||||
extends RendererProps,
|
extends RendererProps,
|
||||||
Omit<PortletSchema, 'className' | 'contentClassName'>{
|
Omit<PortletSchema, 'className' | 'contentClassName'> {
|
||||||
activeKey?: number;
|
activeKey?: number;
|
||||||
tabRender?: (tab: PortletTabSchema, props: PortletProps, index: number) => JSX.Element;
|
tabRender?: (
|
||||||
|
tab: PortletTabSchema,
|
||||||
|
props: PortletProps,
|
||||||
|
index: number
|
||||||
|
) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortletState {
|
export interface PortletState {
|
||||||
activeKey?: number;
|
activeKey?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Portlet extends React.Component<PortletProps, PortletState> {
|
export class Portlet extends React.Component<PortletProps, PortletState> {
|
||||||
static defaultProps: Partial<PortletProps> = {
|
static defaultProps: Partial<PortletProps> = {
|
||||||
className: '',
|
className: '',
|
||||||
mode: 'line',
|
mode: 'line',
|
||||||
divider: true
|
divider: true
|
||||||
|
};
|
||||||
|
renderTab?: (
|
||||||
|
tab: PortletTabSchema,
|
||||||
|
props: PortletProps,
|
||||||
|
index: number
|
||||||
|
) => JSX.Element;
|
||||||
|
constructor(props: PortletProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const activeKey = props.activeKey || 0;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeKey
|
||||||
};
|
};
|
||||||
renderTab?: (tab: PortletTabSchema, props: PortletProps, index: number) => JSX.Element;
|
}
|
||||||
constructor(props: PortletProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const activeKey = props.activeKey || 0;
|
@autobind
|
||||||
|
handleSelect(key: number) {
|
||||||
this.state = {
|
const {onSelect, tabs} = this.props;
|
||||||
activeKey
|
if (typeof key === 'number' && key < tabs.length) {
|
||||||
};
|
this.setState({
|
||||||
|
activeKey: key
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
if (typeof onSelect === 'string') {
|
||||||
handleSelect(key: number) {
|
const selectFunc = str2AsyncFunction(onSelect, 'key', 'props');
|
||||||
const {onSelect, tabs} = this.props;
|
selectFunc && selectFunc(key, this.props);
|
||||||
if (typeof key === 'number' && key < tabs.length) {
|
} else if (typeof onSelect === 'function') {
|
||||||
this.setState({
|
onSelect(key, this.props);
|
||||||
activeKey: key
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof onSelect === 'string') {
|
renderToolbarItem(toolbar: Array<ActionSchema>) {
|
||||||
const selectFunc = str2AsyncFunction(onSelect, 'key', 'props');
|
const {render} = this.props;
|
||||||
selectFunc && selectFunc(key, this.props);
|
let actions: Array<JSX.Element> = [];
|
||||||
} else if (typeof onSelect === 'function') {
|
if (Array.isArray(toolbar)) {
|
||||||
onSelect(key, this.props);
|
toolbar.forEach((action, index) =>
|
||||||
}
|
actions.push(
|
||||||
|
render(
|
||||||
|
`toolbar/${index}`,
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
level: 'link',
|
||||||
|
size: 'sm',
|
||||||
|
...(action as any)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: index
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbar() {
|
||||||
|
const {toolbar, classnames: cx, classPrefix: ns, tabs} = this.props;
|
||||||
|
const activeKey = this.state.activeKey;
|
||||||
|
let tabToolbar = null;
|
||||||
|
let tabToolbarTpl = null;
|
||||||
|
// tabs里的toolbar
|
||||||
|
const toolbarTpl = toolbar ? (
|
||||||
|
<div className={cx(`${ns}toolbar`)}>
|
||||||
|
{this.renderToolbarItem(toolbar)}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
// tab里的toolbar
|
||||||
|
if (typeof activeKey !== 'undefined') {
|
||||||
|
tabToolbar = tabs[activeKey]!.toolbar;
|
||||||
|
tabToolbarTpl = tabToolbar ? (
|
||||||
|
<div className={cx(`${ns}tab-toolbar`)}>
|
||||||
|
{this.renderToolbarItem(tabToolbar)}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbarItem(toolbar: Array<ActionSchema>) {
|
return toolbarTpl || tabToolbarTpl ? (
|
||||||
const {render} = this.props;
|
<div className={cx(`${ns}Portlet-toolbar`)}>
|
||||||
let actions: Array<JSX.Element> = []
|
{toolbarTpl}
|
||||||
if (Array.isArray(toolbar)) {
|
{tabToolbarTpl}
|
||||||
toolbar.forEach((action, index) =>
|
</div>
|
||||||
actions.push(
|
) : null;
|
||||||
render(
|
}
|
||||||
`toolbar/${index}`,
|
|
||||||
{
|
renderDesc() {
|
||||||
type: 'button',
|
const {
|
||||||
level: 'link',
|
description: descTpl,
|
||||||
size: 'sm',
|
render,
|
||||||
...(action as any)
|
classnames: cx,
|
||||||
},
|
classPrefix: ns,
|
||||||
{
|
data
|
||||||
key: index
|
} = this.props;
|
||||||
}
|
const desc = filter(descTpl, data);
|
||||||
)
|
return desc ? (
|
||||||
)
|
<span className={cx(`${ns}Portlet-header-desc`)}>{desc}</span>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
return actions;
|
|
||||||
|
renderTabs() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
classPrefix: ns,
|
||||||
|
tabsClassName,
|
||||||
|
contentClassName,
|
||||||
|
linksClassName,
|
||||||
|
tabRender,
|
||||||
|
render,
|
||||||
|
data,
|
||||||
|
mode: dMode,
|
||||||
|
tabsMode,
|
||||||
|
unmountOnExit,
|
||||||
|
source,
|
||||||
|
mountOnEnter,
|
||||||
|
scrollable,
|
||||||
|
divider
|
||||||
|
} = this.props;
|
||||||
|
const mode = tabsMode || dMode;
|
||||||
|
const arr = resolveVariable(source, data);
|
||||||
|
|
||||||
|
let tabs = this.props.tabs;
|
||||||
|
if (!tabs) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbar() {
|
tabs = Array.isArray(tabs) ? tabs : [tabs];
|
||||||
const {toolbar, classnames: cx, classPrefix: ns, tabs} = this.props;
|
let children: Array<JSX.Element | null> = [];
|
||||||
const activeKey = this.state.activeKey;
|
|
||||||
let tabToolbar = null;
|
|
||||||
let tabToolbarTpl = null;
|
|
||||||
// tabs里的toolbar
|
|
||||||
const toolbarTpl = toolbar ? (
|
|
||||||
<div className={cx(`${ns}toolbar`)}>
|
|
||||||
{this.renderToolbarItem(toolbar)}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
// tab里的toolbar
|
const tabClassname = cx(`${ns}Portlet-tab`, tabsClassName, {
|
||||||
if (typeof activeKey !== 'undefined') {
|
['unactive-select']: tabs.length <= 1,
|
||||||
tabToolbar = tabs[activeKey]!.toolbar;
|
['no-divider']: !divider
|
||||||
tabToolbarTpl = tabToolbar ? (
|
});
|
||||||
<div className={cx(`${ns}tab-toolbar`)}>
|
if (Array.isArray(arr)) {
|
||||||
{this.renderToolbarItem(tabToolbar)}
|
arr.forEach((value, index) => {
|
||||||
</div>
|
const ctx = createObject(
|
||||||
) : null;
|
data,
|
||||||
}
|
isObject(value) ? {index, ...value} : {item: value, index}
|
||||||
|
|
||||||
return (
|
|
||||||
toolbarTpl || tabToolbarTpl
|
|
||||||
? (<div className={cx(`${ns}Portlet-toolbar`)}>
|
|
||||||
{toolbarTpl}
|
|
||||||
{tabToolbarTpl}
|
|
||||||
</div>)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
renderDesc() {
|
children.push(
|
||||||
const {description : descTpl, render, classnames: cx, classPrefix: ns, data} = this.props;
|
...tabs.map((tab, tabIndex) =>
|
||||||
const desc = filter(descTpl, data);
|
isVisible(tab, ctx) ? (
|
||||||
return desc
|
|
||||||
? <span className={cx(`${ns}Portlet-header-desc`)}>{desc}</span>
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTabs() {
|
|
||||||
const {
|
|
||||||
classnames: cx,
|
|
||||||
classPrefix: ns,
|
|
||||||
tabsClassName,
|
|
||||||
contentClassName,
|
|
||||||
linksClassName,
|
|
||||||
tabRender,
|
|
||||||
render,
|
|
||||||
data,
|
|
||||||
mode: dMode,
|
|
||||||
tabsMode,
|
|
||||||
unmountOnExit,
|
|
||||||
source,
|
|
||||||
mountOnEnter,
|
|
||||||
scrollable,
|
|
||||||
divider
|
|
||||||
} = this.props;
|
|
||||||
const mode = tabsMode || dMode;
|
|
||||||
const arr = resolveVariable(source, data);
|
|
||||||
|
|
||||||
let tabs = this.props.tabs;
|
|
||||||
if (!tabs) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs = Array.isArray(tabs) ? tabs : [tabs];
|
|
||||||
let children: Array<JSX.Element | null> = [];
|
|
||||||
|
|
||||||
const tabClassname = cx(`${ns}Portlet-tab`, tabsClassName, {
|
|
||||||
['unactive-select']: tabs.length <=1,
|
|
||||||
['no-divider']: !divider
|
|
||||||
});
|
|
||||||
if (Array.isArray(arr)) {
|
|
||||||
arr.forEach((value, index) => {
|
|
||||||
const ctx = createObject(
|
|
||||||
data,
|
|
||||||
isObject(value) ? {index, ...value} : {item: value, index}
|
|
||||||
);
|
|
||||||
|
|
||||||
children.push(
|
|
||||||
...tabs.map((tab, tabIndex) =>
|
|
||||||
isVisible(tab, ctx) ? (
|
|
||||||
<Tab
|
|
||||||
{...(tab as any)}
|
|
||||||
title={filter(tab.title, ctx)}
|
|
||||||
disabled={isDisabled(tab, ctx)}
|
|
||||||
key={`${index * 1000 + tabIndex}`}
|
|
||||||
eventKey={index * 1000 + tabIndex}
|
|
||||||
mountOnEnter={mountOnEnter}
|
|
||||||
unmountOnExit={
|
|
||||||
typeof tab.reload === 'boolean'
|
|
||||||
? tab.reload
|
|
||||||
: typeof tab.unmountOnExit === 'boolean'
|
|
||||||
? tab.unmountOnExit
|
|
||||||
: unmountOnExit
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{render(
|
|
||||||
`item/${index}/${tabIndex}`,
|
|
||||||
(tab as any)?.type ? (tab as any) : tab.tab || tab.body,
|
|
||||||
{
|
|
||||||
data: ctx
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
) : null
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
children = tabs.map((tab, index) =>
|
|
||||||
isVisible(tab, data) ? (
|
|
||||||
<Tab
|
<Tab
|
||||||
{...(tab as any)}
|
{...(tab as any)}
|
||||||
title={filter(tab.title, data)}
|
title={filter(tab.title, ctx)}
|
||||||
disabled={isDisabled(tab, data)}
|
disabled={isDisabled(tab, ctx)}
|
||||||
key={index}
|
key={`${index * 1000 + tabIndex}`}
|
||||||
eventKey={index}
|
eventKey={index * 1000 + tabIndex}
|
||||||
mountOnEnter={mountOnEnter}
|
mountOnEnter={mountOnEnter}
|
||||||
unmountOnExit={
|
unmountOnExit={
|
||||||
typeof tab.reload === 'boolean'
|
typeof tab.reload === 'boolean'
|
||||||
@ -340,66 +330,94 @@ export class Portlet extends React.Component<PortletProps, PortletState> {
|
|||||||
: unmountOnExit
|
: unmountOnExit
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{this.renderTab
|
{render(
|
||||||
? this.renderTab(tab, this.props, index)
|
`item/${index}/${tabIndex}`,
|
||||||
: tabRender
|
(tab as any)?.type ? (tab as any) : tab.tab || tab.body,
|
||||||
? tabRender(tab, this.props, index)
|
{
|
||||||
: render(
|
data: ctx
|
||||||
`tab/${index}`,
|
}
|
||||||
(tab as any)?.type ? (tab as any) : tab.tab || tab.body
|
)}
|
||||||
)}
|
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null
|
) : null
|
||||||
);
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CTabs
|
|
||||||
classPrefix={ns}
|
|
||||||
classnames={cx}
|
|
||||||
mode={mode}
|
|
||||||
className={tabClassname}
|
|
||||||
contentClassName={contentClassName}
|
|
||||||
linksClassName={linksClassName}
|
|
||||||
activeKey={this.state.activeKey}
|
|
||||||
onSelect={this.handleSelect}
|
|
||||||
toolbar={this.renderToolbar()}
|
|
||||||
additionBtns={this.renderDesc()}
|
|
||||||
scrollable={scrollable}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</CTabs>
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
children = tabs.map((tab, index) =>
|
||||||
|
isVisible(tab, data) ? (
|
||||||
|
<Tab
|
||||||
|
{...(tab as any)}
|
||||||
|
title={filter(tab.title, data)}
|
||||||
|
disabled={isDisabled(tab, data)}
|
||||||
|
key={index}
|
||||||
|
eventKey={index}
|
||||||
|
mountOnEnter={mountOnEnter}
|
||||||
|
unmountOnExit={
|
||||||
|
typeof tab.reload === 'boolean'
|
||||||
|
? tab.reload
|
||||||
|
: typeof tab.unmountOnExit === 'boolean'
|
||||||
|
? tab.unmountOnExit
|
||||||
|
: unmountOnExit
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{this.renderTab
|
||||||
|
? this.renderTab(tab, this.props, index)
|
||||||
|
: tabRender
|
||||||
|
? tabRender(tab, this.props, index)
|
||||||
|
: render(
|
||||||
|
`tab/${index}`,
|
||||||
|
(tab as any)?.type ? (tab as any) : tab.tab || tab.body
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
) : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {
|
<CTabs
|
||||||
className,
|
classPrefix={ns}
|
||||||
data,
|
classnames={cx}
|
||||||
classnames: cx,
|
mode={mode}
|
||||||
classPrefix: ns,
|
className={tabClassname}
|
||||||
style,
|
contentClassName={contentClassName}
|
||||||
hideHeader
|
linksClassName={linksClassName}
|
||||||
} = this.props;
|
activeKey={this.state.activeKey}
|
||||||
const portletClassname = cx(`${ns}Portlet`, className, {
|
onSelect={this.handleSelect}
|
||||||
['no-header']: hideHeader
|
toolbar={this.renderToolbar()}
|
||||||
});
|
additionBtns={this.renderDesc()}
|
||||||
const styleVar =
|
scrollable={scrollable}
|
||||||
typeof style === 'string'
|
>
|
||||||
? resolveVariable(style, data) || {}
|
{children}
|
||||||
: mapValues(style, s => resolveVariable(s, data) || s);
|
</CTabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<div className={portletClassname} style={styleVar}>
|
const {
|
||||||
{this.renderTabs()}
|
className,
|
||||||
</div>
|
data,
|
||||||
)
|
classnames: cx,
|
||||||
}
|
classPrefix: ns,
|
||||||
|
style,
|
||||||
|
hideHeader
|
||||||
|
} = this.props;
|
||||||
|
const portletClassname = cx(`${ns}Portlet`, className, {
|
||||||
|
['no-header']: hideHeader
|
||||||
|
});
|
||||||
|
const styleVar =
|
||||||
|
typeof style === 'string'
|
||||||
|
? resolveVariable(style, data) || {}
|
||||||
|
: mapValues(style, s => resolveVariable(s, data) || s);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={portletClassname} style={styleVar}>
|
||||||
|
{this.renderTabs()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Renderer({
|
@Renderer({
|
||||||
type: 'portlet'
|
type: 'portlet'
|
||||||
})
|
})
|
||||||
export class PortletRenderer extends Portlet {
|
export class PortletRenderer extends Portlet {}
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user