文章目录
- 前言
- 一、功能背景与核心原理?
- 二、使用步骤
-
- 1.发送端为后台管理页的触发按钮页面,负责打开大屏窗口并定时发送登录凭证(定时发送用于兼容大屏页面加载延迟问题)。
- [2.接收端为 Vue3 项目的入口文件(main.ts/main.js),负责监听message事件、校验凭证合法性、调用登录接口、更新登录状态及处理全局 Loading。](#2.接收端为 Vue3 项目的入口文件(main.ts/main.js),负责监听message事件、校验凭证合法性、调用登录接口、更新登录状态及处理全局 Loading。)
- 总结
前言
在后台管理系统与大屏展示系统分离部署的场景下,为简化操作流程、提升用户体验,常需实现 "从后台管理页一键打开大屏页面并自动完成登录" 的功能,避免用户重复输入账号密码
一、功能背景与核心原理?
在后台管理系统开发中,大屏展示系统通常作为独立子系统部署,用户从后台管理页跳转大屏时,重复输入账号密码会降低操作效率。因此需要实现 "点击按钮打开大屏 + 自动登录" 的一体化流程。
关键技术原理
window.open:打开新窗口并保留窗口引用,为后续跨窗口通信提供载体; postMessage:浏览器原生跨文档通信 API,支持跨域
/ 跨窗口安全传递数据; addEventListener('message'):大屏页面监听message事件,接收父窗口传递的登录凭证;
Pinia:Vue3 官方状态管理库,用于持久化存储用户登录状态,保障页面刷新后状态不丢失; 自定义全局 Loading:通过动态创建
DOM 节点实现全屏加载反馈,提升用户操作体验。
二、使用步骤
1.发送端为后台管理页的触发按钮页面,负责打开大屏窗口并定时发送登录凭证(定时发送用于兼容大屏页面加载延迟问题)。
js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理-打开大屏</title>
<!-- 基础样式优化按钮外观 -->
<style>
#open {
padding: 12px 24px;
font-size: 16px;
background: #409eff;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s;
}
#open:hover {
background: #66b1ff;
}
</style>
</head>
<body>
<button id="open">打开大屏</button>
<script>
// 全局变量:保存新窗口引用和定时器(避免重复创建)
let screenWin = null;
let timer = null;
// 点击事件处理逻辑
document.getElementById('open').onclick = () => {
// 1. 打开大屏页面(替换为实际大屏访问地址)
screenWin = window.open('http:页面地址/home', '_blank');
// 2. 清除旧定时器,避免多个定时器同时发送消息
if (timer) clearInterval(timer);
// 3. 每2秒发送一次登录凭证(兼容大屏页面加载延迟)
timer = setInterval(() => {
// 窗口关闭/未正常打开则停止发送并清理定时器
if (!screenWin || screenWin.closed) {
clearInterval(timer);
return;
}
// 发送登录凭证(生产环境需将*替换为大屏实际域名)
screenWin.postMessage(
{
key1: 'admmin', // 账号
key2: 'admin' // 加密后的密码/登录令牌
},
'*'
);
}, 2000);
};
</script>
</body>
</html>
2.接收端为 Vue3 项目的入口文件(main.ts/main.js),负责监听message事件、校验凭证合法性、调用登录接口、更新登录状态及处理全局 Loading。
c
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
import { GetLogin } from './utils/api'; // 登录接口(需替换为实际接口)
import router from './router'; // 路由实例(需提前配置)
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { useAuthStore } from './store/index'; // 登录状态Store
// ========== 兼容处理:低版本浏览器无crypto.randomUUID ==========
if (typeof crypto !== 'undefined' && !crypto.randomUUID) {
crypto.randomUUID = function (): `${string}-${string}-${string}-${string}-${string}` {
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(uuid)) {
throw new Error('Generated UUID does not match expected format');
}
return uuid as `${string}-${string}-${string}-${string}-${string}`;
};
}
// ========== 创建并配置Vue实例 ==========
const pinia = createPinia();
// 注册Pinia持久化插件(保障登录状态刷新不丢失)
pinia.use(piniaPluginPersistedstate);
const app = createApp(App);
// 注册核心插件
app.use(pinia);
app.use(router);
app.use(ElementPlus);
// ========== 全局Loading工具函数 ==========
/**
* 显示全局Loading
* @param text 加载提示文本
*/
function showGlobalLoading(text = '页面加载中...') {
// 避免重复创建Loading节点
if (document.getElementById('global-loading')) return;
const div = document.createElement('div');
div.id = 'global-loading';
div.innerHTML = `
<div style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:99999;background:rgba(0,0,0,0.6);">
<div style="color:#fff;font-size:18px;padding:12px 20px;border-radius:6px;background:rgba(0,0,0,0.45);">
${text}
</div>
</div>
`;
document.body.appendChild(div);
}
/**
* 隐藏全局Loading
*/
function hideGlobalLoading() {
const el = document.getElementById('global-loading');
if (el) el.remove();
}
// ========== 消息校验与登录逻辑 ==========
// 获取Pinia的Auth Store实例(需在app.use(pinia)之后)
const authStore = useAuthStore();
/**
* 校验postMessage消息合法性
* @param data 消息数据
* @returns 是否合法
*/
function isValidMessage(data: any) {
// 基础校验:对象类型且包含账号/密码字段
return data && typeof data === 'object' && data.key1 && data.key2;
}
/**
* 处理登录逻辑
* @param loginData 登录凭证
*/
async function handleLogin(loginData: any) {
try {
showGlobalLoading('正在跳转中...');
// 构造登录接口参数(根据实际接口格式调整)
const payload = {
action: 'GetLogin',
userName: loginData.key1,
userPwd: loginData.key2,
};
// 调用登录接口
const response = await login(payload);
if (response && response.Status) {
// 登录成功:更新Pinia登录状态
authStore.setUser(response.Result);
// 跳转至大屏首页(catch捕获重复路由错误)
router.push('/home').catch(() => {});
// 移除消息监听,避免重复登录
window.removeEventListener('message', receiveMessage, false);
} else {
console.warn('登录失败:', response?.Message || '接口返回异常');
// 可选:使用Element Plus消息提示用户
// ElMessage.error('登录失败:' + (response?.Message || '凭证错误'));
}
} catch (err) {
console.error('登录流程异常:', err);
// ElMessage.error('登录失败:网络异常');
} finally {
// 无论成功/失败,均隐藏全局Loading
hideGlobalLoading();
}
}
/**
* 监听postMessage消息
* @param e 消息事件对象
*/
function receiveMessage(e: MessageEvent) {
// 生产环境必须校验消息来源(示例:仅允许指定域名的消息)
// if (e.origin !== 'http://192.168.1.100:8080') return;
// 消息不合法则直接返回
if (!isValidMessage(e.data)) return;
// 立即显示Loading,提升用户体验
showGlobalLoading('正在跳转中...');
// 处理登录逻辑
handleLogin(e.data);
}
// ========== 注册监听与兜底处理 ==========
// 监听message事件,接收父窗口消息
window.addEventListener('message', receiveMessage, false);
// 路由切换后兜底隐藏Loading(避免异常导致Loading常驻)
router.afterEach(() => {
hideGlobalLoading();
});
// 挂载Vue实例
app.mount('#app');
该处使用的url网络请求的数据。
总结
本文基于postMessage跨窗口通信机制,结合 Vue3 + Pinia 技术栈,完整实现了后台管理系统跳转大屏并自动登录的功能。核心流程为:父窗口打开大屏窗口并定时发送登录凭证 → 大屏页面监听并校验消息 → 调用登录接口更新状态 → 跳转大屏首页。