BroadcastChannel实现浏览器标签页通信

浏览器标签页通信常用于列表页进入详情页所进行的标签页间通信的情况,实际使用例如博客页面的列表和文章编辑页面、音乐网站的列表和音乐播放详情等。

window.open()实现

使用常用的window.open()API也可以实现,但会存在以下问题:

  1. 打开新页面时浏览器标签页焦点也会跳转到新页面(如果没有标签页焦点不变的需求可以忽略不计);
  2. 打开新页面导致新页面全局刷新(多页应用或跳转的新页面内容较少可忽略不计);

BroadcastChannel实现

BroadcastChannel 是 Web API 里用于在同源的不同浏览器上下文(像不同的窗口、标签页、iframe 等)间进行通信的机制,因此可以规避以上两个问题:

实现过程

  1. 创建两个路由页面

    • 列表页 /
    • 详情页/detail
  2. 列表页

    <template>
      <div class="table-box">
        <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="username" label="用户名" />
          <el-table-column prop="sex" label="性别" />
          <el-table-column fixed="right" label="操作" min-width="120">
            <template #default="scope">
              <el-button
                link
                type="primary"
                size="small"
                @click="handleClick(scope.row)"
              >
                详情
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
    <script setup>
    import { ref } from "vue";
    import { userList } from "@/utils/data";
    const handleClick = async (row) => {
      // channel通信
      const channel = new BroadcastChannel("user_detail");
      const tabCount = localStorage.getItem("TAB_COUNT");
      if (!tabCount || tabCount === "1") {
        await localStorage.setItem("TAB_COUNT", "2");
        await sessionStorage.setItem("TAB_ID", row.id);
        window.open("/detail", "user_detail");
      } else {
        channel.postMessage(row.id);
      }
    };
    
    const tableData = ref(userList);
    </script>
    
    
  3. 详情页

    <template>
      <el-descriptions
        class="margin-top"
        title="用户信息表"
        :column="3"
        size="large"
        border
      >
        <template #extra>
          <el-button type="primary" @click="goBack">返回</el-button>
        </template>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              <el-icon :style="iconStyle">
                <user />
              </el-icon>
              用户名
            </div>
          </template>
          {{ curUser.username }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              <el-icon :style="iconStyle">
                <iphone />
              </el-icon>
              年龄
            </div>
          </template>
          {{ curUser.age }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              <el-icon :style="iconStyle">
                <location />
              </el-icon>
              籍贯
            </div>
          </template>
          {{ curUser.city }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              <el-icon :style="iconStyle">
                <tickets />
              </el-icon>
              性别
            </div>
          </template>
          {{ curUser.sex }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              <el-icon :style="iconStyle">
                <office-building />
              </el-icon>
              住址
            </div>
          </template>
          {{ curUser.address }}
        </el-descriptions-item>
      </el-descriptions>
    </template>
    
    <script setup>
    import { ref, onMounted } from "vue";
    import { useRoute } from "vue-router";
    import { userList } from "@/utils/data";
    
    const route = useRoute();
    const initialUser = {
      date: "",
      username: "",
      sex: "",
      age: 18,
      city: "",
      address: "",
      id: "",
    };
    const curUser = ref({ ...initialUser });
    const goBack = async () => {
      await localStorage.setItem("TAB_COUNT", "1");
      await sessionStorage.removeItem("TAB_ID");
      window.close();
    };
    const iconStyle = {
      fontSize: "20px",
      color: "#409eff",
    };
    
    const channel = new BroadcastChannel("user_detail");
    channel.addEventListener("message", (event) => {
      console.log("接收到消息:", event.data);
      sessionStorage.removeItem("TAB_ID");
      const curUserId = event.data;
      const user = userList.find((user) => user.id === curUserId);
      if (user) {
        curUser.value = user;
      }
    });
    
    onMounted(() => {
      const curUserId = sessionStorage.getItem("TAB_ID");
      if (curUserId) {
        const user = userList.find((user) => user.id === curUserId);
        if (user) {
          curUser.value = user;
        }
      }
      // 监听页面卸载事件
      window.addEventListener("unload", async () => {
        await localStorage.setItem("TAB_COUNT", "1");
        await sessionStorage.removeItem("TAB_ID");
      });
    });
    </script>
    
    

实现效果

在这里插入图片描述

实现原理

使用BroadcastChannel创建通道进行浏览器标签页通信,利用浏览器本地缓存记录标签页打开数量,如果数量为1(仅有列表页,没有详情页),则使用window.open打开;反之如果数量为2,则使用BroadcastChannel通道发送消息实现页面数据更新;

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lewiis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值