论多窗口相互关联下window.open打开已在的窗口时只激活不刷新的实现方案

news2024/11/16 23:41:13

前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~
主页: oliver尹的主页
格言: 跌倒了爬起来就好~
来个关注吧,点个赞吧,谢谢

论多窗口相互关联下window.open打开已在的窗口时只激活不刷新的实现方案

  • 一、前言
  • 二、本文内容概述
  • 三、待解决问题
  • 四、问题解决说明
    • 4.1 刷新问题解决
    • 4.2 多窗口的相互关联激活
      • 4.2.1 确认窗口name值
      • 4.2.2 确认窗口是否已经打开过
      • 4.2.3 给窗口命名
    • 4.3 缓存的问题
  • 五、代码下载
  • 六、小结

一、前言

近期,我司有个小伙伴遇到这么个场景实现起来感觉有点困难与我讨论,大概是这样的:
项目框架使用的是Vue开发的单页应用,这个单页应用在打开某个大模块路由时 需要使用window.open()去打开新的模块,也就是说如果打开的是大模块,那么会新建一个窗口,在这个窗口中打开页面,但是如果打开的换菜单又是小路由,那么使用的VueRouter切换路由,这就导致了一个问题,在重复点击大模块时出现了重复打开新窗口,并且后续又陆陆续续出现了许许多多的问题…因此有了本文作记录,以备后续不时之需~
耐心看完,也许你会有所收获~

二、本文内容概述

本文主要解决的场景是:
在类似Vue的单页应用项目中,在任意一个窗口内首次打开指定页面时通过新建窗口的方式打开,在任意一个窗口内打开已打开的界面时,仅激活对应窗口,比如:在A页面使用类似 window.open() 的效果打开了同域下的B页面和C页面(A,B,C页面具有相同的路由系统),切换窗口至B页面,点击B页面下的C页面地址,此时不会再次新建C页面窗口,而是将已打开的C页面窗口激活至可视状态,且C页面窗口不触发刷新保留原来的内容与操作状态;
同时,在任意界面中执行浏览器的f5刷新,不影响使用效果;
在这里插入图片描述
先简单说一下实现思路吧,在openUrl的内部判断当前名为title的窗口是否打开过,如果没有打开过,执行 window.open() 方法新建窗口,如果打开过,执行 window.open('javascript:;', title) 去激活名为title的窗口,让它显示出来
如果有小伙伴想要文件,请直接跳转至 第五部分下载文件 即可~

三、待解决问题

为了完成上面的场景需求,实际开发中遇到的主要问题一共存在三个:

  1. 刷新的问题,当使用 window.open(url,name) 打开对应name的窗口时,如果该name的窗口已存在确实会仅仅是激活对应name的窗口,但是这个激活会执行刷新,它刷新这个网页,假如这个网页存在复杂表单,用户输入的一半的内容或者页面操作的状态都将会被刷新掉,据产品经理讲体验甚是不好…
  2. 多页面相互激活的问题,这个问题就比较有意思了,由于是Vue开发的单页应用,整个路由系统其实是同一套,这就会出现一个问题,在A页面通过 window.open() 打开了B页面和C页面,而B页面和C页面自身其实也包含完整的路由系统的,这个路由系统中自然也有A,B,C的路由地址,那如果在B页面通过 window.open() 打开C页面,此时正确的逻辑应该是仅仅去激活C页面的窗口,而不是重新再打开一个,示例图如下:
    在这里插入图片描述

是不是挺有意思,这里面大概率存在通信问题,比如在C页面怎么知道在A页面中打开过哪些窗口并且获得窗口name等等~

  1. 缓存的问题,即浏览器刷新,在A页面执行浏览器的的f5刷新后,可以在点击对应模块时依然仅仅是去激活对应的模块页面,

四、问题解决说明

关于 window.open() 具体参数以及各个参数的说明可以看MDN的官方解释,具体链接如下:window.open

4.1 刷新问题解决

先说核心实现吧,刷新的问题核心解决应该是在激活的实现,而不是重新通过 window.open() 去打开url,经过实验,确认通过以下这个实现

window.open('javascript:;', name)

可能会有小伙伴奇怪,论激活窗口的功能不应该是通过 window.focus() 去实现么,使用 window.focus() 确实可以将指定name值的窗口激活,实现如下:

let win = null;
if(win){
  win.focus()
}
else{
  win = window.open(url, name);
}

但是由于我们的场景比较奇葩,需要实现跨窗口激活,也就是说在C页面打开B页面时需要在C页面执行B页面这个 win.focus(),但由于win这个对象它其实是一个最顶层的 window对象,它无法被传递,被转化,即想使用postMessage发送,会提示发送失败,根本无法跨窗口,同理,win也无法被缓存,一旦刷新界面,这个win对象就会丢失无法留存,自然待解决问题中第三个问题也同样无法实现;
在尝试 window.focus() 这条路走不通后,发现了另外一种方法,即上面说到的 window.open('javascript:;', name) 这个方法,这个方法同样可以解决激活窗口的问题,并且这个name值的类型是一个字符串,是窗口的名字也是 winodw.open() 中的name,代码如下

