news 2026/9/18 9:10:49

NocoBase RunJS 视图上下文 `ctx.view` 完全指南:弹窗、抽屉与内嵌视图的控制核心

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NocoBase RunJS 视图上下文 `ctx.view` 完全指南:弹窗、抽屉与内嵌视图的控制核心

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.viewerctx.openView打开的视图内容中可用。

从源码看,ctx.view的注入位置位于packages/core/flow-engine/src/views/useDialog.tsxpackages/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()关闭当前视图,或使用HeaderFooter渲染标题和底部
表单提交后提交成功后调用ctx.view.close(result)关闭并回传结果
JSBlock / Action根据ctx.view.type判断当前视图类型,或读取ctx.view.inputArgs中的打开参数
关联选择、子表格读取inputArgs中的collectionNamefilterByTkparentId等做数据加载

类型定义

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:由打开配置传入,为trueclose()默认返回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'当前视图类型
inputArgsRecord<string, any>打开视图时传入的参数,见下方
HeaderReact.FC \| null头部组件,用于渲染标题、操作区
FooterReact.FC \| null底部组件,用于渲染按钮等
close(result?, force?)void关闭当前视图,可传result回传给调用方
update(newConfig)void更新视图配置(如宽度、标题)
navigationViewNavigation \| undefined页面内视图导航,含 Tab 切换等

目前仅dialogdrawer支持HeaderFooter

Header / Footer 的实现原理

HeaderFooter并非普通渲染组件,而是「插槽」组件:它们在视图内部被渲染时,通过useEffectchildren/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):

  1. preventClose && !force,直接返回false,不关闭;
  2. 调用runViewBeforeClose(currentDialog, { result, force }),执行beforeClose钩子;若钩子返回false,则不关闭(runViewBeforeClose.ts);
  3. 若视图由路由触发且存在navigation.back,则交由路由系统销毁视图并清理 URL;
  4. 否则调用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.navigationViewNavigation实例,实现位于 ViewNavigation.ts,它维护一个不可变的viewStack(视图栈),提供:

  • changeTo(viewParam):替换栈顶视图参数并用replace方式导航(如切换 Tab);
  • navigateTo(viewParam, opts?):向栈中压入新视图并用push方式导航;
  • back():弹出栈顶视图并导航回上一级。

URL 由generatePathnameFromViewParams生成,例如[{ viewUid: 'xxx' }, { viewUid: 'yyy' }]会得到/admin/xxx/view/yyy,其中filterByTksourceIdtabUid都会被编码进路径段,因此视图状态是可刷新保持的

inputArgs 常见字段

不同打开场景下inputArgs字段不同,常见包括:

字段说明
viewUid视图 UID
collectionName数据表名
filterByTk主键筛选(单条详情)
parentId父级 ID(关联场景)
sourceId来源记录 ID
parentItem父项数据
scene场景(如createeditselect
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只在dialogdrawer中有效,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等文件就大量使用了useFlowViewContextctx.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.viewerFlowViewer类的实例(FlowView.tsx),提供dialog()drawer()popover()embed()四个方法,统一走open()方法分配递增的zIndex并挂载视图,其 content 可以是任意 React 内容(字符串内容会经DOMPurify.sanitize净化后渲染);
  • ctx.viewFlowViewer.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目前仅在dialogdrawer中生效
  • destroy具备幂等保护(源码中的destroyed标志),多次调用不会重复销毁,因此路由清理与手动关闭可以安全叠加

相关

  • ctx.openView():打开已配置的流程视图
  • ctx.modal:轻量级弹窗(信息、确认等)
  • FlowView.tsx:FlowView类型与FlowViewer类源码
  • useDialog.tsx / useDrawer.tsx:视图生命周期与上下文装配实现
  • FlowContextProvider.tsx:FlowViewContextuseFlowViewContext/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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 9:10:49

深入llvm-project:理解LLVM IR、构建与Pass开发

如果你是在搜索引擎里敲下“llvm-project”这个词才点进来的&#xff0c;我猜你大概率正面临三种情况之一&#xff1a;要么是编译某个开源项目时看到一长串 LLVM 依赖手足无措&#xff0c;要么是在 glxinfo 输出里看到llvmpipe (LLVM 15.0.7, 256 bits)这样的字符串想知道它到底…

作者头像 李华
网站建设 2026/9/18 9:10:35

Python下划线命名规则:_、__与__xx__的语义契约

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:09:48

Linux离线安装SVN实践:基于本地yum源的完整配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:09:08

专科生论文降AI率实战:9款工具实测与避坑指南

第一次看到检测报告上“AI疑似率38%”的时候&#xff0c;我整个人是懵的。那篇实训报告是我熬了三个晚上、一句一句敲出来的&#xff0c;从排错日志到设备参数都有据可查&#xff0c;结果系统直接给我标了一堆红。后来跟专业课老师聊完&#xff0c;又拿自己手头的稿子反复试了七…

作者头像 李华
网站建设 2026/9/18 9:09:00

Linux下统计文件个数的正确姿势:find/ls/wc实战详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:07:57

基于Vue+SpringBoot的图书管理系统全栈开发实践

1. 项目概述这个图书管理系统是一个典型的全栈Web应用开发项目&#xff0c;采用当下最流行的前后端分离架构。前端使用Vue.js框架实现响应式用户界面&#xff0c;后端基于SpringBoot快速构建RESTful API服务&#xff0c;数据存储则选用MySQL关系型数据库。整套系统开箱即用&…

作者头像 李华