Merge pull request #104 from catchonme/master

统一API验证方式
audio 支持src取变量值
This commit is contained in:
catchme 2019-07-11 20:10:54 +08:00 committed by GitHub
commit b167e86ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 357 additions and 310 deletions

View File

@ -63,14 +63,17 @@ button-group 有两种模式,除了能让按钮组合在一起,还能做类
- `options` 选项配置,类型为数组,成员格式如下。
- `label` 文字
- `value`
- `image` 图片的 http 地址。
- `image` 图片的 `http` 地址。
- `source` Api 地址,如果选项不固定,可以通过配置 `source` 动态拉取。
- `multiple` 默认为 `false`, 设置成 `true` 表示可多选。
- `joinValues` 默认为 `true`
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
- `delimiter` 默认为 `,`
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 `value` 的值封装为数组,作为当前表单项的值。
- `clearable` 默认为 `true`, 表示可以取消选中。
- `size` 按钮大小,从小到大依次为`xs, sm, md, lg`
- `disabled` 是否禁用`options` 中选项
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="250" scope="form"

View File

@ -7,7 +7,11 @@
- `label` 文字
- `value`
- `source` Api 地址,如果选项不固定,可以通过配置 `source` 动态拉取。另外也可以用 `$xxxx` 来获取当前作用域中的变量。
更多配置请参考 [FormItem](./FormItem.md)。
- `joinValues` 默认为 `true`
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 `value` 的值封装为数组,作为当前表单项的值。
- `delimiter` 默认为 `,`
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="300" scope="form-item"

View File

@ -21,8 +21,12 @@
- `addButtonText` 新增按钮文字,默认为 `"新增"`
- `minLength` 限制最小长度。
- `maxLength` 限制最大长度。
- `scaffold`初始值。默认为 `{}`
- `scaffold`组表单项初始值。默认为 `{}`
- `canAccessSuperData` 指定是否可以自动获取上层的数据并映射到表单项上,默认是`false`。
- `conditions` 数组的形式包含所有条件的渲染类型,单个数组内的`test` 为判断条件,数组内的`controls`为符合该条件后渲染的`schema`
- `typeSwitchable` 是否可切换条件,配合`conditions`使用
- `formClassName` 单组表单项的类名
- `noBorder` 单组表单项是否有边框
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="450" scope="form"

View File

