From 623f979671ed761750e5fbfb5849b4de8cdbeaa4 Mon Sep 17 00:00:00 2001 From: gchust Date: Thu, 19 Jun 2025 10:49:44 +0800 Subject: [PATCH 1/9] fix: add sub models --- .../client/src/flow/models/TableModel.tsx | 8 +++++-- .../components/subModel/AddActionButton.tsx | 23 +++++++++++++++---- .../components/subModel/AddBlockButton.tsx | 8 +++---- .../components/subModel/AddFieldButton.tsx | 9 ++++++-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/core/client/src/flow/models/TableModel.tsx b/packages/core/client/src/flow/models/TableModel.tsx index b44b19f131..daa0169a73 100644 --- a/packages/core/client/src/flow/models/TableModel.tsx +++ b/packages/core/client/src/flow/models/TableModel.tsx @@ -10,6 +10,7 @@ import { SettingOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { + AddActionButton, AddActionModel, AddFieldButton, Collection, @@ -83,7 +84,10 @@ export class TableModel extends BlockFlowModel { extraContext={{ currentModel: this, currentResource: this.resource }} /> ))} - + + + {/* [ @@ -104,7 +108,7 @@ export class TableModel extends BlockFlowModel { ]} > - + */} boolean; + /** + * 自定义 items(如果提供,将覆盖默认的action菜单) + */ + items?: SubModelItemsType; } /** @@ -51,12 +59,17 @@ export const AddActionButton: React.FC = ({ subModelKey = 'actions', children = , subModelType = 'array', + items, + filter, onModelAdded, }) => { - const items = useMemo(() => { - const blockClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass); + const allActionsItems = useMemo(() => { + const actionClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass); const registeredBlocks = []; - for (const [className, ModelClass] of blockClasses) { + for (const [className, ModelClass] of actionClasses) { + if (filter && !filter(ModelClass, className)) { + continue; + } const item = { key: className, label: ModelClass.meta?.title || className, @@ -76,7 +89,7 @@ export const AddActionButton: React.FC = ({ model={model} subModelKey={subModelKey} subModelType={subModelType} - items={items} + items={items ?? allActionsItems} onModelAdded={onModelAdded} > {children} diff --git a/packages/core/flow-engine/src/components/subModel/AddBlockButton.tsx b/packages/core/flow-engine/src/components/subModel/AddBlockButton.tsx index c8423da652..df284dd10e 100644 --- a/packages/core/flow-engine/src/components/subModel/AddBlockButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddBlockButton.tsx @@ -34,13 +34,13 @@ interface AddBlockButtonProps { */ children?: React.ReactNode; /** - * 自定义 items(如果提供,将覆盖默认的数据源选择行为) + * 自定义 items(如果提供,将覆盖默认的区块菜单) */ items?: SubModelItemsType; /** - * 过滤区块类型的函数 + * 过滤Model菜单的函数 */ - filterBlocks?: (blockClass: ModelConstructor, className: string) => boolean; + filter?: (blockClass: ModelConstructor, className: string) => boolean; /** * 追加额外的菜单项到默认菜单中 */ @@ -80,7 +80,7 @@ export const AddBlockButton: React.FC = ({ children = , subModelType = 'array', items, - filterBlocks, + filter: filterBlocks, appendItems, onModelAdded, }) => { diff --git a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx index e33a9ad053..1968002d78 100644 --- a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx @@ -42,6 +42,10 @@ export interface AddFieldButtonProps { * 显示的UI组件 */ children?: React.ReactNode; + /** + * 自定义 items(如果提供,将覆盖默认的字段菜单) + */ + items?: SubModelItemsType; } /** @@ -63,6 +67,7 @@ export const AddFieldButton: React.FC = ({ subModelType = 'array', collection, buildCreateModelOptions, + items, appendItems, onModelAdded, }) => { @@ -117,7 +122,7 @@ export const AddFieldButton: React.FC = ({ }; }, [model, subModelBaseClass, fields, buildCreateModelOptions]); - const items = useMemo(() => { + const fieldItems = useMemo(() => { return mergeSubModelItems([buildFieldItems, appendItems], { addDividers: true }); }, [buildFieldItems, appendItems]); @@ -126,7 +131,7 @@ export const AddFieldButton: React.FC = ({ model={model} subModelKey={subModelKey} subModelType={subModelType} - items={items} + items={items ?? fieldItems} onModelAdded={onModelAdded} > {children} From f8b6edf282f880a3de1906ca1293924cefead786 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 12:14:18 +0800 Subject: [PATCH 2/9] fix: sharedContext --- .../src/flow/models/AddNewActionModel.tsx | 5 ++++- .../src/flow/models/BulkDeleteActionModel.tsx | 4 ++-- .../src/flow/models/DeleteActionModel.tsx | 10 ++++++++-- .../core/client/src/flow/models/FormModel.tsx | 9 ++++----- .../client/src/flow/models/LinkActionModel.tsx | 2 +- .../src/flow/models/SubmitActionModel.tsx | 18 +++++++++++------- .../src/flow/models/TableColumnModel.tsx | 7 +------ .../core/client/src/flow/models/TableModel.tsx | 8 ++------ .../client/src/flow/models/ViewActionModel.tsx | 9 ++++++++- .../core/flow-engine/src/models/flowModel.tsx | 14 +++++++++++++- packages/core/flow-engine/src/types.ts | 1 + 11 files changed, 55 insertions(+), 32 deletions(-) diff --git a/packages/core/client/src/flow/models/AddNewActionModel.tsx b/packages/core/client/src/flow/models/AddNewActionModel.tsx index 7eb35eff5d..c697cb8681 100644 --- a/packages/core/client/src/flow/models/AddNewActionModel.tsx +++ b/packages/core/client/src/flow/models/AddNewActionModel.tsx @@ -30,7 +30,10 @@ AddNewActionModel.registerFlow({ function DrawerContent() { return (
- +
); } diff --git a/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx b/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx index f69f1be350..074d900219 100644 --- a/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx @@ -23,11 +23,11 @@ BulkDeleteActionModel.registerFlow({ steps: { step1: { async handler(ctx, params) { - if (!ctx.extra.currentResource) { + if (!ctx.shared?.currentBlockModel?.resource) { ctx.globals.message.error('No resource selected for deletion.'); return; } - const resource = ctx.extra.currentResource as MultiRecordResource; + const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; if (resource.getSelectedRows().length === 0) { ctx.globals.message.warning('No records selected for deletion.'); return; diff --git a/packages/core/client/src/flow/models/DeleteActionModel.tsx b/packages/core/client/src/flow/models/DeleteActionModel.tsx index 39fb9109bc..69343ce890 100644 --- a/packages/core/client/src/flow/models/DeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/DeleteActionModel.tsx @@ -7,6 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { MultiRecordResource } from '@nocobase/flow-engine'; import type { ButtonType } from 'antd/es/button'; import React from 'react'; import { ActionModel } from './ActionModel'; @@ -54,11 +55,16 @@ DeleteActionModel.registerFlow({ }, step1: { async handler(ctx, params) { - if (!ctx.extra.currentResource || !ctx.extra.currentRecord) { + if (!ctx.shared?.currentBlockModel?.resource) { + ctx.globals.message.error('No resource selected for deletion.'); + return; + } + if (!ctx.shared.currentRecord) { ctx.globals.message.error('No resource or record selected for deletion.'); return; } - await ctx.extra.currentResource.destroy(ctx.extra.currentRecord); + const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; + await resource.destroy(ctx.shared.currentRecord); ctx.globals.message.success('Record deleted successfully.'); }, }, diff --git a/packages/core/client/src/flow/models/FormModel.tsx b/packages/core/client/src/flow/models/FormModel.tsx index 5aef11b2a2..8a324ef005 100644 --- a/packages/core/client/src/flow/models/FormModel.tsx +++ b/packages/core/client/src/flow/models/FormModel.tsx @@ -33,7 +33,7 @@ export class FormModel extends BlockFlowModel { {this.mapSubModels('fields', (field) => ( - + ))} {this.mapSubModels('actions', (action) => ( - + ))} @@ -118,9 +118,8 @@ FormModel.registerFlow({ resource.setAPIClient(ctx.globals.api); ctx.model.resource = resource; } - console.log('FormModel flow context', ctx.shared, ctx.model.getSharedContext()); - if (ctx.shared.currentRecord) { - ctx.model.resource.setFilterByTk(ctx.shared.currentRecord.id); + if (ctx.shared.parentRecord) { + ctx.model.resource.setFilterByTk(ctx.shared.parentRecord.id); await ctx.model.resource.refresh(); ctx.model.form.setInitialValues(ctx.model.resource.getData()); } diff --git a/packages/core/client/src/flow/models/LinkActionModel.tsx b/packages/core/client/src/flow/models/LinkActionModel.tsx index 0125c985c3..1d063e89d6 100644 --- a/packages/core/client/src/flow/models/LinkActionModel.tsx +++ b/packages/core/client/src/flow/models/LinkActionModel.tsx @@ -25,7 +25,7 @@ LinkActionModel.registerFlow({ step1: { handler(ctx, params) { ctx.globals.modal.confirm({ - title: `${ctx.extra.currentRecord?.id}`, + title: `${ctx.shared.currentRecord?.id}`, content: 'Are you sure you want to perform this action?', }); }, diff --git a/packages/core/client/src/flow/models/SubmitActionModel.tsx b/packages/core/client/src/flow/models/SubmitActionModel.tsx index f10f99cef7..dbd9a9f9cd 100644 --- a/packages/core/client/src/flow/models/SubmitActionModel.tsx +++ b/packages/core/client/src/flow/models/SubmitActionModel.tsx @@ -25,17 +25,21 @@ SubmitActionModel.registerFlow({ steps: { step1: { async handler(ctx, params) { - if (ctx.extra.currentModel) { - await ctx.extra.currentModel.form.submit(); - const values = ctx.extra.currentModel.form.values; - await ctx.extra.currentModel.resource.save(values); + if (!ctx.shared?.currentBlockModel?.resource) { + ctx.globals.message.error('No resource selected for submission.'); + return; } + const currentBlockModel = ctx.shared.currentBlockModel; + const currentResource = ctx.shared.currentBlockModel.resource; + await currentBlockModel.form.submit(); + const values = currentBlockModel.form.values; + await currentBlockModel.resource.save(values); + console.log('Form submitted successfully:', ctx.shared.parentBlockModel); + // currentResource.refresh(); + ctx.shared.parentBlockModel?.resource?.refresh(); if (ctx.shared.currentDrawer) { ctx.shared.currentDrawer.destroy(); } - if (ctx.shared.currentResource) { - ctx.shared.currentResource.refresh(); - } }, }, }, diff --git a/packages/core/client/src/flow/models/TableColumnModel.tsx b/packages/core/client/src/flow/models/TableColumnModel.tsx index 72e1bd5c4f..4ca138b572 100644 --- a/packages/core/client/src/flow/models/TableColumnModel.tsx +++ b/packages/core/client/src/flow/models/TableColumnModel.tsx @@ -100,12 +100,7 @@ const Columns = observer(({ record, model, index }) => { {model.mapSubModels('actions', (action: ActionModel) => { const fork = action.createFork({}, `${index}`); return ( - + ); })} diff --git a/packages/core/client/src/flow/models/TableModel.tsx b/packages/core/client/src/flow/models/TableModel.tsx index b44b19f131..ea7ebc229a 100644 --- a/packages/core/client/src/flow/models/TableModel.tsx +++ b/packages/core/client/src/flow/models/TableModel.tsx @@ -77,11 +77,7 @@ export class TableModel extends BlockFlowModel { {this.mapSubModels('actions', (action) => ( - + ))} - + ); } diff --git a/packages/core/flow-engine/src/models/flowModel.tsx b/packages/core/flow-engine/src/models/flowModel.tsx index 9fc633133d..fc60b8e41f 100644 --- a/packages/core/flow-engine/src/models/flowModel.tsx +++ b/packages/core/flow-engine/src/models/flowModel.tsx @@ -95,6 +95,10 @@ export class FlowModel(subKey: K, extra?: Record) { + async applySubModelsAutoFlows( + subKey: K, + extra?: Record, + shared?: Record, + ) { await Promise.all( this.mapSubModels(subKey, async (column) => { + column.setSharedContext(shared); await column.applyAutoFlows(extra); }), ); @@ -731,6 +740,9 @@ export class FlowModel { uid: string; + async?: boolean; // 是否异步加载模型 props?: IModelComponentProps; stepParams?: Record; subModels?: Structure['subModels']; From aa4ea33698cee1a2723be7bc33964ce4f846fa65 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 13:54:45 +0800 Subject: [PATCH 3/9] fix: fork model --- .../flow-engine/src/models/forkFlowModel.ts | 394 +++++++++--------- 1 file changed, 206 insertions(+), 188 deletions(-) diff --git a/packages/core/flow-engine/src/models/forkFlowModel.ts b/packages/core/flow-engine/src/models/forkFlowModel.ts index 7c51dd2602..53100071b8 100644 --- a/packages/core/flow-engine/src/models/forkFlowModel.ts +++ b/packages/core/flow-engine/src/models/forkFlowModel.ts @@ -7,191 +7,209 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ - import { action, define, observable } from '@formily/reactive'; - import type { IModelComponentProps } from '../types'; - import { FlowModel } from './flowModel'; - - /** - * ForkFlowModel 作为 FlowModel 的轻量代理实例: - * - 共享 master(原始 FlowModel)上的所有业务数据与方法 - * - 仅在 props 层面拥有本地覆盖(localProps),其余字段全部透传到 master - * - 透传的函数中 this 指向 fork 实例,而非 master,确保正确的上下文 - * - 使用 Object.create 创建临时上下文,确保 this.constructor 指向正确的类(避免异步竞态条件) - * - setter 方法中的 this 也指向 fork 实例,保持一致的上下文行为 - * - 不会被注册到 FlowEngine.modelInstances 中,保持 uid → master 唯一性假设 - */ - export class ForkFlowModel { - /** 与 master 相同的 UID,用于日志调试 */ - public readonly uid: string; - /** 调试标识,便于在日志或断言中快速识别 */ - public readonly isFork = true; - - /** 本地覆盖的 props,fork 层面的 UI/状态 */ - public localProps: IModelComponentProps; - - /** master 引用 */ - private master: TMaster; - - /** 是否已被释放 */ - private disposed = false; - - /** fork 在 master.forks 中的索引 */ - public readonly forkId: number; - - constructor(master: TMaster, initialProps: IModelComponentProps = {}, forkId = 0) { - this.master = master; - this.uid = master.uid; - this.localProps = { ...initialProps }; - this.forkId = forkId; - - define(this, { - localProps: observable, - setProps: action, - }); - - // 返回代理对象,实现自动透传 - return new Proxy(this, { - get: (target: any, prop: PropertyKey, receiver: any) => { - // disposed check - if (prop === 'disposed') return target.disposed; - - // 特殊处理 constructor,应该返回 master 的 constructor - if (prop === 'constructor') { - return target.master.constructor; - } - if (prop === 'props') { - // 对 props 做合并返回 - return { ...target.master.getProps(), ...target.localProps }; - } - // fork 自身属性 / 方法优先 - if (prop in target) { - return Reflect.get(target, prop, receiver); - } - - // 默认取 master 上的值 - const value = (target.master as any)[prop]; - - // 如果是函数,需要绑定到 fork 实例,让 this 指向 fork - // 使用闭包捕获正确的 constructor,避免异步方法中的竞态条件 - if (typeof value === 'function') { - const masterConstructor = target.master.constructor; - return function (this: any, ...args: any[]) { - // 创建一个临时的 this 对象,包含正确的 constructor - const contextThis = Object.create(this); - Object.defineProperty(contextThis, 'constructor', { - value: masterConstructor, - configurable: true, - enumerable: false, - writable: false, - }); - - return value.apply(contextThis, args); - }.bind(receiver); - } - return value; - }, - set: (target: any, prop: PropertyKey, value: any, receiver: any) => { - if (prop === 'props') { - return true; - } - - // 如果 fork 自带字段,则写到自身(例如 localProps) - if (prop in target) { - return Reflect.set(target, prop, value, receiver); - } - - // 其余写入 master,但需要确保 setter 中的 this 指向 fork - // 检查 master 上是否有对应的 setter - const descriptor = this.getPropertyDescriptor(target.master, prop); - if (descriptor && descriptor.set) { - // 如果有 setter,直接用 receiver(fork 实例)作为 this 调用 - // 这样 setter 中的 this 就指向 fork,可以正确调用 fork 的方法 - descriptor.set.call(receiver, value); - return true; - } else { - // 没有 setter,直接赋值到 master - (target.master as any)[prop] = value; - return true; - } - }, - }); - } - - /** - * 获取对象及其原型链上的属性描述符 - */ - private getPropertyDescriptor(obj: any, prop: PropertyKey): PropertyDescriptor | undefined { - let current = obj; - while (current) { - const descriptor = Object.getOwnPropertyDescriptor(current, prop); - if (descriptor) { - return descriptor; - } - current = Object.getPrototypeOf(current); - } - return undefined; - } - - /** - * 修改局部 props,仅影响当前 fork - */ - setProps(key: string | IModelComponentProps, value?: any): void { - if (this.disposed) return; - - if (typeof key === 'string') { - this.localProps[key] = value; - } else { - this.localProps = { ...this.localProps, ...key }; - } - } - - /** - * render 依旧使用 master 的方法,但合并后的 props 需要透传 - */ - render() { - if (this.disposed) return null; - // 将 master.render 以 fork 作为 this 调用,使其读取到合并后的 props - const mergedProps = { ...this.master.getProps(), ...this.localProps }; - // 临时替换 this.props - const originalProps = (this as any).props; - (this as any).props = mergedProps; - try { - return (this.master.render as any).call(this); - } finally { - (this as any).props = originalProps; - } - } - - /** - * 释放 fork:从 master.forks 中移除自身并断开引用 - */ - dispose() { - if (this.disposed) return; - this.disposed = true; - if (this.master && (this.master as any).forks) { - (this.master as any).forks.delete(this as any); - } - // 从 master 的 forkCache 中移除自己 - if (this.master && (this.master as any).forkCache) { - const forkCache = (this.master as any).forkCache; - for (const [key, fork] of forkCache.entries()) { - if (fork === this) { - forkCache.delete(key); - break; - } - } - } - // @ts-ignore - this.master = null; - } - - /** - * 获取合并后的 props(master + localProps,local 优先) - */ - getProps(): IModelComponentProps { - return { ...this.master.getProps(), ...this.localProps }; - } - } - - // 类型断言:让 ForkFlowModel 可以被当作 FlowModel 使用 - export interface ForkFlowModel extends FlowModel {} \ No newline at end of file +import { action, define, observable } from '@formily/reactive'; +import type { IModelComponentProps } from '../types'; +import { FlowModel } from './flowModel'; + +/** + * ForkFlowModel 作为 FlowModel 的轻量代理实例: + * - 共享 master(原始 FlowModel)上的所有业务数据与方法 + * - 仅在 props 层面拥有本地覆盖(localProps),其余字段全部透传到 master + * - 透传的函数中 this 指向 fork 实例,而非 master,确保正确的上下文 + * - 使用 Object.create 创建临时上下文,确保 this.constructor 指向正确的类(避免异步竞态条件) + * - setter 方法中的 this 也指向 fork 实例,保持一致的上下文行为 + * - 不会被注册到 FlowEngine.modelInstances 中,保持 uid → master 唯一性假设 + */ +export class ForkFlowModel { + /** 与 master 相同的 UID,用于日志调试 */ + public readonly uid: string; + /** 调试标识,便于在日志或断言中快速识别 */ + public readonly isFork = true; + + /** 本地覆盖的 props,fork 层面的 UI/状态 */ + public localProps: IModelComponentProps; + + /** master 引用 */ + private master: TMaster; + + /** 是否已被释放 */ + private disposed = false; + + /** fork 在 master.forks 中的索引 */ + public readonly forkId: number; + + /** 用于共享上下文的对象,存储跨 fork 的共享数据 */ + private _sharedContext: Record = {}; + + constructor(master: TMaster, initialProps: IModelComponentProps = {}, forkId = 0) { + this.master = master; + this.uid = master.uid; + this.localProps = { ...initialProps }; + this.forkId = forkId; + + define(this, { + localProps: observable, + setProps: action, + }); + + // 返回代理对象,实现自动透传 + return new Proxy(this, { + get: (target: any, prop: PropertyKey, receiver: any) => { + // disposed check + if (prop === 'disposed') return target.disposed; + + // 特殊处理 constructor,应该返回 master 的 constructor + if (prop === 'constructor') { + return target.master.constructor; + } + if (prop === 'props') { + // 对 props 做合并返回 + return { ...target.master.getProps(), ...target.localProps }; + } + // fork 自身属性 / 方法优先 + if (prop in target) { + return Reflect.get(target, prop, receiver); + } + + // 默认取 master 上的值 + const value = (target.master as any)[prop]; + + // 如果是函数,需要绑定到 fork 实例,让 this 指向 fork + // 使用闭包捕获正确的 constructor,避免异步方法中的竞态条件 + if (typeof value === 'function') { + const masterConstructor = target.master.constructor; + return function (this: any, ...args: any[]) { + // 创建一个临时的 this 对象,包含正确的 constructor + const contextThis = Object.create(this); + Object.defineProperty(contextThis, 'constructor', { + value: masterConstructor, + configurable: true, + enumerable: false, + writable: false, + }); + + return value.apply(contextThis, args); + }.bind(receiver); + } + return value; + }, + set: (target: any, prop: PropertyKey, value: any, receiver: any) => { + if (prop === 'props') { + return true; + } + + // 如果 fork 自带字段,则写到自身(例如 localProps) + if (prop in target) { + return Reflect.set(target, prop, value, receiver); + } + + // 其余写入 master,但需要确保 setter 中的 this 指向 fork + // 检查 master 上是否有对应的 setter + const descriptor = this.getPropertyDescriptor(target.master, prop); + if (descriptor && descriptor.set) { + // 如果有 setter,直接用 receiver(fork 实例)作为 this 调用 + // 这样 setter 中的 this 就指向 fork,可以正确调用 fork 的方法 + descriptor.set.call(receiver, value); + return true; + } else { + // 没有 setter,直接赋值到 master + (target.master as any)[prop] = value; + return true; + } + }, + }); + } + + public setSharedContext(ctx: Record) { + this._sharedContext = ctx; + } + + public getSharedContext() { + if (this.async || !this.parent) { + return this._sharedContext; + } + return { + ...this.parent?.getSharedContext(), + ...this._sharedContext, // 当前实例的 context 优先级最高 + }; + } + + /** + * 获取对象及其原型链上的属性描述符 + */ + private getPropertyDescriptor(obj: any, prop: PropertyKey): PropertyDescriptor | undefined { + let current = obj; + while (current) { + const descriptor = Object.getOwnPropertyDescriptor(current, prop); + if (descriptor) { + return descriptor; + } + current = Object.getPrototypeOf(current); + } + return undefined; + } + + /** + * 修改局部 props,仅影响当前 fork + */ + setProps(key: string | IModelComponentProps, value?: any): void { + if (this.disposed) return; + + if (typeof key === 'string') { + this.localProps[key] = value; + } else { + this.localProps = { ...this.localProps, ...key }; + } + } + + /** + * render 依旧使用 master 的方法,但合并后的 props 需要透传 + */ + render() { + if (this.disposed) return null; + // 将 master.render 以 fork 作为 this 调用,使其读取到合并后的 props + const mergedProps = { ...this.master.getProps(), ...this.localProps }; + // 临时替换 this.props + const originalProps = (this as any).props; + (this as any).props = mergedProps; + try { + return (this.master.render as any).call(this); + } finally { + (this as any).props = originalProps; + } + } + + /** + * 释放 fork:从 master.forks 中移除自身并断开引用 + */ + dispose() { + if (this.disposed) return; + this.disposed = true; + if (this.master && (this.master as any).forks) { + (this.master as any).forks.delete(this as any); + } + // 从 master 的 forkCache 中移除自己 + if (this.master && (this.master as any).forkCache) { + const forkCache = (this.master as any).forkCache; + for (const [key, fork] of forkCache.entries()) { + if (fork === this) { + forkCache.delete(key); + break; + } + } + } + // @ts-ignore + this.master = null; + } + + /** + * 获取合并后的 props(master + localProps,local 优先) + */ + getProps(): IModelComponentProps { + return { ...this.master.getProps(), ...this.localProps }; + } +} + +// 类型断言:让 ForkFlowModel 可以被当作 FlowModel 使用 +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ForkFlowModel extends FlowModel {} From d9ad97fb17eaaf5d2339cb31f9b06120b961a8ea Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 14:04:04 +0800 Subject: [PATCH 4/9] fix: type error --- packages/core/flow-engine/src/models/flowModel.tsx | 5 ++++- packages/core/flow-engine/src/models/forkFlowModel.ts | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/flow-engine/src/models/flowModel.tsx b/packages/core/flow-engine/src/models/flowModel.tsx index fc60b8e41f..07acfd004a 100644 --- a/packages/core/flow-engine/src/models/flowModel.tsx +++ b/packages/core/flow-engine/src/models/flowModel.tsx @@ -58,7 +58,10 @@ export class FlowModel> = new Map(); - // model 树的共享运行上下文 + + /** + * model 树的共享运行上下文 + */ private _sharedContext: Record = {}; constructor(options: FlowModelOptions) { diff --git a/packages/core/flow-engine/src/models/forkFlowModel.ts b/packages/core/flow-engine/src/models/forkFlowModel.ts index 53100071b8..921518dc09 100644 --- a/packages/core/flow-engine/src/models/forkFlowModel.ts +++ b/packages/core/flow-engine/src/models/forkFlowModel.ts @@ -39,7 +39,7 @@ export class ForkFlowModel { public readonly forkId: number; /** 用于共享上下文的对象,存储跨 fork 的共享数据 */ - private _sharedContext: Record = {}; + // private _sharedContext: Record = {}; constructor(master: TMaster, initialProps: IModelComponentProps = {}, forkId = 0) { this.master = master; @@ -121,16 +121,16 @@ export class ForkFlowModel { } public setSharedContext(ctx: Record) { - this._sharedContext = ctx; + this['_sharedContext'] = ctx; } public getSharedContext() { if (this.async || !this.parent) { - return this._sharedContext; + return this['_sharedContext'] || {}; } return { ...this.parent?.getSharedContext(), - ...this._sharedContext, // 当前实例的 context 优先级最高 + ...this['_sharedContext'], // 当前实例的 context 优先级最高 }; } From 4388daea08e6d6f13b6ef861fb7e2e9ec0497db4 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 17:54:46 +0800 Subject: [PATCH 5/9] feat: improve code --- .../src/flow/models/DeleteActionModel.tsx | 4 +- .../src/flow/models/FilterActionModel.tsx | 71 +++++++++++++++++++ .../src/flow/models/LinkActionModel.tsx | 2 +- .../src/flow/models/RefreshActionModel.tsx | 33 +++++++++ .../src/flow/models/SubmitActionModel.tsx | 2 +- .../src/flow/models/TableColumnModel.tsx | 2 +- .../src/flow/models/ViewActionModel.tsx | 2 +- packages/core/client/src/flow/models/index.ts | 2 + .../core/flow-engine/src/models/flowModel.tsx | 7 ++ .../flow-engine/src/models/forkFlowModel.ts | 7 ++ .../src/resources/baseRecordResource.ts | 20 +++++- 11 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 packages/core/client/src/flow/models/FilterActionModel.tsx create mode 100644 packages/core/client/src/flow/models/RefreshActionModel.tsx diff --git a/packages/core/client/src/flow/models/DeleteActionModel.tsx b/packages/core/client/src/flow/models/DeleteActionModel.tsx index 69343ce890..2f0db1dc57 100644 --- a/packages/core/client/src/flow/models/DeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/DeleteActionModel.tsx @@ -59,12 +59,12 @@ DeleteActionModel.registerFlow({ ctx.globals.message.error('No resource selected for deletion.'); return; } - if (!ctx.shared.currentRecord) { + if (!ctx.extra.currentRecord) { ctx.globals.message.error('No resource or record selected for deletion.'); return; } const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; - await resource.destroy(ctx.shared.currentRecord); + await resource.destroy(ctx.extra.currentRecord); ctx.globals.message.success('Record deleted successfully.'); }, }, diff --git a/packages/core/client/src/flow/models/FilterActionModel.tsx b/packages/core/client/src/flow/models/FilterActionModel.tsx new file mode 100644 index 0000000000..f410e18eed --- /dev/null +++ b/packages/core/client/src/flow/models/FilterActionModel.tsx @@ -0,0 +1,71 @@ +/** + * 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 { MultiRecordResource } from '@nocobase/flow-engine'; +import { Button, Input, Popover } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import { ActionModel } from './ActionModel'; + +export class FilterActionModel extends ActionModel { + title = 'Filter'; + + render() { + return ( + + { + const resource = this.ctx.shared?.currentBlockModel?.resource as MultiRecordResource; + if (!resource) { + return; + } + resource.addFilterGroup(this.uid, { + $or: [ + { ['nickname.$includes']: e.target.value }, + { ['email.$includes']: e.target.value }, + { ['phone.$includes']: e.target.value }, + ], + }); + resource.refresh(); + }, 500)} + /> + + } + trigger="click" + placement="bottom" + > + + + ); + } +} + +FilterActionModel.registerFlow({ + key: 'event1', + on: { + eventName: 'click', + }, + steps: { + step1: { + async handler(ctx, params) { + if (!ctx.shared?.currentBlockModel?.resource) { + ctx.globals.message.error('No resource selected for refresh.'); + return; + } + const currentBlockModel = ctx.shared.currentBlockModel; + await currentBlockModel.resource.refresh(); + }, + }, + }, +}); diff --git a/packages/core/client/src/flow/models/LinkActionModel.tsx b/packages/core/client/src/flow/models/LinkActionModel.tsx index 1d063e89d6..0125c985c3 100644 --- a/packages/core/client/src/flow/models/LinkActionModel.tsx +++ b/packages/core/client/src/flow/models/LinkActionModel.tsx @@ -25,7 +25,7 @@ LinkActionModel.registerFlow({ step1: { handler(ctx, params) { ctx.globals.modal.confirm({ - title: `${ctx.shared.currentRecord?.id}`, + title: `${ctx.extra.currentRecord?.id}`, content: 'Are you sure you want to perform this action?', }); }, diff --git a/packages/core/client/src/flow/models/RefreshActionModel.tsx b/packages/core/client/src/flow/models/RefreshActionModel.tsx new file mode 100644 index 0000000000..38d2f87d93 --- /dev/null +++ b/packages/core/client/src/flow/models/RefreshActionModel.tsx @@ -0,0 +1,33 @@ +/** + * 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 { ActionModel } from './ActionModel'; + +export class RefreshActionModel extends ActionModel { + title = 'Refresh'; +} + +RefreshActionModel.registerFlow({ + key: 'event1', + on: { + eventName: 'click', + }, + steps: { + step1: { + async handler(ctx, params) { + if (!ctx.shared?.currentBlockModel?.resource) { + ctx.globals.message.error('No resource selected for refresh.'); + return; + } + const currentBlockModel = ctx.shared.currentBlockModel; + await currentBlockModel.resource.refresh(); + }, + }, + }, +}); diff --git a/packages/core/client/src/flow/models/SubmitActionModel.tsx b/packages/core/client/src/flow/models/SubmitActionModel.tsx index dbd9a9f9cd..5ebdb4e88d 100644 --- a/packages/core/client/src/flow/models/SubmitActionModel.tsx +++ b/packages/core/client/src/flow/models/SubmitActionModel.tsx @@ -34,7 +34,7 @@ SubmitActionModel.registerFlow({ await currentBlockModel.form.submit(); const values = currentBlockModel.form.values; await currentBlockModel.resource.save(values); - console.log('Form submitted successfully:', ctx.shared.parentBlockModel); + await currentBlockModel.form.reset(); // currentResource.refresh(); ctx.shared.parentBlockModel?.resource?.refresh(); if (ctx.shared.currentDrawer) { diff --git a/packages/core/client/src/flow/models/TableColumnModel.tsx b/packages/core/client/src/flow/models/TableColumnModel.tsx index 4ca138b572..32a8b65b80 100644 --- a/packages/core/client/src/flow/models/TableColumnModel.tsx +++ b/packages/core/client/src/flow/models/TableColumnModel.tsx @@ -100,7 +100,7 @@ const Columns = observer(({ record, model, index }) => { {model.mapSubModels('actions', (action: ActionModel) => { const fork = action.createFork({}, `${index}`); return ( - + ); })} diff --git a/packages/core/client/src/flow/models/ViewActionModel.tsx b/packages/core/client/src/flow/models/ViewActionModel.tsx index 2f990b36ed..c100f244ba 100644 --- a/packages/core/client/src/flow/models/ViewActionModel.tsx +++ b/packages/core/client/src/flow/models/ViewActionModel.tsx @@ -35,7 +35,7 @@ ViewActionModel.registerFlow({ parentId={ctx.model.uid} sharedContext={{ currentDrawer, - parentRecord: ctx.shared.currentRecord, + parentRecord: ctx.extra.currentRecord, parentBlockModel: ctx.shared.currentBlockModel, }} /> diff --git a/packages/core/client/src/flow/models/index.ts b/packages/core/client/src/flow/models/index.ts index 6979e2fd9c..645c1fc9ce 100644 --- a/packages/core/client/src/flow/models/index.ts +++ b/packages/core/client/src/flow/models/index.ts @@ -14,6 +14,7 @@ export * from './BlockGridFlowModel'; export * from './BulkDeleteActionModel'; export * from './CalendarBlockFlowModel'; export * from './DeleteActionModel'; +export * from './FilterActionModel'; export * from './FormFieldModel'; export * from './FormModel'; export * from './HtmlBlockFlowModel'; @@ -21,6 +22,7 @@ export * from './LinkActionModel'; export * from './PageFlowModel'; export * from './PageTabFlowModel'; export * from './QuickEditForm'; +export * from './RefreshActionModel'; export * from './SubmitActionModel'; export * from './TableColumnModel'; export * from './TableModel'; diff --git a/packages/core/flow-engine/src/models/flowModel.tsx b/packages/core/flow-engine/src/models/flowModel.tsx index 07acfd004a..b0aa0a1edf 100644 --- a/packages/core/flow-engine/src/models/flowModel.tsx +++ b/packages/core/flow-engine/src/models/flowModel.tsx @@ -738,6 +738,13 @@ export class FlowModel) { this._sharedContext = ctx; } diff --git a/packages/core/flow-engine/src/models/forkFlowModel.ts b/packages/core/flow-engine/src/models/forkFlowModel.ts index 921518dc09..6942c29759 100644 --- a/packages/core/flow-engine/src/models/forkFlowModel.ts +++ b/packages/core/flow-engine/src/models/forkFlowModel.ts @@ -134,6 +134,13 @@ export class ForkFlowModel { }; } + get ctx() { + return { + globals: this.flowEngine.getContext(), + shared: this.getSharedContext(), + }; + } + /** * 获取对象及其原型链上的属性描述符 */ diff --git a/packages/core/flow-engine/src/resources/baseRecordResource.ts b/packages/core/flow-engine/src/resources/baseRecordResource.ts index 0f174408f0..46ec1c955a 100644 --- a/packages/core/flow-engine/src/resources/baseRecordResource.ts +++ b/packages/core/flow-engine/src/resources/baseRecordResource.ts @@ -31,6 +31,8 @@ export abstract class BaseRecordResource extends APIResource headers: {} as Record, }; + protected filterGroups = new Map(); + protected splitValue(value: string | string[]): string[] { if (typeof value === 'string') { return value.split(',').map((item) => item.trim()); @@ -104,7 +106,23 @@ export abstract class BaseRecordResource extends APIResource } getFilter(): Record { - return this.request.params.filter; + return { + $and: [...this.filterGroups.values()].filter(Boolean), + }; + } + + resetFilter() { + this.setFilter(this.getFilter()); + } + + addFilterGroup(key: string, filter) { + this.filterGroups.set(key, filter); + this.resetFilter(); + } + + removeFilterGroup(key: string) { + this.filterGroups.delete(key); + this.resetFilter(); } setAppends(appends: string[]) { From ff89d32ca7cc7b06e37c6fded799792958895d59 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 18:29:35 +0800 Subject: [PATCH 6/9] feat: add filter form --- .../client/src/flow/models/BlockFlowModel.tsx | 7 +- .../src/flow/models/FilterFormActionModel.tsx | 79 ++++++++++++ .../src/flow/models/FilterFormModel.tsx | 112 ++++++++++++++++++ .../core/client/src/flow/models/FormModel.tsx | 4 +- .../client/src/flow/models/TableModel.tsx | 3 +- packages/core/client/src/flow/models/index.ts | 2 + packages/core/flow-engine/src/flowEngine.ts | 4 + .../src/resources/baseRecordResource.ts | 2 +- 8 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 packages/core/client/src/flow/models/FilterFormActionModel.tsx create mode 100644 packages/core/client/src/flow/models/FilterFormModel.tsx diff --git a/packages/core/client/src/flow/models/BlockFlowModel.tsx b/packages/core/client/src/flow/models/BlockFlowModel.tsx index 9005a26639..a025826379 100644 --- a/packages/core/client/src/flow/models/BlockFlowModel.tsx +++ b/packages/core/client/src/flow/models/BlockFlowModel.tsx @@ -7,6 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { DefaultStructure, FlowModel } from '@nocobase/flow-engine'; +import { Collection, DefaultStructure, FlowModel, FlowResource } from '@nocobase/flow-engine'; -export class BlockFlowModel extends FlowModel {} +export class BlockFlowModel extends FlowModel { + resource: FlowResource; + collection: Collection; +} diff --git a/packages/core/client/src/flow/models/FilterFormActionModel.tsx b/packages/core/client/src/flow/models/FilterFormActionModel.tsx new file mode 100644 index 0000000000..8676aea966 --- /dev/null +++ b/packages/core/client/src/flow/models/FilterFormActionModel.tsx @@ -0,0 +1,79 @@ +/** + * 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 { FlowEngine, FlowModel, MultiRecordResource } from '@nocobase/flow-engine'; +import { Button } from 'antd'; +import type { ButtonType } from 'antd/es/button'; +import React from 'react'; +import { ActionModel } from './ActionModel'; +import { BlockFlowModel } from './BlockFlowModel'; + +export class FilterFormActionModel extends ActionModel {} + +export class FilterFormSubmitActionModel extends FilterFormActionModel { + title = 'Filter'; + type: ButtonType = 'primary'; +} + +FilterFormSubmitActionModel.registerFlow({ + key: 'event1', + on: { + eventName: 'click', + }, + steps: { + step1: { + async handler(ctx, params) { + if (!ctx.shared?.currentBlockModel?.form) { + ctx.globals.message.error('No form available for submission.'); + return; + } + const currentBlockModel = ctx.shared.currentBlockModel; + await currentBlockModel.form.submit(); + const values = currentBlockModel.form.values; + const flowEngine = ctx.globals.flowEngine as FlowEngine; + flowEngine.forEachModel((model: BlockFlowModel) => { + if (model.resource && model?.collection?.name === currentBlockModel.collection.name) { + (model.resource as MultiRecordResource).addFilterGroup(currentBlockModel.uid, values); + (model.resource as MultiRecordResource).refresh(); + } + }); + }, + }, + }, +}); + +export class FilterFormResetActionModel extends FilterFormActionModel { + title = 'Reset'; +} + +FilterFormResetActionModel.registerFlow({ + key: 'event1', + on: { + eventName: 'click', + }, + steps: { + step1: { + async handler(ctx, params) { + if (!ctx.shared?.currentBlockModel?.form) { + ctx.globals.message.error('No form available for reset.'); + return; + } + const currentBlockModel = ctx.shared.currentBlockModel; + await currentBlockModel.form.reset(); + const flowEngine = ctx.globals.flowEngine as FlowEngine; + flowEngine.forEachModel((model: BlockFlowModel) => { + if (model.resource && model?.collection?.name === currentBlockModel.collection.name) { + (model.resource as MultiRecordResource).removeFilterGroup(currentBlockModel.uid); + (model.resource as MultiRecordResource).refresh(); + } + }); + }, + }, + }, +}); diff --git a/packages/core/client/src/flow/models/FilterFormModel.tsx b/packages/core/client/src/flow/models/FilterFormModel.tsx new file mode 100644 index 0000000000..964294aa7b --- /dev/null +++ b/packages/core/client/src/flow/models/FilterFormModel.tsx @@ -0,0 +1,112 @@ +/** + * 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 { FormButtonGroup, FormLayout } from '@formily/antd-v5'; +import { createForm, Form } from '@formily/core'; +import { FormProvider } from '@formily/react'; +import { AddActionButton, AddFieldButton, Collection, FlowModelRenderer } from '@nocobase/flow-engine'; +import { Card } from 'antd'; +import React from 'react'; +import { BlockFlowModel } from './BlockFlowModel'; +import { FormFieldModel } from './FormFieldModel'; + +export class FilterFormModel extends BlockFlowModel { + form: Form; + // collection: Collection; + + render() { + return ( + + + + {this.mapSubModels('fields', (field) => ( + + ))} + + ({ + use: fieldClass.name, + stepParams: { + default: { + step1: { + fieldPath: `${field.collection.dataSource.name}.${field.collection.name}.${field.name}`, + }, + }, + }, + })} + onModelAdded={async (fieldModel: FormFieldModel) => { + const fieldInfo = fieldModel.stepParams?.field; + if (fieldInfo && typeof fieldInfo.name === 'string') { + // 如果需要设置 collectionField,可以从 collection 中获取 + const fields = this.collection.getFields(); + const field = fields.find((f) => f.name === fieldInfo.name); + if (field) { + fieldModel.collectionField = field; + } + } + }} + subModelKey="fields" + model={this} + collection={this.collection} + subModelBaseClass="FormFieldModel" + /> + + {this.mapSubModels('actions', (action) => ( + + ))} + + + + + ); + } +} + +FilterFormModel.registerFlow({ + key: 'default', + auto: true, + steps: { + step1: { + paramsRequired: true, + hideInSettings: true, + uiSchema: { + dataSourceKey: { + type: 'string', + title: 'Data Source Key', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: 'Enter data source key', + }, + }, + collectionName: { + type: 'string', + title: 'Collection Name', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: 'Enter collection name', + }, + }, + }, + defaultParams: { + dataSourceKey: 'main', + }, + async handler(ctx, params) { + ctx.model.form = ctx.extra.form || createForm(); + if (!ctx.model.collection) { + ctx.model.collection = ctx.globals.dataSourceManager.getCollection( + params.dataSourceKey, + params.collectionName, + ); + } + }, + }, + }, +}); diff --git a/packages/core/client/src/flow/models/FormModel.tsx b/packages/core/client/src/flow/models/FormModel.tsx index 8a324ef005..2e56d76fae 100644 --- a/packages/core/client/src/flow/models/FormModel.tsx +++ b/packages/core/client/src/flow/models/FormModel.tsx @@ -24,8 +24,8 @@ import { FormFieldModel } from './FormFieldModel'; export class FormModel extends BlockFlowModel { form: Form; - resource: SingleRecordResource; - collection: Collection; + declare resource: SingleRecordResource; + // collection: Collection; render() { return ( diff --git a/packages/core/client/src/flow/models/TableModel.tsx b/packages/core/client/src/flow/models/TableModel.tsx index d3034ff39b..0bfbb7b33a 100644 --- a/packages/core/client/src/flow/models/TableModel.tsx +++ b/packages/core/client/src/flow/models/TableModel.tsx @@ -31,8 +31,7 @@ type S = { }; export class TableModel extends BlockFlowModel { - collection: Collection; - resource: MultiRecordResource; + declare resource: MultiRecordResource; getColumns() { return this.mapSubModels('columns', (column) => { diff --git a/packages/core/client/src/flow/models/index.ts b/packages/core/client/src/flow/models/index.ts index 645c1fc9ce..177b87ed8f 100644 --- a/packages/core/client/src/flow/models/index.ts +++ b/packages/core/client/src/flow/models/index.ts @@ -15,6 +15,8 @@ export * from './BulkDeleteActionModel'; export * from './CalendarBlockFlowModel'; export * from './DeleteActionModel'; export * from './FilterActionModel'; +export * from './FilterFormActionModel'; +export * from './FilterFormModel'; export * from './FormFieldModel'; export * from './FormModel'; export * from './HtmlBlockFlowModel'; diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 39e66ae6c9..f2e7352055 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -184,6 +184,10 @@ export class FlowEngine { return this.modelInstances.get(uid) as T | undefined; } + forEachModel(callback: (model: T) => void): void { + this.modelInstances.forEach(callback); + } + /** * 移除一个本地模型实例。 * @param {string} uid 要销毁的 Model 实例的唯一标识符。 diff --git a/packages/core/flow-engine/src/resources/baseRecordResource.ts b/packages/core/flow-engine/src/resources/baseRecordResource.ts index 46ec1c955a..7309b2959d 100644 --- a/packages/core/flow-engine/src/resources/baseRecordResource.ts +++ b/packages/core/flow-engine/src/resources/baseRecordResource.ts @@ -102,7 +102,7 @@ export abstract class BaseRecordResource extends APIResource } setFilter(filter: Record) { - return this.addRequestParameter('filter', filter); + return this.addRequestParameter('filter', JSON.stringify(filter)); } getFilter(): Record { From b12b01583bfa68de5c8b35c090bf2e6c6080fb3d Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 18:47:10 +0800 Subject: [PATCH 7/9] feat: update doc --- .../client/docs/zh-CN/core/flow-engine/flow-resource.md | 1 + packages/core/client/src/flow/models/TableModel.tsx | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/core/client/docs/zh-CN/core/flow-engine/flow-resource.md b/packages/core/client/docs/zh-CN/core/flow-engine/flow-resource.md index 60ba6fb19a..955bdf0eb5 100644 --- a/packages/core/client/docs/zh-CN/core/flow-engine/flow-resource.md +++ b/packages/core/client/docs/zh-CN/core/flow-engine/flow-resource.md @@ -111,6 +111,7 @@ console.log(apiResource.getData()); - `setSourceId(sourceId) / getSourceId()`: 设置/获取源对象 ID。 - `setDataSourceKey(dataSourceKey) / getDataSourceKey()`: 设置/获取数据源标识(通过 header)。 - `setFilter(filter) / getFilter()`: 设置/获取过滤条件。 +- `addFilterGroup(key, filter) / removeFilterGroup(key)`: 设置/移除条件组。 - `setAppends(appends) / getAppends()`: 设置/获取附加字段。 - `addAppends(appends) / removeAppends(appends)`: 添加/移除附加字段。 - `setFilterByTk(filterByTk) / getFilterByTk()`: 设置/获取主键过滤条件。 diff --git a/packages/core/client/src/flow/models/TableModel.tsx b/packages/core/client/src/flow/models/TableModel.tsx index 0bfbb7b33a..653cf9415d 100644 --- a/packages/core/client/src/flow/models/TableModel.tsx +++ b/packages/core/client/src/flow/models/TableModel.tsx @@ -191,13 +191,6 @@ TableModel.define({ columns: [ { use: 'TableActionsColumnModel', - subModels: { - actions: [ - { - use: 'ActionModel', - }, - ], - }, }, ], }, From ec2417f3d64d810e39fc14f1bc918c1084407236 Mon Sep 17 00:00:00 2001 From: gchust Date: Thu, 19 Jun 2025 19:18:11 +0800 Subject: [PATCH 8/9] fix: build error --- .../client/src/flow/models/CalendarBlockFlowModel.tsx | 8 ++------ packages/core/client/src/flow/models/QuickEditForm.tsx | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/core/client/src/flow/models/CalendarBlockFlowModel.tsx b/packages/core/client/src/flow/models/CalendarBlockFlowModel.tsx index e932d1041d..638b12dd98 100644 --- a/packages/core/client/src/flow/models/CalendarBlockFlowModel.tsx +++ b/packages/core/client/src/flow/models/CalendarBlockFlowModel.tsx @@ -7,12 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Application, Plugin } from '@nocobase/client'; -import { Collection, FlowModel, MultiRecordResource } from '@nocobase/flow-engine'; +import { Collection, MultiRecordResource } from '@nocobase/flow-engine'; import { Card, Modal } from 'antd'; import moment from 'moment'; -import dataSource from 'packages/core/client/docs/zh-CN/core/flow-models/demos/data-source'; -import { createdAt } from 'packages/plugins/@nocobase/plugin-mock-collections/src/server/field-interfaces'; import React from 'react'; import { Calendar, momentLocalizer } from 'react-big-calendar'; import 'react-big-calendar/lib/css/react-big-calendar.css'; @@ -21,8 +18,7 @@ import { BlockFlowModel } from './BlockFlowModel'; const localizer = momentLocalizer(moment); export class CalendarBlockFlowModel extends BlockFlowModel { - collection: Collection; - resource: MultiRecordResource; + declare resource: MultiRecordResource; render() { const data = this.resource.getData(); return ( diff --git a/packages/core/client/src/flow/models/QuickEditForm.tsx b/packages/core/client/src/flow/models/QuickEditForm.tsx index 57833b1eec..d00ca03cb7 100644 --- a/packages/core/client/src/flow/models/QuickEditForm.tsx +++ b/packages/core/client/src/flow/models/QuickEditForm.tsx @@ -18,14 +18,12 @@ import { FlowModelRenderer, SingleRecordResource, } from '@nocobase/flow-engine'; -import dataSource from 'packages/core/client/docs/zh-CN/core/flow-models/demos/data-source'; import React from 'react'; import { BlockFlowModel } from './BlockFlowModel'; export class QuickEditForm extends BlockFlowModel { form: Form; - resource: SingleRecordResource; - collection: Collection; + declare resource: SingleRecordResource; static async open(options: { flowEngine: FlowEngine; collectionField: CollectionField; filterByTk: string }) { const model = options.flowEngine.createModel({ From b18336b0380f269c17eecbc34ee9406e0f9da945 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 19 Jun 2025 22:37:11 +0800 Subject: [PATCH 9/9] feat: improve code --- packages/core/client/src/flow/FlowPage.tsx | 12 +- packages/core/client/src/flow/index.ts | 4 +- .../src/flow/models/FilterFormActionModel.tsx | 79 -------- .../src/flow/models/TableColumnModel.tsx | 185 ------------------ .../{ => actions}/AddNewActionModel.tsx | 35 +++- .../{ => actions}/BulkDeleteActionModel.tsx | 8 +- .../{ => actions}/DeleteActionModel.tsx | 11 +- .../{ => actions}/FilterActionModel.tsx | 32 +-- .../models/{ => actions}/LinkActionModel.tsx | 18 +- .../{ => actions}/RefreshActionModel.tsx | 7 +- .../models/{ => actions}/ViewActionModel.tsx | 16 +- .../flow/models/{ => base}/ActionModel.tsx | 40 ++-- .../BlockModel.tsx} | 6 +- .../FieldModel.tsx} | 4 +- .../GridModel.tsx} | 31 +-- .../{PageFlowModel.tsx => base/PageModel.tsx} | 12 +- .../PageTabModel.tsx} | 8 +- .../calendar/CalendarBlockModel.tsx} | 37 ++-- .../form/FormActionModel.tsx} | 19 +- .../{ => data-blocks/form}/FormFieldModel.tsx | 2 +- .../{ => data-blocks/form}/FormModel.tsx | 14 +- .../{ => data-blocks/form}/QuickEditForm.tsx | 9 +- .../table/TableActionsColumnModel.tsx | 85 ++++++++ .../data-blocks/table/TableColumnModel.tsx | 115 +++++++++++ .../{ => data-blocks/table}/TableModel.tsx | 6 +- .../form/FilterFormActionModel.tsx | 12 ++ .../form/FilterFormFieldModel.tsx | 15 ++ .../form}/FilterFormModel.tsx | 12 +- .../form/FilterFormResetActionModel.tsx | 45 +++++ .../form/FilterFormSubmitActionModel.tsx | 48 +++++ packages/core/client/src/flow/models/index.ts | 48 ++--- .../html/HtmlBlockModel.tsx} | 10 +- .../src/modules/menu/FlowPageMenuItem.tsx | 2 +- .../admin-layout/convertRoutesToSchema.ts | 2 +- .../components/subModel/AddBlockButton.tsx | 6 +- 35 files changed, 523 insertions(+), 472 deletions(-) delete mode 100644 packages/core/client/src/flow/models/FilterFormActionModel.tsx delete mode 100644 packages/core/client/src/flow/models/TableColumnModel.tsx rename packages/core/client/src/flow/models/{ => actions}/AddNewActionModel.tsx (58%) rename packages/core/client/src/flow/models/{ => actions}/BulkDeleteActionModel.tsx (88%) rename packages/core/client/src/flow/models/{ => actions}/DeleteActionModel.tsx (91%) rename packages/core/client/src/flow/models/{ => actions}/FilterActionModel.tsx (66%) rename packages/core/client/src/flow/models/{ => actions}/LinkActionModel.tsx (55%) rename packages/core/client/src/flow/models/{ => actions}/RefreshActionModel.tsx (84%) rename packages/core/client/src/flow/models/{ => actions}/ViewActionModel.tsx (80%) rename packages/core/client/src/flow/models/{ => base}/ActionModel.tsx (63%) rename packages/core/client/src/flow/models/{BlockFlowModel.tsx => base/BlockModel.tsx} (66%) rename packages/core/client/src/flow/models/{FieldFlowModel.tsx => base/FieldModel.tsx} (88%) rename packages/core/client/src/flow/models/{BlockGridFlowModel.tsx => base/GridModel.tsx} (58%) rename packages/core/client/src/flow/models/{PageFlowModel.tsx => base/PageModel.tsx} (87%) rename packages/core/client/src/flow/models/{PageTabFlowModel.tsx => base/PageTabModel.tsx} (82%) rename packages/core/client/src/flow/models/{CalendarBlockFlowModel.tsx => data-blocks/calendar/CalendarBlockModel.tsx} (86%) rename packages/core/client/src/flow/models/{SubmitActionModel.tsx => data-blocks/form/FormActionModel.tsx} (76%) rename packages/core/client/src/flow/models/{ => data-blocks/form}/FormFieldModel.tsx (97%) rename packages/core/client/src/flow/models/{ => data-blocks/form}/FormModel.tsx (92%) rename packages/core/client/src/flow/models/{ => data-blocks/form}/QuickEditForm.tsx (93%) create mode 100644 packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx create mode 100644 packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx rename packages/core/client/src/flow/models/{ => data-blocks/table}/TableModel.tsx (97%) create mode 100644 packages/core/client/src/flow/models/filter-blocks/form/FilterFormActionModel.tsx create mode 100644 packages/core/client/src/flow/models/filter-blocks/form/FilterFormFieldModel.tsx rename packages/core/client/src/flow/models/{ => filter-blocks/form}/FilterFormModel.tsx (91%) create mode 100644 packages/core/client/src/flow/models/filter-blocks/form/FilterFormResetActionModel.tsx create mode 100644 packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx rename packages/core/client/src/flow/models/{HtmlBlockFlowModel.tsx => other-blocks/html/HtmlBlockModel.tsx} (89%) diff --git a/packages/core/client/src/flow/FlowPage.tsx b/packages/core/client/src/flow/FlowPage.tsx index 2b3ec8083b..502051b5db 100644 --- a/packages/core/client/src/flow/FlowPage.tsx +++ b/packages/core/client/src/flow/FlowPage.tsx @@ -18,26 +18,26 @@ function InternalFlowPage({ uid, sharedContext }) { return ; } -export const FlowPage = () => { +export const FlowRoute = () => { const params = useParams(); - return ; + return ; }; -export const FlowPageComponent = (props) => { +export const FlowPage = (props) => { const { uid, parentId, sharedContext } = props; const flowEngine = useFlowEngine(); const { loading, data } = useRequest( async () => { const options = { uid, - use: 'PageFlowModel', + use: 'PageModel', subModels: { tabs: [ { - use: 'PageTabFlowModel', + use: 'PageTabModel', subModels: { grid: { - use: 'BlockGridFlowModel', + use: 'BlockGridModel', }, }, }, diff --git a/packages/core/client/src/flow/index.ts b/packages/core/client/src/flow/index.ts index 8396ce03bd..02adaa9601 100644 --- a/packages/core/client/src/flow/index.ts +++ b/packages/core/client/src/flow/index.ts @@ -12,12 +12,12 @@ import _ from 'lodash'; import { Plugin } from '../application/Plugin'; import { FlowEngineRunner } from './FlowEngineRunner'; import { MockFlowModelRepository } from './FlowModelRepository'; -import { FlowPage } from './FlowPage'; +import { FlowRoute } from './FlowPage'; import * as models from './models'; export class PluginFlowEngine extends Plugin { async load() { - this.app.addComponents({ FlowPage }); + this.app.addComponents({ FlowRoute }); this.app.flowEngine.setModelRepository(new MockFlowModelRepository()); const filteredModels = Object.fromEntries( Object.entries(models).filter( diff --git a/packages/core/client/src/flow/models/FilterFormActionModel.tsx b/packages/core/client/src/flow/models/FilterFormActionModel.tsx deleted file mode 100644 index 8676aea966..0000000000 --- a/packages/core/client/src/flow/models/FilterFormActionModel.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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 { FlowEngine, FlowModel, MultiRecordResource } from '@nocobase/flow-engine'; -import { Button } from 'antd'; -import type { ButtonType } from 'antd/es/button'; -import React from 'react'; -import { ActionModel } from './ActionModel'; -import { BlockFlowModel } from './BlockFlowModel'; - -export class FilterFormActionModel extends ActionModel {} - -export class FilterFormSubmitActionModel extends FilterFormActionModel { - title = 'Filter'; - type: ButtonType = 'primary'; -} - -FilterFormSubmitActionModel.registerFlow({ - key: 'event1', - on: { - eventName: 'click', - }, - steps: { - step1: { - async handler(ctx, params) { - if (!ctx.shared?.currentBlockModel?.form) { - ctx.globals.message.error('No form available for submission.'); - return; - } - const currentBlockModel = ctx.shared.currentBlockModel; - await currentBlockModel.form.submit(); - const values = currentBlockModel.form.values; - const flowEngine = ctx.globals.flowEngine as FlowEngine; - flowEngine.forEachModel((model: BlockFlowModel) => { - if (model.resource && model?.collection?.name === currentBlockModel.collection.name) { - (model.resource as MultiRecordResource).addFilterGroup(currentBlockModel.uid, values); - (model.resource as MultiRecordResource).refresh(); - } - }); - }, - }, - }, -}); - -export class FilterFormResetActionModel extends FilterFormActionModel { - title = 'Reset'; -} - -FilterFormResetActionModel.registerFlow({ - key: 'event1', - on: { - eventName: 'click', - }, - steps: { - step1: { - async handler(ctx, params) { - if (!ctx.shared?.currentBlockModel?.form) { - ctx.globals.message.error('No form available for reset.'); - return; - } - const currentBlockModel = ctx.shared.currentBlockModel; - await currentBlockModel.form.reset(); - const flowEngine = ctx.globals.flowEngine as FlowEngine; - flowEngine.forEachModel((model: BlockFlowModel) => { - if (model.resource && model?.collection?.name === currentBlockModel.collection.name) { - (model.resource as MultiRecordResource).removeFilterGroup(currentBlockModel.uid); - (model.resource as MultiRecordResource).refresh(); - } - }); - }, - }, - }, -}); diff --git a/packages/core/client/src/flow/models/TableColumnModel.tsx b/packages/core/client/src/flow/models/TableColumnModel.tsx deleted file mode 100644 index 32a8b65b80..0000000000 --- a/packages/core/client/src/flow/models/TableColumnModel.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/** - * 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 { EditOutlined, SettingOutlined } from '@ant-design/icons'; -import { css } from '@emotion/css'; -import { observer } from '@formily/reactive-react'; -import { AddActionModel, CollectionField, FlowModelRenderer, FlowsFloatContextMenu } from '@nocobase/flow-engine'; -import { Space, TableColumnProps, Tooltip } from 'antd'; -import React from 'react'; -import { ActionModel } from './ActionModel'; -import { FieldFlowModel, SupportedFieldInterfaces } from './FieldFlowModel'; -import { QuickEditForm } from './QuickEditForm'; - -export class TableColumnModel extends FieldFlowModel { - static readonly supportedFieldInterfaces: SupportedFieldInterfaces = '*'; - - getColumnProps(): TableColumnProps { - return { - ...this.props, - title: ( - - {this.props.title} - - ), - ellipsis: true, - onCell: (record) => ({ - className: css` - .edit-icon { - position: absolute; - display: none; - color: #1890ff; - margin-left: 8px; - cursor: pointer; - top: 50%; - right: 8px; - transform: translateY(-50%); - } - &:hover { - background: rgba(24, 144, 255, 0.1) !important; - } - &:hover .edit-icon { - display: inline-flex; - } - `, - }), - render: this.render(), - }; - } - - renderQuickEditButton(record) { - return ( - - { - e.stopPropagation(); - await QuickEditForm.open({ - flowEngine: this.flowEngine, - collectionField: this.field as CollectionField, - filterByTk: record.id, - }); - await this.parent.resource.refresh(); - }} - /> - - ); - } - - render() { - return (value, record, index) => ( - <> - {value} - {this.renderQuickEditButton(record)} - - ); - } -} - -TableColumnModel.define({ - title: 'Table Column', - icon: 'TableColumn', - defaultOptions: { - use: 'TableColumnModel', - }, - sort: 0, -}); - -const Columns = observer(({ record, model, index }) => { - return ( - - {model.mapSubModels('actions', (action: ActionModel) => { - const fork = action.createFork({}, `${index}`); - return ( - - ); - })} - - ); -}); - -export class TableActionsColumnModel extends TableColumnModel { - static readonly supportedFieldInterfaces: SupportedFieldInterfaces = null; - - getColumnProps() { - return { - // title: 'Actions', - ...this.props, - title: ( - - - {this.props.title || 'Actions'} - [ - { - key: 'view', - label: 'View', - createModelOptions: { - use: 'ViewActionModel', - }, - }, - { - key: 'link', - label: 'Link', - createModelOptions: { - use: 'LinkActionModel', - }, - }, - { - key: 'delete', - label: 'Delete', - createModelOptions: { - use: 'DeleteActionModel', - }, - }, - ]} - > - - - - - ), - render: this.render(), - }; - } - - render() { - return (value, record, index) => ; - } -} - -TableColumnModel.registerFlow({ - key: 'default', - auto: true, - steps: { - step1: { - handler(ctx, params) { - if (!params.fieldPath) { - return; - } - if (ctx.model.field) { - return; - } - const field = ctx.globals.dataSourceManager.getCollectionField(params.fieldPath); - ctx.model.field = field; - ctx.model.fieldPath = params.fieldPath; - ctx.model.setProps('title', field.title); - ctx.model.setProps('dataIndex', field.name); - }, - }, - }, -}); diff --git a/packages/core/client/src/flow/models/AddNewActionModel.tsx b/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx similarity index 58% rename from packages/core/client/src/flow/models/AddNewActionModel.tsx rename to packages/core/client/src/flow/models/actions/AddNewActionModel.tsx index c697cb8681..240263c683 100644 --- a/packages/core/client/src/flow/models/AddNewActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx @@ -7,22 +7,42 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import type { ButtonType } from 'antd/es/button'; +import { ButtonProps } from 'antd'; import React from 'react'; -import { FlowPageComponent } from '../FlowPage'; -import { ActionModel } from './ActionModel'; +import { FlowPage } from '../../FlowPage'; +import { ActionModel } from '../base/ActionModel'; export class AddNewActionModel extends ActionModel { - title = 'Add new'; + defaultProps: ButtonProps = { + type: 'primary', + children: 'Add new', + }; } AddNewActionModel.registerFlow({ + sort: 200, + title: '事件', key: 'event1', on: { eventName: 'click', }, steps: { step1: { + title: '弹窗配置', + uiSchema: { + width: { + type: 'number', + title: '宽度', + 'x-decorator': 'FormItem', + 'x-component': 'NumberPicker', + 'x-component-props': { + placeholder: '请输入宽度', + }, + }, + }, + defaultParams: { + width: 800, + }, handler(ctx, params) { // eslint-disable-next-line prefer-const let currentDrawer: any; @@ -30,7 +50,7 @@ AddNewActionModel.registerFlow({ function DrawerContent() { return (
- @@ -39,8 +59,9 @@ AddNewActionModel.registerFlow({ } currentDrawer = ctx.globals.drawer.open({ - title: '命令式 Drawer', - width: 800, + // title: '命令式 Drawer', + header: null, + width: params.width, content: , }); }, diff --git a/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx similarity index 88% rename from packages/core/client/src/flow/models/BulkDeleteActionModel.tsx rename to packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx index 074d900219..9041edd294 100644 --- a/packages/core/client/src/flow/models/BulkDeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx @@ -8,11 +8,13 @@ */ import { MultiRecordResource } from '@nocobase/flow-engine'; -import React from 'react'; -import { ActionModel } from './ActionModel'; +import { ButtonProps } from 'antd'; +import { ActionModel } from '../base/ActionModel'; export class BulkDeleteActionModel extends ActionModel { - title = 'Delete'; + defaultProps: ButtonProps = { + children: 'Delete', + }; } BulkDeleteActionModel.registerFlow({ diff --git a/packages/core/client/src/flow/models/DeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx similarity index 91% rename from packages/core/client/src/flow/models/DeleteActionModel.tsx rename to packages/core/client/src/flow/models/actions/DeleteActionModel.tsx index 2f0db1dc57..870608506e 100644 --- a/packages/core/client/src/flow/models/DeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx @@ -8,13 +8,14 @@ */ import { MultiRecordResource } from '@nocobase/flow-engine'; -import type { ButtonType } from 'antd/es/button'; -import React from 'react'; -import { ActionModel } from './ActionModel'; +import type { ButtonProps } from 'antd'; +import { ActionModel } from '../base/ActionModel'; export class DeleteActionModel extends ActionModel { - title = 'Delete'; - type: ButtonType = 'link'; + defaultProps: ButtonProps = { + children: 'Delete', + type: 'link', + }; } DeleteActionModel.registerFlow({ diff --git a/packages/core/client/src/flow/models/FilterActionModel.tsx b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx similarity index 66% rename from packages/core/client/src/flow/models/FilterActionModel.tsx rename to packages/core/client/src/flow/models/actions/FilterActionModel.tsx index f410e18eed..b620fecc84 100644 --- a/packages/core/client/src/flow/models/FilterActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx @@ -8,13 +8,16 @@ */ import { MultiRecordResource } from '@nocobase/flow-engine'; -import { Button, Input, Popover } from 'antd'; +import { Button, ButtonProps, Input, Popover } from 'antd'; import _ from 'lodash'; import React from 'react'; -import { ActionModel } from './ActionModel'; +import { ActionModel } from '../base/ActionModel'; export class FilterActionModel extends ActionModel { - title = 'Filter'; + defaultProps: ButtonProps = { + type: 'default', + children: 'Filter', + }; render() { return ( @@ -43,29 +46,8 @@ export class FilterActionModel extends ActionModel { trigger="click" placement="bottom" > - + + -// -// ); -// } - -type BlockGridFlowModelStructure = { +type GridModelStructure = { subModels: { - items: BlockFlowModel[]; + items: BlockModel[]; }; }; -export class BlockGridFlowModel extends FlowModel { +export class GridModel extends FlowModel { + subModelBaseClass = 'BlockModel'; render() { return (
- +
); } } + +export class BlockGridModel extends GridModel { + subModelBaseClass = 'BlockModel'; +} diff --git a/packages/core/client/src/flow/models/PageFlowModel.tsx b/packages/core/client/src/flow/models/base/PageModel.tsx similarity index 87% rename from packages/core/client/src/flow/models/PageFlowModel.tsx rename to packages/core/client/src/flow/models/base/PageModel.tsx index d21e9359f1..3edf3d0831 100644 --- a/packages/core/client/src/flow/models/PageFlowModel.tsx +++ b/packages/core/client/src/flow/models/base/PageModel.tsx @@ -13,13 +13,13 @@ import { Button, Tabs } from 'antd'; import _ from 'lodash'; import React from 'react'; -type PageFlowModelStructure = { +type PageModelStructure = { subModels: { tabs: FlowModel[]; }; }; -export class PageFlowModel extends FlowModel { +export class PageModel extends FlowModel { addTab(tab: any) { const model = this.addSubModel('tabs', tab); model.save(); @@ -48,11 +48,11 @@ export class PageFlowModel extends FlowModel { , subModelType = 'array',