核心原理
使用 URL Protocol 协议注册 + 前端跳转协议 的方式实现,兼容主流浏览器(需用户提前注册协议)。
完整实现步骤
1. 注册自定义 URL 协议(Windows 系统)
创建 open-app.reg 文件,内容如下:
Windows Registry Editor Version 5.00
; 删除旧的(如果存在)
[-HKEY_CLASSES_ROOT\myapp]
[HKEY_CLASSES_ROOT\myapp]
@="URL:MyApp Protocol v2"
"URL Protocol"=""
"EditFlags"=dword:00000002 ; 强制重新关联
[HKEY_CLASSES_ROOT\myapp\DefaultIcon]
@="C:\\Path\\To\\YourApp.exe,0"
[HKEY_CLASSES_ROOT\myapp\shell]
[HKEY_CLASSES_ROOT\myapp\shell\open]
[HKEY_CLASSES_ROOT\myapp\shell\open\command]
@="\"C:\\Path\\To\\YourApp.exe\" \"%1\""
说明:
- 替换 C:\Path\To\YourApp.exe 为实际 exe 路径(如 C:\ProgramFiles\MyApp\app.exe)
- 双击运行该文件完成注册
2.Vue3 + TypeScript 前端实现
<template>
<button @click="openLocalApp" :disabled="isOpening">
{{ isOpening ? '正在启动...' : '启动本地应用' }}
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
isOpening: false,
fallbackShown: false
};
},
methods: {
async openLocalApp() {
if (this.isOpening) return;
this.isOpening = true;
// 方法1:使用 iframe(最兼容)
const hiddenFrame = document.createElement('iframe');
hiddenFrame.style.display = 'none';
hiddenFrame.src = 'myapp://launch?timestamp=' + Date.now(); // 添加时间戳避免缓存
document.body.appendChild(hiddenFrame);
// // 方法2:使用 window.open(备用)
// const fallbackTimer = setTimeout(() => {
// if (!this.fallbackShown) {
// this.fallbackShown = true;
// window.open('myapp://launch', '_blank');
// }
// }, 100);
// 监听各种可能的事件来判断应用是否启动
const cleanup = () => {
this.isOpening = false;
// clearTimeout(fallbackTimer);
clearTimeout(failTimer);
hiddenFrame.remove();
window.removeEventListener('blur', blurHandler);
document.removeEventListener('visibilitychange', visibilityHandler);
};
const blurHandler = () => {
console.log('应用启动成功 - 窗口失去焦点');
cleanup();
// 可选:显示成功提示
setTimeout(() => {
alert('应用启动成功!');
}, 100);
};
const visibilityHandler = () => {
if (document.hidden) {
console.log('应用启动成功 - 页面被隐藏');
cleanup();
}
};
window.addEventListener('blur', blurHandler, { once: true });
document.addEventListener('visibilitychange', visibilityHandler, { once: true });
// 超时处理
const failTimer = setTimeout(() => {
if (this.isOpening) {
cleanup();
// 提供详细的错误帮助
const userChoice = confirm(
'应用启动可能需要额外权限。\n\n' +
'可能的原因:\n' +
'1. 防火墙或安全软件阻止\n' +
'2. 需要手动允许协议启动\n' +
'3. 应用路径不正确\n\n' +
'点击"确定"查看详细解决方案'
);
if (userChoice) {
window.open('/help#protocol-guide', '_blank');
}
}
}, 5000); // 5秒超时
}
}
});
</script>