mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 10:38:16 +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',
|
||||
'renderLabel',
|
||||
'trackExpression',
|
||||
'editorSetting'
|
||||
'editorSetting',
|
||||
'updatePristineAfterStoreDataReInit'
|
||||
];
|
||||
|
||||
const componentCache: SimpleMap = new SimpleMap();
|
||||
|
@ -207,7 +207,8 @@ export function HocStoreFactory(renderer: {
|
||||
...(store.hasRemoteData ? store.data : null), // todo 只保留 remote 数据
|
||||
...this.formatData(props.defaultData),
|
||||
...this.formatData(props.data)
|
||||
})
|
||||
}),
|
||||
props.updatePristineAfterStoreDataReInit === false
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
@ -234,7 +235,8 @@ export function HocStoreFactory(renderer: {
|
||||
store,
|
||||
props.syncSuperStore === true
|
||||
)
|
||||
)
|
||||
),
|
||||
props.updatePristineAfterStoreDataReInit === false
|
||||
);
|
||||
} else if (props.data && (props.data as any).__super) {
|
||||
store.initData(
|
||||
@ -250,16 +252,20 @@ export function HocStoreFactory(renderer: {
|
||||
props.store?.storeType === 'ComboStore'
|
||||
? undefined
|
||||
: syncDataFromSuper(
|
||||
props.data,
|
||||
{...store.data, ...props.data},
|
||||
(props.data as any).__super,
|
||||
(prevProps.data as any).__super,
|
||||
store,
|
||||
false
|
||||
)
|
||||
)
|
||||
),
|
||||
props.updatePristineAfterStoreDataReInit === false
|
||||
);
|
||||
} else {
|
||||
store.initData(createObject(props.scope, props.data));
|
||||
store.initData(
|
||||
createObject(props.scope, props.data),
|
||||
props.updatePristineAfterStoreDataReInit === false
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
!props.trackExpression &&
|
||||
@ -282,8 +288,9 @@ export function HocStoreFactory(renderer: {
|
||||
...store.data
|
||||
}),
|
||||
|
||||
store.storeType === 'FormStore' &&
|
||||
prevProps.store?.storeType === 'CRUDStore'
|
||||
props.updatePristineAfterStoreDataReInit === false ||
|
||||
(store.storeType === 'FormStore' &&
|
||||
prevProps.store?.storeType === 'CRUDStore')
|
||||
);
|
||||
}
|
||||
// nextProps.data.__super !== props.data.__super) &&
|
||||
@ -299,7 +306,8 @@ export function HocStoreFactory(renderer: {
|
||||
createObject(props.scope, {
|
||||
// ...nextProps.data,
|
||||
...store.data
|
||||
})
|
||||
}),
|
||||
props.updatePristineAfterStoreDataReInit === false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import {isAlive} from 'mobx-state-tree';
|
||||
|
||||
import type {LabelAlign} from './Item';
|
||||
import {injectObjectChain} from '../utils';
|
||||
import {reaction} from 'mobx';
|
||||
|
||||
export interface FormHorizontal {
|
||||
left?: number;
|
||||
@ -371,6 +372,7 @@ export interface FormProps
|
||||
onFailed?: (reason: string, errors: any) => any;
|
||||
onFinished: (values: object, action: any) => any;
|
||||
onValidate: (values: object, form: any) => any;
|
||||
onValidChange?: (valid: boolean, props: any) => void; // 表单数据合法性变更
|
||||
messages: {
|
||||
fetchSuccess?: string;
|
||||
fetchFailed?: string;
|
||||
@ -443,6 +445,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
'onChange',
|
||||
'onFailed',
|
||||
'onFinished',
|
||||
'onValidate',
|
||||
'onValidChange',
|
||||
'onSaved',
|
||||
'canAccessSuperData',
|
||||
'lazyChange',
|
||||
@ -460,8 +464,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
[propName: string]: Array<() => Promise<any>>;
|
||||
} = {};
|
||||
asyncCancel: () => void;
|
||||
disposeOnValidate: () => void;
|
||||
disposeRulesValidate: () => void;
|
||||
toDispose: Array<() => void> = [];
|
||||
shouldLoadInitApi: boolean = false;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
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() {
|
||||
@ -531,6 +546,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
store,
|
||||
messages: {fetchSuccess, fetchFailed},
|
||||
onValidate,
|
||||
onValidChange,
|
||||
promptPageLeave,
|
||||
env,
|
||||
rules
|
||||
@ -540,49 +556,63 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
|
||||
if (onValidate) {
|
||||
const finalValidate = promisify(onValidate);
|
||||
this.disposeOnValidate = this.addHook(async () => {
|
||||
const result = await finalValidate(store.data, store);
|
||||
this.toDispose.push(
|
||||
this.addHook(async () => {
|
||||
const result = await finalValidate(store.data, store);
|
||||
|
||||
if (result && isObject(result)) {
|
||||
Object.keys(result).forEach(key => {
|
||||
let msg = result[key];
|
||||
const items = store.getItemsByPath(key);
|
||||
if (result && isObject(result)) {
|
||||
Object.keys(result).forEach(key => {
|
||||
let msg = result[key];
|
||||
const items = store.getItemsByPath(key);
|
||||
|
||||
// 没有找到
|
||||
if (!Array.isArray(items) || !items.length) {
|
||||
return;
|
||||
}
|
||||
// 没有找到
|
||||
if (!Array.isArray(items) || !items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在setError之前,提前把残留的error信息清除掉,否则每次onValidate后都会一直把报错 append 上去
|
||||
items.forEach(item => item.clearError());
|
||||
// 在setError之前,提前把残留的error信息清除掉,否则每次onValidate后都会一直把报错 append 上去
|
||||
items.forEach(item => item.clearError());
|
||||
|
||||
if (msg) {
|
||||
msg = Array.isArray(msg) ? msg : [msg];
|
||||
items.forEach(item => item.addError(msg));
|
||||
}
|
||||
if (msg) {
|
||||
msg = Array.isArray(msg) ? msg : [msg];
|
||||
items.forEach(item => item.addError(msg));
|
||||
}
|
||||
|
||||
delete result[key];
|
||||
});
|
||||
delete result[key];
|
||||
});
|
||||
|
||||
isEmpty(result)
|
||||
? store.clearRestError()
|
||||
: store.setRestError(Object.keys(result).map(key => result[key]));
|
||||
}
|
||||
});
|
||||
isEmpty(result)
|
||||
? store.clearRestError()
|
||||
: 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) {
|
||||
this.disposeRulesValidate = this.addHook(() => {
|
||||
if (!store.valid) {
|
||||
return;
|
||||
}
|
||||
this.toDispose.push(
|
||||
this.addHook(() => {
|
||||
if (!store.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
rules.forEach(
|
||||
item =>
|
||||
!evalExpression(item.rule, store.data) &&
|
||||
store.addRestError(item.message, item.name)
|
||||
);
|
||||
});
|
||||
rules.forEach(
|
||||
item =>
|
||||
!evalExpression(item.rule, store.data) &&
|
||||
store.addRestError(item.message, item.name)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
|
||||
@ -654,8 +684,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
// this.lazyHandleChange.flush();
|
||||
this.lazyEmitChange.cancel();
|
||||
this.asyncCancel && this.asyncCancel();
|
||||
this.disposeOnValidate && this.disposeOnValidate();
|
||||
this.disposeRulesValidate && this.disposeRulesValidate();
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
window.removeEventListener('beforeunload', this.beforePageUnload);
|
||||
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;
|
||||
|
||||
if (!isAlive(store)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const diff = difference(store.data, store.pristine);
|
||||
if (skipIfNothingChanges && !Object.keys(diff).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 提前准备好 onChange 的参数。
|
||||
// 因为 store.data 会在 await 期间被 WithStore.componentDidUpdate 中的 store.initData 改变。导致数据丢失
|
||||
const changeProps = [
|
||||
store.data,
|
||||
difference(store.data, store.pristine),
|
||||
this.props
|
||||
];
|
||||
|
||||
const changeProps = [store.data, diff, this.props];
|
||||
const dispatcher = await dispatchEvent(
|
||||
'change',
|
||||
createObject(data, store.data)
|
||||
|
@ -34,7 +34,8 @@ export const ComboStore = iRendererStore
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
length: 0,
|
||||
activeKey: 0
|
||||
activeKey: 0,
|
||||
memberValidMap: types.optional(types.frozen(), {})
|
||||
})
|
||||
.views(self => {
|
||||
function getForms() {
|
||||
@ -166,13 +167,21 @@ export const ComboStore = iRendererStore
|
||||
self.activeKey = key;
|
||||
}
|
||||
|
||||
function setMemberValid(valid: boolean, index: number) {
|
||||
self.memberValidMap = {
|
||||
...self.memberValidMap,
|
||||
[index]: valid
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
setActiveKey,
|
||||
bindUniuqueItem,
|
||||
unBindUniuqueItem,
|
||||
addForm,
|
||||
onChildStoreDispose
|
||||
onChildStoreDispose,
|
||||
setMemberValid
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -2052,6 +2052,7 @@
|
||||
--Tabs-onActive-bg: var(--background);
|
||||
--Tabs-onActive-borderColor: var(--borderColor);
|
||||
--Tabs-onActive-color: var(--colors-neutral-text-2);
|
||||
--Tabs-onError-color: var(--colors-error-5);
|
||||
--Tabs-onDisabled-color: var(--colors-neutral-text-7);
|
||||
--Tabs-onHover-borderColor: var(--colors-neutral-line-8);
|
||||
--Tabs-add-icon-size: #{px2rem(15px)};
|
||||
@ -4120,6 +4121,7 @@
|
||||
var(--combo-vertical-right-border-color)
|
||||
var(--combo-vertical-bottom-border-color)
|
||||
var(--combo-vertical-left-border-color);
|
||||
--Combo--vertical-item--onError-borderColor: var(--colors-error-5);
|
||||
--Combo--vertical-item-borderRadius: var(
|
||||
--combo-vertical-top-left-border-radius
|
||||
)
|
||||
|
@ -242,6 +242,10 @@
|
||||
border-color: var(--Tabs-onActive-borderColor);
|
||||
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-paddingLeft);
|
||||
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 {
|
||||
|
@ -50,6 +50,7 @@ export interface TabProps extends ThemeProps {
|
||||
tip?: string;
|
||||
tab?: Schema;
|
||||
className?: string;
|
||||
tabClassName?: string;
|
||||
activeKey?: string | number;
|
||||
reload?: 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[1]!.value).toBe('123');
|
||||
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
|
||||
|
@ -194,6 +194,7 @@ test('Renderer:inputArray with minLength & maxLength', async () => {
|
||||
expect(container.querySelector('.cxd-Combo-addBtn')).toBeInTheDocument();
|
||||
// 最大值
|
||||
fireEvent.click(container.querySelector('.cxd-Combo-addBtn')!);
|
||||
await wait(300);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('a.cxd-Combo-delBtn')).toBeInTheDocument();
|
||||
expect(
|
||||
|
@ -915,8 +915,13 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
handleFilterReset(values: object, action: any) {
|
||||
const {store, syncLocation, env, pageField, perPageField} = this.props;
|
||||
|
||||
const resetQuery: any = {};
|
||||
Object.keys(values).forEach(key => (resetQuery[key] = ''));
|
||||
store.updateQuery(
|
||||
store.pristineQuery,
|
||||
{
|
||||
...resetQuery,
|
||||
...store.pristineQuery
|
||||
},
|
||||
syncLocation && env && env.updateLocation
|
||||
? (location: any) => env.updateLocation(location)
|
||||
: undefined,
|
||||
|
@ -465,7 +465,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
: query;
|
||||
|
||||
store.updateQuery(
|
||||
resetQuery ? this.props.store.pristineQuery : query,
|
||||
resetQuery ? {...query, ...this.props.store.pristineQuery} : query,
|
||||
syncLocation && env && env.updateLocation
|
||||
? (location: any) => env.updateLocation(location, true)
|
||||
: undefined,
|
||||
@ -1086,11 +1086,16 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
key: index + 'filter',
|
||||
data: this.props.store.filterData,
|
||||
onSubmit: (data: any) => this.handleSearch({query: data}),
|
||||
onReset: () =>
|
||||
onReset: (data: any) => {
|
||||
const resetQueries: any = {};
|
||||
Object.keys(data!).forEach(key => (resetQueries[key] = ''));
|
||||
|
||||
this.handleSearch({
|
||||
query: resetQueries,
|
||||
resetQuery: true,
|
||||
replaceQuery: true
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ import {
|
||||
FormBaseControl,
|
||||
resolveEventData,
|
||||
ApiObject,
|
||||
FormHorizontal
|
||||
FormHorizontal,
|
||||
evalExpressionWithConditionBuilder,
|
||||
IFormStore,
|
||||
getVariable,
|
||||
IFormItemStore,
|
||||
deleteVariable
|
||||
} from 'amis-core';
|
||||
import {ActionObject, Api} 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 memoize from 'lodash/memoize';
|
||||
import {Icon} from 'amis-ui';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
import {
|
||||
isAlive,
|
||||
clone as cloneModel,
|
||||
destroy as destroyModel
|
||||
} from 'mobx-state-tree';
|
||||
import {
|
||||
FormBaseControlSchema,
|
||||
SchemaApi,
|
||||
@ -47,7 +56,6 @@ import {
|
||||
import {ListenerAction} from 'amis-core';
|
||||
import type {SchemaTokenizeableString} from '../../Schema';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import {isMobile} from 'amis-core';
|
||||
|
||||
export type ComboCondition = {
|
||||
test: string;
|
||||
@ -393,6 +401,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
this.dragTipRef = this.dragTipRef.bind(this);
|
||||
this.flush = this.flush.bind(this);
|
||||
this.handleComboTypeChange = this.handleComboTypeChange.bind(this);
|
||||
this.handleSubFormValid = this.handleSubFormValid.bind(this);
|
||||
this.defaultValue = {
|
||||
...props.scaffold
|
||||
};
|
||||
@ -532,8 +541,11 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
getValueAsArray(props = this.props) {
|
||||
const {flat, joinValues, delimiter, type} = props;
|
||||
let value = props.value;
|
||||
const {flat, joinValues, delimiter, type, formItem} = props;
|
||||
// 因为 combo 多个子表单可能同时发生变化。
|
||||
// onChagne 触发多次,上次变更还没应用到 props.value 上来,这次触发变更就会包含历史数据,把上次触发的数据给重置成旧的了。
|
||||
// 通过 props.getValue() 拿到的是最新的
|
||||
let value = props.getValue();
|
||||
|
||||
if (joinValues && flat && typeof value === 'string') {
|
||||
value = value.split(delimiter || ',');
|
||||
@ -704,13 +716,32 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
handleChange(values: any, diff: any, {index}: any) {
|
||||
const {flat, store, joinValues, delimiter, disabled, submitOnChange, type} =
|
||||
this.props;
|
||||
const {
|
||||
flat,
|
||||
store,
|
||||
joinValues,
|
||||
delimiter,
|
||||
disabled,
|
||||
submitOnChange,
|
||||
type,
|
||||
syncFields,
|
||||
name
|
||||
} = this.props;
|
||||
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 不要递归更新自己
|
||||
if (Array.isArray(syncFields)) {
|
||||
syncFields.forEach(field => {
|
||||
if (name?.startsWith(field)) {
|
||||
values = {...values};
|
||||
deleteVariable(values, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let value = this.getValueAsArray();
|
||||
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) {
|
||||
const {
|
||||
syncDefaultValue,
|
||||
@ -804,9 +840,27 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
formInited,
|
||||
onChange,
|
||||
submitOnChange,
|
||||
setPrinstineValue
|
||||
setPrinstineValue,
|
||||
formItem,
|
||||
name,
|
||||
syncFields
|
||||
} = 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({
|
||||
index,
|
||||
values,
|
||||
@ -879,7 +933,13 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
}
|
||||
|
||||
validate(): any {
|
||||
const {messages, nullable, translate: __} = this.props;
|
||||
const {
|
||||
messages,
|
||||
nullable,
|
||||
value: rawValue,
|
||||
translate: __,
|
||||
store
|
||||
} = this.props;
|
||||
const value = this.getValueAsArray();
|
||||
const minLength = this.resolveVariableProps(this.props, 'minLength');
|
||||
const maxLength = this.resolveVariableProps(this.props, 'maxLength');
|
||||
@ -894,18 +954,62 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
(messages && messages.maxLengthValidateFailed) || 'Combo.maxLength',
|
||||
{maxLength}
|
||||
);
|
||||
} else if (this.subForms.length && (!nullable || value)) {
|
||||
return Promise.all(this.subForms.map(item => item.validate())).then(
|
||||
values => {
|
||||
if (~values.indexOf(false)) {
|
||||
return __(
|
||||
(messages && messages.validateFailed) || 'validateFailed'
|
||||
);
|
||||
}
|
||||
} else if (nullable && !rawValue) {
|
||||
return; // 不校验
|
||||
} else if (value.length) {
|
||||
return Promise.all(
|
||||
value.map(async (values: any, index: number) => {
|
||||
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 会失效。
|
||||
mountOnEnter={!hasUnique}
|
||||
unmountOnExit={false}
|
||||
className={
|
||||
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||
}
|
||||
tabClassName={
|
||||
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||
}
|
||||
>
|
||||
{condition && typeSwitchable !== false ? (
|
||||
<div className={cx('Combo-itemTag')}>
|
||||
@ -1483,7 +1593,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
itemClassName,
|
||||
itemsWrapperClassName,
|
||||
static: isStatic,
|
||||
mobileUI
|
||||
mobileUI,
|
||||
store
|
||||
} = this.props;
|
||||
|
||||
let items = this.props.items;
|
||||
@ -1541,7 +1652,11 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(`Combo-item`, itemClassName)}
|
||||
className={cx(
|
||||
`Combo-item`,
|
||||
itemClassName,
|
||||
store.memberValidMap[index] === false ? 'has-error' : ''
|
||||
)}
|
||||
key={this.keys[index]}
|
||||
>
|
||||
{!isStatic && !disabled && draggable && thelist.length > 1 ? (
|
||||
@ -1620,7 +1735,8 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
nullable,
|
||||
translate: __,
|
||||
itemClassName,
|
||||
mobileUI
|
||||
mobileUI,
|
||||
store
|
||||
} = this.props;
|
||||
|
||||
let items = this.props.items;
|
||||
@ -1644,7 +1760,13 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
disabled ? 'is-disabled' : ''
|
||||
)}
|
||||
>
|
||||
<div className={cx(`Combo-item`, itemClassName)}>
|
||||
<div
|
||||
className={cx(
|
||||
`Combo-item`,
|
||||
itemClassName,
|
||||
store.memberValidMap[0] === false ? 'has-error' : ''
|
||||
)}
|
||||
>
|
||||
{condition && typeSwitchable !== false ? (
|
||||
<div className={cx('Combo-itemTag')}>
|
||||
<label>{__('Combo.type')}</label>
|
||||
@ -1712,14 +1834,17 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
className: cx(`Combo-form`, formClassName)
|
||||
},
|
||||
{
|
||||
index: 0,
|
||||
disabled: disabled,
|
||||
static: isStatic,
|
||||
data,
|
||||
onChange: this.handleSingleFormChange,
|
||||
ref: this.makeFormRef(0),
|
||||
onValidChange: this.handleSubFormValid,
|
||||
onInit: this.handleSingleFormInit,
|
||||
canAccessSuperData,
|
||||
formStore: undefined
|
||||
formStore: undefined,
|
||||
updatePristineAfterStoreDataReInit: false
|
||||
}
|
||||
);
|
||||
} else if (multiple && index !== undefined && index >= 0) {
|
||||
@ -1744,13 +1869,15 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
onAction: this.handleAction,
|
||||
onRadioChange: this.handleRadioChange,
|
||||
ref: this.makeFormRef(index),
|
||||
onValidChange: this.handleSubFormValid,
|
||||
canAccessSuperData,
|
||||
lazyChange: changeImmediately ? false : true,
|
||||
formLazyChange: false,
|
||||
value: undefined,
|
||||
formItemValue: undefined,
|
||||
formStore: undefined,
|
||||
...(tabsMode ? {} : {lazyLoad})
|
||||
...(tabsMode ? {} : {lazyLoad}),
|
||||
updatePristineAfterStoreDataReInit: false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -355,15 +355,12 @@ export default observer((props: TableRowProps) => {
|
||||
id={item.id}
|
||||
newIndex={item.newIndex}
|
||||
isHover={item.isHover}
|
||||
partial={item.partial}
|
||||
checked={item.checked}
|
||||
modified={item.modified}
|
||||
moved={item.moved}
|
||||
depth={item.depth}
|
||||
expandable={item.expandable}
|
||||
checkdisable={item.checkdisable}
|
||||
loading={item.loading}
|
||||
error={item.error}
|
||||
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
|
||||
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
|
||||
data={canAccessSuperData ? item.locals : item.data}
|
||||
|
Loading…
Reference in New Issue
Block a user