mirror of
https://github.com/nocobase/nocobase.git
synced 2026-10-29 18:16:18 +08:00
Merge branch 'next' into develop
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
} from '@nocobase/flow-engine';
|
||||
import { Form, FormInstance } from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import { getValuesByPath } from '@nocobase/shared';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { commonConditionHandler, ConditionBuilder } from '../../../components/ConditionBuilder';
|
||||
import {
|
||||
@@ -306,6 +307,18 @@ export class FormBlockModel<
|
||||
const topValue = this.form?.getFieldValue?.(top);
|
||||
if (topValue == null) return false;
|
||||
if (Array.isArray(topValue) && topValue.length === 0) return false;
|
||||
|
||||
// 本地优先:支持对多关系的 dot 聚合路径(例如 assignees.name)。
|
||||
// lodash.get 对数组聚合路径会返回 undefined(如 _.get({ assignees:[{name:'A'}] }, 'assignees.name')),
|
||||
// 因而这里先用 getValuesByPath 做一次前端可解析性检查,命中则直接前端解析。
|
||||
const formValuesSnapshot = runtime.getFormValuesSnapshot();
|
||||
if (formValuesSnapshot && typeof formValuesSnapshot === 'object') {
|
||||
const localResolved = getValuesByPath(formValuesSnapshot as Record<string, any>, subPath);
|
||||
if (typeof localResolved !== 'undefined') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 已配置字段:仅关联字段的子路径按需服务端补全(保持现有语义)
|
||||
const assocResolver = createAssociationSubpathResolver(
|
||||
() => this.collection,
|
||||
|
||||
@@ -435,6 +435,48 @@ describe('FormBlockModel (form/formValues injection & server resolve anchors)',
|
||||
expect(api.request).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('configured toMany dot aggregation path uses local value and skips server', async () => {
|
||||
const model = await setupFormModel();
|
||||
|
||||
const api = {
|
||||
request: vi.fn(async () => {
|
||||
return { data: { ok: true } } as any;
|
||||
}),
|
||||
} as any;
|
||||
(model.flowEngine.context as any).defineProperty('api', { value: api });
|
||||
|
||||
function HookCaller() {
|
||||
model.useHooksBeforeRender();
|
||||
return null;
|
||||
}
|
||||
render(React.createElement(HookCaller));
|
||||
|
||||
const mem: Record<string, any> = {};
|
||||
const fakeForm = {
|
||||
setFieldsValue: (v: any) => Object.assign(mem, v),
|
||||
getFieldsValue: () => ({ ...mem }),
|
||||
getFieldValue: (namePath: any) => getByPath(mem, namePath),
|
||||
setFieldValue: (k: string, v: any) => (mem[k] = v),
|
||||
};
|
||||
(model.context as any).defineProperty('form', { value: fakeForm });
|
||||
fakeForm.setFieldsValue({
|
||||
assignees: [
|
||||
{ id: 3, name: 'A' },
|
||||
{ id: 5, name: 'B' },
|
||||
],
|
||||
});
|
||||
mockFormGridEnabledFields(model, ['assignees']);
|
||||
|
||||
const tpl = { names: '{{ ctx.formValues.assignees.name }}' } as any;
|
||||
const out = await (model.context as any).resolveJsonTemplate(tpl);
|
||||
expect(out).toEqual({ names: ['A', 'B'] });
|
||||
expect(api.request).toHaveBeenCalledTimes(0);
|
||||
|
||||
const names = await (model.context as any).getVar('ctx.formValues.assignees.name');
|
||||
expect(names).toEqual(['A', 'B']);
|
||||
expect(api.request).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('unconfigured field: new record returns undefined and does not call server', async () => {
|
||||
const model = await setupFormModel();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user