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,
|
||||
"options": [
|
||||
{
|
||||
"label": "成员",
|
||||
"selectMode": "tree",
|
||||
"label": "法师",
|
||||
"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": "诸葛亮",
|
||||
"value": "zhugeliang"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "用户",
|
||||
"selectMode": "chained",
|
||||
"label": "战士",
|
||||
"children": [
|
||||
{
|
||||
"label": "法师",
|
||||
"children": [
|
||||
{
|
||||
"label": "诸葛亮",
|
||||
"value": "zhugeliang2"
|
||||
}
|
||||
]
|
||||
"label": "曹操",
|
||||
"value": "caocao"
|
||||
},
|
||||
{
|
||||
"label": "战士",
|
||||
"children": [
|
||||
{
|
||||
"label": "曹操",
|
||||
"value": "caocao2"
|
||||
},
|
||||
{
|
||||
"label": "钟无艳",
|
||||
"value": "zhongwuyan2"
|
||||
}
|
||||
]
|
||||
"label": "钟无艳",
|
||||
"value": "zhongwuyan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "打野",
|
||||
"children": [
|
||||
{
|
||||
"label": "李白",
|
||||
"value": "libai"
|
||||
},
|
||||
{
|
||||
"label": "打野",
|
||||
"children": [
|
||||
{
|
||||
"label": "李白",
|
||||
"value": "libai2"
|
||||
},
|
||||
{
|
||||
"label": "韩信",
|
||||
"value": "hanxin2"
|
||||
},
|
||||
{
|
||||
"label": "云中君",
|
||||
"value": "yunzhongjun2"
|
||||
}
|
||||
]
|
||||
"label": "韩信",
|
||||
"value": "hanxin"
|
||||
},
|
||||
{
|
||||
"label": "云中君",
|
||||
"value": "yunzhongjun"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ order: 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置工具栏
|
||||
|
||||
```schema: scope="body"
|
||||
@ -106,7 +107,7 @@ order: 60
|
||||
|
||||
## 隐藏头部
|
||||
|
||||
去掉头部,默认只展示内容tab第一项的内容
|
||||
去掉头部,默认只展示内容 tab 第一项的内容
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
@ -121,9 +122,9 @@ order: 60
|
||||
}
|
||||
```
|
||||
|
||||
## 设置style
|
||||
## 设置 style
|
||||
|
||||
默认tabs只有一项的时候没有选中状态
|
||||
默认 tabs 只有一项的时候没有选中状态
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
@ -140,8 +141,6 @@ order: 60
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 去掉分隔线
|
||||
|
||||
```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 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| tabsClassName | `string` | | Tabs Dom 的类名 |
|
||||
| contentClassName | `string` | | Tabs content Dom 的类名 |
|
||||
| tabs | `Array` | | tabs 内容 |
|
||||
| source | `Object` | | tabs 关联数据,关联后可以重复生成选项卡 |
|
||||
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,不随tab切换而变化 |
|
||||
| style | `string \| Object` | | 自定义样式|
|
||||
| description | [模板](../../docs/concepts/template)| | 标题右侧信息 |
|
||||
| hideHeader | `boolean` | false | 隐藏头部 |
|
||||
| divider | `boolean` | false | 去掉分隔线 |
|
||||
| tabs[x].title | `string` | | Tab 标题 |
|
||||
| tabs[x].icon | `icon` | | Tab 的图标 |
|
||||
| tabs[x].tab | [SchemaNode](../types/schemanode) | | 内容区 |
|
||||
| tabs[x].toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,随tab切换而变化
|
||||
| tabs[x].reload | `boolean` | | 设置以后内容每次都会重新渲染,对于 crud 的重新拉取很有用 |
|
||||
| tabs[x].unmountOnExit | `boolean` | | 每次退出都会销毁当前 tab 栏内容 |
|
||||
| tabs[x].className | `string` | `"bg-white b-l b-r b-b wrapper-md"` | Tab 区域样式 |
|
||||
| mountOnEnter | `boolean` | false | 只有在点中 tab 的时候才渲染 |
|
||||
| unmountOnExit | `boolean` | false | 切换 tab 的时候销毁 |
|
||||
| scrollable | `boolean` | false | 是否导航支持内容溢出滚动,`vertical`和`chrome`模式下不支持该属性;`chrome`模式默认压缩标签 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------------- | ------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| type | `string` | `"portlet"` | 指定为 Portlet 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| tabsClassName | `string` | | Tabs Dom 的类名 |
|
||||
| contentClassName | `string` | | Tabs content Dom 的类名 |
|
||||
| tabs | `Array` | | tabs 内容 |
|
||||
| source | `Object` | | tabs 关联数据,关联后可以重复生成选项卡 |
|
||||
| toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,不随 tab 切换而变化 |
|
||||
| style | `string \| Object` | | 自定义样式 |
|
||||
| description | [模板](../../docs/concepts/template) | | 标题右侧信息 |
|
||||
| hideHeader | `boolean` | false | 隐藏头部 |
|
||||
| divider | `boolean` | false | 去掉分隔线 |
|
||||
| tabs[x].title | `string` | | Tab 标题 |
|
||||
| tabs[x].icon | `icon` | | Tab 的图标 |
|
||||
| tabs[x].tab | [SchemaNode](../types/schemanode) | | 内容区 |
|
||||
| tabs[x].toolbar | [SchemaNode](../types/schemanode) | | tabs 中的工具栏,随 tab 切换而变化 |
|
||||
| tabs[x].reload | `boolean` | | 设置以后内容每次都会重新渲染,对于 crud 的重新拉取很有用 |
|
||||
| tabs[x].unmountOnExit | `boolean` | | 每次退出都会销毁当前 tab 栏内容 |
|
||||
| tabs[x].className | `string` | `"bg-white b-l b-r b-b wrapper-md"` | Tab 区域样式 |
|
||||
| mountOnEnter | `boolean` | false | 只有在点中 tab 的时候才渲染 |
|
||||
| unmountOnExit | `boolean` | false | 切换 tab 的时候销毁 |
|
||||
| scrollable | `boolean` | false | 是否导航支持内容溢出滚动,`vertical`和`chrome`模式下不支持该属性;`chrome`模式默认压缩标签 |
|
||||
|
@ -658,6 +658,14 @@ export const components = [
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'TransferPicker 穿梭选择器',
|
||||
path: '/zh-CN/components/form/transfer-picker',
|
||||
getComponent: () =>
|
||||
import('../../docs/zh-CN/components/form/transfer-picker.md').then(
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'TabsTransfer 组合穿梭器',
|
||||
path: '/zh-CN/components/form/tabs-transfer',
|
||||
@ -667,12 +675,12 @@ export const components = [
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'TransferPicker 穿梭选择器',
|
||||
path: '/zh-CN/components/form/transfer-picker',
|
||||
label: 'TabsTransferPicker 组合穿梭选择器',
|
||||
path: '/zh-CN/components/form/tabs-transfer-picker',
|
||||
getComponent: () =>
|
||||
import('../../docs/zh-CN/components/form/transfer-picker.md').then(
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
import(
|
||||
'../../docs/zh-CN/components/form/tabs-transfer-picker.md'
|
||||
).then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: 'InputTree 树形选择框',
|
||||
|
@ -111,6 +111,7 @@ import {TreeSelectControlSchema} from './renderers/Form/TreeSelect';
|
||||
import {UUIDControlSchema} from './renderers/Form/UUID';
|
||||
import {FormControlSchema} from './renderers/Form/Control';
|
||||
import {TransferPickerControlSchema} from './renderers/Form/TransferPicker';
|
||||
import {TabsTransferPickerControlSchema} from './renderers/Form/TabsTransferPicker';
|
||||
|
||||
// 每加个类型,这补充一下。
|
||||
export type SchemaType =
|
||||
@ -311,6 +312,7 @@ export type SchemaType =
|
||||
| 'textarea'
|
||||
| 'transfer'
|
||||
| 'transfer-picker'
|
||||
| 'tabs-transfer-picker'
|
||||
| 'input-tree'
|
||||
| 'tree-select'
|
||||
| 'table-view'
|
||||
@ -435,6 +437,7 @@ export type SchemaObject =
|
||||
| TextareaControlSchema
|
||||
| TransferControlSchema
|
||||
| TransferPickerControlSchema
|
||||
| TabsTransferPickerControlSchema
|
||||
| TreeControlSchema
|
||||
| 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 {themeable} from '../theme';
|
||||
import {Transfer, TransferProps} from './Transfer';
|
||||
import Transfer, {TransferProps} from './Transfer';
|
||||
import {uncontrollable} from 'uncontrollable';
|
||||
import React from 'react';
|
||||
import ResultBox from './ResultBox';
|
||||
import {Icon} from './icons';
|
||||
import PickerContainer from './PickerContainer';
|
||||
import InputBox from './InputBox';
|
||||
import {BaseSelection} from './Selection';
|
||||
import {autobind, flattenTree} from '../utils/helper';
|
||||
import ResultList from './ResultList';
|
||||
import {Options} from './Select';
|
||||
import {autobind} from '../utils/helper';
|
||||
|
||||
export interface TransferPickerProps extends TransferProps {
|
||||
export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
|
||||
// 新的属性?
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
}
|
||||
|
||||
export class TransferPicker extends Transfer<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);
|
||||
}
|
||||
|
||||
export class TransferPicker extends React.Component<TransferPickerProps> {
|
||||
@autobind
|
||||
handleClose() {
|
||||
this.setState({
|
||||
@ -63,92 +36,15 @@ export class TransferPicker extends Transfer<TransferPickerProps> {
|
||||
disabled,
|
||||
className,
|
||||
onChange,
|
||||
onSearch,
|
||||
options,
|
||||
option2value,
|
||||
inline,
|
||||
showArrow,
|
||||
resultTitle,
|
||||
statistics,
|
||||
sortable,
|
||||
resultItemRender,
|
||||
size
|
||||
size,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PickerContainer
|
||||
title={__('Select.placeholder')}
|
||||
popOverRender={({onClose, value, onChange}) => {
|
||||
this.valueArray = BaseSelection.value2array(
|
||||
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>
|
||||
</>
|
||||
);
|
||||
return <Transfer {...rest} value={value} onChange={onChange} />;
|
||||
}}
|
||||
value={value}
|
||||
onConfirm={this.handleConfirm}
|
||||
|
@ -118,6 +118,7 @@ import './renderers/Form/IconPicker';
|
||||
import './renderers/Form/Formula';
|
||||
import './renderers/Form/FieldSet';
|
||||
import './renderers/Form/TabsTransfer';
|
||||
import './renderers/Form/TabsTransferPicker';
|
||||
import './renderers/Form/Group';
|
||||
import './renderers/Form/InputGroup';
|
||||
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,
|
||||
selectTitle,
|
||||
resultTitle,
|
||||
pickerSize
|
||||
pickerSize,
|
||||
columns,
|
||||
leftMode,
|
||||
leftOptions
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -108,6 +111,9 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
|
||||
selectTitle={selectTitle}
|
||||
resultTitle={resultTitle}
|
||||
size={pickerSize}
|
||||
columns={columns}
|
||||
leftMode={leftMode}
|
||||
leftOptions={leftOptions}
|
||||
/>
|
||||
|
||||
<Spinner overlay key="info" show={loading} />
|
||||
|
@ -6,15 +6,21 @@ import {Renderer, RendererProps} from '../factory';
|
||||
import {resolveVariable} from '../utils/tpl-builtin';
|
||||
import {str2AsyncFunction} from '../utils/api';
|
||||
import {
|
||||
isVisible,
|
||||
autobind,
|
||||
isDisabled,
|
||||
isObject,
|
||||
createObject
|
||||
isVisible,
|
||||
autobind,
|
||||
isDisabled,
|
||||
isObject,
|
||||
createObject
|
||||
} from '../utils/helper';
|
||||
|
||||
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';
|
||||
|
||||
@ -23,314 +29,298 @@ import {ActionSchema} from './Action';
|
||||
* 文档:https://baidu.gitee.io/amis/docs/components/portlet
|
||||
*/
|
||||
export interface PortletTabSchema extends Omit<BaseSchema, 'type'> {
|
||||
/**
|
||||
* Tab 标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Tab 标题
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* 内容
|
||||
* @deprecated 用 body 属性
|
||||
*/
|
||||
tab?: SchemaCollection;
|
||||
/**
|
||||
* 内容
|
||||
* @deprecated 用 body 属性
|
||||
*/
|
||||
tab?: SchemaCollection;
|
||||
|
||||
/**
|
||||
* 可以在右侧配置点其他功能按钮,随着tab切换而切换
|
||||
*/
|
||||
toolbar?: Array<ActionSchema>;
|
||||
/**
|
||||
* 可以在右侧配置点其他功能按钮,随着tab切换而切换
|
||||
*/
|
||||
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'> {
|
||||
/**
|
||||
* 指定为 portlet 类型
|
||||
*/
|
||||
type: 'portlet';
|
||||
/**
|
||||
* 指定为 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切换
|
||||
*/
|
||||
toolbar?: Array<ActionSchema>;
|
||||
/**
|
||||
* 可以在右侧配置点其他功能按钮。不会随着tab切换
|
||||
*/
|
||||
toolbar?: Array<ActionSchema>;
|
||||
|
||||
/**
|
||||
* 是否支持溢出滚动
|
||||
*/
|
||||
scrollable?: boolean;
|
||||
/**
|
||||
* 是否支持溢出滚动
|
||||
*/
|
||||
scrollable?: boolean;
|
||||
|
||||
/**
|
||||
* header和内容是否展示分割线
|
||||
*/
|
||||
divider?: boolean;
|
||||
/**
|
||||
* header和内容是否展示分割线
|
||||
*/
|
||||
divider?: boolean;
|
||||
|
||||
/**
|
||||
* 标题右侧的描述
|
||||
*/
|
||||
description?: SchemaTpl;
|
||||
/**
|
||||
* 标题右侧的描述
|
||||
*/
|
||||
description?: SchemaTpl;
|
||||
|
||||
/**
|
||||
* 隐藏头部
|
||||
*/
|
||||
hideHeader?: boolean;
|
||||
/**
|
||||
* 隐藏头部
|
||||
*/
|
||||
hideHeader?: boolean;
|
||||
|
||||
/**
|
||||
* 自定义样式
|
||||
*/
|
||||
style?: string | {
|
||||
/**
|
||||
* 自定义样式
|
||||
*/
|
||||
style?:
|
||||
| string
|
||||
| {
|
||||
[propName: string]: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface PortletProps
|
||||
extends RendererProps,
|
||||
Omit<PortletSchema, 'className' | 'contentClassName'>{
|
||||
activeKey?: number;
|
||||
tabRender?: (tab: PortletTabSchema, props: PortletProps, index: number) => JSX.Element;
|
||||
extends RendererProps,
|
||||
Omit<PortletSchema, 'className' | 'contentClassName'> {
|
||||
activeKey?: number;
|
||||
tabRender?: (
|
||||
tab: PortletTabSchema,
|
||||
props: PortletProps,
|
||||
index: number
|
||||
) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface PortletState {
|
||||
activeKey?: number;
|
||||
activeKey?: number;
|
||||
}
|
||||
|
||||
export class Portlet extends React.Component<PortletProps, PortletState> {
|
||||
static defaultProps: Partial<PortletProps> = {
|
||||
className: '',
|
||||
mode: 'line',
|
||||
divider: true
|
||||
static defaultProps: Partial<PortletProps> = {
|
||||
className: '',
|
||||
mode: 'line',
|
||||
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;
|
||||
|
||||
this.state = {
|
||||
activeKey
|
||||
};
|
||||
@autobind
|
||||
handleSelect(key: number) {
|
||||
const {onSelect, tabs} = this.props;
|
||||
if (typeof key === 'number' && key < tabs.length) {
|
||||
this.setState({
|
||||
activeKey: key
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSelect(key: number) {
|
||||
const {onSelect, tabs} = this.props;
|
||||
if (typeof key === 'number' && key < tabs.length) {
|
||||
this.setState({
|
||||
activeKey: key
|
||||
});
|
||||
}
|
||||
if (typeof onSelect === 'string') {
|
||||
const selectFunc = str2AsyncFunction(onSelect, 'key', 'props');
|
||||
selectFunc && selectFunc(key, this.props);
|
||||
} else if (typeof onSelect === 'function') {
|
||||
onSelect(key, this.props);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof onSelect === 'string') {
|
||||
const selectFunc = str2AsyncFunction(onSelect, 'key', 'props');
|
||||
selectFunc && selectFunc(key, this.props);
|
||||
} else if (typeof onSelect === 'function') {
|
||||
onSelect(key, this.props);
|
||||
}
|
||||
renderToolbarItem(toolbar: Array<ActionSchema>) {
|
||||
const {render} = this.props;
|
||||
let actions: Array<JSX.Element> = [];
|
||||
if (Array.isArray(toolbar)) {
|
||||
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>) {
|
||||
const {render} = this.props;
|
||||
let actions: Array<JSX.Element> = []
|
||||
if (Array.isArray(toolbar)) {
|
||||
toolbar.forEach((action, index) =>
|
||||
actions.push(
|
||||
render(
|
||||
`toolbar/${index}`,
|
||||
{
|
||||
type: 'button',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
...(action as any)
|
||||
},
|
||||
{
|
||||
key: index
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return actions;
|
||||
return toolbarTpl || tabToolbarTpl ? (
|
||||
<div className={cx(`${ns}Portlet-toolbar`)}>
|
||||
{toolbarTpl}
|
||||
{tabToolbarTpl}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderDesc() {
|
||||
const {
|
||||
description: descTpl,
|
||||
render,
|
||||
classnames: cx,
|
||||
classPrefix: ns,
|
||||
data
|
||||
} = this.props;
|
||||
const desc = filter(descTpl, data);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
tabs = Array.isArray(tabs) ? tabs : [tabs];
|
||||
let children: Array<JSX.Element | null> = [];
|
||||
|
||||
// tab里的toolbar
|
||||
if (typeof activeKey !== 'undefined') {
|
||||
tabToolbar = tabs[activeKey]!.toolbar;
|
||||
tabToolbarTpl = tabToolbar ? (
|
||||
<div className={cx(`${ns}tab-toolbar`)}>
|
||||
{this.renderToolbarItem(tabToolbar)}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
return (
|
||||
toolbarTpl || tabToolbarTpl
|
||||
? (<div className={cx(`${ns}Portlet-toolbar`)}>
|
||||
{toolbarTpl}
|
||||
{tabToolbarTpl}
|
||||
</div>)
|
||||
: 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}
|
||||
);
|
||||
}
|
||||
|
||||
renderDesc() {
|
||||
const {description : descTpl, render, classnames: cx, classPrefix: ns, data} = this.props;
|
||||
const desc = filter(descTpl, data);
|
||||
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) ? (
|
||||
children.push(
|
||||
...tabs.map((tab, tabIndex) =>
|
||||
isVisible(tab, ctx) ? (
|
||||
<Tab
|
||||
{...(tab as any)}
|
||||
title={filter(tab.title, data)}
|
||||
disabled={isDisabled(tab, data)}
|
||||
key={index}
|
||||
eventKey={index}
|
||||
title={filter(tab.title, ctx)}
|
||||
disabled={isDisabled(tab, ctx)}
|
||||
key={`${index * 1000 + tabIndex}`}
|
||||
eventKey={index * 1000 + tabIndex}
|
||||
mountOnEnter={mountOnEnter}
|
||||
unmountOnExit={
|
||||
typeof tab.reload === 'boolean'
|
||||
@ -340,66 +330,94 @@ export class Portlet extends React.Component<PortletProps, PortletState> {
|
||||
: 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
|
||||
)}
|
||||
{render(
|
||||
`item/${index}/${tabIndex}`,
|
||||
(tab as any)?.type ? (tab as any) : tab.tab || tab.body,
|
||||
{
|
||||
data: ctx
|
||||
}
|
||||
)}
|
||||
</Tab>
|
||||
) : 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() {
|
||||
const {
|
||||
className,
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={portletClassname} style={styleVar}>
|
||||
{this.renderTabs()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
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({
|
||||
type: 'portlet'
|
||||
})
|
||||
export class PortletRenderer extends Portlet {
|
||||
}
|
||||
export class PortletRenderer extends Portlet {}
|
||||
|
Loading…
Reference in New Issue
Block a user