mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-03 04:18:29 +08:00
Merge branch 'master' of https://github.com/2betop/amis
This commit is contained in:
commit
07df519931
@ -12,6 +12,7 @@ CRUD 支持三种模式:`table`、`cards`、`list`,默认为 `table`。
|
||||
| className | `string` | | 表格外层 Dom 的类名 |
|
||||
| [api](#api) | [Api](./Types.md#Api) | | CRUD 用来获取列表数据的 api。 |
|
||||
| loadDataOnce | `boolean` | | 是否一次性加载所有数据(前端分页) |
|
||||
| loadDataOnceFetchOnFilter | `boolean` | `true` | 在开启loadDataOnce时,filter时是否去重新请求api |
|
||||
| source | `string` | | 数据映射接口返回某字段的值,不设置会默认把接口返回的`items`或者`rows`填充进`mode`区域 |
|
||||
| filter | [Form](./Form/Form.md) | | 设置过滤器,当该表单提交后,会把数据带给当前 `mode` 刷新列表。 |
|
||||
| filterTogglable | `boolean` | `false` | 是否可显隐过滤器 |
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
"hoist-non-react-statics": "3.3.0",
|
||||
"jquery": "^3.2.1",
|
||||
"keycode": "^2.1.9",
|
||||
"lodash": "^4.17.15",
|
||||
"match-sorter": "2.2.1",
|
||||
"mobx": "^4.5.0",
|
||||
"mobx-react": "^6.1.4",
|
||||
@ -72,8 +73,7 @@
|
||||
"sortablejs": "1.10.0",
|
||||
"tslib": "^1.10.0",
|
||||
"uncontrollable": "4.1.0",
|
||||
"video-react": "0.9.4",
|
||||
"lodash": "^4.17.15"
|
||||
"video-react": "0.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dom-helpers": "^3.4.1",
|
||||
|
@ -90,6 +90,20 @@
|
||||
animation-name: bounceOut;
|
||||
}
|
||||
|
||||
&-close {
|
||||
position: absolute;
|
||||
top: $gap-xs;
|
||||
right: $gap-sm;
|
||||
color: $white;
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
display: $Toast-display;
|
||||
font-size: $fontSizeMd;
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import VisibilitySensor = require('react-visibility-sensor');
|
||||
import Spinner from './Spinner';
|
||||
|
||||
export interface LazyComponentProps {
|
||||
component?: React.ReactType;
|
||||
@ -27,7 +28,7 @@ export default class LazyComponent extends React.Component<
|
||||
LazyComponentState
|
||||
> {
|
||||
static defaultProps = {
|
||||
placeholder: '加载中...',
|
||||
placeholder: <Spinner />,
|
||||
unMountOnHidden: false,
|
||||
partialVisibility: true
|
||||
};
|
||||
@ -89,6 +90,7 @@ export default class LazyComponent extends React.Component<
|
||||
childProps,
|
||||
visiblilityProps,
|
||||
partialVisibility,
|
||||
children,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@ -105,6 +107,8 @@ export default class LazyComponent extends React.Component<
|
||||
<div className="visibility-sensor">
|
||||
{Component && visible ? (
|
||||
<Component {...rest} {...childProps} />
|
||||
) : children && visible ? (
|
||||
children
|
||||
) : (
|
||||
placeholder
|
||||
)}
|
||||
@ -126,6 +130,8 @@ export default class LazyComponent extends React.Component<
|
||||
} else if (Component) {
|
||||
// 只监听不可见到可见,一旦可见了,就销毁检查。
|
||||
return <Component {...rest} {...childProps} />;
|
||||
} else if (children) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <div>{placeholder}</div>;
|
||||
|
@ -13,8 +13,14 @@ import Transition, {
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import Html from './Html';
|
||||
import {uuid, autobind} from '../utils/helper';
|
||||
import {ClassNamesFn, themeable} from '../theme';
|
||||
import {uuid, autobind, noop} from '../utils/helper';
|
||||
import {ClassNamesFn, themeable, classnames} from '../theme';
|
||||
import {Icon} from './icons';
|
||||
|
||||
interface Config {
|
||||
closeButton?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
const fadeStyles: {
|
||||
[propName: string]: string;
|
||||
@ -25,12 +31,6 @@ const fadeStyles: {
|
||||
};
|
||||
|
||||
let toastRef: any = null;
|
||||
let config: {
|
||||
closeButton?: boolean;
|
||||
timeOut?: number;
|
||||
extendedTimeOut?: number;
|
||||
} = {};
|
||||
|
||||
const show = (
|
||||
content: string,
|
||||
title: string = '',
|
||||
@ -40,7 +40,7 @@ const show = (
|
||||
if (!toastRef || !toastRef[method]) {
|
||||
return;
|
||||
}
|
||||
toastRef[method](content, title || '', {...config, ...conf});
|
||||
toastRef[method](content, title || '', {...conf});
|
||||
};
|
||||
|
||||
interface ToastComponentProps {
|
||||
@ -52,14 +52,13 @@ interface ToastComponentProps {
|
||||
| 'bottom-left'
|
||||
| 'bottom-right';
|
||||
closeButton: boolean;
|
||||
timeOut: number;
|
||||
extendedTimeOut: number;
|
||||
timeout: number;
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface Item {
|
||||
interface Item extends Config {
|
||||
title?: string;
|
||||
body: string;
|
||||
level: 'info' | 'success' | 'error' | 'warning';
|
||||
@ -76,12 +75,11 @@ export class ToastComponent extends React.Component<
|
||||
> {
|
||||
static defaultProps: Pick<
|
||||
ToastComponentProps,
|
||||
'position' | 'closeButton' | 'timeOut' | 'extendedTimeOut'
|
||||
'position' | 'closeButton' | 'timeout'
|
||||
> = {
|
||||
position: 'top-right',
|
||||
closeButton: false,
|
||||
timeOut: 5000,
|
||||
extendedTimeOut: 3000
|
||||
timeout: 5000
|
||||
};
|
||||
|
||||
// 当前ToastComponent是否真正render了
|
||||
@ -90,15 +88,6 @@ export class ToastComponent extends React.Component<
|
||||
items: []
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const {closeButton, timeOut, extendedTimeOut} = this.props;
|
||||
config = {
|
||||
closeButton,
|
||||
timeOut,
|
||||
extendedTimeOut
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.hasRendered = true;
|
||||
toastRef = this;
|
||||
@ -157,27 +146,27 @@ export class ToastComponent extends React.Component<
|
||||
return null;
|
||||
}
|
||||
|
||||
const {classPrefix: ns, className, timeOut, position} = this.props;
|
||||
const {classnames: cx, className, timeout, position} = this.props;
|
||||
const items = this.state.items;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
`${ns}Toast-wrap ${ns}Toast-wrap--${position.replace(
|
||||
/\-(\w)/g,
|
||||
(_, l) => l.toUpperCase()
|
||||
`Toast-wrap Toast-wrap--${position.replace(/\-(\w)/g, (_, l) =>
|
||||
l.toUpperCase()
|
||||
)}`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<ToastMessage
|
||||
classnames={classnames}
|
||||
key={item.id}
|
||||
classPrefix={ns}
|
||||
title={item.title}
|
||||
body={item.body}
|
||||
level={item.level || 'info'}
|
||||
timeOut={timeOut}
|
||||
timeout={item.timeout ?? timeout}
|
||||
closeButton={item.closeButton}
|
||||
onDismiss={this.handleDismissed.bind(this, index)}
|
||||
/>
|
||||
))}
|
||||
@ -192,7 +181,8 @@ interface ToastMessageProps {
|
||||
title?: string;
|
||||
body: string;
|
||||
level: 'info' | 'success' | 'error' | 'warning';
|
||||
timeOut: number;
|
||||
timeout: number;
|
||||
closeButton?: boolean;
|
||||
position:
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
@ -201,7 +191,7 @@ interface ToastMessageProps {
|
||||
| 'bottom-left'
|
||||
| 'bottom-right';
|
||||
onDismiss?: () => void;
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
allowHtml: boolean;
|
||||
}
|
||||
|
||||
@ -209,9 +199,12 @@ interface ToastMessageState {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export class ToastMessage extends React.Component<ToastMessageProps> {
|
||||
export class ToastMessage extends React.Component<
|
||||
ToastMessageProps,
|
||||
ToastMessageState
|
||||
> {
|
||||
static defaultProps = {
|
||||
timeOut: 5000,
|
||||
timeout: 5000,
|
||||
classPrefix: '',
|
||||
position: 'top-right',
|
||||
allowHtml: true,
|
||||
@ -225,15 +218,6 @@ export class ToastMessage extends React.Component<ToastMessageProps> {
|
||||
// content: React.RefObject<HTMLDivElement>;
|
||||
timer: NodeJS.Timeout;
|
||||
mounted: boolean = false;
|
||||
constructor(props: ToastMessageProps) {
|
||||
super(props);
|
||||
|
||||
// this.content = React.createRef();
|
||||
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
this.handleEntered = this.handleEntered.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
@ -247,21 +231,25 @@ export class ToastMessage extends React.Component<ToastMessageProps> {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleMouseEnter() {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleMouseLeave() {
|
||||
this.handleEntered();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEntered() {
|
||||
const timeOut = this.props.timeOut;
|
||||
if (this.mounted) {
|
||||
this.timer = setTimeout(this.close, timeOut);
|
||||
const timeout = this.props.timeout;
|
||||
if (this.mounted && timeout) {
|
||||
this.timer = setTimeout(this.close, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
close() {
|
||||
clearTimeout(this.timer);
|
||||
this.setState({
|
||||
@ -272,8 +260,8 @@ export class ToastMessage extends React.Component<ToastMessageProps> {
|
||||
render() {
|
||||
const {
|
||||
onDismiss,
|
||||
classPrefix: ns,
|
||||
position,
|
||||
classnames: cx,
|
||||
closeButton,
|
||||
title,
|
||||
body,
|
||||
allowHtml,
|
||||
@ -290,26 +278,20 @@ export class ToastMessage extends React.Component<ToastMessageProps> {
|
||||
onExited={onDismiss}
|
||||
>
|
||||
{(status: string) => {
|
||||
// if (status === ENTERING) {
|
||||
// // force reflow
|
||||
// // 由于从 mount 进来到加上 in 这个 class 估计是时间太短,上次的样式还没应用进去,所以这里强制reflow一把。
|
||||
// // 否则看不到动画。
|
||||
// this.content.current && this.content.current.offsetWidth;
|
||||
// }
|
||||
|
||||
return (
|
||||
<div
|
||||
// ref={this.content}
|
||||
className={cx(
|
||||
`${ns}Toast ${ns}Toast--${level}`,
|
||||
fadeStyles[status]
|
||||
)}
|
||||
className={cx(`Toast Toast--${level}`, fadeStyles[status])}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onClick={this.close}
|
||||
onClick={closeButton ? noop : this.close}
|
||||
>
|
||||
{title ? <div className={`${ns}Toast-title`}>{title}</div> : null}
|
||||
<div className={`${ns}Toast-body`}>
|
||||
{closeButton ? (
|
||||
<a onClick={this.close} className={cx(`Toast-close`)}>
|
||||
<Icon icon="close" className="icon" />
|
||||
</a>
|
||||
) : null}
|
||||
{title ? <div className={cx('Toast-title')}>{title}</div> : null}
|
||||
<div className={cx('Toast-body')}>
|
||||
{allowHtml ? <Html html={body} /> : body}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -461,10 +461,14 @@ class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
||||
// 限制:只有 schema 除外的 props 变化,或者 schema 里面的某个成员值发生变化才更新。
|
||||
shouldComponentUpdate(nextProps: SchemaRendererProps) {
|
||||
const props = this.props;
|
||||
const list: Array<string> = difference(Object.keys(nextProps), ['schema']);
|
||||
const list: Array<string> = difference(Object.keys(nextProps), [
|
||||
'schema',
|
||||
'scope'
|
||||
]);
|
||||
|
||||
if (
|
||||
difference(Object.keys(props), ['schema']).length !== list.length ||
|
||||
difference(Object.keys(props), ['schema', 'scope']).length !==
|
||||
list.length ||
|
||||
anyChanged(list, this.props, nextProps)
|
||||
) {
|
||||
return true;
|
||||
@ -763,7 +767,11 @@ export function HocStoreFactory(renderer: {
|
||||
...store.data
|
||||
})
|
||||
);
|
||||
} else if (props.scope !== nextProps.scope) {
|
||||
} else if (
|
||||
nextProps.scope &&
|
||||
nextProps.data === nextProps.store!.data &&
|
||||
props.data !== nextProps.data
|
||||
) {
|
||||
store.initData(
|
||||
createObject(nextProps.scope, {
|
||||
// ...nextProps.data,
|
||||
|
@ -69,6 +69,7 @@ interface CRUDProps extends RendererProps {
|
||||
syncResponse2Query?: boolean;
|
||||
keepItemSelectionOnPageChange?: boolean;
|
||||
loadDataOnce?: boolean;
|
||||
loadDataOnceFetchOnFilter?: boolean; // 在开启loadDataOnce时,filter时是否去重新请求api
|
||||
source?: string;
|
||||
}
|
||||
|
||||
@ -114,6 +115,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
'labelTpl',
|
||||
'labelField',
|
||||
'loadDataOnce',
|
||||
'loadDataOnceFetchOnFilter',
|
||||
'source'
|
||||
];
|
||||
static defaultProps = {
|
||||
@ -129,7 +131,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
silentPolling: false,
|
||||
filterTogglable: false,
|
||||
filterDefaultVisible: true,
|
||||
loadDataOnce: false
|
||||
loadDataOnce: false,
|
||||
loadDataOnceFetchOnFilter: true
|
||||
};
|
||||
|
||||
control: any;
|
||||
@ -379,7 +382,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
onAction,
|
||||
messages,
|
||||
pageField,
|
||||
stopAutoRefreshWhenModalIsOpen
|
||||
stopAutoRefreshWhenModalIsOpen,
|
||||
env
|
||||
} = this.props;
|
||||
|
||||
if (!selectedItems.length && action.requireSelected !== false) {
|
||||
@ -430,6 +434,9 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
action.reload
|
||||
? this.reloadTarget(action.reload, store.data)
|
||||
: this.search({[pageField || 'page']: 1}, undefined, true);
|
||||
|
||||
action.redirect &&
|
||||
env.jumpTo(filter(action.redirect, store.data), action);
|
||||
})
|
||||
.catch(() => null);
|
||||
} else if (onAction) {
|
||||
@ -681,6 +688,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
pickerMode,
|
||||
env,
|
||||
loadDataOnce,
|
||||
loadDataOnceFetchOnFilter,
|
||||
source
|
||||
} = this.props;
|
||||
|
||||
@ -716,6 +724,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
autoAppend: true,
|
||||
forceReload,
|
||||
loadDataOnce,
|
||||
loadDataOnceFetchOnFilter,
|
||||
source,
|
||||
silent,
|
||||
pageField,
|
||||
|
@ -58,7 +58,7 @@ export default class ChainedSelectControl extends React.Component<
|
||||
componentDidUpdate(prevProps: ChainedSelectProps) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.value !== prevProps.value) {
|
||||
if (props.formInited && props.value !== prevProps.value) {
|
||||
this.loadMore();
|
||||
}
|
||||
}
|
||||
|
@ -21,20 +21,7 @@ import Select from '../../components/Select';
|
||||
import {dataMapping} from '../../utils/tpl-builtin';
|
||||
import {isEffectiveApi} from '../../utils/api';
|
||||
import {Alert2} from '../../components';
|
||||
import memoize from 'fast-memoize';
|
||||
|
||||
const formatValue = memoize(
|
||||
(value: any, index: number, data: any) => {
|
||||
return createObject(
|
||||
extendObject(data, {index, __index: index, ...data}),
|
||||
value
|
||||
);
|
||||
},
|
||||
{
|
||||
serializer: (args: Array<any>) => JSON.stringify(args.slice(0, 2))
|
||||
}
|
||||
);
|
||||
|
||||
import memoize from 'lodash/memoize';
|
||||
export interface Condition {
|
||||
test: string;
|
||||
controls: Array<Schema>;
|
||||
@ -73,6 +60,8 @@ export interface ComboProps extends FormControlProps {
|
||||
tabsMode: boolean;
|
||||
tabsStyle: '' | 'line' | 'card' | 'radio';
|
||||
tabsLabelTpl?: string;
|
||||
lazyLoad?: boolean;
|
||||
strictMode?: boolean;
|
||||
messages?: {
|
||||
validateFailed?: string;
|
||||
minLengthValidateFailed?: string;
|
||||
@ -116,7 +105,9 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
'noBorder',
|
||||
'conditions',
|
||||
'tabsMode',
|
||||
'tabsStyle'
|
||||
'tabsStyle',
|
||||
'lazyLoad',
|
||||
'strictMode'
|
||||
];
|
||||
|
||||
subForms: Array<any> = [];
|
||||
@ -188,6 +179,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
this.memoizedFormatValue.cache.clear?.();
|
||||
this.makeFormRef.cache.clear?.();
|
||||
}
|
||||
|
||||
getValueAsArray(props = this.props) {
|
||||
@ -540,8 +533,19 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
}
|
||||
|
||||
memoizedFormatValue = memoize(
|
||||
(strictMode: boolean, value: any, index: number, data: any) => {
|
||||
return createObject(
|
||||
extendObject(data, {index, __index: index, ...data}),
|
||||
value
|
||||
);
|
||||
},
|
||||
(strictMode: boolean, ...args: Array<any>) =>
|
||||
strictMode ? JSON.stringify(args.slice(0, 2)) : JSON.stringify(args)
|
||||
);
|
||||
|
||||
formatValue(value: any, index: number) {
|
||||
const {flat, data, store} = this.props;
|
||||
const {flat, data, strictMode} = this.props;
|
||||
|
||||
if (flat) {
|
||||
value = {
|
||||
@ -551,7 +555,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
|
||||
value = value || this.defaultValue;
|
||||
|
||||
return formatValue(value, index, data);
|
||||
return this.memoizedFormatValue(strictMode !== false, value, index, data);
|
||||
}
|
||||
|
||||
pickCondition(value: any): Condition | null {
|
||||
@ -818,7 +822,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
dragIcon,
|
||||
deleteIcon,
|
||||
noBorder,
|
||||
conditions
|
||||
conditions,
|
||||
lazyLoad
|
||||
} = this.props;
|
||||
|
||||
let controls = this.props.controls;
|
||||
@ -943,6 +948,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
onInit: this.handleFormInit,
|
||||
onAction: this.handleAction,
|
||||
ref: this.makeFormRef(index),
|
||||
lazyLoad,
|
||||
canAccessSuperData,
|
||||
value: undefined,
|
||||
formItemValue: undefined
|
||||
|
@ -1023,7 +1023,7 @@ export default class FileControl extends React.Component<FileProps, FileState> {
|
||||
|
||||
{failed ? (
|
||||
<div className={cx('FileControl-sum')}>
|
||||
已成功上传{uploaded}个文件,{failed}个文件上传上传失败,
|
||||
已成功上传{uploaded}个文件,{failed}个文件上传失败,
|
||||
<a onClick={this.retry}>重新上传</a>失败文件
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -86,8 +86,8 @@ export default class MatrixCheckbox extends React.Component<
|
||||
rows: nextProps.rows || []
|
||||
});
|
||||
} else if (
|
||||
nextProps.source !== props.source ||
|
||||
props.data !== nextProps.data
|
||||
nextProps.formInited &&
|
||||
(nextProps.source !== props.source || props.data !== nextProps.data)
|
||||
) {
|
||||
let prevApi = buildApi(props.source as string, props.data as object, {
|
||||
ignoreData: true
|
||||
|
@ -3,7 +3,7 @@
|
||||
* List、ButtonGroup 等等
|
||||
*/
|
||||
import {Api, Schema} from '../../types';
|
||||
import {isEffectiveApi, isApiOutdated} from '../../utils/api';
|
||||
import {isEffectiveApi, isApiOutdated, isValidApi} from '../../utils/api';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
import {
|
||||
anyChanged,
|
||||
@ -158,15 +158,6 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
|
||||
let loadOptions: boolean = initFetch !== false;
|
||||
|
||||
if (isPureVariable(source as string) && formItem) {
|
||||
formItem.setOptions(
|
||||
normalizeOptions(
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || []
|
||||
)
|
||||
);
|
||||
loadOptions = false;
|
||||
}
|
||||
|
||||
if (formItem && joinValues === false && defaultValue) {
|
||||
const selectedOptions = extractValue
|
||||
? formItem
|
||||
@ -210,7 +201,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
const props = this.props;
|
||||
const formItem = props.formItem as IFormItemStore;
|
||||
|
||||
if (!formItem) {
|
||||
if (!formItem || !props.formInited) {
|
||||
return;
|
||||
} else if (!prevProps.formItem) {
|
||||
// todo 优化 name 变化情况。
|
||||
@ -242,6 +233,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
prevOptions !== options &&
|
||||
formItem.setOptions(normalizeOptions(options || []));
|
||||
} else if (
|
||||
isEffectiveApi(props.source, props.data) &&
|
||||
isApiOutdated(
|
||||
prevProps.source,
|
||||
props.source,
|
||||
@ -440,7 +432,14 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
reload() {
|
||||
const {source, formItem, data, onChange} = this.props;
|
||||
|
||||
if (!formItem || !isEffectiveApi(source, data)) {
|
||||
if (formItem && isPureVariable(source as string)) {
|
||||
formItem.setOptions(
|
||||
normalizeOptions(
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || []
|
||||
)
|
||||
);
|
||||
return;
|
||||
} else if (!formItem || !isEffectiveApi(source, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,8 @@ import qs = require('qs');
|
||||
import {dataMapping} from '../../utils/tpl-builtin';
|
||||
import {isApiOutdated, isEffectiveApi} from '../../utils/api';
|
||||
import Spinner from '../../components/Spinner';
|
||||
import {LazyComponent} from '../../components';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
export type FormGroup = FormSchema & {
|
||||
title?: string;
|
||||
className?: string;
|
||||
@ -88,6 +90,7 @@ export interface FormProps extends RendererProps, FormSchema {
|
||||
persistData: boolean; // 开启本地缓存
|
||||
clearPersistDataAfterSubmit: boolean; // 提交成功后清空本地缓存
|
||||
trimValues?: boolean;
|
||||
lazyLoad?: boolean;
|
||||
onInit?: (values: object, props: any) => any;
|
||||
onReset?: (values: object) => void;
|
||||
onSubmit?: (values: object, action: any) => any;
|
||||
@ -155,7 +158,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
'onFinished',
|
||||
'canAccessSuperData',
|
||||
'lazyChange',
|
||||
'formLazyChange'
|
||||
'formLazyChange',
|
||||
'lazyLoad'
|
||||
];
|
||||
|
||||
hooks: {
|
||||
@ -317,7 +321,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
store.parentStore.storeType === 'ComboStore'
|
||||
) {
|
||||
const combo = store.parentStore as IComboStore;
|
||||
combo.removeForm(store);
|
||||
isAlive(combo) && combo.removeForm(store);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1058,7 +1062,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
actionsClassName,
|
||||
bodyClassName,
|
||||
classnames: cx,
|
||||
affixFooter
|
||||
affixFooter,
|
||||
lazyLoad
|
||||
} = this.props;
|
||||
|
||||
let body: JSX.Element = this.renderBody();
|
||||
@ -1086,6 +1091,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
) as JSX.Element;
|
||||
}
|
||||
|
||||
if (lazyLoad) {
|
||||
body = <LazyComponent>{body}</LazyComponent>;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {types, SnapshotIn} from 'mobx-state-tree';
|
||||
import {types, SnapshotIn, isAlive} from 'mobx-state-tree';
|
||||
import {iRendererStore} from './iRenderer';
|
||||
import {FormItemStore, IFormItemStore} from './formItem';
|
||||
import {FormStore, IFormStore} from './form';
|
||||
@ -93,7 +93,9 @@ export const ComboStore = iRendererStore
|
||||
}
|
||||
|
||||
function removeForm(form: IFormStore) {
|
||||
self.forms.remove(form);
|
||||
// form 可能再它自己销毁的是已经被移除了。因为调用的是 destroy,所以 self.forms 里面也被一起移除。
|
||||
// 再来尝试移除,会报错。
|
||||
self.forms.includes(form) && self.forms.remove(form);
|
||||
}
|
||||
|
||||
function setActiveKey(key: number) {
|
||||
|
@ -110,6 +110,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
options?: fetchOptions & {
|
||||
forceReload?: boolean;
|
||||
loadDataOnce?: boolean; // 配置数据是否一次性加载,如果是这样,由前端来完成分页,排序等功能。
|
||||
loadDataOnceFetchOnFilter?: boolean; // 在开启loadDataOnce时,filter时是否去重新请求api
|
||||
source?: string; // 支持自定义属于映射,默认不配置,读取 rows 或者 items
|
||||
loadDataMode?: boolean;
|
||||
syncResponse2Query?: boolean;
|
||||
@ -120,6 +121,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
options: fetchOptions & {
|
||||
forceReload?: boolean;
|
||||
loadDataOnce?: boolean; // 配置数据是否一次性加载,如果是这样,由前端来完成分页,排序等功能。
|
||||
loadDataOnceFetchOnFilter?: boolean; // 在开启loadDataOnce时,filter时是否去重新请求api
|
||||
source?: string; // 支持自定义属于映射,默认不配置,读取 rows 或者 items
|
||||
loadDataMode?: boolean;
|
||||
syncResponse2Query?: boolean;
|
||||
@ -127,7 +129,8 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
options.forceReload === false &&
|
||||
(options.forceReload === false ||
|
||||
options.loadDataOnceFetchOnFilter === false) &&
|
||||
options.loadDataOnce &&
|
||||
self.total
|
||||
) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {types, getRoot, Instance, destroy} from 'mobx-state-tree';
|
||||
import {types, getRoot, Instance, destroy, isAlive} from 'mobx-state-tree';
|
||||
import {extendObject, createObject} from '../utils/helper';
|
||||
import {IRendererStore} from './index';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
@ -26,7 +26,8 @@ export const iRendererStore = types
|
||||
return {
|
||||
// todo 不能自己引用自己
|
||||
get parentStore(): any {
|
||||
return self.parentId &&
|
||||
return isAlive(self) &&
|
||||
self.parentId &&
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).storeType === 'RendererStore'
|
||||
? (getRoot(self) as IRendererStore).stores.get(self.parentId)
|
||||
|
@ -80,6 +80,10 @@ export function syncDataFromSuper(
|
||||
|
||||
if (superObject || prevSuperObject) {
|
||||
keys.forEach(key => {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
((superObject && typeof superObject[key] !== 'undefined') ||
|
||||
(prevSuperObject && typeof prevSuperObject[key] !== 'undefined')) &&
|
||||
|
@ -351,11 +351,13 @@ export const resolveVariable = (path: string, data: any = {}): any => {
|
||||
}, data);
|
||||
};
|
||||
|
||||
export const isPureVariable = (path: string) =>
|
||||
/^\$(?:([a-z0-9_.]+)|{[^}{]+})$/.test(path);
|
||||
export const isPureVariable = (path?: any) =>
|
||||
typeof path === 'string'
|
||||
? /^\$(?:([a-z0-9_.]+)|{[^}{]+})$/.test(path)
|
||||
: false;
|
||||
|
||||
export const resolveVariableAndFilter = (
|
||||
path: string,
|
||||
path?: string,
|
||||
data: object = {},
|
||||
defaultFilter: string = '| html'
|
||||
): any => {
|
||||
|
Loading…
Reference in New Issue
Block a user