Vue3 实现后台管理系统跳转大屏自动登录功能

文章目录


前言

在后台管理系统与大屏展示系统分离部署的场景下,为简化操作流程、提升用户体验,常需实现 "从后台管理页一键打开大屏页面并自动完成登录" 的功能,避免用户重复输入账号密码


一、功能背景与核心原理?

在后台管理系统开发中,大屏展示系统通常作为独立子系统部署,用户从后台管理页跳转大屏时,重复输入账号密码会降低操作效率。因此需要实现 "点击按钮打开大屏 + 自动登录" 的一体化流程。

关键技术原理

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 技术栈,完整实现了后台管理系统跳转大屏并自动登录的功能。核心流程为:父窗口打开大屏窗口并定时发送登录凭证 → 大屏页面监听并校验消息 → 调用登录接口更新状态 → 跳转大屏首页。

相关推荐
小二·6 小时前
前端监控体系完全指南:从错误捕获到用户行为分析(Vue 3 + Sentry + Web Vitals)
前端·vue.js·sentry
阿珊和她的猫7 小时前
IIFE:JavaScript 中的立即调用函数表达式
开发语言·javascript·状态模式
阿珊和她的猫7 小时前
`require` 与 `import` 的区别剖析
前端·webpack
+VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
智商偏低7 小时前
JSEncrypt
javascript
谎言西西里7 小时前
零基础 Coze + 前端 Vue3 边玩边开发:宠物冰球运动员生成器
前端·coze
+VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑7 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
GIS之路8 小时前
GDAL 实现数据空间查询
前端
OEC小胖胖8 小时前
01|从 Monorepo 到发布产物:React 仓库全景与构建链路
前端·react.js·前端框架