window.open(url, name);

这种方法的实现和focus()的实现非常接近,无非就是将 win.focus() 改成了 window.open('javascript:;', name)

if(判断是否打开过){
  window.open('javascript:;', name)
}
else{
 window.open(url, name);
}

4.2 多窗口的相互关联激活

多窗口的相互关联激活细想一下,上面采用的是 window.open('javascript:;', name) 实现的激活,在这个代码中唯一的变量就是一个字符串格式的name,扩展一下思维,在任意一个窗口里知道了其它窗口的name,是不是都可以通过 window.open('javascript:;', name) 实现激活,想了想从理论上来说这个方法应该是可行的(当然事实证明确实可以),剩下的就是要解决以下这三个小问题:

  1. 知道已打开窗口的name值,目的是为了方便使用 window.open('javascript:;', name) 去激活;
  2. 判断窗口是否已经打开过,目的是为了方便知道使用 window.open('javascript:;', name) 去激活窗口还是使用 window.open(url, name) 去打开窗口;
  3. 给每一个打开的窗口命名,这个name值是必须唯一的,并且需要和执行window.open('javascript:;', name) 中的name对应起来,不对应起来的话执行open的时候会找不到name值的窗口,自然也就无法实现激活;

4.2.1 确认窗口name值

name值的小问题其实非常好解决,这是一个Vue单页应用,路由名称和路由地址是固定的,也就是说,所有个这些个新窗口的路由也完全相同,而我们的路由往往是由菜单名字和路由地址组成的,具有唯一性(你总不能两个菜单的名字完全一模一样吧),因此往往是如下这种结构

// template
<div v-for="item in menuList" :key="item.value" @click="changeRouter(item)">
  <div>{{item.title}}</div>  
</div>

// js
changeRouter(item){
  window.open('javascript:;', item.title)
}

因此,到这里基本解决了一个小问题,多窗口由于共享的是一个路由系统,name的名字其实在各个页面是相互知道的,我们只需要知道这个name的窗口是否处于打开状态即可;

4.2.2 确认窗口是否已经打开过

如何确认窗口是否已经打开过?想了下,由于多个窗口实际上处于同域下,localStorage自然成了首选,当打开一个新页面时,往localStorage里插入一条消息,关闭页面的时候再到localStorage中将对应的窗口信息删掉;
大致的代码如下:
首先是存储信息

// 执行首次跳转
window.open(url, name)
// 将跳转的名字和地址保存下来
setTsLocalData(name, url)

function setLocalData(key, value) {
  let local = localStorage.getItem("demo")

  if (local) {
    local = JSON.parse(local)
  }

  console.log(local)
  const item = { ...local }
  item[key] = value

  localStorage.setItem("demo", JSON.stringify(item))
}

接着在执行跳转前加一个判断,判断打开过的页面不再执行 window.open(url, name) ,而是转而去执行window.open(‘javascript:;’, name)激活窗口

const urlname = getLocalName(url)
// 存在name的话执行激活,不执行跳转
 if (urlname) {
  window.open('javascript:;', urlname)
  return
}

// 执行首次跳转
window.open(url, name)
// ...上方代码

4.2.3 给窗口命名

当通过window.open打开某个页面后,自动给窗口命名,名字的来源来自于localStorage,还是因为同域的关系,共享了localStorage,我们可以通过当前的网址去localStorage做匹配,匹配到网址后取到对应的key,这个key就是窗口的名字,将其赋值给窗口

function loadWidName(url) {
  const newUrl = getLocalName(url)
  window.name = newUrl
}

function getLocalName(url) {
  let local = localStorage.getItem(localStorageKey)
  if (!local) return false

  local = JSON.parse(local)

  // return Object.prototype.hasOwnProperty.call(local, key);
  let name = ''
  for (const key in local) {
    if (!Object.prototype.hasOwnProperty.call(local, key)) continue

    if (local[key] === url) {
      name = key
      break
    }
  }
  return name
}

注意的是,loadWidName这个函数得在页面一加载就执行,因为必须得在执行open之前就给窗口命名成功,否则执行open的时候会认为当前页面没有被打开过,直接进行跳转了;

4.3 缓存的问题

