在监控大屏、指挥驾驶舱、多屏看板等场景中,我们常需要将浏览器窗口精准投放到不同的扩展显示器上,同时保证各窗口间登录态同步。本文基于Vue 3技术栈,分享纯前端实现多显示器检测、窗口定位及跨窗口通信的完整方案,无需依赖Electron,仅用浏览器原生API就能搞定!
一、项目背景与核心目标
最近接到一个多屏展示的需求:管理系统需要在主显示器操作,同时自动在扩展显示器上打开多个子窗口展示不同模块的数据,且所有窗口需保持登录状态一致。
技术栈已确定为Vue 3 + Vite + Pinia + Element-Plus,核心目标拆解为两点:
- 精准识别多显示器,将子窗口自动定位到扩展屏
- 实现主窗口与子窗口的登录态同步,确保子窗口请求正常携带认证信息
二、核心技术选型:浏览器原生API搞定多屏需求
一开始考虑过用Electron增强窗口控制能力,但为了降低技术复杂度和打包体积,最终选择纯浏览器方案。关键技术选型如下:
| 功能场景 | 技术方案 | 核心优势 |
|---|---|---|
| 多显示器检测 | window.getScreenDetails() | 原生枚举屏幕信息,支持区分主屏/扩展屏 |
| 跨窗口通信 | BroadcastChannel | 同源窗口双向广播,API简单无需轮询 |
| 登录态存储 | Cookie | 自动跟随请求携带,与现有认证逻辑兼容 |
| 窗口定位控制 | window.open() 特征参数 | 通过坐标直接指定窗口打开位置 |
三、关键技术实现:从屏幕识别到状态同步
3.1 路由设计:主窗口与子窗口分离
首先规划路由结构,主窗口负责控制逻辑,子窗口对应独立展示页面,通过动态参数区分不同子窗口:
arduino
const routes = [
{ path: '/login', name: 'login', component: LoginView }, // 登录页
{ path: '/', name: 'main', component: MainView }, // 主控制窗口
{ path: '/sub/:id', name: 'sub', component: SubView, props: true }, // 扩展屏子窗口
]
3.2 多显示器检测:精准定位扩展屏
核心依赖window.getScreenDetails()(Chromium系浏览器支持,需安全上下文+用户授权),通过该API可获取所有屏幕的坐标、尺寸及是否为主屏的标识。
主窗口点击按钮触发子窗口创建的完整逻辑:
typescript
import { ElMessage } from 'element-plus'
import { getToken } from '@/utils/auth'
async function sendToChildren() {
const childId = Date.now().toString() // 生成唯一子窗口ID
const origin = window.location.origin // 避免跨域问题,使用当前源
const getDetails = (window as any).getScreenDetails // 类型断言兼容TS
if (getDetails) {
try {
const screens = await getDetails() // 申请窗口放置权限,用户授权后返回屏幕列表
// 筛选扩展屏(isPrimary为false),为每个扩展屏创建子窗口
screens.screens.forEach((screen: any) => {
if (!screen.isPrimary) {
// 按扩展屏坐标和尺寸配置窗口参数
const features = `left=${screen.left},top=${screen.top},width=${screen.width},height=${screen.height}`
const url = `${origin}/sub/${childId}`
const childWindow = window.open(url, '_blank', features)
if (!childWindow) {
ElMessage.error('弹窗被浏览器阻止,请在地址栏右侧允许弹窗权限')
return
}
// 发送token到子窗口(延迟确保子窗口监听已挂载)
sendTokenToChild(childId)
}
})
} catch (err) {
ElMessage.error('获取屏幕信息失败,请检查窗口放置权限')
}
} else {
// 兼容性降级:不支持多屏API时,打开普通子窗口
const url = `${origin}/sub/${childId}`
const childWindow = window.open(url, '_blank', 'noopener')
if (!childWindow) {
ElMessage.error('弹窗被浏览器阻止,请允许弹窗后重试')
return
}
sendTokenToChild(childId)
ElMessage.warning('当前浏览器不支持多屏控制,已打开默认子窗口')
}
}
// 封装BroadcastChannel发送token的逻辑
function sendTokenToChild(childId: string) {
const channel = new BroadcastChannel('auth-channel') // 同源窗口共享频道
const token = getToken() // 从Cookie获取当前登录token
setTimeout(() => {
channel.postMessage({ type: 'token', token, id: childId })
channel.close() // 发送完成关闭频道,避免内存泄漏
}, 800)
}
3.3 跨窗口通信:BroadcastChannel同步登录态
BroadcastChannel允许同源下的多个窗口、标签页之间实时通信,我们创建名为auth-channel的专用频道,用于主窗口向子窗口广播登录token。
子窗口(SubView.vue)接收并存储token的逻辑:
javascript
import { onMounted, onBeforeUnmount } from 'vue'
import { setToken } from '@/utils/auth'
let unlisten: () => void // 存储移除监听的方法
onMounted(() => {
// 创建与主窗口同名的通信频道
const channel = new BroadcastChannel('auth-channel')
// 监听主窗口发送的消息
const messageHandler = (event: MessageEvent) => {
const data = event.data || {}
// 验证消息类型,避免接收无关消息
if (data.type === 'token' && typeof data.token === 'string') {
setToken(data.token) // 将token写入Cookie,供请求拦截器使用
console.log(`子窗口${data.id}已同步登录态`)
}
}
channel.addEventListener('message', messageHandler)
// 组件卸载时移除监听,防止内存泄漏
unlisten = () => {
channel.removeEventListener('message', messageHandler)
channel.close()
}
})
onBeforeUnmount(() => {
if (typeof unlisten === 'function') unlisten()
})
3.4 认证工具封装:Cookie操作简化
基于js-cookie封装token的存取方法,与现有请求拦截器无缝衔接:
javascript
import Cookies from 'js-cookie'
const TokenKey = 'admin-token' // 与后端约定的token键名
// 获取token
export function getToken() {
return Cookies.get(TokenKey)
}
// 存储token(设置过期时间为1天)
export function setToken(token) {
return Cookies.set(TokenKey, token, { expires: 1 })
}
// 清除token
export function removeToken() {
return Cookies.remove(TokenKey)
}
四、避坑指南与最佳实践
权限问题:getScreenDetails()需要用户授权"窗口放置"权限,首次使用需通过弹窗引导用户开启,可在按钮点击前添加权限申请说明。
弹窗拦截:window.open()易被浏览器拦截,需确保在用户主动点击事件内触发,同时添加拦截提示引导用户放行。
兼容性:仅Chromium系浏览器(Chrome/Edge 100+)支持多屏API,需为其他浏览器提供降级方案。
内存泄漏:BroadcastChannel和事件监听在组件卸载时必须销毁,避免长期占用资源。
协议要求:window.getScreenDetails()仅在HTTPS协议下可用,部署时需确保站点使用HTTPS,避免HTTP环境下API失效。
五、总结与扩展方向
本方案基于纯浏览器API实现了多显示器窗口控制与通信,无需引入Electron等桌面应用框架,与Vue 3技术栈耦合度低,可快速集成到现有项目中。核心优势在于轻量化和原生兼容性,适合多屏看板、监控系统等场景。
后续可优化的方向:
- 适配新规范
window.getScreens()(getScreenDetails()的替代API) - 增加子窗口状态反馈机制,主窗口实时监控子窗口是否在线
- 对于强窗口控制需求,可提供Electron方案作为备选(支持非同源窗口通信)
如果你的项目也有类似多屏需求,欢迎留言交流遇到的问题,我会及时回复~