Merge branch 'next' into develop

This commit is contained in:
nocobase[bot]
2026-02-13 03:14:47 +00:00
2 changed files with 55 additions and 0 deletions
@@ -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();