diff --git a/packages/core/client/src/flow/actions/confirm.tsx b/packages/core/client/src/flow/actions/confirm.tsx
index 75bdc8268f4..aa7fdb60690 100644
--- a/packages/core/client/src/flow/actions/confirm.tsx
+++ b/packages/core/client/src/flow/actions/confirm.tsx
@@ -43,8 +43,8 @@ export const confirm = defineAction({
},
defaultParams: {
enable: true,
- title: 'Please Confirm',
- content: 'Are you sure you want to proceed with this action?',
+ title: escapeT('Please Confirm'),
+ content: escapeT('Are you sure you want to perform the action?'),
},
async handler(ctx, params) {
if (params.enable) {
diff --git a/packages/core/client/src/flow/models/actions/UpdateActionModel.tsx b/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx
similarity index 80%
rename from packages/core/client/src/flow/models/actions/UpdateActionModel.tsx
rename to packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx
index ff991f59c5f..acb21e11dc2 100644
--- a/packages/core/client/src/flow/models/actions/UpdateActionModel.tsx
+++ b/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx
@@ -10,8 +10,10 @@
import { escapeT, FlowModelRenderer, useFlowEngine, useFlowSettingsContext } from '@nocobase/flow-engine';
import { Alert, ButtonProps } from 'antd';
import React, { useEffect, useRef } from 'react';
-import { ActionModel, ActionSceneEnum, CollectionActionModel, RecordActionModel } from '../base';
-import { AssignFormModel } from '../blocks';
+import { ActionModel, ActionSceneEnum } from '../base/ActionModel';
+import { CollectionActionModel } from '../base/CollectionActionModel';
+import { RecordActionModel } from '../base/RecordActionModel';
+import { AssignFormModel } from '../blocks/assign-form/AssignFormModel';
// import { RemoteFlowModelRenderer } from '../../FlowPage';
const SETTINGS_FLOW_KEY = 'assignSettings';
@@ -63,22 +65,26 @@ function AssignFieldsEditor() {
if (isBulk && (formModel as any)?.context?.defineProperty) {
formModel.context.defineProperty('record', { get: () => undefined });
}
- const grid = (formModel as any)?.subModels?.grid;
- const items = grid?.subModels?.items || [];
- for (const it of items) {
- const saved =
- typeof it?.getStepParams === 'function' ? it.getStepParams('fieldSettings', 'assignValue')?.value : undefined;
- if (typeof saved !== 'undefined') {
- (it as any).assignValue = saved;
- }
- }
initializedRef.current = true;
}, [action, blockModel?.collection, formModel]);
return formModel ? : null;
}
-export class UpdateActionModel extends ActionModel<{
+function Info() {
+ const ctx = useFlowSettingsContext();
+ return (
+
+ );
+}
+
+export class UpdateRecordActionModel extends ActionModel<{
subModels: {
assignForm: AssignFormModel;
};
@@ -88,7 +94,7 @@ export class UpdateActionModel extends ActionModel<{
assignFormUid?: string;
defaultProps: ButtonProps = {
- title: escapeT('Update'),
+ title: escapeT('Update record'),
type: 'link',
};
@@ -97,8 +103,8 @@ export class UpdateActionModel extends ActionModel<{
}
}
-UpdateActionModel.define({
- label: escapeT('Update'),
+UpdateRecordActionModel.define({
+ label: escapeT('Update record'),
// 使用函数型 createModelOptions,从父级上下文提取资源信息,直接注入到子模型的 resourceSettings.init
createModelOptions: (ctx) => {
const dsKey = ctx.collection.dataSourceKey;
@@ -116,7 +122,7 @@ UpdateActionModel.define({
},
});
-UpdateActionModel.registerFlow({
+UpdateRecordActionModel.registerFlow({
key: SETTINGS_FLOW_KEY,
title: escapeT('Action settings'),
// 配置流仅用于收集参数,避免作为自动流程执行
@@ -138,9 +144,7 @@ UpdateActionModel.registerFlow({
return {
tip: {
'x-decorator': 'FormItem',
- 'x-component': () => (
-
- ),
+ 'x-component': () => ,
},
editor: {
'x-decorator': 'FormItem',
@@ -149,31 +153,20 @@ UpdateActionModel.registerFlow({
};
},
async beforeParamsSave(ctx) {
- const m = ctx.model as UpdateActionModel;
- let form: AssignFormModel = m?.assignFormUid && ctx.engine.getModel?.(m.assignFormUid);
- if (!form && ctx.engine) {
- form = (await ctx.engine.loadModel({
- uid: m.assignFormUid || undefined,
- parentId: ctx.model.uid,
- subKey: 'assignForm',
- })) as any;
- }
+ const m = ctx.model as UpdateRecordActionModel;
+ // 跨视图栈按 uid 定位到设置面板中的真实 AssignForm 实例
+ const form: AssignFormModel = m?.assignFormUid && ctx.engine.getModel?.(m.assignFormUid, true);
if (!form) return;
const assignedValues = form?.getAssignedValues?.() || {};
const grid = form?.subModels?.grid;
const items = grid?.subModels?.items || [];
- for (const it of items) {
- if (typeof it?.setStepParams === 'function') {
- it.setStepParams('fieldSettings', 'assignValue', { value: it.assignValue });
- }
- }
ctx.model.setStepParams(SETTINGS_FLOW_KEY, 'assignFieldValues', { assignedValues });
},
},
},
});
-UpdateActionModel.registerFlow({
+UpdateRecordActionModel.registerFlow({
key: 'apply',
on: 'click',
steps: {
diff --git a/packages/core/client/src/flow/models/actions/__tests__/AssignFormRefill.test.ts b/packages/core/client/src/flow/models/actions/__tests__/AssignFormRefill.test.ts
new file mode 100644
index 00000000000..68edac3d049
--- /dev/null
+++ b/packages/core/client/src/flow/models/actions/__tests__/AssignFormRefill.test.ts
@@ -0,0 +1,55 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
+import { UpdateRecordActionModel } from '../UpdateRecordActionModel';
+
+/**
+ * 精简版 AssignFormModel(仅用于单测):
+ * - 避免依赖复杂上下文;专注验证 beforeParamsSave 的聚合入参写入逻辑。
+ */
+class TestAssignFormModel extends FlowModel {
+ private _values: Record = {};
+ setAssignedValues(v: Record) {
+ this._values = v || {};
+ }
+ getAssignedValues(): Record {
+ return this._values || {};
+ }
+}
+
+describe('AssignForm value refill and save (beforeParamsSave)', () => {
+ it('UpdateRecordActionModel: saves non-empty assignedValues from AssignForm', async () => {
+ const root = new FlowEngine();
+
+ // 仅 root 引擎:真实场景视图作用域引擎由弹窗创建,此处不需 link 模拟
+ root.registerModels({ UpdateRecordActionModel, AssignFormModel: TestAssignFormModel });
+
+ const action = root.createModel({ use: 'UpdateRecordActionModel', uid: 'act-u' });
+
+ const form = root.createModel({
+ use: 'AssignFormModel',
+ uid: 'form-u',
+ parentId: action.uid,
+ subKey: 'assignForm',
+ });
+ form.setAssignedValues({ nickname: 'Alice', score: 99 });
+ (action as any).assignFormUid = form.uid;
+
+ const flow = action.getFlow('assignSettings') as any;
+ const step = flow?.steps?.assignFieldValues;
+ expect(step?.beforeParamsSave).toBeTypeOf('function');
+
+ await step.beforeParamsSave({ engine: root, model: action });
+
+ const saved = action.getStepParams('assignSettings', 'assignFieldValues');
+ expect(saved?.assignedValues).toEqual({ nickname: 'Alice', score: 99 });
+ });
+});
diff --git a/packages/core/client/src/flow/models/actions/index.ts b/packages/core/client/src/flow/models/actions/index.ts
index 9541309485a..c93cb1ad37b 100644
--- a/packages/core/client/src/flow/models/actions/index.ts
+++ b/packages/core/client/src/flow/models/actions/index.ts
@@ -16,6 +16,6 @@ export * from './JSCollectionActionModel';
export * from './JSRecordActionModel';
export * from './PopupCollectionActionModel';
export * from './RefreshActionModel';
-export * from './UpdateActionModel';
+export * from './UpdateRecordActionModel';
export * from './ViewActionModel';
//
diff --git a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx
index 37690f32875..93655a87981 100644
--- a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx
+++ b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx
@@ -85,7 +85,17 @@ export class AssignFormGridModel extends FormGridModel {
addOrEnsureItem(fieldName: string, value?: any) {
const collection = (this.context as any)?.collection;
const items = this.subModels?.items || [];
- const existing = items.find((m) => m?.context.fieldPath === fieldName);
+ // AssignFormItemModel 并非 CollectionFieldModel,不存在 context.fieldPath,
+ // 需要依据步骤参数中声明的 fieldSettings.init.fieldPath 来判断是否已存在同名条目
+ const existing = items.find((m) => {
+ try {
+ const init =
+ typeof (m as any)?.getStepParams === 'function' ? (m as any).getStepParams('fieldSettings', 'init') : null;
+ return init?.fieldPath === fieldName;
+ } catch (_e) {
+ return false;
+ }
+ });
if (existing) {
// 更新已有项的当前值,便于重新打开时回填
if (typeof (existing as any)?.setStepParams === 'function') {
diff --git a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx
index b7fbf5c8703..04184ae721f 100644
--- a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx
+++ b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx
@@ -133,7 +133,7 @@ export class AssignFormItemModel extends FormItemModel {
});
if (!created) return;
- // 将集合/数据源/字段/区块/资源注入临时根,保证字段组件行为一致
+ // 将集合/数据源/字段/区块/资源/表单注入临时根,保证字段组件行为一致
created.context?.defineProperty?.('collection', { value: collection });
const ds = ctx?.dataSource;
if (ds) created.context?.defineProperty?.('dataSource', { value: ds });
@@ -141,6 +141,10 @@ export class AssignFormItemModel extends FormItemModel {
if (cf2) created.context?.defineProperty?.('collectionField', { value: cf2 });
const block = ctx?.blockModel;
if (block) created.context?.defineProperty?.('blockModel', { value: block });
+ const parentForm = ctx.form;
+ if (parentForm) {
+ created.context?.defineProperty?.('form', { value: parentForm });
+ }
if (created.context) {
Object.defineProperty(created.context, 'resource', {
configurable: true,
@@ -267,6 +271,7 @@ export class AssignFormItemModel extends FormItemModel {
}}
metaTree={mergedMetaTree}
converters={converters}
+ clearValue={''}
/>
);
diff --git a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormModel.tsx b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormModel.tsx
index 8fd89d1efd5..78eca36a381 100644
--- a/packages/core/client/src/flow/models/blocks/assign-form/AssignFormModel.tsx
+++ b/packages/core/client/src/flow/models/blocks/assign-form/AssignFormModel.tsx
@@ -7,15 +7,52 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { FlowModelRenderer, SingleRecordResource, createCollectionContextMeta, escapeT } from '@nocobase/flow-engine';
+import {
+ FlowModelRenderer,
+ SingleRecordResource,
+ createCollectionContextMeta,
+ escapeT,
+ createCurrentRecordMetaFactory,
+ type PropertyMetaFactory,
+} from '@nocobase/flow-engine';
import React from 'react';
+import { Form } from 'antd';
import { FormBlockModel, FormComponent } from '../form/FormBlockModel';
+import { FilterManager } from '../filter-manager/FilterManager';
/**
* 赋值配置表单
*/
// 使用范型标注 subModels.grid 的类型,提升类型提示与可读性
export class AssignFormModel extends FormBlockModel<{ subModels: { grid: any } }> {
+ // 覆盖:不注入 formValues(当前表单)到上下文元数据,避免变量树出现“当前表单”
+ useHooksBeforeRender() {
+ // 仍需提供 antd form 实例以保证 FormComponent 正常渲染
+ // 但不定义 `formValues`,从而不暴露“当前表单”变量
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [form] = Form.useForm();
+ this.context.defineProperty('form', { get: () => form });
+ }
+
+ onInit(options: any) {
+ super.onInit(options);
+ // 默认:补充 meta,使“当前记录”变量可用
+ const recordMeta: PropertyMetaFactory = createCurrentRecordMetaFactory(this.context, () => this.collection);
+ this.context.defineProperty('record', {
+ get: () => this.getCurrentRecord?.(),
+ cache: false,
+ resolveOnServer: true,
+ meta: recordMeta,
+ });
+ // 该模型常在“配置面板”场景独立使用,不一定挂在 BlockGridModel 下,
+ // 因此需要在本地补齐 filterManager,避免基础刷新流访问时出现空指针。
+ this.context.defineProperty('filterManager', {
+ once: true,
+ get: () => new FilterManager(this, options?.filterManager),
+ });
+ // 配置表单不需要在挂载时触发资源刷新,避免无意义的网络交互
+ this.isManualRefresh = true;
+ }
createResource(ctx: any, params: any) {
const resource = this.context.createResource(SingleRecordResource);
// 行为与 CreateFormModel 一致:视为新记录,避免额外 GET
diff --git a/packages/core/client/src/flow/models/blocks/form/CreateFormModel.tsx b/packages/core/client/src/flow/models/blocks/form/CreateFormModel.tsx
index fdf3c65c473..f1344613d77 100644
--- a/packages/core/client/src/flow/models/blocks/form/CreateFormModel.tsx
+++ b/packages/core/client/src/flow/models/blocks/form/CreateFormModel.tsx
@@ -18,7 +18,7 @@ import {
} from '@nocobase/flow-engine';
import { Space } from 'antd';
import React from 'react';
-import { BlockSceneEnum } from '../../base';
+import { BlockSceneEnum } from '../../base/BlockModel';
import { FormBlockModel, FormComponent } from './FormBlockModel';
// CreateFormModel - 专门用于新增记录
diff --git a/packages/core/client/src/flow/models/blocks/form/FormBlockModel.tsx b/packages/core/client/src/flow/models/blocks/form/FormBlockModel.tsx
index 032b9587242..8a1bbea321b 100644
--- a/packages/core/client/src/flow/models/blocks/form/FormBlockModel.tsx
+++ b/packages/core/client/src/flow/models/blocks/form/FormBlockModel.tsx
@@ -19,7 +19,8 @@ import {
import { Form, FormInstance } from 'antd';
import { omit } from 'lodash';
import React from 'react';
-import { BlockGridModel, CollectionBlockModel } from '../../base';
+import { BlockGridModel } from '../../base/BlockGridModel';
+import { CollectionBlockModel } from '../../base/CollectionBlockModel';
import { FormActionModel } from './FormActionModel';
import { FormGridModel } from './FormGridModel';
import { commonConditionHandler, ConditionBuilder } from '../../../components/ConditionBuilder';
diff --git a/packages/core/client/src/flow/models/fields/VariableFieldFormModel.tsx b/packages/core/client/src/flow/models/fields/VariableFieldFormModel.tsx
index 37f7f4e47f5..dcaa6936461 100644
--- a/packages/core/client/src/flow/models/fields/VariableFieldFormModel.tsx
+++ b/packages/core/client/src/flow/models/fields/VariableFieldFormModel.tsx
@@ -47,6 +47,16 @@ export class VariableFieldFormModel extends FlowModel {
{this.mapSubModels('fields', (field) => {
+ // 确保字段模型具备稳定的 id/name,便于依赖路径的组件(如公式字段)正确解析
+ const init = field?.getStepParams?.('fieldSettings', 'init') || {};
+ const fp = init?.fieldPath as string | undefined;
+ if (fp) {
+ const namePath = fp.includes('.') ? fp.split('.') : [fp];
+ const toSet: any = {};
+ if (!field?.props?.id) toSet.id = namePath;
+ if (!field?.props?.name) toSet.name = namePath;
+ if (Object.keys(toSet).length) field?.setProps?.(toSet);
+ }
return ;
})}
diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json
index 0e9bc79e3ca..475f92eab10 100644
--- a/packages/core/client/src/locale/en-US.json
+++ b/packages/core/client/src/locale/en-US.json
@@ -992,5 +992,6 @@
"No form available for reset.": "No form available for reset.",
"No form available for submission.": "No form available for submission.",
"Collapse button": "Collapse",
- "Expand button": "Expand"
+ "Expand button": "Expand",
+ "No assigned fields configured": "No assigned fields configured"
}
diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json
index 773c3cd569e..aa1a8263597 100644
--- a/packages/core/client/src/locale/zh-CN.json
+++ b/packages/core/client/src/locale/zh-CN.json
@@ -1393,6 +1393,7 @@
"Collapse settings": "折叠设置",
"Upload file settings": "文件上传设置",
"Allow selection of existing file": "允许选择已有文件",
+ "No assigned fields configured": "未配置字段",
"Current device type": "当前设备类型",
"Computer": "电脑",
"Mobile": "手机",
diff --git a/packages/core/flow-engine/src/components/variables/VariableInput.tsx b/packages/core/flow-engine/src/components/variables/VariableInput.tsx
index 5059af360b1..69c14af17eb 100644
--- a/packages/core/flow-engine/src/components/variables/VariableInput.tsx
+++ b/packages/core/flow-engine/src/components/variables/VariableInput.tsx
@@ -254,11 +254,29 @@ const VariableInputComponent: React.FC = ({
if (disabled) {
return;
}
- setCurrentMetaTreeNode(null);
const cleared = clearValue !== undefined ? clearValue : null;
setInnerValue(cleared);
+
+ // 若 clearValue 能解析到某个路径(例如 ['constant']),
+ // 则尝试立即定位到对应的 MetaTreeNode,以便渲染正确的常量组件。
+ try {
+ const path = resolvePathFromValue?.(cleared);
+ if (Array.isArray(resolvedMetaTree) && path && path.length > 0) {
+ const node = findMetaTreeNodeByPath(resolvedMetaTree as MetaTreeNode[], path as string[]);
+ if (node) {
+ setCurrentMetaTreeNode(node);
+ emitChange(cleared as any, node);
+ return;
+ }
+ }
+ } catch (_) {
+ // 忽略解析异常,走默认回退
+ }
+
+ // 默认回退(无法定位具体 MetaTreeNode 时)
+ setCurrentMetaTreeNode(null);
emitChange(cleared as any);
- }, [emitChange, disabled, clearValue]);
+ }, [emitChange, disabled, clearValue, resolvedMetaTree, resolvePathFromValue]);
const stableProps = useMemo(() => {
const { style, onFocus, onBlur, disabled, ...otherProps } = restProps;
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateActionModel.tsx b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateActionModel.tsx
index 8625d1dbabd..9185309a342 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateActionModel.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateActionModel.tsx
@@ -57,22 +57,9 @@ function AssignFieldsEditor() {
formModel.setInitialAssignedValues(prev?.assignedValues || {});
// 批量配置态:移除 ctx.record(Action 为区块级,不具备单条记录上下文)
// formModel.context.defineProperty('formValues', { get: () => undefined });
- // formModel.context.defineProperty('record', {
- // get: () => action.context.record,
- // meta: createCollectionContextMeta(
- // () => action.context.dataSourceManager.getDataSource(dsKey).getCollection(collName),
- // action.context.t('Current record'),
- // ),
- // });
- const grid = formModel?.subModels?.grid;
- const items = grid?.subModels?.items || [];
- for (const it of items) {
- const saved =
- typeof it?.getStepParams === 'function' ? it.getStepParams('fieldSettings', 'assignValue')?.value : undefined;
- if (typeof saved !== 'undefined') {
- (it as any).assignValue = saved;
- }
- }
+ formModel.context.defineProperty('record', {
+ get: () => undefined,
+ });
initializedRef.current = true;
}, [action, blockModel?.collection, formModel]);
@@ -128,7 +115,7 @@ BulkUpdateActionModel.registerFlow({
defaultParams: {
enable: false,
title: escapeT('Bulk update'),
- content: 'Are you sure you want to proceed with this action?',
+ content: escapeT('Are you sure you want to perform the Update record action?'),
},
},
updateMode: {
@@ -158,23 +145,11 @@ BulkUpdateActionModel.registerFlow({
},
async beforeParamsSave(ctx) {
const m = ctx.model as BulkUpdateActionModel;
- let form: AssignFormModel = (m?.assignFormUid && (ctx.engine.getModel?.(m.assignFormUid) as any)) as any;
- if (!form && ctx.engine) {
- form = (await ctx.engine.loadModel({
- uid: m.assignFormUid || undefined,
- parentId: ctx.model.uid,
- subKey: 'assignForm',
- })) as any;
- }
+ // 跨视图栈按 uid 定位到设置面板中的真实 AssignForm 实例
+ const form: AssignFormModel = (m?.assignFormUid &&
+ (ctx.engine.getModel?.(m.assignFormUid, true) as any)) as any;
if (!form) return;
const assignedValues = form?.getAssignedValues?.() || {};
- const grid = (form as any)?.subModels?.grid;
- const items = grid?.subModels?.items || [];
- for (const it of items) {
- if (typeof it?.setStepParams === 'function') {
- it.setStepParams('fieldSettings', 'assignValue', { value: (it as any).assignValue });
- }
- }
ctx.model.setStepParams(SETTINGS_FLOW_KEY, 'assignFieldValues', { assignedValues });
},
},