diff --git a/packages/amis-core/src/SchemaRenderer.tsx b/packages/amis-core/src/SchemaRenderer.tsx index a1871156f..50913dff4 100644 --- a/packages/amis-core/src/SchemaRenderer.tsx +++ b/packages/amis-core/src/SchemaRenderer.tsx @@ -64,7 +64,8 @@ export const RENDERER_TRANSMISSION_OMIT_PROPS = [ 'label', 'renderLabel', 'trackExpression', - 'editorSetting' + 'editorSetting', + 'updatePristineAfterStoreDataReInit' ]; const componentCache: SimpleMap = new SimpleMap(); diff --git a/packages/amis-core/src/WithStore.tsx b/packages/amis-core/src/WithStore.tsx index 1ce4827fb..73530d140 100644 --- a/packages/amis-core/src/WithStore.tsx +++ b/packages/amis-core/src/WithStore.tsx @@ -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 ); } } diff --git a/packages/amis-core/src/renderers/Form.tsx b/packages/amis-core/src/renderers/Form.tsx index 27be84697..c2437154b 100644 --- a/packages/amis-core/src/renderers/Form.tsx +++ b/packages/amis-core/src/renderers/Form.tsx @@ -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 { 'onChange', 'onFailed', 'onFinished', + 'onValidate', + 'onValidChange', 'onSaved', 'canAccessSuperData', 'lazyChange', @@ -460,8 +464,7 @@ export default class Form extends React.Component { [propName: string]: Array<() => Promise>; } = {}; asyncCancel: () => void; - disposeOnValidate: () => void; - disposeRulesValidate: () => void; + toDispose: Array<() => void> = []; shouldLoadInitApi: boolean = false; timer: ReturnType; mounted: boolean; @@ -518,6 +521,18 @@ export default class Form extends React.Component { ) ); } + + // 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 { store, messages: {fetchSuccess, fetchFailed}, onValidate, + onValidChange, promptPageLeave, env, rules @@ -540,49 +556,63 @@ export default class Form extends React.Component { 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 { // 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 { }; } - 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) diff --git a/packages/amis-core/src/store/combo.ts b/packages/amis-core/src/store/combo.ts index b82156321..741d15743 100644 --- a/packages/amis-core/src/store/combo.ts +++ b/packages/amis-core/src/store/combo.ts @@ -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 }; }); diff --git a/packages/amis-ui/scss/_components.scss b/packages/amis-ui/scss/_components.scss index 8720dd51c..1f93868c5 100644 --- a/packages/amis-ui/scss/_components.scss +++ b/packages/amis-ui/scss/_components.scss @@ -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 ) diff --git a/packages/amis-ui/scss/components/_tabs.scss b/packages/amis-ui/scss/components/_tabs.scss index 04e4f29ee..05e5a6a20 100644 --- a/packages/amis-ui/scss/components/_tabs.scss +++ b/packages/amis-ui/scss/components/_tabs.scss @@ -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; + } } } diff --git a/packages/amis-ui/scss/components/form/_combo.scss b/packages/amis-ui/scss/components/form/_combo.scss index e90f64b52..8a636c20f 100644 --- a/packages/amis-ui/scss/components/form/_combo.scss +++ b/packages/amis-ui/scss/components/form/_combo.scss @@ -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 { diff --git a/packages/amis-ui/src/components/Tabs.tsx b/packages/amis-ui/src/components/Tabs.tsx index ea5c2f0f5..31d595899 100644 --- a/packages/amis-ui/src/components/Tabs.tsx +++ b/packages/amis-ui/src/components/Tabs.tsx @@ -50,6 +50,7 @@ export interface TabProps extends ThemeProps { tip?: string; tab?: Schema; className?: string; + tabClassName?: string; activeKey?: string | number; reload?: boolean; mountOnEnter?: boolean; diff --git a/packages/amis/__tests__/event-action/renderers/paginationWrapper.test.tsx b/packages/amis/__tests__/event-action/renderers/paginationWrapper.test.tsx new file mode 100644 index 000000000..378604351 --- /dev/null +++ b/packages/amis/__tests__/event-action/renderers/paginationWrapper.test.tsx @@ -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']); +}); diff --git a/packages/amis/__tests__/renderers/Form/combo.test.tsx b/packages/amis/__tests__/renderers/Form/combo.test.tsx index c40724bcd..965b47226 100644 --- a/packages/amis/__tests__/renderers/Form/combo.test.tsx +++ b/packages/amis/__tests__/renderers/Form/combo.test.tsx @@ -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 diff --git a/packages/amis/__tests__/renderers/Form/inputArray.test.tsx b/packages/amis/__tests__/renderers/Form/inputArray.test.tsx index 11a9fd293..df487804e 100644 --- a/packages/amis/__tests__/renderers/Form/inputArray.test.tsx +++ b/packages/amis/__tests__/renderers/Form/inputArray.test.tsx @@ -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( diff --git a/packages/amis/src/renderers/CRUD.tsx b/packages/amis/src/renderers/CRUD.tsx index 90fa09554..c95d5f2aa 100644 --- a/packages/amis/src/renderers/CRUD.tsx +++ b/packages/amis/src/renderers/CRUD.tsx @@ -915,8 +915,13 @@ export default class CRUD extends React.Component { 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, diff --git a/packages/amis/src/renderers/CRUD2.tsx b/packages/amis/src/renderers/CRUD2.tsx index 2d166cb4f..1bcaee206 100644 --- a/packages/amis/src/renderers/CRUD2.tsx +++ b/packages/amis/src/renderers/CRUD2.tsx @@ -465,7 +465,7 @@ export default class CRUD2 extends React.Component { : 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 { 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 - }) + }); + } }) ); } diff --git a/packages/amis/src/renderers/Form/Combo.tsx b/packages/amis/src/renderers/Form/Combo.tsx index 6c915e954..cdf2e5b77 100644 --- a/packages/amis/src/renderers/Form/Combo.tsx +++ b/packages/amis/src/renderers/Form/Combo.tsx @@ -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 { 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 { } 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 { } 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 { ); } + 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 { 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 { } 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 { (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 { // 不能按需渲染,因为 unique 会失效。 mountOnEnter={!hasUnique} unmountOnExit={false} + className={ + store.memberValidMap[index] === false ? 'has-error' : '' + } + tabClassName={ + store.memberValidMap[index] === false ? 'has-error' : '' + } > {condition && typeSwitchable !== false ? (
@@ -1483,7 +1593,8 @@ export default class ComboControl extends React.Component { 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 { return (
{!isStatic && !disabled && draggable && thelist.length > 1 ? ( @@ -1620,7 +1735,8 @@ export default class ComboControl extends React.Component { nullable, translate: __, itemClassName, - mobileUI + mobileUI, + store } = this.props; let items = this.props.items; @@ -1644,7 +1760,13 @@ export default class ComboControl extends React.Component { disabled ? 'is-disabled' : '' )} > -
+
{condition && typeSwitchable !== false ? (
@@ -1712,14 +1834,17 @@ export default class ComboControl extends React.Component { 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 { 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 } ); } diff --git a/packages/amis/src/renderers/Table/TableRow.tsx b/packages/amis/src/renderers/Table/TableRow.tsx index a5a1a42ee..fef111005 100644 --- a/packages/amis/src/renderers/Table/TableRow.tsx +++ b/packages/amis/src/renderers/Table/TableRow.tsx @@ -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}