在想想,仔细想想,缓存的问题是不是已经被解决掉了,缓存最大的困难是什么,是窗口的命名,一旦进行刷新窗口的名字就丢失了,因此在别的窗口执行 window.open('javascript:;', item.title) 的时候会找不到带有这个名字的窗口;
而如果在一加载页面的时候就执行了上面4.2.3这个方法,去主动给窗口命名,那么刷新的时候自然也会去执行这个命名的过程,自然再怎么刷新窗口的name值一直是保持有的;
缓存的问题除了主动给窗口命名还剩下的就是关闭窗口是去 移除对应缓存 了,如果不移除,那么把窗口关闭后在其他窗口中点击关闭窗口时,会认为该窗口依然处于打开状态,那就会去执行window.open('javascript:;', urlname),打开一个空白页面~
移除代码很简单,给window添加一个beforeunload事件

window.addEventListener('beforeunload', function () {
    let local = localStorage.getItem("demo")
    if (!local) return false
  
    local = JSON.parse(local)
    const newItem = {}
    for (let item in local) {
      if (item !== key) {
        newItem[item] = local[item]
      }
    }
    localStorage.setItem("demo", JSON.stringify(newItem))
  return
})

到这里基本就实现了这个功能了

五、代码下载

代码已经上传到CSDN上了,由于是正式使用的我简单压缩了一下,下载地址如下:前端window.open实现激活而非打开的功能;
如果有需要源码的小伙伴留言或者私信留下邮箱,博主看到后会及时发送的~问题不大
至于使用也非常简单,在main.js中引入文件,这个文件扩展了一个类似于 window.open() 的方法,所有执行 window.open() 的地方改用我们的方法代替,在方法内部去判断是新建窗口还是激活窗口,比如

// main.js
import openUrl from "xxx"

// 使用时在内部实现逻辑判断是新建还是激活
window.openUrl(url,title) 代替 winow.open(url,title)

六、小结

本文主要解决了在类似Vue的单页应用项目中,在任意一个窗口内首次打开指定页面时通过新建窗口的方式打开,在任意一个窗口内打开已打开的界面时,仅激活对应窗口的场景功能;
这个场景可能比较冷门,但确实真实存在并被我司的小伙伴遇到了,当然博主这种方法是不是最优解不太清楚,如果有小伙伴知道更优的方式,一定,一定记得留言告诉博主,谢谢~
如果有帮助,点个赞,点个关注吧~谢谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/113671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot+Netty实现基于天翼物联网平台CTWing(AIOT)终端TCP协议(透传模式)-设备终端(南向设备)

电信的天翼物联网平台CTWing(AIOT)原先是我们俗称的aep&#xff0c;主要用于接入nb-iot设备&#xff0c;当然也可以接入其他的设备&#xff0c;在熟悉AIOT平台后&#xff0c;做后端的我有时候急需终端样品(智能门禁&#xff0c;支付识别终端&#xff0c;CPE终端&#xff0c;考勤…

使用 Swift/SwiftUI 的音频可视化

IOS 应用程序中的音频可视化是一项流行的功能,在实现聊天功能时可能需要它。将它添加到我最近的项目之一时,我个人遇到了一些问题。 你们中的一些人可能在开发包含聊天功能的应用程序时遇到过问题,其中某些消息可能包含音频。这些消息需要在应用程序内进行适当的音频可视化,…

【算法题解】3. 颠倒二进制位

文章目录题目解法一解题思路代码实现复杂度分析解法二解题思路代码实现复杂度分析解法三解题思路代码实现复杂度分析题目 颠倒给定的 32 位无符号整数的二进制位。来自&#xff1a;leetcode 解法一 解题思路 取 n 的最低位&#xff0c;赋值给 ans 的最低位&#xff08;ans 初…

mybatis 多对一查询的处理方式,1.按照查询嵌套处理(子查询),2、按照结果嵌套处理(连表查询)

多对一查询 1、实体类 Student实体类&#xff1a; public class Student { private int id; private String name; private Teacher teacher;//对象属性}Teacheer 实体类&#xff1a; public class Teacher { private int id; private String name;2、Mybatis配置文件mybatis…

FastDDS(4)安装步骤

eProsima Fast DDS for Linux的最新二进制安装版本可在eProssma网站找到。 eProsima Fast DDSSpecialized on high performance middleware. Check out our comparatives such as Apache Thrift vs Protocol Buffers vs Fast Buffers or ZMQ vs Fast RTPS.https://www.eprosim…

从一道经典题来弄懂Eventloop(搞不懂算我输)

前言 时间不知不觉来到了11月底&#xff0c;马上也要准备一下寒假的实习了。 最近打算把面试中的一些拦路虎给解决掉&#xff01;&#xff01; 先拿臭名昭著的Eventloop开刀~ 经典题 async function foo() {console.log(foo) } async function bar() {console.log(bar start…

RabbitMQ:基本消息模型

单生产单消费模型&#xff0c;即基本消息模型或简单消费模型&#xff0c;即完成基本的一对一消息转发。 RabbitMQ 单生产单消费模型主要有以下三个角色构成&#xff1a; 生产者&#xff08;producer/ publisher&#xff09;&#xff1a;一个发送消息的用户应用程序。消费者&…

