Merge pull request #318 from 2betop/master

Spinner 优化 & filter 问题修复
This commit is contained in:
liaoxuezhi 2019-10-30 20:13:44 +08:00 committed by GitHub
commit 392c19caec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 159 additions and 174 deletions

View File

@ -369,7 +369,7 @@ $Spinner-height: px2rem(26px) !default;
$Spinner--lg-width: px2rem(50px) !default;
$Spinner--lg-height: px2rem(50px) !default;
$Spinner-bg: url('./spinner-default.svg') !default;
$Spinner-bg: url('./spinner-default.svg?__inline') !default;
// Tabs
$Tabs-linkFontSize: $fontSizeBase !default;

View File

@ -16,18 +16,12 @@
right: 0;
bottom: 0;
background: $Spinner-overlay-bg;
transition: ease-out opacity 0.3s;
.#{$ns}Spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
opacity: 0;
.#{$ns}Spinner--lg {
width: $Spinner--lg-width;
height: $Spinner--lg-height;
line-height: $Spinner--lg-height;
&.in {
opacity: 1;
}
}
@ -43,14 +37,30 @@
width: $Spinner--lg-width;
height: $Spinner--lg-height;
}
transition: ease-out all 0.3s;
}
// 当启用 overlay 的时候应该是居中模式
.#{$ns}Spinner--overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
.#{$ns}Spinner--overlay.#{$ns}Spinner--lg {
width: $Spinner--lg-width;
height: $Spinner--lg-height;
line-height: $Spinner--lg-height;
}
@include media-breakpoint-up(md) {
.#{$ns}Layout .#{$ns}Page-body > .#{$ns}Spinner-overlay {
.#{$ns}Layout .#{$ns}Page-body>.#{$ns}Spinner-overlay {
left: $Layout-aside-width;
}
.#{$ns}Layout--folded .#{$ns}Page-body > .#{$ns}Spinner-overlay {
.#{$ns}Layout--folded .#{$ns}Page-body>.#{$ns}Spinner-overlay {
left: $Layout-aside--folded-width;
}
}
}

View File

