combo 支持tabs 模式

This commit is contained in:
liaoxuezhi 2019-09-04 23:36:19 +08:00
parent bab2f59ba2
commit b1eb30cf7c
7 changed files with 300 additions and 45 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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