JS面试题--JavaScript数据类型

数据类型 1.JavaScript有哪些数据类型&#xff0c;它们的区别&#xff1f; JavaScript共有八种数据类型&#xff0c;分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是ES6 中新增的数据类型&#xff1a; ● Symbol 代表…

Linux系统基础——程序和进程

代码&#xff0c;程序&#xff0c;进程 特此说明: 刘超的趣谈linux操作系统是比较重要的参考资料&#xff0c;本文大部分内容和图片来源于这个专栏。 1 实验环境 运行一个demo&#xff0c;主要功能是主进程通过系统调用fork一个新的进程&#xff0c;子进程功能是加载二进制文件…

背包模型~

背包模型 概述 ​ 最长上升子序列&#xff1a;序列DP&#xff08;相邻两个被选择的有关系&#xff09; 背包问题&#xff1a;组合DP&#xff0c;在全局的考虑之下最小 f[i][j]&#xff1a;i 表示搞了多少&#xff0c;j 表示限制 集合&#xff1a;所有仅仅从前 i 个物品当…

论文推荐:CCNet用于语义分割的交叉注意力

CCNet&#xff0c; Transformer递归交叉自注意力&#xff0c;比非局部神经网络更有效。华中科技大学、地平线、ReLER 和伊利诺伊大学香槟分校联合研发 论文提出了交叉网络 (CCNet)&#xff0c;对于每个像素&#xff0c;CCNet 中的一个新的交叉注意力模块收集其交叉路径上所有像…

智慧路口:未来都市的智能节点

摘要交通路口是部署未来智慧城市的计算、通信和情报服务的最合适地点。需要收集和处理的大量数据&#xff0c;再加上隐私和安全问题&#xff0c;促使边缘计算范式的使用&#xff0c;这种范式与大都市的物理交叉路口很好地吻合。本文主要针对高带宽、低时延的应用&#xff0c;在…

2007-2022年消费者信心、满意度、预期指数月度数据(CCI、CEI、CSI、CGPI)

根据企业商品价格指数与消费者信心指数、消费者满意指数、消费者预期指数的月度数据&#xff0c;可以探究商品价格对消费者信心的影响作用和作用机制。 商品价格与消费者信心的三类细分指数均具有滞后二阶关联&#xff0c;其中&#xff0c;商品价格对对消费者预期指数的影响作…

IndexedDB的包装器JsStore - 实现登录功能及事务处理

JsStore是IndexedDB的包装器。它提供了简单的SQL像api&#xff0c;这是容易学习和使用。 IndexedDb查询可以在web worker内部执行&#xff0c;JsStore通过提供一个单独的worker文件来保持这种功能。 最近有位叫Pioneer网友一直在问我关于事务的实现方式&#xff0c;关于…

谷粒学院——Day13【微信扫描登录】

OAuth2 OAuth2的使用场景 一、OAuth2解决什么问题 1. OAuth2提出的背景 照片拥有者想要在云冲印服务上打印照片&#xff0c;云冲印服务需要访问云存储服务上的资源。 2. 图例 资源拥有者&#xff1a;照片拥有者。 客户应用&#xff1a;云冲印。 受保护的资源&#xff…

Python pandas库|任凭弱水三千,我只取一瓢饮(2)

上一篇链接&#xff1a; Python pandas库&#xff5c;任凭弱水三千&#xff0c;我只取一瓢饮&#xff08;2&#xff09;_Hann Yang的博客-CSDN博客 I~Q&#xff1a; Function10~25 Types[Function][9:25] [infer_freq, interval_range, isna, isnull, json_normalize, lres…

④【Maven】Maven的构建命令

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Maven的构建命令一、注意二、&#x1f680;清理…

指针与数组的联系与区别【一万六千字超详解】

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;《初识C语言》 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言数组的性质1.1 数组的内存布局1…

第四章 Spring的基础用法

文章目录 Spring的起源和背景理解依赖注入Spring容器理解Spring容器中的Bean管理容器中的Bean及其依赖注入自动装配使用Java类进行配置管理使用静态工厂、实例工厂创建Bean实例抽象Bean与子Bean容器中的工厂Bean管理Bean的生命周期几种特殊的依赖注入Spring的简化配置SpEL的功…

kali中间人攻击

数据来源 一、中间人攻击原理 1. 利用的ARP协议的漏洞 2. ARP协议原理&#xff1a; 1&#xff09;发送ARP广播请求目标MAC地址 2&#xff09;目标主机发送ARP单播应答&#xff0c;响应MAC地址 3. ARP攻击原理 攻击人通过发送虚假的ARP应答实现ARP缓存投毒!而受害人没有办法进行…