@ -6,17 +6,24 @@
*/
import React from 'react';
import {Renderer, RendererProps} from '../factory';
import {ClassNamesFn, themeable} from '../theme';
import {classPrefix, classnames} from '../themes/default';
import Transition, {ENTERED, ENTERING} from 'react-transition-group/Transition';
interface SpinnerProps extends RendererProps {
const fadeStyles: {
[propName: string]: string;
} = {
[ENTERING]: 'in',
[ENTERED]: 'in'
};
interface SpinnerProps {
overlay: boolean;
spinnerClassName: string;
mode: string;
size: string;
classPrefix: string;
classnames: ClassNamesFn;
show: boolean;
}
export class Spinner extends React.Component<SpinnerProps, object> {
@ -24,26 +31,45 @@ export class Spinner extends React.Component<SpinnerProps, object> {
overlay: false,
spinnerClassName: '',
mode: '',
size: ''
size: '',
show: true
};
div: React.RefObject<HTMLDivElement> = React.createRef();
overlay: React.RefObject<HTMLDivElement> = React.createRef();
render() {
const {mode, overlay, spinnerClassName, classPrefix: ns, classnames: cx, size} = this.props;
const {show, classnames: cx, spinnerClassName, mode, size, overlay} = this.props;
return (
<Transition mountOnEnter unmountOnExit in={show} timeout={350}>
{(status: string) => {
if (status === ENTERING) {
// force reflow
// 由于从 mount 进来到加上 in 这个 class 估计是时间太短上次的样式还没应用进去所以这里强制reflow一把。
// 否则看不到动画。
this.div.current!.offsetWidth;
this.overlay.current && this.overlay.current.offsetWidth;
}
const spinner = (
<div
className={cx(`${ns}Spinner`, spinnerClassName, {
[`Spinner--${mode}`]: !!mode,
[`Spinner--${size}`]: !!size
})}
/>
return (
<>
{overlay ? (
<div ref={this.overlay} className={cx(`Spinner-overlay`, fadeStyles[status])} />
) : null}
<div
ref={this.div}
className={cx(`Spinner`, spinnerClassName, fadeStyles[status], {
[`Spinner--${mode}`]: mode,
[`Spinner--overlay`]: overlay,
[`Spinner--${size}`]: size
})}
/>
</>
);
}}
</Transition>
);
if (overlay) {
return <div className={cx(`Spinner-overlay`)}>{spinner}</div>;
}
return spinner;
}
}

View File

@ -27,6 +27,7 @@ import {isValidApi, buildApi, isEffectiveApi} from '../utils/api';
import omit = require('lodash/omit');
import find = require('lodash/find');
import Html from '../components/Html';
import {Spinner} from '../components';
interface CRUDProps extends RendererProps {
api?: Api;
@ -1367,23 +1368,11 @@ export default class CRUD extends React.Component<CRUDProps, any> {
onPopOverClose: this.handleChildPopOverClose,
headerToolbarRender: this.renderHeaderToolbar,
footerToolbarRender: this.renderFooterToolbar,
data: store.mergedData,
data: store.mergedData
}
)}
{store.loading
? render(
'info',
{
type: 'spinner',
overlay: true
},
{
size: 'lg',
key: 'info'
}
)
: null}
<Spinner overlay size="lg" key="info" show={store.loading} />
{render(
'dialog',

View File

@ -13,6 +13,7 @@ import {reaction} from 'mobx';
import {Icon} from '../components/icons';
import {ModalStore, IModalStore} from '../store/modal';
import {findDOMNode} from 'react-dom';
import {Spinner} from '../components';
export interface DialogProps extends RendererProps {
title?: string; // 标题
@ -324,18 +325,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
<div className={cx('Modal-footer')}>
{store.loading || store.error ? (
<div className={cx('Dialog-info')} key="info">
{store.loading
? render(
'info',
{
type: 'spinner'
},
{
key: 'info',
size: 'sm'
}
)
: null}
<Spinner size="sm" key="info" show={store.loading} />
{store.error ? <span className={cx('Dialog-error')}>{store.msg}</span> : null}
</div>
) : null}

View File

@ -13,6 +13,7 @@ import {reaction} from 'mobx';
import {findDOMNode} from 'react-dom';
import {IModalStore, ModalStore} from '../store/modal';
import {filter} from '../utils/tpl';
import {Spinner} from '../components';
export interface DrawerProps extends RendererProps {
title?: string; // 标题
@ -284,17 +285,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
<div className={cx('Drawer-footer')}>
{store.loading || store.error ? (
<div className={cx('Drawer-info')}>
{store.loading
? render(
'info',
{
type: 'spinner'
},
{
key: 'info'
}
)
: null}
<Spinner size="sm" key="info" show={store.loading} />
{store.error ? <span className={cx('Drawer-error')}>{store.msg}</span> : null}
</div>
) : null}

View File

@ -7,7 +7,7 @@ import React from 'react';
import cx from 'classnames';
import {FormControlProps, FormItem} from './Item';
import {buildApi, isValidApi, isEffectiveApi} from '../../utils/api';
import {Checkbox} from '../../components';
import {Checkbox, Spinner} from '../../components';
export interface Column {
label: string;
@ -241,13 +241,7 @@ export default class MatrixCheckbox extends React.Component<MatrixProps, MatrixS
this.renderInput()
)}
{loading
? render('loading', {
type: 'spinner',
overlay: true,
size: 'lg'
})
: null}
<Spinner size="lg" overlay key="info" show={loading} />
</div>
);
}

View File

@ -3,6 +3,7 @@ import {OptionsControl, OptionsControlProps, Option} from './Options';
import {xorBy, find} from 'lodash';
import Checkbox from '../../components/Checkbox';
import {closeIcon, Icon} from '../../components/icons';
import {Spinner} from '../../components';
export interface TransferSelectProps extends OptionsControlProps {
viewMode?: 'table' | 'normal';
@ -377,19 +378,7 @@ export class TransferSelect extends React.Component<TransferSelectProps, Transfe
{viewMode === 'table' ? this.renderTableSelectedOptions() : this.renderNormalSelectedOptions()}
{loading
? render(
'loading',
{
type: 'spinner',
overlay: true
},
{
size: 'lg',
key: 'info'
}
)
: null}
<Spinner size="lg" overlay key="info" show={loading} />
</div>
);
}

View File

@ -7,6 +7,7 @@ import {Action, Schema, PlainObject, Api, Payload} from '../../types';
import {isEffectiveApi} from '../../utils/api';
import {filter} from '../../utils/tpl';
import {Option} from '../../components/Checkboxes';
import {Spinner} from '../../components';
export interface TreeProps extends OptionsControlProps {
placeholder?: any;
@ -28,11 +29,11 @@ export interface TreeProps extends OptionsControlProps {
}
export interface TreeState {
isAddModalOpened: boolean,
isEditModalOpened: boolean,
parent: Option | null,
prev: Option | null,
data: any
isAddModalOpened: boolean;
isEditModalOpened: boolean;
parent: Option | null;
prev: Option | null;
data: any;
}
export default class TreeControl extends React.Component<TreeProps, TreeState> {
@ -51,7 +52,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
parent: null,
prev: null,
data: null
}
};
reload() {
const reload = this.props.reloadOptions;
@ -65,10 +66,13 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
@autobind
handleAddModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
this.saveRemote({
...values,
parent: this.state.parent
}, 'add');
this.saveRemote(
{
...values,
parent: this.state.parent
},
'add'
);
this.closeAddDialog();
}
@ -79,21 +83,19 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
@autobind
handleEditModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
this.saveRemote({
...values,
prev: this.state.prev
}, 'edit');
this.saveRemote(
{
...values,
prev: this.state.prev
},
'edit'
);
this.closeEditDialog();
}
@autobind
async saveRemote(item: any, type: 'add' | 'edit') {
const {
addApi,
editApi,
data,
env
} = this.props;
const {addApi, editApi, data, env} = this.props;
let remote: Payload | null = null;
if (type == 'add' && isEffectiveApi(addApi, createObject(data, item))) {
@ -106,7 +108,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
env.notify('error', remote.msg || '保存失败');
return;
}
this.reload();
}
@ -207,11 +209,8 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
return (
<div className={cx(`${ns}TreeControl`, className)}>
{loading ? (
render('loading', {
type: 'spinner'
})
) : (
<Spinner size="sm" key="info" show={loading} />
{loading ? null : (
<TreeSelector
classPrefix={ns}
valueField={valueField}
@ -250,35 +249,37 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
/>
)}
{addMode && render(
'modal',
{
type: 'dialog',
...addDialog
},
{
key: 'addModal',
data: data,
onConfirm: this.handleAddModalConfirm,
onClose: this.closeAddDialog,
show: this.state.isAddModalOpened
}
)}
{addMode &&
render(
'modal',
{
type: 'dialog',
...addDialog
},
{
key: 'addModal',
data: data,
onConfirm: this.handleAddModalConfirm,
onClose: this.closeAddDialog,
show: this.state.isAddModalOpened
}
)}
{editMode && render(
'modal',
{
type: 'dialog',
...editDialog
},
{
key: 'editModal',
data: data,
onConfirm: this.handleEditModalConfirm,
onClose: this.closeEditDialog,
show: this.state.isEditModalOpened
}
)}
{editMode &&
render(
'modal',
{
type: 'dialog',
...editDialog
},
{
key: 'editModal',
data: data,
onConfirm: this.handleEditModalConfirm,
onClose: this.closeEditDialog,
show: this.state.isEditModalOpened
}
)}
</div>
);
}

View File

@ -11,6 +11,7 @@ import {isVisible, autobind, bulkBindFunctions} from '../utils/helper';
import {ScopedContext, IScopedContext} from '../Scoped';
import Alert from '../components/Alert2';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {Spinner} from '../components';
export interface PageProps extends RendererProps {
title?: string; // 标题
@ -403,13 +404,7 @@ export default class Page extends React.Component<PageProps> {
<div className={cx('Page-main')}>
{this.renderHeader()}
<div className={cx(`Page-body`, bodyClassName)}>
{store.loading
? render('spinner', {
type: 'spinner',
overlay: true,
size: 'lg'
})
: null}
<Spinner size="lg" overlay key="info" show={store.loading} />
{store.error ? (
<Alert level="danger" showCloseButton onClose={store.clearMessage}>

View File

@ -8,6 +8,7 @@ import cx from 'classnames';
import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import {observer} from 'mobx-react';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {Spinner} from '../components';
export interface ServiceProps extends RendererProps {
api?: Api;
@ -199,19 +200,7 @@ export default class Service extends React.Component<ServiceProps> {
{this.renderBody()}
{store.loading
? render(
'info',
{
type: 'spinner',
overlay: true
},
{
key: 'info',
size: 'lg'
}
)
: null}
<Spinner size="lg" overlay key="info" show={store.loading} />
</div>
);
}

View File

@ -1,8 +1,15 @@
import Spinner from '../components/Spinner';
import {Renderer} from '../factory';
import {Renderer, RendererProps} from '../factory';
import React from 'react';
interface SpinnerProps extends RendererProps {}
@Renderer({
test: /(^|\/)spinner$/,
name: 'spinner'
})
export class SpinnerRenderer extends Spinner {}
export class SpinnerRenderer extends React.Component<SpinnerProps> {
render() {
return <Spinner {...this.props} />;
}
}

View File

@ -9,6 +9,7 @@ import {observer} from 'mobx-react';
import {createObject, until, isVisible} from '../utils/helper';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {IFormStore} from '../store/form';
import {Spinner} from '../components';
export interface WizardProps extends RendererProps {
store: IServiceStore;
@ -583,13 +584,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
)}
{this.renderActions()}
{store.loading
? render('spinner', {
type: 'spinner',
overlay: true,
size: 'lg'
})
: null}
<Spinner size="lg" overlay key="info" show={store.loading} />
</div>
);
}

View File

@ -232,7 +232,16 @@ export const filters: {
arg1 = expOrDirective;
}
arg1 = arg1 ? (/^('|")(.*)\1$/.test(arg1) ? RegExp.$2 : resolveVariable(arg1, this as any)) : '';
fn = value => !!~String(value).toLowerCase().indexOf(arg1);
// 比对的值是空时直接返回。
if (!arg1) {
return input;
}
fn = value =>
!!~String(value)
.toLowerCase()
.indexOf(arg1);
}
keys = keys.split(/\s*,\s*/);