@ -7,14 +7,12 @@
- `inputFormat` 默认 `YYYY-MM-DD HH:mm:ss` 用来配置显示的时间格式。
- `placeholder` 默认 `请选择日期`
- `timeConstraints` 请参考: [react-datetime](https://github.com/YouCanBookMe/react-datetime)
- `value` 这里面 value 需要特殊说明一下,因为支持相对值。如:
- `value` 这里面 `value` 需要特殊说明一下,因为支持相对值。如:
- `-2mins` 2 分钟前
- `+2days` 2 天后
- `-10week` 十周前
- `minDate` 限制最小日期,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
- `maxDate` 限制最大日期,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
- `minTime` 限制最小时间,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
- `maxTime` 限制最大时间,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
可用单位: `min`、`hour`、`day`、`week`、`month`、`year`。所有单位支持复数形式。

View File

@ -13,7 +13,7 @@
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
- `delimiter` 默认为 `,`
- `clearable` 默认为 `true`, 表示可以取消选中
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 value 的值封装为数组,作为当前表单项的值
- 更多配置请参考 [FormItem](./FormItem.md)
单选

View File

@ -7,6 +7,8 @@
- `rows` 行信息, 数组中 `label` 字段是必须给出的
- `rowLabel` 行标题说明
- `source` Api 地址,如果选项不固定,可以通过配置 `source` 动态拉取。
- `multiple` 多选,默认为 `true`
- `singleSelectMode` 设置单选模式,`multiple`为`false`时有效,可设置为`cell`, `row`, `column` 分别为全部选项中只能单选某个单元格、每行只能单选某个单元格,每列只能单选某个单元格
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="250" scope="form-item"

View File

@ -12,11 +12,11 @@
- `joinValues` 默认为 `true`
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 value 的值封装为数组,作为当前表单项的值。
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 `value` 的值封装为数组,作为当前表单项的值。
- `delimiter` 默认为 `,`
- `clearable` 默认为 `false`, 当设置为 `true` 时,已选中的选项右侧会有个小 `X` 用来取消设置。
- `searchable` 默认为 `false`,表示可以通过输入部分内容检索出选项。
- `checkall` 默认为 `false` 开启后支持全选
- `checkAll` 默认为 `false` 开启后支持全选
- `checkAllLabel` 默认为 `全选`, 全选的文字
- `defaultCheckAll` 是否默认全选,默认为`false`
- 更多配置请参考 [FormItem](./FormItem.md)

View File

@ -17,6 +17,11 @@
- `source` 通过 `options` 只能配置静态数据,如果设置了 `source` 则会从接口拉取,实现动态效果。
- `autoComplete``source` 不同的是,每次用户输入都会去接口获取提示。
- `multiple` 默认为 `false`, 设置成 `true` 表示可多选。
- `joinValues` 默认为 `true`
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
- `delimiter` 默认为 `,`
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 value 的值封装为数组,作为当前表单项的值。
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="200" scope="form-item"

View File

@ -5,17 +5,13 @@
- `type` 请设置成 `time`
- `format` 默认 `X` 即时间戳格式,用来提交的时间格式。更多格式类型请参考 [moment](http://momentjs.com/).
- `inputFormat` 默认 `HH:mm` 用来配置显示的时间格式。
- `timeFormat` 默认 `HH:mm` 用来配置选择的时间格式。
- `placeholder` 默认 `请选择日期`
- `timeConstraints` 请参考: [react-datetime](https://github.com/YouCanBookMe/react-datetime)
- `value` 这里面 value 需要特殊说明一下,因为支持相对值。如:
- `-2mins` 2 分钟前
- `+2days` 2 天后
- `-10week` 十周前
- `minTime` 限制最小时间,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
- `maxTime` 限制最大时间,可用 `${xxx}` 取值,或者输入相对时间,或者时间戳。如:`${start}`、`+3days`、`+3days+2hours`或者 `${start|default:-2days}+3days`
可用单位: `min`、`hour`、`day`、`week`、`month`、`year`。所有单位支持复数形式。
- 更多配置请参考 [FormItem](./FormItem.md)
```schema:height="250" scope="form"

View File

@ -17,6 +17,8 @@
}
.#{$ns}Carousel {
min-width: $Carousel-minWidth;
height: $Carousel-height;
position: relative;
display: block;
background: $Carousel-bg;
@ -56,8 +58,8 @@
}
&-container {
min-width: $Carousel-minWidth;
height: $Carousel-height;
width: 100%;
height: 100%;
position: relative;
overflow: hidden;

View File

@ -4,6 +4,7 @@ import {Renderer, RendererProps} from '../factory';
import {autobind} from '../utils/helper';
import {volumeIcon, muteIcon, playIcon, pauseIcon} from '../components/icons';
import {resolveVariable} from '../utils/tpl-builtin';
import {filter} from '../utils/tpl';
export interface AudioProps extends RendererProps {
className?: string;
@ -49,7 +50,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
};
state: AudioState = {
src: this.props.value || this.props.src || resolveVariable(this.props.name, this.props.data) || '',
src: this.props.value || (this.props.src ? filter(this.props.src, this.props.data) : '') || resolveVariable(this.props.name, this.props.data) || '',
isReady: false,
muted: false,
playing: false,
@ -82,9 +83,12 @@ export class Audio extends React.Component<AudioProps, AudioState> {
componentWillReceiveProps(nextProps:AudioProps) {
const props = this.props;
if (props.value !== nextProps.value || props.src !== nextProps.src) {
if (
props.value !== nextProps.value ||
filter(props.src as string, props.data) !== filter(nextProps.src as string, nextProps.data)
) {
this.setState({
src: nextProps.value || nextProps.src,
src: nextProps.value || filter(nextProps.src as string, nextProps.data),
playing: false
}, () => {
this.audio.load();

View File

@ -10,7 +10,7 @@ import {
isObjectShallowModified,
noop,
isVisible,
getVariable,
getVariable
} from '../utils/helper';
import {observer} from 'mobx-react';
import partition = require('lodash/partition');
@ -22,7 +22,7 @@ import pick = require('lodash/pick');
import qs from 'qs';
import {findDOMNode} from 'react-dom';
import {evalExpression, filter} from '../utils/tpl';
import {isValidApi, buildApi} from '../utils/api';
import {isValidApi, buildApi, isEffectiveApi} from '../utils/api';
import omit = require('lodash/omit');
import find = require('lodash/find');
import Html from '../components/Html';
@ -278,23 +278,24 @@ export default class CRUD extends React.Component<CRUDProps, any> {
// 由于 ajax 一段时间后再弹出,肯定被浏览器给阻止掉的,所以提前弹。
action.redirect && action.blank && env.jumpTo(filter(action.redirect, data), action);
return store
.saveRemote(action.api as string, data, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveFailed),
})
.then(async (payload: object) => {
const data = createObject(ctx, payload);
return isEffectiveApi(action.api, data) &&
store
.saveRemote(action.api, data, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveFailed),
})
.then(async (payload: object) => {
const data = createObject(ctx, payload);
if (action.feedback && isVisible(action.feedback, data)) {
await this.openFeedback(action.feedback, data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
if (action.feedback && isVisible(action.feedback, data)) {
await this.openFeedback(action.feedback, data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
action.redirect && !action.blank && env.jumpTo(filter(action.redirect, data), action);
action.reload ? this.reloadTarget(action.reload, data) : this.search(undefined, undefined, true);
})
.catch(() => {});
action.redirect && !action.blank && env.jumpTo(filter(action.redirect, data), action);
action.reload ? this.reloadTarget(action.reload, data) : this.search(undefined, undefined, true);
})
.catch(() => {});
} else if (pickerMode && (action.actionType === 'confirm' || action.actionType === 'submit')) {
return Promise.resolve({
items: store.selectedItems.concat(),
@ -334,22 +335,23 @@ export default class CRUD extends React.Component<CRUDProps, any> {
ctx
);
} else if (action.actionType === 'ajax') {
store
.saveRemote(action.api as string, ctx, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveFailed),
})
.then(async () => {
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
isEffectiveApi(action.api, ctx) &&
store
.saveRemote(action.api as string, ctx, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveFailed),
})
.then(async () => {
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
action.reload
? this.reloadTarget(action.reload, store.data)
: this.search({[pageField || 'page']: 1}, undefined, true);
})
.catch(() => null);
action.reload
? this.reloadTarget(action.reload, store.data)
: this.search({[pageField || 'page']: 1}, undefined, true);
})
.catch(() => null);
} else if (onAction) {
onAction(e, action, ctx);
}
@ -562,8 +564,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
);
this.lastQuery = store.query;
const data = createObject(store.data, store.query);
api &&
(!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, data)) &&
isEffectiveApi(api, data) &&
store
.fetchInitData(api, data, {
successMessage: messages && messages.fetchSuccess,
@ -652,18 +653,19 @@ export default class CRUD extends React.Component<CRUDProps, any> {
data.unModifiedItems = unModifiedItems;
}
store
.saveRemote(quickSaveApi, data, {
successMessage: messages && messages.saveFailed,
errorMessage: messages && messages.saveSuccess,
})
.then(() => {
if ((quickSaveApi as ApiObject).reload) {
this.reloadTarget((quickSaveApi as ApiObject).reload as string, data);
}
this.search();
})
.catch(() => {});
isEffectiveApi(quickSaveApi, store.data) &&
store
.saveRemote(quickSaveApi, data, {
successMessage: messages && messages.saveFailed,
errorMessage: messages && messages.saveSuccess,
})
.then(() => {
if ((quickSaveApi as ApiObject).reload) {
this.reloadTarget((quickSaveApi as ApiObject).reload as string, data);
}
this.search();
})
.catch(() => {});
} else {
if (!quickSaveItemApi) {
env && env.alert('CRUD quickSaveItemApi is required!');
@ -675,15 +677,16 @@ export default class CRUD extends React.Component<CRUDProps, any> {
modified: diff,
});
store
.saveRemote(quickSaveItemApi, createObject(data, rows))
.then(() => {
if ((quickSaveItemApi as ApiObject).reload) {
this.reloadTarget((quickSaveItemApi as ApiObject).reload as string, data);
}
this.search();
})
.catch(() => {});
isEffectiveApi(quickSaveItemApi, store.data) &&
store
.saveRemote(quickSaveItemApi, createObject(data, rows))
.then(() => {
if ((quickSaveItemApi as ApiObject).reload) {
this.reloadTarget((quickSaveItemApi as ApiObject).reload as string, data);
}
this.search();
})
.catch(() => {});
}
}
@ -761,16 +764,17 @@ export default class CRUD extends React.Component<CRUDProps, any> {
hasIdField && (model.ids = rows.map((item: any) => item[primaryField as string]).join(','));
hasIdField && orderField && (model.order = rows.map(item => pick(item, [primaryField as string, orderField])));
store
.saveRemote(saveOrderApi, model)
.then(() => {
if ((saveOrderApi as ApiObject).reload) {
this.reloadTarget((saveOrderApi as ApiObject).reload as string, model);
}
isEffectiveApi(saveOrderApi, store.data) &&
store
.saveRemote(saveOrderApi, model)
.then(() => {
if ((saveOrderApi as ApiObject).reload) {
this.reloadTarget((saveOrderApi as ApiObject).reload as string, model);
}
this.search();
})
.catch(() => {});
this.search();
})
.catch(() => {});
}
handleSelect(items: Array<any>, unSelectedItems: Array<any>) {
@ -970,7 +974,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
`bulk-action/${index}`,
{
size: 'sm',
...omit(btn, ['visibileOn', 'hiddenOn', 'disabledOn']),
...omit(btn, ['visibleOn', 'hiddenOn', 'disabledOn']),
type: 'button',
},
{
@ -991,7 +995,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
`bulk-action/${index}`,
{
size: 'sm',
...omit(btn, ['visibileOn', 'hiddenOn', 'disabledOn']),
...omit(btn, ['visibleOn', 'hiddenOn', 'disabledOn']),
type: 'button',
},
{

View File

@ -262,7 +262,6 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
className={cx('Carousel-container')}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={carouselStyles}
>
{options.map((option:any, key:number) => (
<Transition
@ -294,7 +293,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
}
return (
<div className={cx(`Carousel Carousel--${controlsTheme}`, className)}>
<div className={cx(`Carousel Carousel--${controlsTheme}`, className)} style={carouselStyles}>
{body ? body : placeholder}
</div>
);

View File

@ -8,7 +8,7 @@ import cx from 'classnames';
import LazyComponent from '../components/LazyComponent';
import {resizeSensor} from '../utils/resize-sensor';
import {resolveVariableAndFilter} from '../utils/tpl-builtin';
import {isApiOutdated} from '../utils/api';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {ScopedContext, IScopedContext} from '../Scoped';
export interface ChartProps extends RendererProps {
@ -128,7 +128,7 @@ export class Chart extends React.Component<ChartProps> {
return;
}
if (api && (api as ApiObject).sendOn && !evalExpression((api as ApiObject).sendOn as string, store.data)) {
if (!isEffectiveApi(api, store.data)) {
return;
}

View File

@ -5,9 +5,11 @@ import {
OptionsControlProps,
Option} from './Options';
import Select from '../../components/Select';
import {Api} from '../../types';
import {isEffectiveApi} from '../../utils/api';
export interface ChainedSelectProps extends OptionsControlProps {
autoComplete?: string;
autoComplete?: Api;
searchable?: boolean;
};
@ -83,7 +85,7 @@ export default class ChainedSelectControl extends React.Component<ChainedSelectP
idx++;
}
if (!arr[idx] || !env || !source) {
if (!arr[idx] || !env || !source || !isEffectiveApi(source, data)) {
return;
}

View File

@ -6,11 +6,8 @@ import {
import cx from 'classnames';
import ColorPicker from '../../components/ColorPicker';
export interface ColorProps extends FormControlProps {
placeholder?: string;
inputFormat?: string;
timeFormat?: string;
format?: string;
timeConstrainst?: object;
closeOnSelect?:boolean;

View File

@ -21,6 +21,7 @@ import { evalExpression, filter } from '../../utils/tpl';
import find = require('lodash/find');
import Select from '../../components/Select';
import { dataMapping } from '../../utils/tpl-builtin';
import { isEffectiveApi } from '../../utils/api';
export interface Condition {
test: string;
@ -245,6 +246,10 @@ export default class ComboControl extends React.Component<ComboProps> {
return;
}
if (!isEffectiveApi(deleteApi, ctx)) {
return;
}
const result = await env.fetcher(deleteApi, ctx);
if (!result.ok) {

View File

@ -115,9 +115,9 @@ export class DatetimeControlRenderer extends DateControl {
static defaultProps = {
...DateControl.defaultProps,
placeholder: '请选择日期以及时间',
inputFormat: 'YYYY-MM-DD HH:mm',
inputFormat: 'YYYY-MM-DD HH:mm:ss',
dateFormat: 'LL',
timeFormat: 'HH:mm',
timeFormat: 'HH:mm:ss',
closeOnSelect: false,
strictMode: false
};

View File

@ -630,7 +630,7 @@ export default class FileControl extends React.Component<FileProps, FileState> {
<div className="clear">
{multiple && (!maxLength || files.length < maxLength) || !multiple && !files.length ? (
<label className={cx("btn m-r-xs", btnClassName, {disabled})}>
<input type="file" accept={accept} multiple={multiple} className="invisible" onChange={this.handleDrop} />
<input type="file" accept={accept} disabled={disabled} multiple={multiple} className="invisible" onChange={this.handleDrop} />
{btnLabel}
</label>
) : null}

View File

@ -6,7 +6,7 @@
import React from 'react';
import cx from 'classnames';
import { FormControlProps, FormItem } from './Item';
import { buildApi, isValidApi } from '../../utils/api';
import { buildApi, isValidApi, isEffectiveApi } from '../../utils/api';
import { Checkbox } from '../../components';
export interface Column {
@ -104,7 +104,7 @@ export default class MatrixCheckbox extends React.Component<MatrixProps, MatrixS
onChange
} = this.props;
if (!source || this.state.loading) {
if (!isEffectiveApi(source, data) || this.state.loading) {
return;
}

View File

@ -1,5 +1,5 @@
import {Api} from '../../types';
import {buildApi, isValidApi} from '../../utils/api';
import {buildApi, isEffectiveApi, isValidApi} from '../../utils/api';
import {
anyChanged
} from '../../utils/helper';
@ -183,7 +183,7 @@ export function registerOptionsControl(config: OptionsConfig) {
let prevApi = buildApi(props.source, props.data as object, {ignoreData: true});
let nextApi = buildApi(nextProps.source, nextProps.data as object, {ignoreData: true});
if (prevApi.url !== nextApi.url && isValidApi(nextApi.url) && (!nextApi.sendOn || evalExpression(nextApi.sendOn, nextProps.data))) {
if (prevApi.url !== nextApi.url && isEffectiveApi(nextApi, nextProps.data)) {
formItem.loadOptions(nextProps.source, nextProps.data, undefined, true, nextProps.onChange);
}
}
@ -336,7 +336,7 @@ export function registerOptionsControl(config: OptionsConfig) {
onChange
} = this.props;
if (config.autoLoadOptionsFromSource === false || !formItem || !source || source.sendOn && !evalExpression(source.sendOn, data)) {
if (config.autoLoadOptionsFromSource === false || !formItem || !isEffectiveApi(source, data)) {
return;
}

View File

@ -8,9 +8,11 @@ import {
import Select from '../../components/Select';
import find = require('lodash/find');
import debouce = require('lodash/debounce');
import {Api} from '../../types';
import {isEffectiveApi} from '../../utils/api';
export interface SelectProps extends OptionsControlProps {
autoComplete?: string;
autoComplete?: Api;
searchable?: boolean;
};
@ -110,23 +112,24 @@ export default class SelectControl extends React.Component<SelectProps, any> {
setLoading(true);
return env
.fetcher(autoComplete as string, {
...data,
term: input,
value: input
})
.then(ret => {
let options = ret.data && (ret.data as any).options || ret.data || [];
this.cache[input] = options;
let combinedOptions = this.mergeOptions(options);
setOptions(combinedOptions);
return autoComplete && isEffectiveApi(autoComplete, data) &&
env
.fetcher(autoComplete, {
...data,
term: input,
value: input
})
.then(ret => {
let options = ret.data && (ret.data as any).options || ret.data || [];
this.cache[input] = options;
let combinedOptions = this.mergeOptions(options);
setOptions(combinedOptions);
return Promise.resolve({
options: combinedOptions,
});
})
.finally(() => setLoading(false));
return Promise.resolve({
options: combinedOptions,
});
})
.finally(() => setLoading(false));
}
mergeOptions(options: Array<object>) {
@ -188,7 +191,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
value={selectedOptions}
options={options}
onNewOptionClick={this.handleNewOptionClick}
loadOptions={autoComplete ? this.loadRemote : null}
loadOptions={isEffectiveApi(autoComplete) ? this.loadRemote : null}
creatable={creatable}
searchable={autoComplete || creatable ? true : searchable}
onChange={this.changeValue}

View File

@ -7,6 +7,7 @@ import cx from 'classnames';
import Button from '../../components/Button';
import {createObject, isObjectShallowModified} from '../../utils/helper';
import { RendererData, Action, Api, Payload } from '../../types';
import { isEffectiveApi } from '../../utils/api';
import { filter } from '../../utils/tpl';
import omit = require('lodash/omit');
import { dataMapping } from '../../utils/tpl-builtin';
@ -235,13 +236,15 @@ export default class FormTable extends React.Component<TableProps, TableState> {
const isNew = !isObjectShallowModified(scaffold, origin, false);
let remote:Payload | null = null;
if (isNew && addApi) {
if (isNew && addApi && isEffectiveApi(addApi, createObject(data, item))) {
remote = await env.fetcher(addApi, createObject(data, item));
} else if (updateApi) {
} else if (updateApi && isEffectiveApi(updateApi, createObject(data, item))) {
remote = await env.fetcher(updateApi, createObject(data, item));
}
if (remote && !remote.ok) {
if (remote === null) {
return;
} else if (remote && !remote.ok) {
env.notify('error', remote.msg || '保存失败');
return;
} else if (remote && remote.ok) {
@ -299,6 +302,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
if (!confirmed) { // 如果不确认,则跳过!
return;
}
if (!isEffectiveApi(deleteApi, ctx)) {
return;
}
const result = await env.fetcher(deleteApi, ctx);

View File

@ -13,6 +13,7 @@ import { filter } from '../../utils/tpl';
import find = require('lodash/find');
import { closeIcon, enterIcon } from '../../components/icons';
import { autobind } from '../../utils/helper';
import {isEffectiveApi} from '../../utils/api';
// declare function matchSorter(items:Array<any>, input:any, options:any): Array<any>;
@ -96,8 +97,7 @@ export default class TextControl extends React.PureComponent<TextProps, TextStat
formInited
} = this.props;
if (autoComplete && formItem) {
if (isEffectiveApi(autoComplete, data) && formItem) {
if (formInited) {
formItem.loadOptions(autoComplete, {
...data,
@ -339,7 +339,7 @@ export default class TextControl extends React.PureComponent<TextProps, TextStat
data
} = this.props;
if (autoComplete && formItem) {
if (isEffectiveApi(autoComplete, data) && formItem) {
formItem.loadOptions(autoComplete, {
...data,
term: this.state.inputValue || formItem.lastSelectValue

View File

@ -13,10 +13,12 @@ import TreeSelector from '../../components/Tree';
import matchSorter from 'match-sorter';
import debouce = require('lodash/debounce');
import find = require('lodash/find');
import {Api} from '../../types';
import {isEffectiveApi} from '../../utils/api';
export interface TreeSelectProps extends OptionsControlProps {
placeholder?: any;
autoComplete?: string;
autoComplete?: Api;
};
export interface TreeSelectState {
@ -168,12 +170,13 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
handleInputChange(e:React.ChangeEvent<HTMLInputElement>) {
const {
autoComplete
autoComplete,
data
} = this.props;
this.setState({
inputValue: e.currentTarget.value
}, autoComplete ? () => this.loadRemote(this.state.inputValue) : undefined);
}, isEffectiveApi(autoComplete, data) ? () => this.loadRemote(this.state.inputValue) : undefined);
}
handleInputKeyDown(event:React.KeyboardEvent) {
@ -230,7 +233,7 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
setLoading,
} = this.props;
if (!autoComplete) {
if (!autoComplete || !isEffectiveApi(autoComplete, data)) {
return;
} else if (!env || !env.fetcher) {
throw new Error('fetcher is required');
@ -249,7 +252,7 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
setLoading(true);
return env
.fetcher(autoComplete as string, {
.fetcher(autoComplete, {
...data,
term: input,
value: input
@ -344,7 +347,7 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
autoComplete
} = this.props;
let filtedOptions = !autoComplete && searchable && this.state.inputValue ? this.filterOptions(options, this.state.inputValue) : options;
let filtedOptions = !isEffectiveApi(autoComplete) && searchable && this.state.inputValue ? this.filterOptions(options, this.state.inputValue) : options;
return (
@ -423,7 +426,7 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
'TreeSelect--inline': inline,
'TreeSelect--single': !multiple,
'TreeSelect--multi': multiple,
'TreeSelect--searchable': searchable || autoComplete,
'TreeSelect--searchable': searchable || isEffectiveApi(autoComplete),
'is-opened': this.state.isOpened,
'is-focused': this.state.isFocused,
'is-disabled': disabled
@ -433,7 +436,7 @@ export default class TreeSelectControl extends React.Component<TreeSelectProps,
<div className={cx('TreeSelect-valueWrap')}>
{this.renderValues()}
{searchable || autoComplete ? (
{searchable || isEffectiveApi(autoComplete) ? (
<input
onChange={this.handleInputChange}
value={this.state.inputValue}

View File

@ -34,7 +34,7 @@ import Scoped, { ScopedContext, IScopedContext } from '../../Scoped';
import { IComboStore } from '../../store/combo';
import qs = require('qs');
import { dataMapping } from '../../utils/tpl-builtin';
import { isApiOutdated } from '../../utils/api';
import { isApiOutdated, isEffectiveApi } from '../../utils/api';
export type FormGroup = FormSchema & {
title?: string;
className?: string;
@ -75,6 +75,7 @@ export interface FormProps extends RendererProps, FormSchema {
asyncApi?: Api; // 如果 api 处理时间过长,可以开启 asyncApi 来处理。轮询检测是否真的完成了。
finishedField?: string;
initFetch?: boolean; // 是否初始拉取?
initFetchOn?: string;
className?: string;
body?: SchemaNode;
wrapWithPanel?: boolean;
@ -206,6 +207,7 @@ export default class Form extends React.Component<FormProps, object> {
const {
initApi,
initFetch,
initFetchOn,
initAsyncApi,
initFinishedField,
store,
@ -244,19 +246,13 @@ export default class Form extends React.Component<FormProps, object> {
});
}
if (
initApi && initFetch !== false
&& (
!(initApi as ApiObject).sendOn
|| evalExpression((initApi as ApiObject).sendOn as string, store.data)
)
) {
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
store
.fetchInitData(initApi as any, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
onSuccess: () => {
if (!initAsyncApi || store.data[initFinishedField || 'finished']) {
if (!isEffectiveApi(initAsyncApi, store.data) || store.data[initFinishedField || 'finished']) {
return;
}
@ -276,7 +272,6 @@ export default class Form extends React.Component<FormProps, object> {
const props = this.props;
const store = props.store;
if (isApiOutdated(prevProps.initApi, props.initApi, prevProps.data, props.data)) {
const {
fetchSuccess,
@ -332,24 +327,26 @@ export default class Form extends React.Component<FormProps, object> {
}
} = this.props;
initAsyncApi && store.updateData({
[initFinishedField || 'finished']: false
});
isEffectiveApi(initAsyncApi, store.data) &&
store.updateData({
[initFinishedField || 'finished']: false
});
initApi && (!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data)) && store.fetchData(initApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
silent,
onSuccess: () => {
if (!initAsyncApi || store.data[initFinishedField || 'finished']) {
return;
isEffectiveApi(initApi, store.data) &&
store.fetchData(initApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
silent,
onSuccess: () => {
if (!isEffectiveApi(initAsyncApi, store.data) || store.data[initFinishedField || 'finished']) {
return;
}
return until(() => store.checkRemote(initAsyncApi, store.data)
, (ret:any) => ret && ret[initFinishedField || 'finished']
, (cancel) => this.asyncCancel = cancel);
}
return until(() => store.checkRemote(initAsyncApi, store.data)
, (ret:any) => ret && ret[initFinishedField || 'finished']
, (cancel) => this.asyncCancel = cancel);
}
}).then(this.initInterval);
}).then(this.initInterval);
}
receive(values:object) {
@ -522,34 +519,35 @@ export default class Form extends React.Component<FormProps, object> {
} else if (action.api || api) {
let finnalAsyncApi = action.asyncApi || asyncApi;
finnalAsyncApi && store.updateData({
isEffectiveApi(finnalAsyncApi, store.data) && store.updateData({
[finishedField || 'finished']: false
});
return store
.saveRemote(action.api || api as Api, values, {
successMessage: saveSuccess,
errorMessage: saveFailed,
onSuccess: () => {
if (!finnalAsyncApi || store.data[finishedField || 'finished']) {
return;
return isEffectiveApi(action.api || api as Api, store.data) &&
store
.saveRemote(action.api || api as Api, values, {
successMessage: saveSuccess,
errorMessage: saveFailed,
onSuccess: () => {
if (!isEffectiveApi(finnalAsyncApi, store.data) || store.data[finishedField || 'finished']) {
return;
}
return until(() => store.checkRemote(finnalAsyncApi as Api, store.data)
, (ret:any) => ret && ret[finishedField || 'finished']
, (cancel) => this.asyncCancel = cancel);
}
return until(() => store.checkRemote(finnalAsyncApi as Api, store.data)
, (ret:any) => ret && ret[finishedField || 'finished']
, (cancel) => this.asyncCancel = cancel);
}
})
.then(async (response) => {
onSaved && onSaved(values, response);
})
.then(async (response) => {
onSaved && onSaved(values, response);
// submit 也支持 feedback
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
// submit 也支持 feedback
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
return values;
});
return values;
});
}
return Promise.resolve(values);
@ -585,27 +583,28 @@ export default class Form extends React.Component<FormProps, object> {
} else if (action.actionType === 'drawer') {
store.openDrawer(data);
} else if (action.actionType === 'ajax') {
if (!action.api) {
if (!isEffectiveApi(action.api)) {
return env.alert(`当 actionType 为 ajax 时,请设置 api 属性`);
}
return store
.saveRemote(action.api as Api, data, {
successMessage: action.messages && action.messages.success || saveSuccess,
errorMessage: action.messages && action.messages.failed || saveFailed
})
.then(async (response) => {
response && onChange && onChange(store.data, difference(store.data, store.pristine));
store.validated && this.validate(true);
return isEffectiveApi(action.api, data) &&
store
.saveRemote(action.api as Api, data, {
successMessage: action.messages && action.messages.success || saveSuccess,
errorMessage: action.messages && action.messages.failed || saveFailed
})
.then(async (response) => {
response && onChange && onChange(store.data, difference(store.data, store.pristine));
store.validated && this.validate(true);
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
action.redirect && env.updateLocation(filter(action.redirect, store.data));
action.reload && this.reloadTarget(action.reload, store.data);
})
.catch(() => { });
action.redirect && env.updateLocation(filter(action.redirect, store.data));
action.reload && this.reloadTarget(action.reload, store.data);
})
.catch(() => { });
} else if (action.actionType === 'reload') {
action.target && this.reloadTarget(action.target, data);
} else if (onAction) {

View File

@ -7,8 +7,9 @@ import getExprProperties from '../utils/filter-schema';
import {filter, evalExpression} from '../utils/tpl';
import {createObject, mapTree, someTree} from '../utils/helper';
import {resolveVariable} from '../utils/tpl-builtin';
import {isApiOutdated} from '../utils/api';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {ScopedContext, IScopedContext} from '../Scoped';
import {Api} from '../types';
export interface Link {
className?: string;
@ -32,7 +33,7 @@ export interface NavigationProps extends RendererProps {
className?: string;
stacked?: boolean;
links?: Links;
source?: string;
source?: Api;
}
export default class Navigation extends React.Component<NavigationProps, NavigationState> {
@ -50,6 +51,7 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
links: this.syncLinks(
props,
(props.source &&
typeof props.source === 'string' &&
/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source) &&
resolveVariable(props.source, props.data)) ||
props.links
@ -68,14 +70,14 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
componentWillReceiveProps(nextProps: NavigationProps) {
const props = this.props;
if (nextProps.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(nextProps.source)) {
if (nextProps.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(nextProps.source as string)) {
if (nextProps.source !== props.source) {
this.setState({
links: this.syncLinks(nextProps),
});
} else {
const links = resolveVariable(nextProps.source, nextProps.data);
const prevLinks = resolveVariable(props.source, props.data);
const links = resolveVariable(nextProps.source as string, nextProps.data);
const prevLinks = resolveVariable(props.source as string, props.data);
if (links !== prevLinks) {
this.setState({
@ -97,7 +99,7 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
componentDidUpdate(prevProps: NavigationProps) {
const props = this.props;
if (props.source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source)) {
if (props.source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source as string)) {
isApiOutdated(prevProps.source, props.source, prevProps.data, props.data) && this.reload();
}
}
@ -114,7 +116,7 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
const {data, env, source} = this.props;
const finalData = values ? createObject(data, values) : data;
if (!source || (source.sendOn && !evalExpression(source.sendOn, data))) {
if (!source || !isEffectiveApi(source, data)) {
return;
}

View File

@ -10,7 +10,7 @@ import qs from 'qs';
import {isVisible, autobind, bulkBindFunctions} from '../utils/helper';
import {ScopedContext, IScopedContext} from '../Scoped';
import Alert from '../components/Alert2';
import {isApiOutdated} from '../utils/api';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
export interface PageProps extends RendererProps {
title?: string; // 标题
@ -107,15 +107,11 @@ export default class Page extends React.Component<PageProps> {
}
componentDidMount() {
const {initApi, initFetch, store, messages} = this.props;
const {initApi, initFetch, initFetchOn, store, messages} = this.props;
this.mounted = true;
if (
initApi &&
initFetch &&
(!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data))
) {
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
store
.fetchInitData(initApi, store.data, {
successMessage: messages && messages.fetchSuccess,
@ -153,7 +149,7 @@ export default class Page extends React.Component<PageProps> {
(props.initFetch !== false && isApiOutdated(prevProps.initApi, initApi, prevProps.data, props.data))
) {
const messages = props.messages;
(!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data)) &&
isEffectiveApi(initApi, store.data) &&
store
.fetchData(initApi as Api, store.data, {
successMessage: messages && messages.fetchSuccess,
@ -189,20 +185,21 @@ export default class Page extends React.Component<PageProps> {
} else if (action.actionType === 'drawer') {
store.openDrawer(ctx);
} else if (action.actionType === 'ajax') {
store
.saveRemote(action.api as string, ctx, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveSuccess),
})
.then(async () => {
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
isEffectiveApi(action.api, ctx) &&
store
.saveRemote(action.api as string, ctx, {
successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveSuccess),
})
.then(async () => {
if (action.feedback && isVisible(action.feedback, store.data)) {
await this.openFeedback(action.feedback, store.data);
}
action.redirect && env.jumpTo(filter(action.redirect, store.data), action);
action.reload && this.reloadTarget(action.reload, store.data);
})
.catch(() => {});
action.redirect && env.jumpTo(filter(action.redirect, store.data), action);
action.reload && this.reloadTarget(action.reload, store.data);
})
.catch(() => {});
} else if (action.actionType === 'copy' && (action.content || action.copy)) {
env.copy && env.copy(filter(action.content || action.copy, ctx));
}
@ -280,7 +277,7 @@ export default class Page extends React.Component<PageProps> {
const {store, initApi} = this.props;
clearTimeout(this.timer);
initApi &&
isEffectiveApi(initApi, store.data) &&
store
.fetchData(initApi, store.data, {
silent,

View File

@ -7,12 +7,13 @@ import {filter, evalExpression} from '../utils/tpl';
import cx from 'classnames';
import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import {observer} from 'mobx-react';
import {isApiOutdated} from '../utils/api';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
export interface ServiceProps extends RendererProps {
api?: Api;
schemaApi?: Api;
initFetch?: boolean;
initFetchOn?: string;
initFetchSchema?: boolean;
interval?: number;
silentPolling?: boolean;
@ -51,6 +52,7 @@ export default class Service extends React.Component<ServiceProps> {
initFetchSchema,
api,
initFetch,
initFetchOn,
store,
messages: {
fetchSuccess,
@ -60,22 +62,14 @@ export default class Service extends React.Component<ServiceProps> {
this.mounted = true;
if (
schemaApi &&
initFetchSchema !== false &&
(!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))
) {
if (isEffectiveApi(schemaApi, store.data, initFetchSchema)) {
store.fetchSchema(schemaApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed
}).then(this.initInterval);
}
if (
api &&
initFetch !== false &&
(!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))
) {
if (isEffectiveApi(api, store.data, initFetch, initFetchOn)) {
store.fetchInitData(api, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed
@ -94,7 +88,7 @@ export default class Service extends React.Component<ServiceProps> {
}
} = props;
isApiOutdated(prevProps.api, props.api, prevProps.data, props.data) &&
isApiOutdated(prevProps.api, props.api, prevProps.data, props.data)
store.fetchData(props.api as Api, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed
@ -102,9 +96,9 @@ export default class Service extends React.Component<ServiceProps> {
isApiOutdated(prevProps.schemaApi, props.schemaApi, prevProps.data, props.data) &&
store.fetchSchema(props.schemaApi as Api, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed
}).then(this.initInterval);
successMessage: fetchSuccess,
errorMessage: fetchFailed
}).then(this.initInterval);
}
componentWillUnmount() {
@ -127,29 +121,21 @@ export default class Service extends React.Component<ServiceProps> {
return this.receive(query);
}
const {schemaApi, fetchSchema, api, fetch, store, messages: {
const {schemaApi, initFetchSchema, api, initFetch, initFetchOn, store, messages: {
fetchSuccess,
fetchFailed
}} = this.props;
clearTimeout(this.timer);
if (
schemaApi &&
fetchSchema !== false &&
(!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))
) {
if (isEffectiveApi(schemaApi, store.data, initFetchSchema)) {
store.fetchSchema(schemaApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed
}).then(this.initInterval);
}
if (
api &&
fetch !== false &&
(!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))
) {
if (isEffectiveApi(api, store.data, initFetch, initFetchOn)) {
store
.fetchData(api, store.data, {
silent,

View File

@ -1,7 +1,7 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import {Renderer, RendererProps} from '../factory';
import {SchemaNode, Action, Schema, Api} from '../types';
import {SchemaNode, Action, Schema, Api, ApiObject} from '../types';
import forEach = require('lodash/forEach');
import {filter} from '../utils/tpl';
import cx from 'classnames';
@ -12,6 +12,7 @@ import {TableStore, ITableStore, IColumn, IRow} from '../store/table';
import {observer} from 'mobx-react';
import {anyChanged, getScrollParent, difference, noop} from '../utils/helper';
import {resolveVariable} from '../utils/tpl-builtin';
import {isEffectiveApi} from '../utils/api';
import debounce = require('lodash/debounce');
import xor = require('lodash/xor');
import QuickEdit from './QuickEdit';
@ -1828,13 +1829,14 @@ export class HeadCellFilterDropDown extends React.Component<HeadCellFilterProps,
}
fetchOptions() {
const {env, filterable} = this.props;
env.fetcher(filterable.source).then(ret => {
let options = (ret.data && ret.data.options) || [];
this.setState({
filterOptions: ret && ret.data && this.alterOptions(options),
const {env, filterable, data} = this.props;
isEffectiveApi(filterable.source, data) &&
env.fetcher(filterable.source).then(ret => {
let options = (ret.data && ret.data.options) || [];
this.setState({
filterOptions: ret && ret.data && this.alterOptions(options),
});
});
});
}
alterOptions(options: Array<any>) {

View File

@ -5,6 +5,7 @@ import cx from 'classnames';
import getExprProperties from '../utils/filter-schema';
import {Api, Payload} from '../types';
import update = require('react-addons-update');
import {isEffectiveApi} from '../utils/api';
export interface TaskProps extends RendererProps {
className?: string;
@ -107,11 +108,11 @@ export default class Task extends React.Component<TaskProps, TaskState> {
return;
}
if (interval && !checkApi) {
if (interval && !isEffectiveApi(checkApi)) {
return alert('checkApi 没有设置, 不能及时获取任务状态');
}
env &&
isEffectiveApi(checkApi, data) && env &&
env
.fetcher(checkApi, data)
.then(this.handleLoaded)
@ -135,9 +136,9 @@ export default class Task extends React.Component<TaskProps, TaskState> {
submitTask(item: TaskItem, index: number, retry = false) {
const {submitApi, reSubmitApi, loadingStatusCode, errorStatusCode, data, env} = this.props;
if (!retry && !submitApi) {
if (!retry && !isEffectiveApi(submitApi)) {
return alert('submitApi 没有配置');
} else if (retry && !reSubmitApi) {
} else if (retry && !isEffectiveApi(reSubmitApi)) {
return alert('reSubmitApi 没有配置');
}
@ -158,9 +159,10 @@ export default class Task extends React.Component<TaskProps, TaskState> {
} as any)
);
env &&
const api = retry ? reSubmitApi : submitApi;
isEffectiveApi(api, data) && env &&
env
.fetcher(retry ? reSubmitApi : submitApi, {
.fetcher(api, {
...data,
...item,
})

View File

@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import {Renderer, RendererProps} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service';
import {Api, SchemaNode, Schema, Action} from '../types';
@ -9,7 +8,7 @@ import {filter, evalExpression} from '../utils/tpl';
import cx = require('classnames');
import {observer} from 'mobx-react';
import {createObject, until, isVisible} from '../utils/helper';
import {buildApi, isValidApi, isApiOutdated} from '../utils/api';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {IFormStore} from '../store/form';
export type TabProps = Schema & {
@ -92,13 +91,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
onInit,
} = this.props;
if (initApi && initFetch !== false && (!initApi.sendOn || evalExpression(initApi.sendOn, data))) {
if (isEffectiveApi(initApi, store.data, initFetch)) {
store
.fetchInitData(initApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
onSuccess: () => {
if (!initAsyncApi || store.data[initFinishedField || 'finished']) {
if (!isEffectiveApi(initAsyncApi, store.data) || store.data[initFinishedField || 'finished']) {
return;
}
@ -202,7 +201,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
const step = steps[this.state.currentStep - 1];
let finnalAsyncApi = step && step.asyncApi || this.state.currentStep === steps.length && asyncApi;
if (!step || !finnalAsyncApi) {
if (!step || !isEffectiveApi(finnalAsyncApi, store.data)) {
return;
}
@ -306,16 +305,16 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
if (this.state.currentStep < steps.length) {
let finnalAsyncApi = action.asyncApi || step.asyncApi;
finnalAsyncApi &&
isEffectiveApi(finnalAsyncApi, store.data) &&
store.updateData({
[finishedField || 'finished']: false,
});
if (step.api || action.api) {
if (isEffectiveApi(step.api || action.api, store.data)) {
store
.saveRemote(action.api || step.api, store.data, {
onSuccess: () => {
if (!finnalAsyncApi || store.data[finishedField || 'finished']) {
if (!isEffectiveApi(finnalAsyncApi, store.data) || store.data[finishedField || 'finished']) {
return;
}
@ -340,7 +339,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
} else if (action.api || step.api || api) {
let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi;
finnalAsyncApi &&
isEffectiveApi(finnalAsyncApi, store.data) &&
store.updateData({
[finishedField || 'finished']: false,
});
@ -348,43 +347,44 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
const formStore = this.form ? (this.form.props.store as IFormStore) : store;
store.markSaving(true);
formStore
.saveRemote(action.api || step.api || api, store.data, {
onSuccess: () => {
if (!finnalAsyncApi || store.data[finishedField || 'finished']) {
return;
isEffectiveApi(action.api || step.api || api, store.data) &&
formStore
.saveRemote(action.api || step.api || api, store.data, {
onSuccess: () => {
if (!isEffectiveApi(finnalAsyncApi, store.data) || store.data[finishedField || 'finished']) {
return;
}
return until(
() => store.checkRemote(finnalAsyncApi as Api, store.data),
(ret: any) => ret && ret[finishedField || 'finished'],
cancel => (this.asyncCancel = cancel)
);
},
})
.then(value => {
store.updateData({
...store.data,
...value
});
store.markSaving(false);
if (onFinished && onFinished(value, action) === false) {
// 如果是 false 后面的操作就不执行
return value;
}
if (redirect) {
env.updateLocation(filter(redirect, store.data));
} else if (reload) {
this.reloadTarget(reload, store.data);
}
return until(
() => store.checkRemote(finnalAsyncApi as Api, store.data),
(ret: any) => ret && ret[finishedField || 'finished'],
cancel => (this.asyncCancel = cancel)
);
},
})
.then(value => {
store.updateData({
...store.data,
...value
});
store.markSaving(false);
if (onFinished && onFinished(value, action) === false) {
// 如果是 false 后面的操作就不执行
return value;
}
if (redirect) {
env.updateLocation(filter(redirect, store.data));
} else if (reload) {
this.reloadTarget(reload, store.data);
}
return value;
})
.catch(e => {
store.markSaving(false);
console.error(e);
});
})
.catch(e => {
store.markSaving(false);
console.error(e);
});
}
}

View File

@ -1,4 +1,3 @@
import { ApiObject } from './../types';
import {
types,
getParent,
@ -14,6 +13,7 @@ import {
} from './index';
import {
Api,
ApiObject,
Payload,
fetchOptions
} from '../types';

View File

@ -15,6 +15,7 @@ const rSchema = /(?:^|raw\:)(get|post|put|delete|patch):/i;
import qs from 'qs';
import { evalExpression } from './tpl';
import {
isObject,
isObjectShallowModified
} from './helper';
@ -156,6 +157,27 @@ export function isValidApi(api: string) {
return api && /^(?:https?:\/\/[^\/]+)?(\/[^\s\/\?]*){1,}(\?.*)?$/.test(api);
}
export function isEffectiveApi(api?: Api, data?: any, initFetch?: boolean, initFetchOn?: string) {
if (!api) {
return false;
}
if (initFetch === false) {
return false;
}
if (initFetchOn && data && !evalExpression(initFetchOn, data)) {
return false;
}
if (typeof api === 'string' && isValidApi(api)) {
return true;
} else if (isObject(api) && isValidApi((api as ApiObject).url)) {
if ((api as ApiObject).sendOn && data && !evalExpression((api as ApiObject).sendOn as string, data)) {
return false;
}
return true;
}
return false;
}
export function isSameApi(apiA: ApiObject | ApiCacheConfig, apiB: ApiObject | ApiCacheConfig): boolean {
return apiA.method === apiB.method && apiA.url === apiB.url && !isObjectShallowModified(apiA.data, apiB.data, false);
}