mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
Pick combo 修改相关 (#9878)
* fix: 修复 table2 树形数据展示对应错误问题 * fix: 修复 combo 同步父级数据可能存在展示值和实际值不一致的问题 Close: #8773 (#8831) * fix: 修复 combo 中有 pipeIn & pipeOut 场景时报错 Close: #8970 * fix: 修复 combo tabs 模式新成员中有必填字段未填写也能通过校验的问题 * fix: 修复 combo 可能无限触发 onChange 的问题 * fix: 修复 combo 同步父级数据可能存在展示值和实际值不一致的问题 Close: #8773 * fix: 修复 combo 中有 pipeIn & pipeOut 场景时报错 Close: #8970 (#8980) * fix: 修复页面设计器重复执行onChange的问题 * fix: 修复数据下发同步问题 (#9625) * fix: 修复 crud 重置失效的问题 Close: #9686 (#9693) * fix: crud2条件查询表单重置失效 (#9706) * chore: combo 中减少表单项重绘 * fix typecheck error --------- Co-authored-by: wutong25 <wutong25@baidu.com> Co-authored-by: walkin <wyc19966@hotmail.com>
This commit is contained in:
parent
b36586253b
commit
6d3cac5920
@ -64,7 +64,8 @@ export const RENDERER_TRANSMISSION_OMIT_PROPS = [
|
|||||||
'label',
|
'label',
|
||||||
'renderLabel',
|
'renderLabel',
|
||||||
'trackExpression',
|
'trackExpression',
|
||||||
'editorSetting'
|
'editorSetting',
|
||||||
|
'updatePristineAfterStoreDataReInit'
|
||||||
];
|
];
|
||||||
|
|
||||||
const componentCache: SimpleMap = new SimpleMap();
|
const componentCache: SimpleMap = new SimpleMap();
|
||||||
|
@ -207,7 +207,8 @@ export function HocStoreFactory(renderer: {
|
|||||||
...(store.hasRemoteData ? store.data : null), // todo 只保留 remote 数据
|
...(store.hasRemoteData ? store.data : null), // todo 只保留 remote 数据
|
||||||
...this.formatData(props.defaultData),
|
...this.formatData(props.defaultData),
|
||||||
...this.formatData(props.data)
|
...this.formatData(props.data)
|
||||||
})
|
}),
|
||||||
|
props.updatePristineAfterStoreDataReInit === false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@ -234,7 +235,8 @@ export function HocStoreFactory(renderer: {
|
|||||||
store,
|
store,
|
||||||
props.syncSuperStore === true
|
props.syncSuperStore === true
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
props.updatePristineAfterStoreDataReInit === false
|
||||||
);
|
);
|
||||||
} else if (props.data && (props.data as any).__super) {
|
} else if (props.data && (props.data as any).__super) {
|
||||||
store.initData(
|
store.initData(
|
||||||
@ -250,16 +252,20 @@ export function HocStoreFactory(renderer: {
|
|||||||
props.store?.storeType === 'ComboStore'
|
props.store?.storeType === 'ComboStore'
|
||||||
? undefined
|
? undefined
|
||||||
: syncDataFromSuper(
|
: syncDataFromSuper(
|
||||||
props.data,
|
{...store.data, ...props.data},
|
||||||
(props.data as any).__super,
|
(props.data as any).__super,
|
||||||
(prevProps.data as any).__super,
|
(prevProps.data as any).__super,
|
||||||
store,
|
store,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
props.updatePristineAfterStoreDataReInit === false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
store.initData(createObject(props.scope, props.data));
|
store.initData(
|
||||||
|
createObject(props.scope, props.data),
|
||||||
|
props.updatePristineAfterStoreDataReInit === false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
!props.trackExpression &&
|
!props.trackExpression &&
|
||||||
@ -282,8 +288,9 @@ export function HocStoreFactory(renderer: {
|
|||||||
...store.data
|
...store.data
|
||||||
}),
|
}),
|
||||||
|
|
||||||
store.storeType === 'FormStore' &&
|
props.updatePristineAfterStoreDataReInit === false ||
|
||||||
prevProps.store?.storeType === 'CRUDStore'
|
(store.storeType === 'FormStore' &&
|
||||||
|
prevProps.store?.storeType === 'CRUDStore')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// nextProps.data.__super !== props.data.__super) &&
|
// nextProps.data.__super !== props.data.__super) &&
|
||||||
@ -299,7 +306,8 @@ export function HocStoreFactory(renderer: {
|
|||||||
createObject(props.scope, {
|
createObject(props.scope, {
|
||||||
// ...nextProps.data,
|
// ...nextProps.data,
|
||||||
...store.data
|
...store.data
|
||||||
})
|
}),
|
||||||
|
props.updatePristineAfterStoreDataReInit === false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ import {isAlive} from 'mobx-state-tree';
|
|||||||
|
|
||||||
import type {LabelAlign} from './Item';
|
import type {LabelAlign} from './Item';
|
||||||
import {injectObjectChain} from '../utils';
|
import {injectObjectChain} from '../utils';
|
||||||
|
import {reaction} from 'mobx';
|
||||||
|
|
||||||
export interface FormHorizontal {
|
export interface FormHorizontal {
|
||||||
left?: number;
|
left?: number;
|
||||||
@ -371,6 +372,7 @@ export interface FormProps
|
|||||||
onFailed?: (reason: string, errors: any) => any;
|
onFailed?: (reason: string, errors: any) => any;
|
||||||
onFinished: (values: object, action: any) => any;
|
onFinished: (values: object, action: any) => any;
|
||||||
onValidate: (values: object, form: any) => any;
|
onValidate: (values: object, form: any) => any;
|
||||||
|
onValidChange?: (valid: boolean, props: any) => void; // 表单数据合法性变更
|
||||||
messages: {
|
messages: {
|
||||||
fetchSuccess?: string;
|
fetchSuccess?: string;
|
||||||
fetchFailed?: string;
|
fetchFailed?: string;
|
||||||
@ -443,6 +445,8 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
'onChange',
|
'onChange',
|
||||||
'onFailed',
|
'onFailed',
|
||||||
'onFinished',
|
'onFinished',
|
||||||
|
'onValidate',
|
||||||
|
'onValidChange',
|
||||||
'onSaved',
|
'onSaved',
|
||||||
'canAccessSuperData',
|
'canAccessSuperData',
|
||||||
'lazyChange',
|
'lazyChange',
|
||||||
@ -460,8 +464,7 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
[propName: string]: Array<() => Promise<any>>;
|
[propName: string]: Array<() => Promise<any>>;
|
||||||
} = {};
|
} = {};
|
||||||
asyncCancel: () => void;
|
asyncCancel: () => void;
|
||||||
disposeOnValidate: () => void;
|
toDispose: Array<() => void> = [];
|
||||||
disposeRulesValidate: () => void;
|
|
||||||
shouldLoadInitApi: boolean = false;
|
shouldLoadInitApi: boolean = false;
|
||||||
timer: ReturnType<typeof setTimeout>;
|
timer: ReturnType<typeof setTimeout>;
|
||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
@ -518,6 +521,18 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withStore 里面与上层数据会做同步
|
||||||
|
// 这个时候变更的数据没有同步 onChange 出去,出现数据不一致的问题。
|
||||||
|
// https://github.com/baidu/amis/issues/8773
|
||||||
|
this.toDispose.push(
|
||||||
|
reaction(
|
||||||
|
() => store.initedAt,
|
||||||
|
() => {
|
||||||
|
store.inited && this.emitChange(!!this.props.submitOnChange, true);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -531,6 +546,7 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
store,
|
store,
|
||||||
messages: {fetchSuccess, fetchFailed},
|
messages: {fetchSuccess, fetchFailed},
|
||||||
onValidate,
|
onValidate,
|
||||||
|
onValidChange,
|
||||||
promptPageLeave,
|
promptPageLeave,
|
||||||
env,
|
env,
|
||||||
rules
|
rules
|
||||||
@ -540,49 +556,63 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
|
|
||||||
if (onValidate) {
|
if (onValidate) {
|
||||||
const finalValidate = promisify(onValidate);
|
const finalValidate = promisify(onValidate);
|
||||||
this.disposeOnValidate = this.addHook(async () => {
|
this.toDispose.push(
|
||||||
const result = await finalValidate(store.data, store);
|
this.addHook(async () => {
|
||||||
|
const result = await finalValidate(store.data, store);
|
||||||
|
|
||||||
if (result && isObject(result)) {
|
if (result && isObject(result)) {
|
||||||
Object.keys(result).forEach(key => {
|
Object.keys(result).forEach(key => {
|
||||||
let msg = result[key];
|
let msg = result[key];
|
||||||
const items = store.getItemsByPath(key);
|
const items = store.getItemsByPath(key);
|
||||||
|
|
||||||
// 没有找到
|
// 没有找到
|
||||||
if (!Array.isArray(items) || !items.length) {
|
if (!Array.isArray(items) || !items.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在setError之前,提前把残留的error信息清除掉,否则每次onValidate后都会一直把报错 append 上去
|
// 在setError之前,提前把残留的error信息清除掉,否则每次onValidate后都会一直把报错 append 上去
|
||||||
items.forEach(item => item.clearError());
|
items.forEach(item => item.clearError());
|
||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
msg = Array.isArray(msg) ? msg : [msg];
|
msg = Array.isArray(msg) ? msg : [msg];
|
||||||
items.forEach(item => item.addError(msg));
|
items.forEach(item => item.addError(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
delete result[key];
|
delete result[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
isEmpty(result)
|
isEmpty(result)
|
||||||
? store.clearRestError()
|
? store.clearRestError()
|
||||||
: store.setRestError(Object.keys(result).map(key => result[key]));
|
: store.setRestError(Object.keys(result).map(key => result[key]));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单校验结果发生变化时,触发 onValidChange
|
||||||
|
if (onValidChange) {
|
||||||
|
this.toDispose.push(
|
||||||
|
reaction(
|
||||||
|
() => store.valid,
|
||||||
|
valid => onValidChange(valid, this.props)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(rules) && rules.length) {
|
if (Array.isArray(rules) && rules.length) {
|
||||||
this.disposeRulesValidate = this.addHook(() => {
|
this.toDispose.push(
|
||||||
if (!store.valid) {
|
this.addHook(() => {
|
||||||
return;
|
if (!store.valid) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rules.forEach(
|
rules.forEach(
|
||||||
item =>
|
item =>
|
||||||
!evalExpression(item.rule, store.data) &&
|
!evalExpression(item.rule, store.data) &&
|
||||||
store.addRestError(item.message, item.name)
|
store.addRestError(item.message, item.name)
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
|
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
|
||||||
@ -654,8 +684,8 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
// this.lazyHandleChange.flush();
|
// this.lazyHandleChange.flush();
|
||||||
this.lazyEmitChange.cancel();
|
this.lazyEmitChange.cancel();
|
||||||
this.asyncCancel && this.asyncCancel();
|
this.asyncCancel && this.asyncCancel();
|
||||||
this.disposeOnValidate && this.disposeOnValidate();
|
this.toDispose.forEach(fn => fn());
|
||||||
this.disposeRulesValidate && this.disposeRulesValidate();
|
this.toDispose = [];
|
||||||
window.removeEventListener('beforeunload', this.beforePageUnload);
|
window.removeEventListener('beforeunload', this.beforePageUnload);
|
||||||
this.unBlockRouting?.();
|
this.unBlockRouting?.();
|
||||||
}
|
}
|
||||||
@ -984,21 +1014,21 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async emitChange(submit: boolean) {
|
async emitChange(submit: boolean, skipIfNothingChanges: boolean = false) {
|
||||||
const {onChange, store, submitOnChange, dispatchEvent, data} = this.props;
|
const {onChange, store, submitOnChange, dispatchEvent, data} = this.props;
|
||||||
|
|
||||||
if (!isAlive(store)) {
|
if (!isAlive(store)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const diff = difference(store.data, store.pristine);
|
||||||
|
if (skipIfNothingChanges && !Object.keys(diff).length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 提前准备好 onChange 的参数。
|
// 提前准备好 onChange 的参数。
|
||||||
// 因为 store.data 会在 await 期间被 WithStore.componentDidUpdate 中的 store.initData 改变。导致数据丢失
|
// 因为 store.data 会在 await 期间被 WithStore.componentDidUpdate 中的 store.initData 改变。导致数据丢失
|
||||||
const changeProps = [
|
const changeProps = [store.data, diff, this.props];
|
||||||
store.data,
|
|
||||||
difference(store.data, store.pristine),
|
|
||||||
this.props
|
|
||||||
];
|
|
||||||
|
|
||||||
const dispatcher = await dispatchEvent(
|
const dispatcher = await dispatchEvent(
|
||||||
'change',
|
'change',
|
||||||
createObject(data, store.data)
|
createObject(data, store.data)
|
||||||
|
@ -34,7 +34,8 @@ export const ComboStore = iRendererStore
|
|||||||
minLength: 0,
|
minLength: 0,
|
||||||
maxLength: 0,
|
maxLength: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
activeKey: 0
|
activeKey: 0,
|
||||||
|
memberValidMap: types.optional(types.frozen(), {})
|
||||||
})
|
})
|
||||||
.views(self => {
|
.views(self => {
|
||||||
function getForms() {
|
function getForms() {
|
||||||
@ -166,13 +167,21 @@ export const ComboStore = iRendererStore
|
|||||||
self.activeKey = key;
|
self.activeKey = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMemberValid(valid: boolean, index: number) {
|
||||||
|
self.memberValidMap = {
|
||||||
|
...self.memberValidMap,
|
||||||
|
[index]: valid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
config,
|
||||||
setActiveKey,
|
setActiveKey,
|
||||||
bindUniuqueItem,
|
bindUniuqueItem,
|
||||||
unBindUniuqueItem,
|
unBindUniuqueItem,
|
||||||
addForm,
|
addForm,
|
||||||
onChildStoreDispose
|
onChildStoreDispose,
|
||||||
|
setMemberValid
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2052,6 +2052,7 @@
|
|||||||
--Tabs-onActive-bg: var(--background);
|
--Tabs-onActive-bg: var(--background);
|
||||||
--Tabs-onActive-borderColor: var(--borderColor);
|
--Tabs-onActive-borderColor: var(--borderColor);
|
||||||
--Tabs-onActive-color: var(--colors-neutral-text-2);
|
--Tabs-onActive-color: var(--colors-neutral-text-2);
|
||||||
|
--Tabs-onError-color: var(--colors-error-5);
|
||||||
--Tabs-onDisabled-color: var(--colors-neutral-text-7);
|
--Tabs-onDisabled-color: var(--colors-neutral-text-7);
|
||||||
--Tabs-onHover-borderColor: var(--colors-neutral-line-8);
|
--Tabs-onHover-borderColor: var(--colors-neutral-line-8);
|
||||||
--Tabs-add-icon-size: #{px2rem(15px)};
|
--Tabs-add-icon-size: #{px2rem(15px)};
|
||||||
@ -4120,6 +4121,7 @@
|
|||||||
var(--combo-vertical-right-border-color)
|
var(--combo-vertical-right-border-color)
|
||||||
var(--combo-vertical-bottom-border-color)
|
var(--combo-vertical-bottom-border-color)
|
||||||
var(--combo-vertical-left-border-color);
|
var(--combo-vertical-left-border-color);
|
||||||
|
--Combo--vertical-item--onError-borderColor: var(--colors-error-5);
|
||||||
--Combo--vertical-item-borderRadius: var(
|
--Combo--vertical-item-borderRadius: var(
|
||||||
--combo-vertical-top-left-border-radius
|
--combo-vertical-top-left-border-radius
|
||||||
)
|
)
|
||||||
|
@ -242,6 +242,10 @@
|
|||||||
border-color: var(--Tabs-onActive-borderColor);
|
border-color: var(--Tabs-onActive-borderColor);
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-error > a:first-child {
|
||||||
|
color: var(--Tabs-onError-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +258,12 @@
|
|||||||
var(--combo-vertical-paddingRight) var(--combo-vertical-paddingBottom)
|
var(--combo-vertical-paddingRight) var(--combo-vertical-paddingBottom)
|
||||||
var(--combo-vertical-paddingLeft);
|
var(--combo-vertical-paddingLeft);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&.has-error {
|
||||||
|
border-color: var(
|
||||||
|
--Combo--vertical-item--onError-borderColor
|
||||||
|
) !important; // 因为下面的规则权重更高 &:not(.is-disabled) > .#{$ns}Combo-items > .#{$ns}Combo-item:hover
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .#{$ns}Combo-items > .#{$ns}Combo-item {
|
> .#{$ns}Combo-items > .#{$ns}Combo-item {
|
||||||
|
@ -50,6 +50,7 @@ export interface TabProps extends ThemeProps {
|
|||||||
tip?: string;
|
tip?: string;
|
||||||
tab?: Schema;
|
tab?: Schema;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
tabClassName?: string;
|
||||||
activeKey?: string | number;
|
activeKey?: string | number;
|
||||||
reload?: boolean;
|
reload?: boolean;
|
||||||
mountOnEnter?: boolean;
|
mountOnEnter?: boolean;
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import {fireEvent, render} from '@testing-library/react';
|
||||||
|
import '../../../src';
|
||||||
|
import {render as amisRender} from '../../../src';
|
||||||
|
import {makeEnv, wait} from '../../helper';
|
||||||
|
|
||||||
|
test('paginationWrapper: service + crud', async () => {
|
||||||
|
const fetcher = jest.fn().mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
status: 0,
|
||||||
|
data: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: '110101',
|
||||||
|
name: '东城区',
|
||||||
|
sale: 46861
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '110102',
|
||||||
|
name: '西城区',
|
||||||
|
sale: 44882
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const {container} = render(
|
||||||
|
amisRender(
|
||||||
|
{
|
||||||
|
type: 'page',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'service',
|
||||||
|
id: 'u:ff652047d747',
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: 'https://yapi.baidu-int.com/mock/42601/amis-chart/chart/sales/data2'
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'pagination-wrapper',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
source: '${items}',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'label',
|
||||||
|
label: '地区',
|
||||||
|
type: 'text',
|
||||||
|
id: 'u:331ab3342710'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sale',
|
||||||
|
label: '销售',
|
||||||
|
type: 'text',
|
||||||
|
id: 'u:3dba120eda1d'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: 'u:b3c77cb44fc8',
|
||||||
|
perPageAvailable: [10]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
inputName: 'items',
|
||||||
|
outputName: 'items',
|
||||||
|
perPage: 20,
|
||||||
|
position: 'bottom'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
makeEnv({
|
||||||
|
fetcher
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await wait(200);
|
||||||
|
const tds = [].slice
|
||||||
|
.call(container.querySelectorAll('tbody td'))
|
||||||
|
.map((td: any) => td.textContent);
|
||||||
|
expect(tds).toEqual(['110101', '46861', '110102', '44882']);
|
||||||
|
});
|
@ -659,6 +659,17 @@ test('Renderer:combo with canAccessSuperData & strictMode & syncFields', async (
|
|||||||
expect(comboInputs[0]!.value).toBe('');
|
expect(comboInputs[0]!.value).toBe('');
|
||||||
expect(comboInputs[1]!.value).toBe('123');
|
expect(comboInputs[1]!.value).toBe('123');
|
||||||
expect(comboInputs[2]!.value).toBe('123456');
|
expect(comboInputs[2]!.value).toBe('123456');
|
||||||
|
|
||||||
|
fireEvent.click(submitBtn);
|
||||||
|
await wait(300);
|
||||||
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(onSubmit.mock.calls[0][0]).toMatchObject({
|
||||||
|
super_text: '123456',
|
||||||
|
combo1: [{}],
|
||||||
|
combo2: [{super_text: '123'}],
|
||||||
|
combo3: [{super_text: '123456'}]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 9. tabsMode
|
// 9. tabsMode
|
||||||
|
@ -194,6 +194,7 @@ test('Renderer:inputArray with minLength & maxLength', async () => {
|
|||||||
expect(container.querySelector('.cxd-Combo-addBtn')).toBeInTheDocument();
|
expect(container.querySelector('.cxd-Combo-addBtn')).toBeInTheDocument();
|
||||||
// 最大值
|
// 最大值
|
||||||
fireEvent.click(container.querySelector('.cxd-Combo-addBtn')!);
|
fireEvent.click(container.querySelector('.cxd-Combo-addBtn')!);
|
||||||
|
await wait(300);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(container.querySelector('a.cxd-Combo-delBtn')).toBeInTheDocument();
|
expect(container.querySelector('a.cxd-Combo-delBtn')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
|
@ -915,8 +915,13 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
handleFilterReset(values: object, action: any) {
|
handleFilterReset(values: object, action: any) {
|
||||||
const {store, syncLocation, env, pageField, perPageField} = this.props;
|
const {store, syncLocation, env, pageField, perPageField} = this.props;
|
||||||
|
|
||||||
|
const resetQuery: any = {};
|
||||||
|
Object.keys(values).forEach(key => (resetQuery[key] = ''));
|
||||||
store.updateQuery(
|
store.updateQuery(
|
||||||
store.pristineQuery,
|
{
|
||||||
|
...resetQuery,
|
||||||
|
...store.pristineQuery
|
||||||
|
},
|
||||||
syncLocation && env && env.updateLocation
|
syncLocation && env && env.updateLocation
|
||||||
? (location: any) => env.updateLocation(location)
|
? (location: any) => env.updateLocation(location)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -465,7 +465,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
|||||||
: query;
|
: query;
|
||||||
|
|
||||||
store.updateQuery(
|
store.updateQuery(
|
||||||
resetQuery ? this.props.store.pristineQuery : query,
|
resetQuery ? {...query, ...this.props.store.pristineQuery} : query,
|
||||||
syncLocation && env && env.updateLocation
|
syncLocation && env && env.updateLocation
|
||||||
? (location: any) => env.updateLocation(location, true)
|
? (location: any) => env.updateLocation(location, true)
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -1086,11 +1086,16 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
|||||||
key: index + 'filter',
|
key: index + 'filter',
|
||||||
data: this.props.store.filterData,
|
data: this.props.store.filterData,
|
||||||
onSubmit: (data: any) => this.handleSearch({query: data}),
|
onSubmit: (data: any) => this.handleSearch({query: data}),
|
||||||
onReset: () =>
|
onReset: (data: any) => {
|
||||||
|
const resetQueries: any = {};
|
||||||
|
Object.keys(data!).forEach(key => (resetQueries[key] = ''));
|
||||||
|
|
||||||
this.handleSearch({
|
this.handleSearch({
|
||||||
|
query: resetQueries,
|
||||||
resetQuery: true,
|
resetQuery: true,
|
||||||
replaceQuery: true
|
replaceQuery: true
|
||||||
})
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,12 @@ import {
|
|||||||
FormBaseControl,
|
FormBaseControl,
|
||||||
resolveEventData,
|
resolveEventData,
|
||||||
ApiObject,
|
ApiObject,
|
||||||
FormHorizontal
|
FormHorizontal,
|
||||||
|
evalExpressionWithConditionBuilder,
|
||||||
|
IFormStore,
|
||||||
|
getVariable,
|
||||||
|
IFormItemStore,
|
||||||
|
deleteVariable
|
||||||
} from 'amis-core';
|
} from 'amis-core';
|
||||||
import {ActionObject, Api} from 'amis-core';
|
import {ActionObject, Api} from 'amis-core';
|
||||||
import {ComboStore, IComboStore} from 'amis-core';
|
import {ComboStore, IComboStore} from 'amis-core';
|
||||||
@ -36,7 +41,11 @@ import {isEffectiveApi, str2AsyncFunction} from 'amis-core';
|
|||||||
import {Alert2} from 'amis-ui';
|
import {Alert2} from 'amis-ui';
|
||||||
import memoize from 'lodash/memoize';
|
import memoize from 'lodash/memoize';
|
||||||
import {Icon} from 'amis-ui';
|
import {Icon} from 'amis-ui';
|
||||||
import {isAlive} from 'mobx-state-tree';
|
import {
|
||||||
|
isAlive,
|
||||||
|
clone as cloneModel,
|
||||||
|
destroy as destroyModel
|
||||||
|
} from 'mobx-state-tree';
|
||||||
import {
|
import {
|
||||||
FormBaseControlSchema,
|
FormBaseControlSchema,
|
||||||
SchemaApi,
|
SchemaApi,
|
||||||
@ -47,7 +56,6 @@ import {
|
|||||||
import {ListenerAction} from 'amis-core';
|
import {ListenerAction} from 'amis-core';
|
||||||
import type {SchemaTokenizeableString} from '../../Schema';
|
import type {SchemaTokenizeableString} from '../../Schema';
|
||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
import {isMobile} from 'amis-core';
|
|
||||||
|
|
||||||
export type ComboCondition = {
|
export type ComboCondition = {
|
||||||
test: string;
|
test: string;
|
||||||
@ -393,6 +401,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
this.dragTipRef = this.dragTipRef.bind(this);
|
this.dragTipRef = this.dragTipRef.bind(this);
|
||||||
this.flush = this.flush.bind(this);
|
this.flush = this.flush.bind(this);
|
||||||
this.handleComboTypeChange = this.handleComboTypeChange.bind(this);
|
this.handleComboTypeChange = this.handleComboTypeChange.bind(this);
|
||||||
|
this.handleSubFormValid = this.handleSubFormValid.bind(this);
|
||||||
this.defaultValue = {
|
this.defaultValue = {
|
||||||
...props.scaffold
|
...props.scaffold
|
||||||
};
|
};
|
||||||
@ -532,8 +541,11 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getValueAsArray(props = this.props) {
|
getValueAsArray(props = this.props) {
|
||||||
const {flat, joinValues, delimiter, type} = props;
|
const {flat, joinValues, delimiter, type, formItem} = props;
|
||||||
let value = props.value;
|
// 因为 combo 多个子表单可能同时发生变化。
|
||||||
|
// onChagne 触发多次,上次变更还没应用到 props.value 上来,这次触发变更就会包含历史数据,把上次触发的数据给重置成旧的了。
|
||||||
|
// 通过 props.getValue() 拿到的是最新的
|
||||||
|
let value = props.getValue();
|
||||||
|
|
||||||
if (joinValues && flat && typeof value === 'string') {
|
if (joinValues && flat && typeof value === 'string') {
|
||||||
value = value.split(delimiter || ',');
|
value = value.split(delimiter || ',');
|
||||||
@ -704,13 +716,32 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleChange(values: any, diff: any, {index}: any) {
|
handleChange(values: any, diff: any, {index}: any) {
|
||||||
const {flat, store, joinValues, delimiter, disabled, submitOnChange, type} =
|
const {
|
||||||
this.props;
|
flat,
|
||||||
|
store,
|
||||||
|
joinValues,
|
||||||
|
delimiter,
|
||||||
|
disabled,
|
||||||
|
submitOnChange,
|
||||||
|
type,
|
||||||
|
syncFields,
|
||||||
|
name
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 不要递归更新自己
|
||||||
|
if (Array.isArray(syncFields)) {
|
||||||
|
syncFields.forEach(field => {
|
||||||
|
if (name?.startsWith(field)) {
|
||||||
|
values = {...values};
|
||||||
|
deleteVariable(values, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let value = this.getValueAsArray();
|
let value = this.getValueAsArray();
|
||||||
value[index] = flat ? values.flat : {...values};
|
value[index] = flat ? values.flat : {...values};
|
||||||
|
|
||||||
@ -795,6 +826,11 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSubFormValid(valid: boolean, {index}: any) {
|
||||||
|
const {store} = this.props;
|
||||||
|
store.setMemberValid(valid, index);
|
||||||
|
}
|
||||||
|
|
||||||
handleFormInit(values: any, {index}: any) {
|
handleFormInit(values: any, {index}: any) {
|
||||||
const {
|
const {
|
||||||
syncDefaultValue,
|
syncDefaultValue,
|
||||||
@ -804,9 +840,27 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
formInited,
|
formInited,
|
||||||
onChange,
|
onChange,
|
||||||
submitOnChange,
|
submitOnChange,
|
||||||
setPrinstineValue
|
setPrinstineValue,
|
||||||
|
formItem,
|
||||||
|
name,
|
||||||
|
syncFields
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
// 不要递归更新自己
|
||||||
|
if (Array.isArray(syncFields)) {
|
||||||
|
syncFields.forEach(field => {
|
||||||
|
if (name?.startsWith(field)) {
|
||||||
|
values = {...values};
|
||||||
|
deleteVariable(values, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已经开始验证了,那么打开成员的时候,就要验证一下。
|
||||||
|
if (formItem?.validated) {
|
||||||
|
this.subForms[index]?.validate(true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
this.subFormDefaultValues.push({
|
this.subFormDefaultValues.push({
|
||||||
index,
|
index,
|
||||||
values,
|
values,
|
||||||
@ -879,7 +933,13 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate(): any {
|
validate(): any {
|
||||||
const {messages, nullable, translate: __} = this.props;
|
const {
|
||||||
|
messages,
|
||||||
|
nullable,
|
||||||
|
value: rawValue,
|
||||||
|
translate: __,
|
||||||
|
store
|
||||||
|
} = this.props;
|
||||||
const value = this.getValueAsArray();
|
const value = this.getValueAsArray();
|
||||||
const minLength = this.resolveVariableProps(this.props, 'minLength');
|
const minLength = this.resolveVariableProps(this.props, 'minLength');
|
||||||
const maxLength = this.resolveVariableProps(this.props, 'maxLength');
|
const maxLength = this.resolveVariableProps(this.props, 'maxLength');
|
||||||
@ -894,18 +954,62 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
(messages && messages.maxLengthValidateFailed) || 'Combo.maxLength',
|
(messages && messages.maxLengthValidateFailed) || 'Combo.maxLength',
|
||||||
{maxLength}
|
{maxLength}
|
||||||
);
|
);
|
||||||
} else if (this.subForms.length && (!nullable || value)) {
|
} else if (nullable && !rawValue) {
|
||||||
return Promise.all(this.subForms.map(item => item.validate())).then(
|
return; // 不校验
|
||||||
values => {
|
} else if (value.length) {
|
||||||
if (~values.indexOf(false)) {
|
return Promise.all(
|
||||||
return __(
|
value.map(async (values: any, index: number) => {
|
||||||
(messages && messages.validateFailed) || 'validateFailed'
|
const subForm = this.subForms[index];
|
||||||
);
|
if (subForm) {
|
||||||
}
|
return subForm.validate(true, false, false);
|
||||||
|
} else {
|
||||||
|
// 那些还没有渲染出来的数据
|
||||||
|
// 因为有可能存在分页,有可能存在懒加载,所以没办法直接用 subForm 去校验了
|
||||||
|
const subForm = this.subForms[Object.keys(this.subForms)[0] as any];
|
||||||
|
if (subForm) {
|
||||||
|
const form: IFormStore = subForm.props.store;
|
||||||
|
let valid = false;
|
||||||
|
for (let formitem of form.items) {
|
||||||
|
const cloned: IFormItemStore = cloneModel(formitem);
|
||||||
|
let value: any = getVariable(values, formitem.name, false);
|
||||||
|
|
||||||
return;
|
if (formitem.extraName) {
|
||||||
|
value = [
|
||||||
|
getVariable(values, formitem.name, false),
|
||||||
|
getVariable(values, formitem.extraName, false)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned.changeTmpValue(value, 'dataChanged');
|
||||||
|
valid = await cloned.validate(values);
|
||||||
|
destroyModel(cloned);
|
||||||
|
if (valid === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setMemberValid(valid, index);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).then(values => {
|
||||||
|
if (~values.indexOf(false)) {
|
||||||
|
return __((messages && messages.validateFailed) || 'validateFailed');
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
} else if (this.subForms.length) {
|
||||||
|
return Promise.all(
|
||||||
|
this.subForms.map(item => item.validate(true, false, false))
|
||||||
|
).then(values => {
|
||||||
|
if (~values.indexOf(false)) {
|
||||||
|
return __((messages && messages.validateFailed) || 'validateFailed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1251,6 +1355,12 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
// 不能按需渲染,因为 unique 会失效。
|
// 不能按需渲染,因为 unique 会失效。
|
||||||
mountOnEnter={!hasUnique}
|
mountOnEnter={!hasUnique}
|
||||||
unmountOnExit={false}
|
unmountOnExit={false}
|
||||||
|
className={
|
||||||
|
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||||
|
}
|
||||||
|
tabClassName={
|
||||||
|
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{condition && typeSwitchable !== false ? (
|
{condition && typeSwitchable !== false ? (
|
||||||
<div className={cx('Combo-itemTag')}>
|
<div className={cx('Combo-itemTag')}>
|
||||||
@ -1483,7 +1593,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
itemClassName,
|
itemClassName,
|
||||||
itemsWrapperClassName,
|
itemsWrapperClassName,
|
||||||
static: isStatic,
|
static: isStatic,
|
||||||
mobileUI
|
mobileUI,
|
||||||
|
store
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let items = this.props.items;
|
let items = this.props.items;
|
||||||
@ -1541,7 +1652,11 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(`Combo-item`, itemClassName)}
|
className={cx(
|
||||||
|
`Combo-item`,
|
||||||
|
itemClassName,
|
||||||
|
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||||
|
)}
|
||||||
key={this.keys[index]}
|
key={this.keys[index]}
|
||||||
>
|
>
|
||||||
{!isStatic && !disabled && draggable && thelist.length > 1 ? (
|
{!isStatic && !disabled && draggable && thelist.length > 1 ? (
|
||||||
@ -1620,7 +1735,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
nullable,
|
nullable,
|
||||||
translate: __,
|
translate: __,
|
||||||
itemClassName,
|
itemClassName,
|
||||||
mobileUI
|
mobileUI,
|
||||||
|
store
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let items = this.props.items;
|
let items = this.props.items;
|
||||||
@ -1644,7 +1760,13 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
disabled ? 'is-disabled' : ''
|
disabled ? 'is-disabled' : ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cx(`Combo-item`, itemClassName)}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
`Combo-item`,
|
||||||
|
itemClassName,
|
||||||
|
store.memberValidMap[0] === false ? 'has-error' : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
{condition && typeSwitchable !== false ? (
|
{condition && typeSwitchable !== false ? (
|
||||||
<div className={cx('Combo-itemTag')}>
|
<div className={cx('Combo-itemTag')}>
|
||||||
<label>{__('Combo.type')}</label>
|
<label>{__('Combo.type')}</label>
|
||||||
@ -1712,14 +1834,17 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
className: cx(`Combo-form`, formClassName)
|
className: cx(`Combo-form`, formClassName)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
index: 0,
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
static: isStatic,
|
static: isStatic,
|
||||||
data,
|
data,
|
||||||
onChange: this.handleSingleFormChange,
|
onChange: this.handleSingleFormChange,
|
||||||
ref: this.makeFormRef(0),
|
ref: this.makeFormRef(0),
|
||||||
|
onValidChange: this.handleSubFormValid,
|
||||||
onInit: this.handleSingleFormInit,
|
onInit: this.handleSingleFormInit,
|
||||||
canAccessSuperData,
|
canAccessSuperData,
|
||||||
formStore: undefined
|
formStore: undefined,
|
||||||
|
updatePristineAfterStoreDataReInit: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (multiple && index !== undefined && index >= 0) {
|
} else if (multiple && index !== undefined && index >= 0) {
|
||||||
@ -1744,13 +1869,15 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||||||
onAction: this.handleAction,
|
onAction: this.handleAction,
|
||||||
onRadioChange: this.handleRadioChange,
|
onRadioChange: this.handleRadioChange,
|
||||||
ref: this.makeFormRef(index),
|
ref: this.makeFormRef(index),
|
||||||
|
onValidChange: this.handleSubFormValid,
|
||||||
canAccessSuperData,
|
canAccessSuperData,
|
||||||
lazyChange: changeImmediately ? false : true,
|
lazyChange: changeImmediately ? false : true,
|
||||||
formLazyChange: false,
|
formLazyChange: false,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
formItemValue: undefined,
|
formItemValue: undefined,
|
||||||
formStore: undefined,
|
formStore: undefined,
|
||||||
...(tabsMode ? {} : {lazyLoad})
|
...(tabsMode ? {} : {lazyLoad}),
|
||||||
|
updatePristineAfterStoreDataReInit: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -355,15 +355,12 @@ export default observer((props: TableRowProps) => {
|
|||||||
id={item.id}
|
id={item.id}
|
||||||
newIndex={item.newIndex}
|
newIndex={item.newIndex}
|
||||||
isHover={item.isHover}
|
isHover={item.isHover}
|
||||||
partial={item.partial}
|
|
||||||
checked={item.checked}
|
checked={item.checked}
|
||||||
modified={item.modified}
|
modified={item.modified}
|
||||||
moved={item.moved}
|
moved={item.moved}
|
||||||
depth={item.depth}
|
depth={item.depth}
|
||||||
expandable={item.expandable}
|
expandable={item.expandable}
|
||||||
checkdisable={item.checkdisable}
|
checkdisable={item.checkdisable}
|
||||||
loading={item.loading}
|
|
||||||
error={item.error}
|
|
||||||
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
|
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
|
||||||
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
|
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
|
||||||
data={canAccessSuperData ? item.locals : item.data}
|
data={canAccessSuperData ? item.locals : item.data}
|
||||||
|
Loading…
Reference in New Issue
Block a user