尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Dynamics 365 CRM V9.1 NavigateTo API:统一导航与现代化交互开发指南

Dynamics 365 CRM V9.1 NavigateTo API:统一导航与现代化交互开发指南 1. 项目概述从“NavigateTo”看CRM V9.1的交互革命如果你是一位在Dynamics 365 CRM平台上摸爬滚打多年的开发者或实施顾问那么“NavigateTo”这个词对你来说可能意味着一个时代的结束和另一个时代的开始。它不是CRM V9.1版本更新日志里最显眼的功能但绝对是改变我们日常开发习惯和用户体验设计思路的一个关键特性。简单来说Xrm.Navigation.navigateTo是微软在Dynamics 365 Customer Engagement也就是我们常说的CRMV9.1版本中引入的一个客户端API方法。它的核心使命是统一并标准化在模型驱动应用内部进行页面导航的方式。在过去我们想要从一个实体记录表单跳转到另一个相关记录或者打开一个特定的仪表板、视图甚至是自定义的Web资源页面往往需要动用各种“野路子”。比如用window.open在新窗口打开但这样会丢失上下文或者用Xrm.Utility.openEntityForm打开表单但控制粒度有限更古老的方式是直接操作window.parent的框架既不稳定也不安全。NavigateToAPI的出现就是为了终结这种混乱。它提供了一套声明式的、异步的、且完全遵循模型驱动应用沙箱规范的导航机制。这意味着无论是跳转到实体记录、视图、仪表板还是打开作为独立页面的Web资源你都可以用同一套语法清晰、安全地完成并且能精准控制页面打开的位置是当前选项卡、侧边栏、对话框还是新窗口以及传递参数。对于正在构建“永久在线的CRM网站”或复杂业务门户的团队来说NavigateTo的价值尤为突出。它确保了在单页面应用SPA架构下页面跳转不会引起整个应用的重载保持了用户会话的连续性提升了操作流畅度。同时它也是深度集成自定义Web资源比如用React、Vue构建的现代化前端模块到标准CRM表单中的“粘合剂”。理解了NavigateTo你就掌握了在Dynamics 365生态中构建无缝、现代化用户体验的一把关键钥匙。2. NavigateTo API的核心能力与设计哲学2.1 统一导航接口的诞生背景在深入代码之前我们有必要先理解为什么微软要推出这个API。在V9.1之前Dynamics 365的客户端导航是一个“多国演义”的状态。不同的场景对应不同的API甚至是一些非公开的DOM操作这带来了几个显著问题体验碎片化有的跳转会在当前页面完成有的会弹出新窗口有的则会在同一个窗口内替换内容用户无法形成统一的预期。上下文丢失使用window.open等方式打开新页面新页面无法直接访问原页面的上下文如当前用户信息、实体记录ID需要通过URL参数复杂地传递既麻烦又不安全。维护成本高各种导航代码散落在不同的Web资源、命令栏按钮和表单脚本中当微软更新平台UI框架时这些非标准的导航方式很容易失效导致系统不稳定。移动端适配差在CRM移动端应用或响应式界面中一些传统的导航方法行为不可预测无法提供良好的移动体验。Xrm.Navigation.navigateTo的设计目标就是成为模型驱动应用中页面导航的“唯一真理源”。它借鉴了现代Web开发中路由的概念将导航目标抽象为一个包含pageType和一系列属性的对象让开发者能够以更声明式、更可控的方式进行跳转。2.2 方法签名与参数深度解析navigateTo方法的基本签名是异步的这符合现代Web API的设计趋势便于处理导航前后的逻辑。Xrm.Navigation.navigateTo(pageInput, navigationOptions).then( function success() { // 导航成功后的回调 }, function error(error) { // 导航失败的处理 console.error(error.message); } );这里有两个核心参数对象pageInput和navigationOptions。它们的结构决定了导航的行为。pageInput对象定义你要去哪里。 这是导航的核心其属性根据pageType的不同而差异巨大。主要的pageType包括entityRecord导航到某个实体记录的主表单。这是最常用的类型。entityList导航到实体列表视图。webresource导航到一个自定义的HTML Web资源页面。dashboard导航到一个仪表板。我们以一个最常见的场景——跳转到某个客户account的主表单为例看看pageInput该如何配置const pageInput { pageType: entityRecord, entityName: account, entityId: a1b2c3d4-e5f6-7890-abcd-ef1234567890, // 目标客户的GUID formId: {00000000-0000-0000-0000-000000000001}, // 可选指定使用哪个表单ID data: { // 可选预填充表单字段 name: 示例公司, telephone1: 400-123-4567 } };关键点解析entityId如果提供有效的GUID则打开该记录的更新表单。如果不提供或为null则打开该实体的创建表单。formId这是一个非常实用的参数。在大型项目中一个实体可能有多个表单如“销售表单”、“服务表单”。通过指定formId你可以确保用户总是进入正确的业务上下文而不是默认表单。data对象中的字段逻辑名必须准确。这常用于从当前记录快速创建关联记录并预填部分信息极大地提升了数据录入效率。navigationOptions对象定义导航如何发生。 这个对象控制页面的打开方式是提升用户体验的关键。const navigationOptions { target: 2, // 2 代表在“当前页面”导航这是最常用的 width: {value: 800, unit: px}, // 对话框宽度 height: {value: 600, unit: px}, // 对话框高度 position: 1 // 1 代表对话框居中 };target属性是最重要的它接受一个整数枚举值1对话框内。以模态对话框形式打开用户必须处理完该对话框才能返回原页面。适合快速创建或编辑关联记录。2当前页面。在模型驱动应用的主内容区域进行导航替换当前显示的内容。这是标准的页面跳转行为保持了单页面应用的特性。3新窗口。在新的浏览器标签页中打开。适合需要并排查看或独立流程的场景。4侧边栏。在右侧滑出的侧边栏中打开。这是V9之后引入的非常流行的方式适合查看详情、快速编辑等不希望打断主流程的操作。实操心得对于“查看详情”这类辅助操作我强烈推荐使用target: 4侧边栏。它的用户体验远优于对话框或新窗口不会遮挡主内容关闭也方便感觉像是应用原生的一部分。而对于“创建新的关联任务”这类需要专注完成的操作使用target: 1对话框则更合适。3. 四大核心应用场景与实战代码理解了API的基本用法我们来看几个在实际项目中高频出现、能直接提升用户效率的场景。3.1 场景一从列表视图快速定位关联记录这是最经典的需求。用户在某个实体的列表视图如“我的活跃商机”中想快速打开某个商机对应的客户主数据。假设我们在商机opportunity的列表视图命令栏上添加了一个自定义按钮“查看客户”。// 这个函数通常作为列表命令栏按钮的执行函数 function navigateToParentAccount(selectedItems) { // selectedItems 是用户选中的一行或多行记录 const firstSelected selectedItems[0]; // 1. 获取当前选中商机记录的“客户”字段值查找字段 const customerFieldValue firstSelected.getFieldValue(customerid); if (!customerFieldValue) { Xrm.Navigation.openAlertDialog({ text: “所选商机未关联客户。” }); return; } // customerFieldValue 是一个数组包含查找字段的实体类型名和ID const entityType customerFieldValue[0].entityType; // 如 “account” const entityId customerFieldValue[0].id; // 客户的GUID const entityName customerFieldValue[0].name; // 客户名称可用于提示 // 2. 构建导航参数 const pageInput { pageType: entityRecord, entityName: entityType, entityId: entityId }; // 3. 执行导航在侧边栏打开体验最佳 const navOptions { target: 4, // 侧边栏 width: {value: 600, unit: px}, position: 1 }; Xrm.Navigation.navigateTo(pageInput, navOptions).then( () console.log(“客户页面已在侧边栏打开”), (error) console.error(“导航失败:”, error.message) ); }注意事项查找字段Lookup的值是一个对象数组即使只关联了一个记录也是如此。始终通过[0]来访问第一个也是唯一一个关联项。在实际项目中需要增加更健壮的错误处理比如selectedItems为空或多选的情况。对于多选可以设计为只打开第一个选中项的关联客户或者给出提示。3.2 场景二在表单中创建并关联子记录在客户account表单上我们想添加一个按钮一键为该客户创建一个新的联系人contact并自动将联系人的“公司名称”字段关联到当前客户。// 在客户表单的某个命令栏按钮上触发 function createRelatedContact() { // 获取当前客户表单的上下文和ID const accountId Xrm.Page.data.entity.getId().replace(/[{}]/g, ); const accountName Xrm.Page.getAttribute(name).getValue(); // 构建导航到联系人创建表单的输入 const pageInput { pageType: entityRecord, entityName: contact, entityId: null, // null 表示创建新记录 data: { parentcustomerid: [{ entityType: account, id: accountId, name: accountName // 可选但提供了会更友好 }], lastname: “新联系人”, // 提供一个默认姓氏 // 可以预填更多字段如从客户信息中带出地址 address1_city: Xrm.Page.getAttribute(address1_city).getValue() } }; // 以对话框形式打开让用户专注填写联系人详情 const navOptions { target: 1, // 对话框 width: {value: 550, unit: px}, height: {value: 700, unit: px}, position: 1 }; Xrm.Navigation.navigateTo(pageInput, navOptions).then( function success() { // 联系人创建并保存后可以刷新当前页面上的联系人子网格 const contactGrid Xrm.Page.getControl(“Contacts”); // 子网格名称 if (contactGrid contactGrid.refresh) { contactGrid.refresh(); } Xrm.Utility.alertDialog(“联系人已成功创建并关联。”); }, function error(error) { // 用户可能点击了对话框的取消按钮这不一定是错误 if (error error.message !error.message.includes(“cancelled”)) { Xrm.Navigation.openErrorDialog({ message: error.message }); } } ); }实操心得通过data属性预填字段是navigateTo的一大亮点它实现了数据的“智能传递”减少了用户的重复输入。创建完成后在success回调中刷新子网格能给用户即时的反馈体验非常顺畅。但要注意判断子网格控件是否存在。对话框target:1的取消操作会触发error回调但这不是真正的系统错误。在错误处理中最好检查一下错误信息避免将用户取消误报为错误。3.3 场景三将自定义Web资源作为独立应用页面集成假设我们开发了一个用Vue.js构建的、复杂的“合同审批看板”Web资源new_/webresources/contractdashboard.html。我们希望在CRM的站点地图中有一个独立的菜单项来打开它或者从某个合同记录的表单上点击按钮打开它。// 打开一个功能完整的自定义单页面应用 function openContractDashboard() { // 假设我们希望从URL参数中获取当前过滤条件比如当前用户ID const userId Xrm.Utility.getGlobalContext().userSettings.userId; const pageInput { pageType: webresource, webresourceName: “new_/webresources/contractdashboard.html”, // data 属性中的键值对会以查询字符串的形式传递给Web资源页面 data: { “userId”: userId, “viewType”: “myPending” // 自定义参数告诉看板默认加载“我待审批”的视图 } }; const navOptions { target: 2, // 作为主应用页面打开替换当前内容区域 // width/height 对 target2 无效因为会占据整个内容区 }; Xrm.Navigation.navigateTo(pageInput, navOptions); }在你的contractdashboard.html页面中你需要编写JavaScript来接收这些参数// 在contractdashboard.html的脚本中 window.addEventListener(“load”, function() { // 获取由NavigateTo传递过来的数据 const data window.parent.Xrm.Utility.getPageContext().input; if (data) { const userId data.userId; const viewType data.viewType; // 使用这些参数初始化你的Vue/React应用调用后端API获取数据 console.log(为用户 ${userId} 加载 ${viewType} 看板); initDashboard(userId, viewType); } });核心要点当pageType为webresource时data对象中的所有属性都会被转换为URL查询参数如?userIdxxxviewTypemyPending传递给目标页面。在Web资源页面内部需要通过Xrm.Utility.getPageContext().input来获取这些参数。这是与普通URL传参不同的、模型驱动应用内特有的方式。使用target: 2可以将你的自定义应用完全变成CRM主应用的一部分用户体验统一。这是构建“永久在线的CRM网站”中复杂功能模块的标准做法。3.4 场景四动态导航与条件逻辑结合导航不总是简单的跳转经常需要根据一些业务逻辑来决定去向哪里。例如在商机表单上根据“销售阶段”字段的值决定“下一步”按钮是跳转到“报价单”创建页面还是“竞争对手分析”页面。function navigateToNextStep() { const opportunityForm Xrm.Page; const salesStage opportunityForm.getAttribute(“salesstagecode”).getValue(); // 假设是选项集 const customerId opportunityForm.getAttribute(“customerid”).getValue(); let pageInput; let navOptions { target: 2 }; // 根据销售阶段决定导航目标 switch(salesStage) { case 200000: // “方案论证”阶段 pageInput { pageType: “entityList”, entityName: “quote”, // 可以传递视图ID直接打开某个特定的视图 viewId: “{00000000-0000-0000-0000-000000000002}”, // 例如“待生成报价”视图 viewType: 0 // 0代表系统视图 }; break; case 300000: // “提案/报价”阶段 if (!customerId) { Xrm.Navigation.openAlertDialog({ text: “请先选择客户才能创建报价。” }); return; } pageInput { pageType: “entityRecord”, entityName: “quote”, entityId: null, data: { “customerid”: customerId, “opportunityid”: [{ entityType: “opportunity”, id: opportunityForm.data.entity.getId().replace(/[{}]/g, “”) }] } }; navOptions.target 1; // 创建报价用对话框更合适 break; case 400000: // “竞争分析”阶段 pageInput { pageType: “webresource”, webresourceName: “new_/webresources/competitoranalysis.html”, data: { opportunityId: opportunityForm.data.entity.getId() } }; break; default: Xrm.Navigation.openAlertDialog({ text: “当前阶段无指定下一步操作。” }); return; } // 执行条件导航 Xrm.Navigation.navigateTo(pageInput, navOptions).catch(error { console.error(“条件导航失败:”, error); }); }这个例子展示了navigateTo如何与业务逻辑深度结合实现智能的、向导式的用户操作流程使CRM系统从被动的数据记录工具转变为主动的业务推进助手。4. 高级技巧、性能优化与避坑指南掌握了基础用法和常见场景我们再来探讨一些能让你代码更健壮、性能更优的高级技巧和必须绕开的“坑”。4.1 性能优化避免不必要的导航与预加载导航虽然是客户端操作但不当使用也会影响体验。防重复点击在命令栏按钮的点击事件处理函数中最常见的错误就是用户快速双击导致两次导航。简单的解决方案是设置一个标志位。let isNavigating false; function navigateSomewhere() { if (isNavigating) { return; // 如果正在导航中忽略此次点击 } isNavigating true; Xrm.Navigation.navigateTo(/* ... */) .then(() { isNavigating false; }) .catch(() { isNavigating false; }); // 无论成功失败都要重置标志 }预加载判断在导航到某个记录前有时需要先检查该记录是否存在或者用户是否有权限访问。虽然navigateTo自身的错误回调会处理一些情况但提前用Xrm.WebApi进行一个轻量级的检索可以提供更友好的用户提示。function navigateToAccountIfAccessible(accountId) { Xrm.WebApi.retrieveRecord(“account”, accountId, “?$selectname”) .then(record { // 能检索到说明记录存在且有读权限 const pageInput { pageType: “entityRecord”, entityName: “account”, entityId: accountId }; Xrm.Navigation.navigateTo(pageInput, {target: 4}); }) .catch(error { if (error.message.includes(“404”)) { Xrm.Navigation.openAlertDialog({ text: “该客户记录可能已被删除。” }); } else if (error.message.includes(“403”)) { Xrm.Navigation.openAlertDialog({ text: “您无权查看此客户记录。” }); } else { Xrm.Navigation.openErrorDialog({ error: error }); } }); }4.2 状态管理与参数传递的进阶玩法在复杂的自定义页面Webresource之间导航时仅仅通过URL传递简单参数可能不够。我们需要更复杂的状态管理。使用全局上下文或本地存储对于在同一浏览器会话中需要共享的复杂数据如一个多步骤向导的中间状态可以将其存储在window.parent下的一个全局对象中或者使用sessionStorage。// 在页面A设置状态 window.parent._myAppState { wizardStep: 2, selectedProducts: […], draftData: {…} }; // 然后导航到页面B Xrm.Navigation.navigateTo(/* pageInput for page B */); // 在页面B中读取状态 const state window.parent._myAppState; if (state) { // 根据state恢复页面B的界面 }重要警告这种方法不是官方推荐的因为它依赖于全局命名空间可能存在冲突。更稳健的方式是设计一个良好的URL参数结构或者通过服务器端来暂存复杂状态。但对于一些轻量的、临时的会话状态这不失为一种快速方案。深度链接与返回导航当你使用target: 2当前页面导航到一个Web资源后浏览器的“后退”按钮默认是有效的这很好。但如果你希望自定义返回逻辑或者在Web资源内部有复杂的路由你可能需要集成像vue-router或react-router这样的库并小心处理与CRM主框架的集成。4.3 常见问题排查与调试技巧导航无反应或报错“Invalid Argument”检查点首先确认pageType和entityName的拼写绝对正确大小写敏感。entityName必须是实体的逻辑名小写如“account”而不是显示名“Account”。检查点entityId必须是一个有效的GUID字符串。确保你从上下文中获取的ID已经移除了花括号{}或者格式正确。调试在调用navigateTo之前用console.log(JSON.stringify(pageInput))将参数打印出来仔细核对每一个属性。Webresource页面打开后无法获取参数检查点确保在Webresource页面中是通过window.parent.Xrm.Utility.getPageContext().input来获取数据而不是window.Xrm或直接读取URL的location.search。在作为webresource导航时参数不是通过标准URL查询字符串传递的。检查点检查Webresource的发布状态和权限确保当前用户有权限访问该Web资源。对话框target:1关闭后回调不执行现象打开了对话框用户操作后关闭但then或catch回调没有触发。原因这通常是因为对话框页面比如一个实体创建表单在保存时发生了验证错误或者用户直接点击了浏览器关闭按钮而非通过表单的“保存并关闭”或“取消”按钮。某些非标准的关闭方式可能不会正确触发Promise的结算。应对在错误回调中不要假设所有错误都是系统故障。可以检查错误信息如果是“Dialog Closed”或包含“cancelled”则按用户取消处理。在模型驱动应用的命令栏Ribbon中使用在命令栏的CustomRule或EnableRule中无法直接使用Xrm.Navigation因为那是加载时评估的。导航逻辑必须写在按钮的Action调用的JavaScript函数中。确保你的导航函数是全局可访问的并且已作为Web资源正确上传和引用。移动端适配在CRM移动端应用Unified Interface中navigateToAPI同样工作但target参数的行为可能与网页端略有不同。例如target: 4侧边栏在移动端可能表现为全屏模态视图。务必在移动设备上进行测试。避免设置过大的width和height值因为移动端屏幕尺寸有限。5. 从NavigateTo看Dynamics 365客户端开发演进Xrm.Navigation.navigateTo不仅仅是一个API它更代表了Dynamics 365平台客户端开发模式的一次重要演进。在它出现之前我们更多地是在与DOM和未公开的API“斗智斗勇”代码脆弱且难以维护。NavigateTo将导航这个基础操作标准化、承诺化Promise-based、声明化这带来了几个深远影响首先它推动了代码的现代化。异步Promise的使用鼓励开发者编写更清晰、更容易处理成功和失败路径的代码。结合async/await语法可以让导航逻辑读起来像同步代码一样直观。其次它强化了模型驱动应用的边界。通过提供官方、稳定的API微软明确指出了什么是受支持的扩展方式。这降低了因为平台升级而导致自定义功能失效的风险保护了项目的长期投资。最后它提升了用户体验的一致性。无论导航目标是标准实体、自定义实体还是复杂的Web资源用户获得的开合动画、位置、交互方式都是一致的。这种一致性对于用户熟练使用系统、降低培训成本至关重要。对于像“悟空CRM”这类基于Dynamics 365进行深度定制的产品或是任何致力于打造“永久在线的CRM网站”的团队深入理解和熟练运用NavigateToAPI是构建流畅、现代、可维护的前端交互层的基石。它把我们从繁琐的导航兼容性工作中解放出来让我们能更专注于解决实际的业务问题。下次当你在Dynamics 365中需要让页面跳转时请忘掉window.open试试Xrm.Navigation.navigateTo你会发现一个更优雅、更强大的世界。
返回列表