NocoBase RunJS 视图上下文ctx.view完全指南:弹窗、抽屉与内嵌视图的控制核心
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
在 NocoBase 的 RunJS 执行环境中,ctx.view是当前激活视图(弹窗、抽屉、气泡层、嵌入式区域等)的控制器,它既是视图级信息的读取入口(视图类型、打开参数),也是视图级操作的执行入口(关闭、更新、渲染头部与底部、页面内 Tab 导航)。本指南将围绕ctx.view的类型定义、常用属性方法、inputArgs参数体系、与ctx.viewer/ctx.openView的分工关系展开讲解,并结合packages/core/flow-engine的源码实现,帮助你在弹窗表单、关联选择器、JSBlock、事件流等场景中熟练操控视图生命周期。
什么是ctx.view
ctx.view代表「当前所在」的视图实例,它由FlowViewContext提供,仅在通过ctx.viewer或ctx.openView打开的视图内容中可用。
从源码看,ctx.view的注入位置位于packages/core/flow-engine/src/views/useDialog.tsx与packages/core/flow-engine/src/views/useDrawer.tsx:当调用dialog()/drawer()打开视图时,会创建一个独立的作用域引擎(createViewScopedEngine),随后通过ctx.defineProperty('view', { get: () => currentDialog })把当前视图实例挂到上下文中,再用FlowViewContextProvider包住视图内容(FlowContextProvider.tsx)。
// useDialog.tsx / useDrawer.tsx 中的核心装配逻辑(简化) const scopedEngine = createViewScopedEngine(flowContext.engine); ctx.defineProperty('view', { get: () => currentDialog }); return ( <FlowEngineProvider engine={scopedEngine}> <FlowViewContextProvider context={ctx}> <DialogWithContext /> </FlowViewContextProvider> </FlowEngineProvider> );对应的 React Hooks 封装位于 FlowContextProvider.tsx:
export function useFlowViewContext<T = FlowEngineContext>() { return useContext(FlowViewContext) as T; } export function useFlowView() { const ctx = useFlowContext(); return ctx.view as FlowView; }也就是说,在 JSX 渲染的视图内容中,你可以用useFlowViewContext()拿到完整上下文,再访问ctx.view;在 RunJS 脚本中则直接使用ctx.view。
注意:
ctx.view仅在有视图上下文的 RunJS 环境中可用(如ctx.viewer.dialog()的 content 内、弹窗表单、关联选择器内部);在普通页面或后端上下文中为undefined,使用时建议做可选链判断(ctx.view?.close?.())。
适用场景
| 场景 | 说明 |
|---|---|
| 弹窗/抽屉内容 | 在content中通过ctx.view.close()关闭当前视图,或使用Header、Footer渲染标题和底部 |
| 表单提交后 | 提交成功后调用ctx.view.close(result)关闭并回传结果 |
| JSBlock / Action | 根据ctx.view.type判断当前视图类型,或读取ctx.view.inputArgs中的打开参数 |
| 关联选择、子表格 | 读取inputArgs中的collectionName、filterByTk、parentId等做数据加载 |
类型定义
ctx.view的完整类型定义可在源码 FlowView.tsx 中找到,与文档中的描述一一对应:
type FlowView = { type: 'drawer' | 'popover' | 'dialog' | 'embed'; inputArgs: Record<string, any>; Header: React.FC<{ title?: React.ReactNode; extra?: React.ReactNode }> | null; Footer: React.FC<{ children?: React.ReactNode }> | null; close: (result?: any, force?: boolean) => void; update: (newConfig: any) => void; navigation?: ViewNavigation; destroy?: () => void; submit?: () => Promise<any>; // 流配置视图中可用 };从源码实现看,还有两个文档未列出的可选成员值得注意:
beforeClose?: FlowViewBeforeCloseHandler:关闭前的钩子函数,返回false可阻止关闭,具体逻辑见 runViewBeforeClose.ts;preventClose?: boolean:由打开配置传入,为true时close()默认返回false不关闭,除非显式传force = true。
其中close的完整签名在源码中为(result?: any, force?: boolean) => Promise<boolean | void> | boolean | void,即它可能返回一个布尔值表示是否真正关闭(当beforeClose拦截或preventClose生效时返回false)。
视图类型(type)
| 类型 | 说明 |
|---|---|
drawer | 抽屉,从侧边滑出,适合编辑类操作 |
popover | 气泡层,轻量浮层 |
dialog | 居中弹窗(Modal),适合详情/确认类操作 |
embed | 嵌入式区域,不遮挡页面主体,无独立 zIndex |
在 FlowView.tsx 中可以看到一个细节:embed类型不会设置过高的zIndex,以避免遮挡菜单折叠按钮图标,其余类型会按打开的先后顺序递增zIndex。
常用属性和方法
| 属性/方法 | 类型 | 说明 |
|---|---|---|
type | 'drawer' \| 'popover' \| 'dialog' \| 'embed' | 当前视图类型 |
inputArgs | Record<string, any> | 打开视图时传入的参数,见下方 |
Header | React.FC \| null | 头部组件,用于渲染标题、操作区 |
Footer | React.FC \| null | 底部组件,用于渲染按钮等 |
close(result?, force?) | void | 关闭当前视图,可传result回传给调用方 |
update(newConfig) | void | 更新视图配置(如宽度、标题) |
navigation | ViewNavigation \| undefined | 页面内视图导航,含 Tab 切换等 |
目前仅
dialog和drawer支持Header和Footer。
Header / Footer 的实现原理
Header与Footer并非普通渲染组件,而是「插槽」组件:它们在视图内部被渲染时,通过useEffect把children/title/extra同步到视图容器的头部与底部区域,自身渲染null(见 useDialog.tsx)。
// useDialog.tsx 中的 Footer 组件(简化) const FooterComponent: React.FC<{ children?: React.ReactNode }> = ({ children }) => { React.useEffect(() => { currentFooter = children; dialogRef.current?.setFooter(children); return () => { currentFooter = null; dialogRef.current?.setFooter(null); }; }, [children]); return null; // Footer 组件本身不渲染内容 };这意味着你可以在视图内容的任意位置声明<Header title="..." />与<Footer>...</Footer>,它们会自动「上浮」到弹窗/抽屉的标题栏和底部按钮栏。
close 的完整流程
close(result?, force?)的执行路径(以 dialog 为例,见 useDialog.tsx):
- 若
preventClose && !force,直接返回false,不关闭; - 调用
runViewBeforeClose(currentDialog, { result, force }),执行beforeClose钩子;若钩子返回false,则不关闭(runViewBeforeClose.ts); - 若视图由路由触发且存在
navigation.back,则交由路由系统销毁视图并清理 URL; - 否则调用
destroy(result):销毁 DOM 元素、执行onClose回调、resolvePromise(result),并通过事件通知上层视图恢复激活状态(VIEW_ACTIVATED_EVENT)。
其中resolvePromise(result)意味着:close(result)的result会传递给ctx.viewer.open()返回的 Promise,调用方可以在await ctx.viewer.dialog({...})之后拿到关闭时回传的结果。
navigation:页面内视图导航
ctx.view.navigation是ViewNavigation实例,实现位于 ViewNavigation.ts,它维护一个不可变的viewStack(视图栈),提供:
changeTo(viewParam):替换栈顶视图参数并用replace方式导航(如切换 Tab);navigateTo(viewParam, opts?):向栈中压入新视图并用push方式导航;back():弹出栈顶视图并导航回上一级。
URL 由generatePathnameFromViewParams生成,例如[{ viewUid: 'xxx' }, { viewUid: 'yyy' }]会得到/admin/xxx/view/yyy,其中filterByTk、sourceId、tabUid都会被编码进路径段,因此视图状态是可刷新保持的。
inputArgs 常见字段
不同打开场景下inputArgs字段不同,常见包括:
| 字段 | 说明 |
|---|---|
viewUid | 视图 UID |
collectionName | 数据表名 |
filterByTk | 主键筛选(单条详情) |
parentId | 父级 ID(关联场景) |
sourceId | 来源记录 ID |
parentItem | 父项数据 |
scene | 场景(如create、edit、select) |
onChange | 选择/变更后的回调 |
tabUid | 当前 Tab UID(页面内) |
inputArgs直接来自打开时的配置config.inputArgs || {},并在视图中保持响应式。通过ctx.getVar('ctx.view.inputArgs.xxx')或ctx.view.inputArgs.xxx访问。
值得说明的是,inputArgs中还有一个特殊的hidden字段(形如{ value: boolean }),用于「先渲染但暂不显示」的视图场景(见 useDialog.tsx),首次挂载时若hidden.value为真,视图内容会先返回null。
示例
关闭当前视图
// 提交成功后关闭弹窗 await ctx.resource.runAction('create', { data: formData }); ctx.view?.close(); // 关闭并回传结果 ctx.view?.close({ id: newRecord.id, name: newRecord.name });结合上文源码可以知道:第二个示例中{ id, name }会成为ctx.viewer.open()返回 Promise 的 resolve 值,方便调用方在打开处继续处理。
在 content 中使用 Header / Footer
function DialogContent() { const ctx = useFlowViewContext(); const { Header, Footer, close } = ctx.view; return ( <div> <Header title="编辑" extra={<Button size="small">帮助</Button>} /> <div>表单内容...</div> <Footer> <Button onClick={() => close()}>取消</Button> <Button type="primary" onClick={handleSubmit}>确定</Button> </Footer> </div> ); }注意:Header/Footer只在dialog与drawer中有效,popover/embed中它们为null,需要先做判空或按视图类型分支。
根据视图类型或 inputArgs 做分支
if (ctx.view?.type === 'embed') { // 嵌入式视图中隐藏头部 ctx.model.setProps('headerStyle', { display: 'none' }); } const collectionName = ctx.view?.inputArgs?.collectionName; if (collectionName === 'users') { // 用户选择器场景 }这种分支写法在关联字段选择器、子表格编辑等「同一组件被多种视图复用」的场景中非常实用。仓库中packages/core/client-v2/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx等文件就大量使用了useFlowViewContext与ctx.view来感知当前所在视图并读取打开参数。
与 ctx.viewer、ctx.openView 的关系
| 用途 | 推荐用法 |
|---|---|
| 打开新视图 | ctx.viewer.dialog()/ctx.viewer.drawer()或ctx.openView() |
| 操作当前视图 | ctx.view.close()、ctx.view.update() |
| 获取打开参数 | ctx.view.inputArgs |
ctx.viewer负责「打开」视图,ctx.view表示「当前」所在视图实例;ctx.openView用于打开已配置的流程视图。
三个 API 的分工可以从源码层面进一步确认:
ctx.viewer是FlowViewer类的实例(FlowView.tsx),提供dialog()、drawer()、popover()、embed()四个方法,统一走open()方法分配递增的zIndex并挂载视图,其 content 可以是任意 React 内容(字符串内容会经DOMPurify.sanitize净化后渲染);ctx.view是FlowViewer.open()返回对象的一部分(Object.assign(promise, currentDialog)),因此你甚至可以直接拿到返回对象调用close()/update(),而不必等视图内部自己关闭;ctx.openView(uid, options)打开的是 FlowPage(ChildPageModel),内部渲染完整流程页面;若 uid 对应的模型不存在,会自动创建 PopupActionModel 并持久化。更详细的行为与参数可参考 ctx.openView() 文档。
另外,与轻量级弹窗相关的ctx.modal(信息、确认等)可在 ctx.modal 文档 中查看。
注意事项
ctx.view仅在视图内部可用,普通页面中为undefined- 使用可选链:
ctx.view?.close?.()避免在无视图上下文时报错 close(result)的result会传递给ctx.viewer.open()返回的 Promise- 若视图配置了
preventClose,普通close()会被拒绝,需要传force = true强制关闭 Header/Footer目前仅在dialog和drawer中生效destroy具备幂等保护(源码中的destroyed标志),多次调用不会重复销毁,因此路由清理与手动关闭可以安全叠加
相关
- ctx.openView():打开已配置的流程视图
- ctx.modal:轻量级弹窗(信息、确认等)
- FlowView.tsx:
FlowView类型与FlowViewer类源码 - useDialog.tsx / useDrawer.tsx:视图生命周期与上下文装配实现
- FlowContextProvider.tsx:
FlowViewContext与useFlowViewContext/useFlowView/useFlowViewer定义
ctx.viewer提供dialog()、drawer()、popover()、embed()等方法打开视图,其打开的content内可访问ctx.view。
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考