一、功能概述
"浏览器打开"是 XXX 系统的一个功能按钮,点击后会将当前页面从钉钉桌面客户端内 跳转到系统默认浏览器中打开。该功能解决了钉钉内置浏览器在某些场景下的兼容性问题,让用户可以使用完整功能的系统浏览器访问 XXX 系统。
二、核心执行流程
流程图
用户点击"浏览器打开"
│
▼
buildUrlWithAuthParams()
└─ 为当前 URL 附加 corpid 和 token 参数
│
▼
notInDD() 判断环境
│
┌───┴───┐
│ │
在钉钉内 不在钉钉内(纯浏览器)
│ │
▼ ▼
钉钉协议 URL window.open(url)
│
▼
dd.biz.util.openLink()
调用钉钉 JSAPI
│
┌──┴──┐
成功 失败
│ │
▼ ▼
日志 window.open() 兜底
2.1 构建认证 URL
typescript
const currentUrl = buildUrlWithAuthParams(window.location.href);
buildUrlWithAuthParams 函数作用是为 URL 附加认证参数:
typescript
export const buildUrlWithAuthParams = (url: string) => {
const urlObj = new URL(url, window.location.origin);
// 附加企业ID(corpid)
if (!urlObj.searchParams.has('corpid') && (window as any).__corpId) {
urlObj.searchParams.set('corpid', (window as any).__corpId);
}
// 附加认证token
if (!urlObj.searchParams.has('token') && (window as any).__token__) {
urlObj.searchParams.set('token', (window as any).__token__);
}
return urlObj.toString();
}
为什么需要附加参数?
当页面在外部浏览器打开时,原有的钉钉环境注入的 corpid 和 token 可能丢失。通过显式附加这些参数到 URL 中,确保外部浏览器打开后能正常通过认证,无需重新登录。
设计要点:
- 使用
URLAPI 安全解析和操作 URL - 仅在参数不存在时才添加,避免重复拼接
corpid全局变量通过钉钉 JSAPI 注入到window.__corpIdtoken全局变量通过钉钉 JSAPI 注入到window.__token__
2.2 环境判断
typescript
if (!notInDD()) {
// 在钉钉客户端内 → 使用钉钉协议跳转
} else {
// 在普通浏览器中 → 直接 window.open()
}
notInDD()
typescript
export const notInDD = () => dd?.env?.platform === 'notInDingTalk';
判断逻辑:
notInDD()返回true→ 当前在普通浏览器中(不在钉钉客户端内)notInDD()返回false→ 当前在钉钉桌面客户端内
注意区分两个相似函数:
| 函数 | 逻辑 | 区别 |
|---|---|---|
notInDD() |
platform === 'notInDingTalk' |
仅检测是否离开钉钉环境 |
isNotDD() |
platform === 'notInDingTalk' 且 域名不含 ddcrm / 系统域名 |
额外排除"浏览器打开的钉钉CRM"场景 |
2.3 钉钉协议跳转(核心)
当检测到在钉钉客户端内时,构造钉钉自定义协议 URL 并通过 JSAPI 调用:
typescript
const finalUrl = `dingtalk://dingtalkclient/page/link?url=${encodeURIComponent(currentUrl)}&pc_slide=false`
dd.biz.util.openLink({
url: finalUrl,
onSuccess: () => {
console.log('已成功在外部浏览器打开页面');
},
onFail: (err: any) => {
console.error('打开浏览器失败:', err);
window.open(finalUrl); // 兜底方案
},
});
钉钉自定义协议解析
dingtalk://dingtalkclient/page/link 是钉钉桌面客户端的 URL Scheme,用于内部路由:
| 组成部分 | 说明 |
|---|---|
dingtalk:// |
钉钉自定义协议头(类似 http://、https://) |
dingtalkclient/page/link |
钉钉内页面链接路由路径 |
url={encodeURIComponent(currentUrl)} |
目标网页 URL(需 URL 编码) |
pc_slide=false |
PC 端控制参数,false = 在系统默认浏览器打开;true = 在钉钉窗口内以侧边栏打开 |
pc_slide 参数详解:
pc_slide=false:启动系统默认浏览器(Chrome、Edge 等),在浏览器中打开目标 URLpc_slide=true:在钉钉窗口内部以侧边栏/新 Tab 形式打开目标 URL(不离开钉钉客户端)
dd.biz.util.openLink() JSAPI
这是钉钉官方提供的 JSAPI,用于在钉钉客户端内打开链接:
- 参数: 接收一个包含
url、onSuccess、onFail的对象 - 工作原理: 钉钉客户端会识别 URL 中的
dingtalk://协议头,触发系统级 URL Scheme 处理,从而打开系统默认浏览器 - 成功回调: 钉钉确认已触发外部浏览器打开
- 失败回调: 如果 JSAPI 调用失败(如权限不足、版本不兼容),回退到
window.open()兜底
三、依赖关系
handleOpenInBrowser
├── buildUrlWithAuthParams(url) → 附加 corpid + token
├── notInDD() → 判断是否离开钉钉环境
├── dd.biz.util.openLink() → 钉钉 JSAPI 打开链接
├── encodeURIComponent() → URL 编码
└── window.open() → 原生浏览器 API(兜底)
全局变量依赖
| 变量 | 来源 | 说明 |
|---|---|---|
window.__corpId |
钉钉 JSAPI 注入 | 当前企业 ID |
window.__token__ |
钉钉 JSAPI 注入 | 当前用户的认证 token |
window.location.href |
浏览器标准 API | 当前页面完整 URL |
dd.env.platform |
钉钉 JSAPI SDK | 当前运行平台标识 |
dd.biz.util.openLink |
钉钉 JSAPI SDK | 打开链接能力 |
四、异常处理
代码包含多层容错机制:
| 层级 | 场景 | 处理方式 |
|---|---|---|
| 1 | 不在钉钉内 | 直接 window.open(),跳过钉钉协议 |
| 2 | 钉钉 JSAPI 调用失败 | onFail 回调中 window.open() 兜底 |
| 3 | dd 对象不存在 |
使用可选链 dd?.env?.platform 避免崩溃 |
五、相关函数速查
ddOpenInBrowser()
typescript
export const ddOpenInBrowser = () =>
dd?.env?.platform === 'notInDingTalk' &&
(location.hostname.includes('ddcrm') ||
location.hostname.includes('系统域名'));
用于判断用户是否已经在使用浏览器访问钉钉 CRM(而非在钉钉客户端内)。如果是,则"浏览器打开"按钮无需显示。
openNewWindow()
typescript
export const openNewWindow = (url, ...arg) => {
if (!url) return;
const isNeedAuthParams = ddOpenInBrowser();
const finalUrl = isNeedAuthParams ? buildUrlWithAuthParams(url) : url;
window.open(finalUrl, ...arg);
};
通用的"在新窗口打开"方法,在浏览器打开钉钉 CRM 场景下会自动附加认证参数。handleOpenInBrowser 中直接使用了 window.open() 而非此函数,因为 URL 已在前面通过 buildUrlWithAuthParams 构建完毕。
newtab()
通用的"新 Tab 打开"方法,根据环境选择钉钉 JSAPI 或 window.open():
typescript
export function newtab(url = '') {
// ...URL 处理(相对路径转换、添加 ddtab=true 参数)
if (!notInDD()) {
dd.biz.util.openLink({ url: finalUrl, ... });
} else {
openNewWindow(finalUrl);
}
}
六、完整时序图
用户 RightTopNavBtns 浏览器/钉钉 系统浏览器
│ │ │ │
│ 点击"浏览器打开" │ │ │
│─────────────────────────►│ │ │
│ │ │ │
│ buildUrlWithAuthParams() │ │
│ window.location.href │ │
│ + corpid + token │ │
│ │ │ │
│ notInDD() 判断 │ │
│ │ │ │
│ ┌────────────────┤ │ │
│ │ 在钉钉内 │ │ │
│ ▼ │ │ │
│ 构造 dingtalk:// 协议URL │ │ │
│ dd.biz.util.openLink()──┼────────────────────────►│ │
│ │ │ 识别 dingtalk:// │
│ │ │ 协议, 触发系统浏览器 │
│ │ │─────────────────────►│
│ │ │ │ 打开目标 URL
│ │ │ │ (带 corpid+token)
│ │ │◄─────────────────────│
│ │◄────────────────────────│ │
│ │ onSuccess 回调 │ │
│ │ │ │
│ ┌────────────────┤ │ │
│ │ 不在钉钉内 │ │ │
│ ▼ │ │ │
│ window.open(url) ───────┼─────────────────────────┼──────────────────────►│
│ │ │ │ 打开目标 URL
│ │ │ │
│ 页面在外部浏览器中打开 │ │ │
│◄─────────────────────────────────────────────────────────────────────────│
│ │ │ │
七、注意事项
- 钉钉版本兼容性:
dingtalk://dingtalkclient/page/link协议需要钉钉桌面客户端 6.0+ 版本支持 - Mac 与 Windows 差异:
pc_slide参数在两个平台上行为一致,均会调用系统默认浏览器 - URL 编码: 目标 URL 必须经过
encodeURIComponent()编码,否则包含特殊字符(如&、#)时会截断参数 - 认证参数时效:
token有过期时间,如果用户在外部浏览器中长时间停留后刷新,可能需要重新认证 - 安全策略: 部分企业浏览器的安全策略可能拦截
dingtalk://协议唤起,此时会触发onFail兜底