combo 支持tabs 模式
This commit is contained in:
parent
a296c23b91
commit
4579ccfe39
|
@ -565,6 +565,52 @@ export default {
|
|||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: "Tabs",
|
||||
hash: "tabs",
|
||||
body: [
|
||||
{
|
||||
type: "form",
|
||||
api: "/api/mock2/saveForm?waitSeconds=2",
|
||||
title: "",
|
||||
mode: "horizontal",
|
||||
wrapWithPanel: false,
|
||||
className: "m-t",
|
||||
// debug: true,
|
||||
controls: [
|
||||
{
|
||||
type: "combo",
|
||||
name: "combo101",
|
||||
label: "组合多条多行",
|
||||
multiple: true,
|
||||
multiLine: true,
|
||||
value: [{}],
|
||||
tabsMode: true,
|
||||
tabsStyle: 'card',
|
||||
maxLength: 3,
|
||||
controls: [
|
||||
{
|
||||
name: "a",
|
||||
label: "文本",
|
||||
type: "text",
|
||||
placeholder: "文本",
|
||||
value: '',
|
||||
size: 'full'
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
label: "选项",
|
||||
type: "select",
|
||||
options: ["a", "b", "c"],
|
||||
size: 'full'
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: "其他",
|
||||
hash: 'others',
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
> .#{$ns}Tabs-link {
|
||||
margin-bottom: -$Tabs-borderWidth;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
> a {
|
||||
> a:first-child {
|
||||
font-size: $Tabs-linkFontSize;
|
||||
outline: none;
|
||||
border: $Tabs-borderWidth solid transparent;
|
||||
|
@ -27,22 +28,33 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
&:hover > a,
|
||||
> a:focus {
|
||||
> .#{$ns}Combo-toolbarBtn {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: -10px;
|
||||
z-index: 10;
|
||||
display: none;
|
||||
}
|
||||
&:hover > .#{$ns}Combo-toolbarBtn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover > a:first-child,
|
||||
> a:first-child:focus {
|
||||
border-color: $Tabs-onHover-borderColor;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.disabled > a,
|
||||
&.is-disabled > a {
|
||||
&.disabled > a:first-child,
|
||||
&.is-disabled > a:first-child {
|
||||
color: $Tabs-onDisabled-color;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.active > a,
|
||||
&.is-active > a {
|
||||
&.active > a:first-child,
|
||||
&.is-active > a:first-child {
|
||||
color: $Tabs-onActive-color;
|
||||
background-color: $Tabs-onActive-bg;
|
||||
border-color: $Tabs-onActive-borderColor;
|
||||
|
@ -77,9 +89,9 @@
|
|||
|
||||
> li {
|
||||
&.is-active {
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
border-bottom: px2rem(2px) solid $primary;
|
||||
color: $primary;
|
||||
background-color: transparent;
|
||||
|
@ -88,9 +100,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
color: #666;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
|
@ -106,18 +118,18 @@
|
|||
|
||||
> li {
|
||||
&.is-active {
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
color: $primary;
|
||||
background-color: #fff;
|
||||
margin-left: px2rem(1px);
|
||||
}
|
||||
}
|
||||
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
color: #666;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
@ -131,7 +143,7 @@
|
|||
margin-bottom: px2rem(10px);
|
||||
|
||||
> li {
|
||||
> a {
|
||||
> a:first-child {
|
||||
font-size: $fontSizeSm;
|
||||
text-align: center;
|
||||
margin-right: 0;
|
||||
|
@ -141,18 +153,18 @@
|
|||
}
|
||||
|
||||
&.is-active {
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
color: #fff;
|
||||
background-color: $primary;
|
||||
margin-left: px2rem(1px);
|
||||
}
|
||||
}
|
||||
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:first-child,
|
||||
> a:first-child:hover,
|
||||
> a:first-child:focus {
|
||||
color: $primary;
|
||||
background-color: #eaf6fe;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const transitionStyles: {
|
|||
[ENTERED]: 'in'
|
||||
};
|
||||
|
||||
export interface TabProps extends Schema {
|
||||
export interface TabProps {
|
||||
title?: string; // 标题
|
||||
icon?: string;
|
||||
eventKey: string | number;
|
||||
|
@ -27,11 +27,13 @@ export interface TabProps extends Schema {
|
|||
reload?: boolean;
|
||||
mountOnEnter?: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
toolbar?: React.ReactNode;
|
||||
};
|
||||
|
||||
export interface TabsProps {
|
||||
mode?: '' | 'line' | 'card' | 'radio';
|
||||
tabsMode?: '' | 'line' | 'card' | 'radio';
|
||||
additionBtns?: React.ReactNode;
|
||||
handleSelect?: Function;
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
|
@ -43,8 +45,9 @@ export interface TabsProps {
|
|||
}
|
||||
|
||||
export class Tabs extends React.Component<TabsProps> {
|
||||
static defaultProps: Pick<TabsProps, 'mode'> = {
|
||||
mode: ''
|
||||
static defaultProps:Pick<TabsProps, 'mode' | 'contentClassName'> = {
|
||||
mode: '',
|
||||
contentClassName: ''
|
||||
};
|
||||
|
||||
handleSelect(key: any) {
|
||||
|
@ -58,7 +61,7 @@ export class Tabs extends React.Component<TabsProps> {
|
|||
}
|
||||
|
||||
const { classnames: cx, activeKey } = this.props;
|
||||
const { eventKey, disabled, icon, title } = child.props;
|
||||
const { eventKey, disabled, icon, title, toolbar } = child.props;
|
||||
|
||||
return (
|
||||
<li
|
||||
|
@ -71,6 +74,7 @@ export class Tabs extends React.Component<TabsProps> {
|
|||
onClick={() => disabled ? '' : this.handleSelect(eventKey)}
|
||||
>
|
||||
<a>{icon ? <i className={icon} /> : null} {title}</a>
|
||||
{toolbar}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -97,7 +101,8 @@ export class Tabs extends React.Component<TabsProps> {
|
|||
className,
|
||||
mode: dMode,
|
||||
tabsMode,
|
||||
children
|
||||
children,
|
||||
additionBtns
|
||||
} = this.props;
|
||||
|
||||
if (!Array.isArray(children)) {
|
||||
|
@ -120,6 +125,7 @@ export class Tabs extends React.Component<TabsProps> {
|
|||
{children.map((tab, index) => (
|
||||
this.renderNav(tab, index)
|
||||
))}
|
||||
{additionBtns}
|
||||
</ul>
|
||||
|
||||
<div
|
||||
|
|
|
@ -6,17 +6,17 @@ import {
|
|||
FormItem,
|
||||
FormControlProps
|
||||
} from './Item';
|
||||
import cx from 'classnames';
|
||||
import { Schema, Action, Api } from '../../types';
|
||||
import {ComboStore, IComboStore} from '../../store/combo';
|
||||
import { observer } from "mobx-react";
|
||||
import { default as CTabs, Tab } from '../../components/Tabs';
|
||||
|
||||
import {
|
||||
guid,
|
||||
anyChanged,
|
||||
isObject,
|
||||
createObject,
|
||||
extendObject} from '../../utils/helper';
|
||||
extendObject,
|
||||
autobind} from '../../utils/helper';
|
||||
import Sortable = require('sortablejs');
|
||||
import { evalExpression, filter } from '../../utils/tpl';
|
||||
import find = require('lodash/find');
|
||||
|
@ -59,6 +59,9 @@ export interface ComboProps extends FormControlProps {
|
|||
dragIcon: string,
|
||||
deleteIcon: string;
|
||||
store: IComboStore;
|
||||
tabsMode: boolean;
|
||||
tabsStyle: '' | 'line' | 'card' | 'radio';
|
||||
tabsLabelTpl?: string;
|
||||
messages?: {
|
||||
validateFailed?: string;
|
||||
minLengthValidateFailed?: string;
|
||||
|
@ -80,7 +83,9 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
canAccessSuperData: false,
|
||||
addIcon: 'fa fa-plus',
|
||||
dragIcon: 'glyphicon glyphicon-sort',
|
||||
deleteIcon: 'glyphicon glyphicon-remove'
|
||||
deleteIcon: 'glyphicon glyphicon-remove',
|
||||
tabsMode: false,
|
||||
tabsStyle: ''
|
||||
};
|
||||
static propsList: Array<string> = [
|
||||
"minLength",
|
||||
|
@ -98,7 +103,9 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
"dragIcon",
|
||||
"deleteIcon",
|
||||
"noBorder",
|
||||
"conditions"
|
||||
"conditions",
|
||||
"tabsMode",
|
||||
"tabsStyle"
|
||||
];
|
||||
|
||||
subForms:Array<any> = [];
|
||||
|
@ -134,7 +141,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
store.config({
|
||||
minLength,
|
||||
maxLength,
|
||||
length: this.getValueAsArray().length
|
||||
length: this.getValueAsArray().length,
|
||||
});
|
||||
|
||||
formItem && formItem.setSubStore(store);
|
||||
|
@ -149,12 +156,17 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
minLength,
|
||||
maxLength
|
||||
} = nextProps;
|
||||
const values = this.getValueAsArray(nextProps);
|
||||
|
||||
store.config({
|
||||
minLength,
|
||||
maxLength,
|
||||
length: this.getValueAsArray(nextProps).length
|
||||
length: values.length
|
||||
});
|
||||
|
||||
if (store.activeKey >= values.length) {
|
||||
store.setActiveKey(values.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,7 +486,182 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleTabSelect(key:number) {
|
||||
const {
|
||||
store
|
||||
} = this.props;
|
||||
|
||||
store.setActiveKey(key);
|
||||
}
|
||||
|
||||
renderPlaceholder() {
|
||||
return (
|
||||
<span className="text-muted">{this.props.placeholder || '没有数据'}</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabsMode() {
|
||||
const {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
tabsStyle,
|
||||
formClassName,
|
||||
render,
|
||||
disabled,
|
||||
store,
|
||||
flat,
|
||||
subFormMode,
|
||||
addButtonText,
|
||||
addable,
|
||||
removable,
|
||||
typeSwitchable,
|
||||
itemRemovableOn,
|
||||
delimiter,
|
||||
canAccessSuperData,
|
||||
addIcon,
|
||||
deleteIcon,
|
||||
tabsLabelTpl,
|
||||
conditions
|
||||
} = this.props;
|
||||
|
||||
let controls = this.props.controls;
|
||||
let value = this.props.value;
|
||||
|
||||
if (flat && typeof value === 'string') {
|
||||
value = value.split(delimiter || ',');
|
||||
}
|
||||
|
||||
const finnalRemovable = store.removable !== false // minLength ?
|
||||
&& !disabled // 控件自身是否禁用
|
||||
&& removable !== false; // 是否可以删除
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
return this.renderPlaceholder();
|
||||
}
|
||||
|
||||
// todo 支持拖拽排序。
|
||||
|
||||
return (
|
||||
<CTabs
|
||||
mode={tabsStyle}
|
||||
activeKey={store.activeKey}
|
||||
handleSelect={this.handleTabSelect}
|
||||
additionBtns={!disabled ? (
|
||||
<li className={cx(`Tabs-link`)}>
|
||||
{store.addable && addable !== false ? Array.isArray(conditions) && conditions.length ? (
|
||||
render('add-button', {
|
||||
type: 'dropdown-button',
|
||||
icon: addIcon,
|
||||
label: addButtonText || '新增',
|
||||
level: 'info',
|
||||
size: 'sm',
|
||||
closeOnClick: true
|
||||
}, {
|
||||
buttons: conditions.map(item => ({
|
||||
label: item.label,
|
||||
onClick: (e:any) => {
|
||||
this.addItemWith(item)
|
||||
return false;
|
||||
}
|
||||
}))
|
||||
})
|
||||
) : (
|
||||
<a
|
||||
onClick={this.addItem}
|
||||
data-tooltip="新增一条数据"
|
||||
>
|
||||
{addIcon ? (<i className={cx('m-r-xs', addIcon)} />) : null}
|
||||
<span>{addButtonText || '新增'}</span>
|
||||
</a>
|
||||
) : null}
|
||||
</li>
|
||||
) : null}
|
||||
>
|
||||
{value.map((value, index) => {
|
||||
const data = this.formatValue(value, index);
|
||||
let condition:Condition | null = null;
|
||||
|
||||
if (Array.isArray(conditions) && conditions.length) {
|
||||
condition = this.pickCondition(data);
|
||||
controls = condition.controls;
|
||||
}
|
||||
|
||||
let finnalControls = flat ? [{
|
||||
...controls && controls[0],
|
||||
name: 'flat'
|
||||
}] : controls;
|
||||
|
||||
let toolbar = undefined;
|
||||
if (
|
||||
finnalRemovable
|
||||
&& ( // 表达式判断单条是否可删除
|
||||
!itemRemovableOn
|
||||
|| evalExpression(itemRemovableOn, value) !== false
|
||||
)
|
||||
) {
|
||||
toolbar = (
|
||||
<a
|
||||
onClick={this.removeItem.bind(this, index)}
|
||||
key="remove"
|
||||
className={cx(`Combo-toolbarBtn ${!store.removable ? 'is-disabled' : ''}`)}
|
||||
data-tooltip="删除"
|
||||
data-position="bottom"
|
||||
>
|
||||
<i className={deleteIcon} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tab
|
||||
title={filter(tabsLabelTpl || '成员${index|plus}', data)}
|
||||
key={this.keys[index] || (this.keys[index] = guid())}
|
||||
toolbar={toolbar}
|
||||
eventKey={index}
|
||||
mountOnEnter={true}
|
||||
unmountOnExit={false}
|
||||
>
|
||||
{condition && typeSwitchable !== false ? (
|
||||
<div className={cx('Combo-itemTag')}>
|
||||
<label>类型</label>
|
||||
<Select
|
||||
onChange={this.handleComboTypeChange.bind(this, index)}
|
||||
options={(conditions as Array<Condition>).map(item => ({label: item.label , value: item.label}))}
|
||||
value={condition.label}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={cx(`Combo-itemInner`)}>
|
||||
{render(`multiple/${index}`, {
|
||||
type: 'form',
|
||||
controls: finnalControls,
|
||||
wrapperComponent: 'div',
|
||||
wrapWithPanel: false,
|
||||
mode: subFormMode,
|
||||
className: cx(`Combo-form`, formClassName)
|
||||
}, {
|
||||
index,
|
||||
disabled,
|
||||
data,
|
||||
onChange: this.handleChange.bind(this, index),
|
||||
onAction: this.handleAction,
|
||||
ref: (ref:any) => this.formRef(ref, index),
|
||||
canAccessSuperData
|
||||
})}
|
||||
</div>
|
||||
</Tab>
|
||||
)
|
||||
})}
|
||||
</CTabs>
|
||||
);
|
||||
}
|
||||
|
||||
renderMultipe() {
|
||||
if (this.props.tabsMode) {
|
||||
return this.renderTabsMode()
|
||||
}
|
||||
|
||||
const {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
anyChanged,
|
||||
ucFirst,
|
||||
getWidthRate} from '../../utils/helper';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
|
||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||
|
@ -513,7 +514,7 @@ export function registerFormItem(config: FormItemConfig): RendererConfig {
|
|||
Control = HocStoreFactory({
|
||||
storeType: config.storeType,
|
||||
extendsData: config.extendsData
|
||||
})(Control);
|
||||
})(observer(Control));
|
||||
delete config.storeType;
|
||||
}
|
||||
|
||||
|
|
|
@ -214,10 +214,6 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
|
|||
}
|
||||
|
||||
const mode = tabsMode || dMode;
|
||||
const finallyTabs = tabs.map(tab => {
|
||||
tab.isDisabled = isDisabled(tab, data);
|
||||
return tab;
|
||||
});
|
||||
|
||||
return (
|
||||
<CTabs
|
||||
|
@ -229,11 +225,12 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
|
|||
handleSelect={this.handleSelect}
|
||||
activeKey={this.state.activeKey}
|
||||
>
|
||||
{finallyTabs.map((tab, index) => (
|
||||
{tabs.map((tab, index) => (
|
||||
isVisible(tab, data)
|
||||
? (
|
||||
<Tab
|
||||
{...tab}
|
||||
disabled={isDisabled(tab, data)}
|
||||
key={index}
|
||||
eventKey={tab.hash || index}
|
||||
mountOnEnter={mountOnEnter}
|
||||
|
|
|
@ -30,7 +30,8 @@ export const ComboStore = iRendererStore
|
|||
forms: types.array(types.reference(types.late(() => FormStore))),
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
length: 0
|
||||
length: 0,
|
||||
activeKey: 0
|
||||
})
|
||||
.views(self => ({
|
||||
get addable() {
|
||||
|
@ -106,8 +107,13 @@ export const ComboStore = iRendererStore
|
|||
self.forms.remove(form);
|
||||
}
|
||||
|
||||
function setActiveKey(key:number) {
|
||||
self.activeKey = key;
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
setActiveKey,
|
||||
bindUniuqueItem,
|
||||
unBindUniuqueItem,
|
||||
addForm,
|
||||
|
|
Loading…
Reference in New Issue