You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中如何阻止用户在多个浏览器标签页点击同一UI元素

实现方案

优先采用localStorage+storage事件的实现方案,该方案兼容Chrome 4+、Firefox 3.5+、Safari 4+、Edge 12+、IE8+所有主流浏览器,无需额外做兼容性降级,核心逻辑通过跨标签页共享存储状态实现会话独占锁定。

完整修改代码

你只需要替换原有的ChatBot组件代码即可:

import { useState, useEffect } from 'react';
import React from 'react';

// 固定存储key,避免和其他业务逻辑冲突
const CHAT_SESSION_LOCK_KEY = 'active_chat_session_tab';
// 生成当前标签页唯一标识
const CURRENT_TAB_ID = Math.random().toString(36).slice(2);

export default function ChatBot() {
  const [showIcon, setShowIcon] = useState(true);
  const [isSessionOccupied, setIsSessionOccupied] = useState(false);

  useEffect(() => {
    // 检查当前是否有其他标签页占用会话
    const checkSessionStatus = () => {
      const activeTab = localStorage.getItem(CHAT_SESSION_LOCK_KEY);
      setIsSessionOccupied(!!activeTab && activeTab !== CURRENT_TAB_ID);
    };

    // 组件挂载先检查一次状态
    checkSessionStatus();

    // 监听其他标签页的存储变更,实时更新状态
    const handleStorageUpdate = (e) => {
      if (e.key === CHAT_SESSION_LOCK_KEY) {
        checkSessionStatus();
      }
    };
    window.addEventListener('storage', handleStorageUpdate);

    // 标签页关闭时自动释放自己持有的锁
    const handleTabClose = () => {
      const activeTab = localStorage.getItem(CHAT_SESSION_LOCK_KEY);
      if (activeTab === CURRENT_TAB_ID) {
        localStorage.removeItem(CHAT_SESSION_LOCK_KEY);
      }
    };
    window.addEventListener('beforeunload', handleTabClose);

    // 组件卸载清理监听事件
    return () => {
      window.removeEventListener('storage', handleStorageUpdate);
      window.removeEventListener('beforeunload', handleTabClose);
    };
  }, []);

  const addChatScript = () => {
    // 会话被占用时直接拦截点击
    if (isSessionOccupied) return;

    // 锁定会话
    localStorage.setItem(CHAT_SESSION_LOCK_KEY, CURRENT_TAB_ID);
    setShowIcon(false);
    
    const srcUrl = 'https://otherwebsite.com/chat.js';
    const scriptTag = document.createElement('script');
    scriptTag.id = 'chatbot';
    scriptTag.src = srcUrl;
    scriptTag.type = 'text/javascript';
    
    // 脚本加载失败自动释放锁,避免永久锁死
    scriptTag.onerror = () => {
      localStorage.removeItem(CHAT_SESSION_LOCK_KEY);
      setShowIcon(true);
    };

    document.body.appendChild(scriptTag);
  };

  return (
    <div
      className="bot"
      style={{
        position: 'fixed',
        right: '20px',
        bottom: '20px',
        cursor: isSessionOccupied ? 'not-allowed' : 'pointer',
      }}
    >
      {showIcon && (
        <img
          src="https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270"
          onClick={addChatScript}
          alt="聊天机器人"
          title={isSessionOccupied ? "您在其他标签页已有会话在运行" : ""}
        />
      )}
    </div>
  );
}

可选异常优化

如果需要处理浏览器崩溃、强制退出导致锁未释放的极端场景,可以给锁增加过期时间,修改检查逻辑和加锁逻辑即可:

// 检查逻辑增加过期判断
const checkSessionStatus = () => {
  const lockValue = localStorage.getItem(CHAT_SESSION_LOCK_KEY);
  if (!lockValue) {
    setIsSessionOccupied(false);
    return;
  }
  try {
    const { tabId, timestamp } = JSON.parse(lockValue);
    // 锁有效期设置为24小时,可自定义
    const isExpired = Date.now() - timestamp > 24 * 60 * 60 * 1000;
    if (isExpired || tabId === CURRENT_TAB_ID) {
      setIsSessionOccupied(false);
      isExpired && localStorage.removeItem(CHAT_SESSION_LOCK_KEY);
    } else {
      setIsSessionOccupied(true);
    }
  } catch (err) {
    // 兼容旧格式数据,直接清理
    localStorage.removeItem(CHAT_SESSION_LOCK_KEY);
    setIsSessionOccupied(false);
  }
};

// 加锁逻辑修改为存储带时间戳的对象
localStorage.setItem(CHAT_SESSION_LOCK_KEY, JSON.stringify({
  tabId: CURRENT_TAB_ID,
  timestamp: Date.now()
}));

内容的提问来源于stack exchange,提问作者coderX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 14:45:03