OHIF 视口角落自定义图标指南:基于 viewportActionMenu 与 customizationService 的完整实战

发布时间:2026/9/18 19:06:05
OHIF 视口角落自定义图标指南:基于 viewportActionMenu 与 customizationService 的完整实战 OHIF 视口角落自定义图标指南基于 viewportActionMenu 与 customizationService 的完整实战【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读本文讲解如何在 OHIF Viewer 的任意视口Viewport角落添加自定义图标、按钮或下拉菜单。你将掌握customizationService与viewportActionMenu定制化键的用法学会通过$push/$set操作符向八个视口角落注入自定义 React 组件并能利用viewportActionCornersService.getAlignAndSide()为下拉菜单计算正确的弹出对齐方向。文中所有示例均以 add-viewport-icon.md 为核心骨架并补充仓库源码级实现细节作为佐证。认识视口动作菜单Viewport Action Menu系统OHIF 为每个视口内置了一套可定制的动作菜单渲染层官方 FAQ 文档platform/docs/docs/faq/add-viewport-icon.md将其称为viewport action menu system。这套系统允许开发者把自定义组件挂载到视口四周的任意角落而无需修改任何视口组件本体。在 UI 层视口被划分为八个可挂载区域。ui-next包中的 ViewportActionCorners.tsx 通过ViewportActionCornersLocations枚举定义了这些位置topLeft / topRight / bottomLeft / bottomRight // 四角 topMiddle / bottomMiddle / leftMiddle / rightMiddle // 四边中点每一个位置都对应一组 CSS 绝对定位类如左上角absolute top-[4px] left-[0px] pl-[4px]由ViewportActionCorners.Container统一渲染。以$push或$set方式写入viewportActionMenu前缀的定制化最终就会落到这些角落容器中。从零开始在左上角添加一个模式切换下拉菜单官方文档给出了一个完整、可直接复用的示例向视口左上角添加一个模式切换Mode Switch下拉菜单点击后可在 longitudinal / segmentation / tmtv / microscopy 等模式之间跳转。完整代码如下原文继承略有注释补充import React from react; import { Icons } from ohif/ui-next; import { useSystem } from ohif/core; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, Button, } from ohif/ui-next; // 一个自包含的组件工厂返回渲染下拉菜单的 React 组件 function getModeSwitchMenu({ viewportId, element, location }) { const ModeSwitchMenu () { const { servicesManager } useSystem(); const { router } servicesManager.services; const { viewportActionCornersService } servicesManager.services; const handleModeSwitch (mode) { const currentStudyInstanceUID router.query.StudyInstanceUIDs; // 携带当前检查跳转到目标模式 router.navigate(/${mode}?StudyInstanceUIDs${currentStudyInstanceUID}); }; // 根据挂载位置计算下拉菜单的对齐方式 let align center; let side bottom; if (location ! undefined) { const positioning viewportActionCornersService.getAlignAndSide(location); align positioning.align; side positioning.side; } return ( div classNameflex justify-end DropdownMenu DropdownMenuTrigger asChild Button variantghost sizeicon classNametext-highlight Icons.Tool / /Button /DropdownMenuTrigger DropdownMenuContent classNamemin-w-[160px] align{align} side{side} sideOffset{5} DropdownMenuLabel className-ml-1Mode/DropdownMenuLabel DropdownMenuItem onClick{() handleModeSwitch(longitudinal)} Longitudinal /DropdownMenuItem DropdownMenuItem onClick{() handleModeSwitch(segmentation)} Segmentation /DropdownMenuItem DropdownMenuItem onClick{() handleModeSwitch(tmtv)} TMTV /DropdownMenuItem DropdownMenuItem onClick{() handleModeSwitch(microscopy)} Microscopy /DropdownMenuItem /DropdownMenuContent /DropdownMenu /div ); }; return ModeSwitchMenu /; } // 在模式或扩展中通过 customizations 注册该组件 // 下面演示在 onModeEnter 生命周期钩子中完成注册 function onModeEnter({ servicesManager }) { const { customizationService } servicesManager.services; // 将模式切换图标添加到视口左上角 customizationService.setCustomizations({ viewportActionMenu.topLeft: { // 使用 $push 追加到现有条目或使用 $set 整体替换 $push: [ { id: modeSwitch, enabled: true, component: getModeSwitchMenu, }, ], }, }); }示例要点拆解组件工厂签名getModeSwitchMenu({ viewportId, element, location })接收一个对象参数其中viewportId可用于按视口区分行为location是组件被挂载的角落位置。从 servicesManager 取服务通过useSystem()拿到servicesManager进而解构出router与viewportActionCornersService。路由跳转router.navigate()保留了当前检查的StudyInstanceUIDs查询参数切换模式后仍停留在同一检查。动态对齐getAlignAndSide(location)根据角落位置返回{ align, side }直接喂给DropdownMenuContent。组件条目结构id / enabled / component写入viewportActionMenu.*的每个条目都是一个普通对象官方文档明确了三个核心字段字段类型说明idstring组件条目的唯一标识便于后续定位与修改enabledboolean控制该组件是否显示true显示componentfunction一个返回 React 组件的工厂函数即上文中的getModeSwitchMenu一个角落可以承载多个条目组件在角落内的排列顺序由数组中的顺序决定——先写的渲染在左边/上面后写的依次排列对应官方文档Components are rendered in the order they appear的说明。下拉菜单定位getAlignAndSide 的源码依据官方文档推荐使用viewportActionCornersService.getAlignAndSide(location)计算下拉菜单在对应角落的正确弹出方向。该方法的实际实现位于 ToolbarService.ts其内部针对每个角落返回一组align水平对齐与side弹出侧组合角落位置alignsideTopLeftstartbottomTopMiddlecenterbottomTopRightendbottomLeftMiddlestartrightRightMiddleendleftBottomLeftstarttopBottomMiddlecentertopBottomRightendtop例如组件位于右上角时下拉菜单应向右对齐align: end并向下展开side: bottom位于左下角时则应向下对齐align: start并向上展开side: top避免菜单溢出视口边界。方法对无法识别的值会回退到 TopLeft 的行为保证容错。向多个角落同时添加组件官方文档指出一次setCustomizations调用可以同时操作多个角落这比多次调用更高效也更便于统一管理customizationService.setCustomizations({ viewportActionMenu.topLeft: { $push: [ { id: modeSwitch, enabled: true, component: getModeSwitchMenu, }, ], }, viewportActionMenu.topRight: { $push: [ { id: anotherComponent, enabled: true, component: getAnotherComponent, }, ], }, });$push会在该角落已有条目例如 OHIF 内置的窗宽窗位菜单、方向菜单等的基础上追加新组件若希望替换该角落的全部现有条目则改用$setcustomizationService.setCustomizations({ viewportActionMenu.topLeft: { $set: [ { id: modeSwitch, enabled: true, component: getModeSwitchMenu, }, // 注意使用 $set 后这是该角落中仅存的组件 ], }, });定制化合并命令速查表$push/$set只是 OHIF 定制化合并机制的一部分。根据迁移指南 2-CustomizationService/index.md 中的完整表格customizationService.setCustomizations支持以下命令命令说明典型场景$set整体替换一个值替换整个列表或对象$push向数组末尾追加条目在角落追加新组件$unshift向数组开头插入条目将组件排在最前$splice在指定索引插入、删除或替换精确调整列表中间位置$merge更新对象的部分字段只改某个条目的enabled等字段$apply用函数动态计算新值根据运行状态变换数值$filter按匹配条件查找并更新数组内条目针对嵌套结构定向修改$transform动态变换整个定制化值替代早期版本的transform命令注意早期版本名为transform从 3.10 起统一改名为$transform底层合并基于immutability-helper库实现。这些命令同样适用于viewportOverlay.*、panelSegmentation.*等其他定制化键。源码级原理定制化如何变成角落里的图标理解配置到达界面的完整链路有助于排查问题。从仓库源码可以还原出如下调用链注册定制化customizationService.setCustomizations({ viewportActionMenu.topLeft: { $push: [...] } })将条目合并进定制化存储。读取与挂载cornerstone 扩展的 OHIFViewportActionCorners.tsx 是桥接组件。它渲染ViewportActionCorners.Container并把八个角落分别包一个Toolbar每个Toolbar绑定一个buttonSectionViewportActionCorners.TopLeft Toolbar buttonSectionviewportActionMenu.topLeft viewportId{viewportId} location{ButtonLocation.TopLeft} / /ViewportActionCorners.TopLeft也就是说viewportActionMenu.topLeft这类键不仅是定制化键同时也是ToolbarService 的按钮分区button section名。ToolbarService 在 ToolbarService.ts 中把八个位置统一注册为sections.viewportActionMenu.*分区。渲染ViewportActionCorners.Container内部用 Context 的registerCorner把各角落内容注册进状态再按位置类名渲染到视口覆盖层详见 ViewportActionCorners.tsx。按需显示OHIFViewportActionCornersComponent还通过useViewportHover判断视口是否被悬停或激活只有isHovered || isActive为真时才显示角落内容避免图标常驻遮挡影像。需要特别指出该定制化会作用于当前模式下的所有视口。如果希望不同视口展示不同内容官方文档的提示是——在组件逻辑中检查viewportId参数按需返回不同 UI。版本注意事项3.10 与 3.11 的差异3.9 → 3.10旧的cornerstoneOverlay*系列定制化更名为viewportOverlay.*addWindowLevelActionMenu全局配置废弃改为分别通过viewportActionMenu.windowLevelActionMenu与viewportActionMenu.segmentationOverlay控制且每个条目可通过location字段指定其角落位置详见 2-CustomizationService/index.md。3.10 → 3.11ViewportActionCornersService与ViewportActionCornersProvider已被废弃移除。官方迁移指南 viewport-action-menu.md 明确推荐自定义角落组件应定义为工具栏按钮toolbar button通过toolbarService.updateSection(toolbarService.sections.viewportActionMenu.topLeft, [...])放入对应的视口动作菜单分区。本 FAQ 中的viewportActionCornersService用法适用于旧版 API新项目建议优先走 ToolbarService 的按钮分区方案原理与定制化键完全一致——都是往viewportActionMenu.*注入组件。小结向 OHIF 视口角落添加自定义图标的核心套路可以概括为三步写一个返回 React 组件的工厂函数 → 用setCustomizations配合$push/$set注入viewportActionMenu.*键 → 用getAlignAndSide或 ToolbarService 分区处理好下拉菜单定位。配合id/enabled字段与$unshift/$splice/$merge等合并命令开发者可以像搭积木一样自由组合视口角落 UI而无需改动任何视口核心组件。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询