Compare commits
3 Commits
e277352133
...
9df725b748
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9df725b748 | ||
|
|
64f8914951 | ||
|
|
506e5a448c |
@@ -15,6 +15,10 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// ⚡ 模块级变量:防止 React StrictMode 双重初始化
|
||||||
|
let widgetInitialized = false;
|
||||||
|
let idleCallbackId = null;
|
||||||
|
|
||||||
const BytedeskWidget = ({
|
const BytedeskWidget = ({
|
||||||
config,
|
config,
|
||||||
autoLoad = true,
|
autoLoad = true,
|
||||||
@@ -27,110 +31,98 @@ const BytedeskWidget = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 如果不自动加载或配置未设置,跳过
|
// 如果不自动加载或配置未设置,跳过
|
||||||
if (!autoLoad || !config) {
|
if (!autoLoad || !config) {
|
||||||
if (!config) {
|
|
||||||
console.warn('[Bytedesk] 配置未设置,客服组件未加载');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Bytedesk] 开始加载客服Widget...', config);
|
// ⚡ 防止重复初始化(React StrictMode 会双重调用 useEffect)
|
||||||
|
if (widgetInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 加载Bytedesk Widget脚本
|
// ⚡ 使用 requestIdleCallback 延迟加载,不阻塞首屏
|
||||||
const script = document.createElement('script');
|
const loadWidget = () => {
|
||||||
script.src = 'https://www.weiyuai.cn/embed/bytedesk-web.js';
|
// 再次检查,防止竞态条件
|
||||||
script.async = true;
|
if (widgetInitialized) return;
|
||||||
script.id = 'bytedesk-web-script';
|
widgetInitialized = true;
|
||||||
|
|
||||||
script.onload = () => {
|
// 检查脚本是否已存在
|
||||||
console.log('[Bytedesk] Widget脚本加载成功');
|
if (document.getElementById('bytedesk-web-script')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
// 加载Bytedesk Widget脚本
|
||||||
if (window.BytedeskWeb) {
|
const script = document.createElement('script');
|
||||||
console.log('[Bytedesk] 初始化Widget');
|
script.src = 'https://www.weiyuai.cn/embed/bytedesk-web.js';
|
||||||
const bytedesk = new window.BytedeskWeb(config);
|
script.async = true;
|
||||||
bytedesk.init();
|
script.id = 'bytedesk-web-script';
|
||||||
|
|
||||||
widgetRef.current = bytedesk;
|
script.onload = () => {
|
||||||
console.log('[Bytedesk] Widget初始化成功');
|
try {
|
||||||
|
if (window.BytedeskWeb) {
|
||||||
|
const bytedesk = new window.BytedeskWeb(config);
|
||||||
|
bytedesk.init();
|
||||||
|
widgetRef.current = bytedesk;
|
||||||
|
|
||||||
// ⚡ 屏蔽 STOMP WebSocket 错误日志(不影响功能)
|
// ⚡ 屏蔽 STOMP WebSocket 错误日志(不影响功能)
|
||||||
// Bytedesk SDK 内部的 /stomp WebSocket 连接失败不影响核心客服功能
|
const originalConsoleError = console.error;
|
||||||
// SDK 会自动降级使用 HTTP 轮询
|
console.error = function(...args) {
|
||||||
const originalConsoleError = console.error;
|
const errorMsg = args.join(' ');
|
||||||
console.error = function(...args) {
|
if (errorMsg.includes('/stomp') ||
|
||||||
const errorMsg = args.join(' ');
|
errorMsg.includes('stomp onWebSocketError') ||
|
||||||
// 忽略 /stomp 和 STOMP 相关错误
|
(errorMsg.includes('WebSocket connection to') && errorMsg.includes('/stomp'))) {
|
||||||
if (errorMsg.includes('/stomp') ||
|
return;
|
||||||
errorMsg.includes('stomp onWebSocketError') ||
|
}
|
||||||
(errorMsg.includes('WebSocket connection to') && errorMsg.includes('/stomp'))) {
|
originalConsoleError.apply(console, args);
|
||||||
return; // 不输出日志
|
};
|
||||||
|
|
||||||
|
if (onLoad) {
|
||||||
|
onLoad(bytedesk);
|
||||||
}
|
}
|
||||||
originalConsoleError.apply(console, args);
|
} else {
|
||||||
};
|
throw new Error('BytedeskWeb对象未定义');
|
||||||
|
}
|
||||||
if (onLoad) {
|
} catch (error) {
|
||||||
onLoad(bytedesk);
|
console.error('[Bytedesk] 初始化失败:', error);
|
||||||
|
if (onError) {
|
||||||
|
onError(error);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw new Error('BytedeskWeb对象未定义');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
};
|
||||||
console.error('[Bytedesk] Widget初始化失败:', error);
|
|
||||||
|
script.onerror = (error) => {
|
||||||
|
console.error('[Bytedesk] 脚本加载失败:', error);
|
||||||
|
widgetInitialized = false; // 允许重试
|
||||||
if (onError) {
|
if (onError) {
|
||||||
onError(error);
|
onError(error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
scriptRef.current = script;
|
||||||
};
|
};
|
||||||
|
|
||||||
script.onerror = (error) => {
|
// ⚡ 使用 requestIdleCallback 在浏览器空闲时加载
|
||||||
console.error('[Bytedesk] Widget脚本加载失败:', error);
|
if ('requestIdleCallback' in window) {
|
||||||
if (onError) {
|
idleCallbackId = requestIdleCallback(loadWidget, { timeout: 3000 });
|
||||||
onError(error);
|
} else {
|
||||||
}
|
// 降级:使用 setTimeout
|
||||||
};
|
idleCallbackId = setTimeout(loadWidget, 100);
|
||||||
|
}
|
||||||
|
|
||||||
// 添加脚本到页面
|
// 清理函数
|
||||||
document.body.appendChild(script);
|
|
||||||
scriptRef.current = script;
|
|
||||||
|
|
||||||
// 清理函数 - 增强错误处理,防止 React 18 StrictMode 双重清理报错
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log('[Bytedesk] 清理Widget');
|
// 取消待执行的 idle callback
|
||||||
|
if (idleCallbackId) {
|
||||||
// 移除脚本
|
if ('cancelIdleCallback' in window) {
|
||||||
try {
|
cancelIdleCallback(idleCallbackId);
|
||||||
if (scriptRef.current && scriptRef.current.parentNode) {
|
} else {
|
||||||
scriptRef.current.parentNode.removeChild(scriptRef.current);
|
clearTimeout(idleCallbackId);
|
||||||
}
|
}
|
||||||
scriptRef.current = null;
|
idleCallbackId = null;
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Bytedesk] 移除脚本失败(可能已被移除):', error.message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除Widget DOM元素
|
// ⚠️ 不重置 widgetInitialized,保持单例
|
||||||
try {
|
// 不清理 DOM,因为客服 Widget 应该持久存在
|
||||||
const widgetElements = document.querySelectorAll('[class*="bytedesk"], [id*="bytedesk"]');
|
|
||||||
widgetElements.forEach(el => {
|
|
||||||
try {
|
|
||||||
if (el && el.parentNode && el.parentNode.contains(el)) {
|
|
||||||
el.parentNode.removeChild(el);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// 忽略单个元素移除失败(可能已被移除)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Bytedesk] 清理Widget DOM元素失败:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理全局对象
|
|
||||||
try {
|
|
||||||
if (window.BytedeskWeb) {
|
|
||||||
delete window.BytedeskWeb;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Bytedesk] 清理全局对象失败:', error.message);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [config, autoLoad, onLoad, onError]);
|
}, [config, autoLoad, onLoad, onError]);
|
||||||
|
|
||||||
|
|||||||
115
src/index.js
115
src/index.js
@@ -40,91 +40,25 @@ if (process.env.REACT_APP_ENABLE_DEBUG === 'true') {
|
|||||||
function registerServiceWorker() {
|
function registerServiceWorker() {
|
||||||
// ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突)
|
// ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突)
|
||||||
if (process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
if (process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
||||||
console.log(
|
|
||||||
'%c[App] Mock 模式已启用,跳过通知 Service Worker 注册(避免与 MSW 冲突)',
|
|
||||||
'color: #FF9800; font-weight: bold;'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅在支持 Service Worker 的浏览器中注册
|
// 仅在支持 Service Worker 的浏览器中注册
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
// 在页面加载完成后注册
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register('/service-worker.js')
|
.register('/service-worker.js')
|
||||||
.then((registration) => {
|
|
||||||
console.log('[App] ✅ Service Worker 注册成功');
|
|
||||||
console.log('[App] Scope:', registration.scope);
|
|
||||||
|
|
||||||
// 检查当前激活状态
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
console.log('[App] ✅ Service Worker 已激活并控制页面');
|
|
||||||
} else {
|
|
||||||
console.log('[App] ⏳ Service Worker 已注册,等待激活...');
|
|
||||||
console.log('[App] 💡 刷新页面以激活 Service Worker');
|
|
||||||
|
|
||||||
// 监听 controller 变化(Service Worker 激活后触发)
|
|
||||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
||||||
console.log('[App] ✅ Service Worker 控制器已更新');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听 Service Worker 更新
|
|
||||||
registration.addEventListener('updatefound', () => {
|
|
||||||
const newWorker = registration.installing;
|
|
||||||
console.log('[App] 🔄 发现 Service Worker 更新');
|
|
||||||
|
|
||||||
if (newWorker) {
|
|
||||||
newWorker.addEventListener('statechange', () => {
|
|
||||||
console.log(`[App] Service Worker 状态: ${newWorker.state}`);
|
|
||||||
if (newWorker.state === 'activated') {
|
|
||||||
console.log('[App] ✅ Service Worker 已激活');
|
|
||||||
|
|
||||||
// 如果有旧的 Service Worker 在控制页面,提示用户刷新
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
console.log('[App] 💡 Service Worker 已更新,建议刷新页面');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('[App] ❌ Service Worker 注册失败');
|
console.error('[App] Service Worker 注册失败:', error.message);
|
||||||
console.error('[App] 错误类型:', error.name);
|
|
||||||
console.error('[App] 错误信息:', error.message);
|
|
||||||
console.error('[App] 完整错误:', error);
|
|
||||||
|
|
||||||
// 额外检查:验证文件是否可访问
|
|
||||||
fetch('/service-worker.js', { method: 'HEAD' })
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
console.error('[App] Service Worker 文件存在但注册失败');
|
|
||||||
console.error('[App] 💡 可能的原因:');
|
|
||||||
console.error('[App] 1. Service Worker 文件有语法错误');
|
|
||||||
console.error('[App] 2. 浏览器不支持某些 Service Worker 特性');
|
|
||||||
console.error('[App] 3. HTTPS 证书问题(Service Worker 需要 HTTPS)');
|
|
||||||
} else {
|
|
||||||
console.error('[App] Service Worker 文件不存在(HTTP', response.status, ')');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(fetchError => {
|
|
||||||
console.error('[App] 无法访问 Service Worker 文件:', fetchError.message);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.warn('[App] Service Worker is not supported in this browser');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动应用(MSW 异步初始化,不阻塞首屏渲染)
|
// 渲染应用
|
||||||
function startApp() {
|
function renderApp() {
|
||||||
// Create root
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
|
||||||
// ✅ 先渲染应用,不等待 MSW
|
|
||||||
// StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题)
|
// StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题)
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@@ -139,33 +73,26 @@ function startApp() {
|
|||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
// ✅ 后台异步启动 MSW(不阻塞首屏渲染)
|
// 注册 Service Worker(非 Mock 模式)
|
||||||
if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
|
||||||
const initMSW = async () => {
|
|
||||||
try {
|
|
||||||
const { startMockServiceWorker } = await import('./mocks/browser');
|
|
||||||
await startMockServiceWorker();
|
|
||||||
console.log(
|
|
||||||
'%c[MSW] ✅ Mock Service Worker 已在后台启动',
|
|
||||||
'color: #4CAF50; font-weight: bold;'
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[MSW] ❌ Mock Service Worker 启动失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用 requestIdleCallback 在浏览器空闲时初始化,不阻塞首屏
|
|
||||||
if ('requestIdleCallback' in window) {
|
|
||||||
requestIdleCallback(() => initMSW(), { timeout: 3000 });
|
|
||||||
} else {
|
|
||||||
// 降级:使用 setTimeout(0) 延迟执行
|
|
||||||
setTimeout(initMSW, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册 Service Worker
|
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启动应用
|
||||||
|
async function startApp() {
|
||||||
|
// ✅ 开发环境 Mock 模式:先启动 MSW,再渲染应用
|
||||||
|
// 确保所有 API 请求(包括 AuthContext.checkSession)都被正确拦截
|
||||||
|
if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
||||||
|
try {
|
||||||
|
const { startMockServiceWorker } = await import('./mocks/browser');
|
||||||
|
await startMockServiceWorker();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MSW] 启动失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染应用
|
||||||
|
renderApp();
|
||||||
|
}
|
||||||
|
|
||||||
// 启动应用
|
// 启动应用
|
||||||
startApp();
|
startApp();
|
||||||
@@ -47,18 +47,8 @@ export async function startMockServiceWorker() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
isStarted = true;
|
isStarted = true;
|
||||||
console.log(
|
// 精简日志:只保留一行启动提示
|
||||||
'%c[MSW] Mock Service Worker 已启动 🎭 (警告模式)',
|
console.log('%c[MSW] Mock 已启用 🎭', 'color: #4CAF50; font-weight: bold;');
|
||||||
'color: #4CAF50; font-weight: bold; font-size: 14px;'
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'%c警告模式:已定义 Mock → 返回假数据 | 未定义 Mock → 显示警告 ⚠️ | 允许 passthrough',
|
|
||||||
'color: #FF9800; font-weight: bold; font-size: 12px;'
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'%c查看 src/mocks/handlers/ 目录管理 Mock 接口',
|
|
||||||
'color: #2196F3; font-size: 12px;'
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MSW] 启动失败:', error);
|
console.error('[MSW] 启动失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -56,17 +56,8 @@ class SocketService {
|
|||||||
|
|
||||||
// 注册所有暂存的事件监听器(保留 pendingListeners,不清空)
|
// 注册所有暂存的事件监听器(保留 pendingListeners,不清空)
|
||||||
if (this.pendingListeners.length > 0) {
|
if (this.pendingListeners.length > 0) {
|
||||||
console.log(`[socketService] 📦 注册 ${this.pendingListeners.length} 个暂存的事件监听器`);
|
|
||||||
this.pendingListeners.forEach(({ event, callback }) => {
|
this.pendingListeners.forEach(({ event, callback }) => {
|
||||||
// 直接在 Socket.IO 实例上注册(避免递归调用 this.on())
|
this.socket.on(event, callback);
|
||||||
const wrappedCallback = (...args) => {
|
|
||||||
console.log(`%c[socketService] 🔔 收到原始事件: ${event}`, 'color: #2196F3; font-weight: bold;');
|
|
||||||
console.log(`[socketService] 事件数据 (${event}):`, ...args);
|
|
||||||
callback(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.socket.on(event, wrappedCallback);
|
|
||||||
console.log(`[socketService] ✓ 已注册事件监听器: ${event}`);
|
|
||||||
});
|
});
|
||||||
// ⚠️ 重要:不清空 pendingListeners,保留用于重连
|
// ⚠️ 重要:不清空 pendingListeners,保留用于重连
|
||||||
}
|
}
|
||||||
@@ -82,15 +73,8 @@ class SocketService {
|
|||||||
this.customReconnectTimer = null;
|
this.customReconnectTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('socketService', 'Socket.IO connected successfully', {
|
logger.info('socketService', 'Socket.IO connected', { socketId: this.socket.id });
|
||||||
socketId: this.socket.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`%c[socketService] ✅ WebSocket 已连接`, 'color: #4CAF50; font-weight: bold;');
|
|
||||||
console.log('[socketService] Socket ID:', this.socket.id);
|
|
||||||
|
|
||||||
// ⚠️ 已移除自动订阅,让 NotificationContext 负责订阅
|
// ⚠️ 已移除自动订阅,让 NotificationContext 负责订阅
|
||||||
// this.subscribeToAllEvents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听断开连接
|
// 监听断开连接
|
||||||
@@ -174,25 +158,12 @@ class SocketService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
logger.info('socketService', 'Socket not ready, queuing listener', { event });
|
|
||||||
console.log(`[socketService] 📦 Socket 未初始化,暂存事件监听器: ${event}`);
|
|
||||||
this.pendingListeners.push({ event, callback });
|
this.pendingListeners.push({ event, callback });
|
||||||
} else {
|
|
||||||
console.log(`[socketService] ⚠️ 监听器已存在,跳过: ${event}`);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 包装回调函数,添加日志
|
this.socket.on(event, callback);
|
||||||
const wrappedCallback = (...args) => {
|
|
||||||
console.log(`%c[socketService] 🔔 收到原始事件: ${event}`, 'color: #2196F3; font-weight: bold;');
|
|
||||||
console.log(`[socketService] 事件数据 (${event}):`, ...args);
|
|
||||||
callback(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.socket.on(event, wrappedCallback);
|
|
||||||
logger.info('socketService', `Event listener added: ${event}`);
|
|
||||||
console.log(`[socketService] ✓ 已注册事件监听器: ${event}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,8 +181,6 @@ class SocketService {
|
|||||||
} else {
|
} else {
|
||||||
this.socket.off(event);
|
this.socket.off(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('socketService', `Event listener removed: ${event}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,8 +200,6 @@ class SocketService {
|
|||||||
} else {
|
} else {
|
||||||
this.socket.emit(event, data);
|
this.socket.emit(event, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('socketService', `Event emitted: ${event}`, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,65 +322,31 @@ class SocketService {
|
|||||||
* 执行订阅操作(内部方法)
|
* 执行订阅操作(内部方法)
|
||||||
*/
|
*/
|
||||||
_doSubscribe(eventType, importance, onNewEvent, onSubscribed) {
|
_doSubscribe(eventType, importance, onNewEvent, onSubscribed) {
|
||||||
console.log('\n========== [SocketService DEBUG] 开始订阅 ==========');
|
|
||||||
console.log('[SocketService DEBUG] 事件类型:', eventType);
|
|
||||||
console.log('[SocketService DEBUG] 重要性:', importance);
|
|
||||||
console.log('[SocketService DEBUG] Socket 连接状态:', this.connected);
|
|
||||||
console.log('[SocketService DEBUG] Socket ID:', this.socket?.id);
|
|
||||||
|
|
||||||
// 发送订阅请求
|
// 发送订阅请求
|
||||||
const subscribeData = {
|
const subscribeData = {
|
||||||
event_type: eventType,
|
event_type: eventType,
|
||||||
importance: importance,
|
importance: importance,
|
||||||
};
|
};
|
||||||
console.log('[SocketService DEBUG] 准备发送 subscribe_events:', subscribeData);
|
|
||||||
this.emit('subscribe_events', subscribeData);
|
this.emit('subscribe_events', subscribeData);
|
||||||
console.log('[SocketService DEBUG] ✓ 已发送 subscribe_events');
|
|
||||||
|
|
||||||
// 监听订阅确认
|
// 监听订阅确认
|
||||||
this.socket.once('subscription_confirmed', (data) => {
|
this.socket.once('subscription_confirmed', (data) => {
|
||||||
console.log('\n[SocketService DEBUG] ========== 收到订阅确认 ==========');
|
|
||||||
console.log('[SocketService DEBUG] 订阅确认数据:', data);
|
|
||||||
logger.info('socketService', 'Subscription confirmed', data);
|
|
||||||
if (onSubscribed) {
|
if (onSubscribed) {
|
||||||
console.log('[SocketService DEBUG] 调用 onSubscribed 回调');
|
|
||||||
onSubscribed(data);
|
onSubscribed(data);
|
||||||
}
|
}
|
||||||
console.log('[SocketService DEBUG] ========== 订阅确认处理完成 ==========\n');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听订阅错误
|
// 监听订阅错误
|
||||||
this.socket.once('subscription_error', (error) => {
|
this.socket.once('subscription_error', (error) => {
|
||||||
console.error('\n[SocketService ERROR] ========== 订阅错误 ==========');
|
|
||||||
console.error('[SocketService ERROR] 错误信息:', error);
|
|
||||||
logger.error('socketService', 'Subscription error', error);
|
logger.error('socketService', 'Subscription error', error);
|
||||||
console.error('[SocketService ERROR] ========== 订阅错误处理完成 ==========\n');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听新事件推送
|
// 监听新事件推送
|
||||||
// ⚠️ 注意:不要移除其他地方注册的 new_event 监听器(如 NotificationContext)
|
|
||||||
// 多个监听器可以共存,都会被触发
|
|
||||||
if (onNewEvent) {
|
if (onNewEvent) {
|
||||||
console.log('[SocketService DEBUG] 设置 new_event 监听器');
|
|
||||||
|
|
||||||
// ⚠️ 已移除 this.socket.off('new_event'),允许多个监听器共存
|
|
||||||
|
|
||||||
// 添加新的监听器(与其他监听器共存)
|
|
||||||
this.socket.on('new_event', (eventData) => {
|
this.socket.on('new_event', (eventData) => {
|
||||||
console.log('\n[SocketService DEBUG] ========== 收到新事件推送 ==========');
|
|
||||||
console.log('[SocketService DEBUG] 事件数据:', eventData);
|
|
||||||
console.log('[SocketService DEBUG] 事件 ID:', eventData?.id);
|
|
||||||
console.log('[SocketService DEBUG] 事件标题:', eventData?.title);
|
|
||||||
logger.info('socketService', 'New event received', eventData);
|
|
||||||
console.log('[SocketService DEBUG] 准备调用 onNewEvent 回调');
|
|
||||||
onNewEvent(eventData);
|
onNewEvent(eventData);
|
||||||
console.log('[SocketService DEBUG] ✓ onNewEvent 回调已调用');
|
|
||||||
console.log('[SocketService DEBUG] ========== 新事件处理完成 ==========\n');
|
|
||||||
});
|
});
|
||||||
console.log('[SocketService DEBUG] ✓ new_event 监听器已设置(与其他监听器共存)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[SocketService DEBUG] ========== 订阅完成 ==========\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -440,11 +373,7 @@ class SocketService {
|
|||||||
|
|
||||||
// 监听取消订阅确认
|
// 监听取消订阅确认
|
||||||
this.socket.once('unsubscription_confirmed', (data) => {
|
this.socket.once('unsubscription_confirmed', (data) => {
|
||||||
logger.info('socketService', 'Unsubscription confirmed', data);
|
|
||||||
|
|
||||||
// 移除新事件监听器
|
|
||||||
this.socket.off('new_event');
|
this.socket.off('new_event');
|
||||||
|
|
||||||
if (onUnsubscribed) {
|
if (onUnsubscribed) {
|
||||||
onUnsubscribed(data);
|
onUnsubscribed(data);
|
||||||
}
|
}
|
||||||
@@ -462,22 +391,10 @@ class SocketService {
|
|||||||
* @returns {Function} 取消订阅的函数
|
* @returns {Function} 取消订阅的函数
|
||||||
*/
|
*/
|
||||||
subscribeToAllEvents(onNewEvent) {
|
subscribeToAllEvents(onNewEvent) {
|
||||||
console.log('%c[socketService] 🔔 自动订阅所有事件...', 'color: #FF9800; font-weight: bold;');
|
|
||||||
|
|
||||||
// 如果没有提供回调,添加一个默认的日志回调
|
|
||||||
const defaultCallback = (event) => {
|
|
||||||
console.log('%c[socketService] 📨 收到新事件(默认回调)', 'color: #4CAF50; font-weight: bold;');
|
|
||||||
console.log('[socketService] 事件数据:', event);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.subscribeToEvents({
|
this.subscribeToEvents({
|
||||||
eventType: 'all',
|
eventType: 'all',
|
||||||
importance: 'all',
|
importance: 'all',
|
||||||
onNewEvent: onNewEvent || defaultCallback,
|
onNewEvent: onNewEvent || (() => {}),
|
||||||
onSubscribed: (data) => {
|
|
||||||
console.log('%c[socketService] ✅ 订阅成功!', 'color: #4CAF50; font-weight: bold;');
|
|
||||||
console.log('[socketService] 订阅确认:', data);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 返回取消订阅的清理函数
|
// 返回取消订阅的清理函数
|
||||||
|
|||||||
@@ -7,6 +7,34 @@ const isDevelopment =
|
|||||||
process.env.NODE_ENV === 'development' ||
|
process.env.NODE_ENV === 'development' ||
|
||||||
process.env.REACT_APP_ENABLE_DEBUG === 'true';
|
process.env.REACT_APP_ENABLE_DEBUG === 'true';
|
||||||
|
|
||||||
|
// ========== 日志级别配置 ==========
|
||||||
|
// 日志级别:error < warn < info < debug
|
||||||
|
// 默认级别:warn(只显示警告和错误)
|
||||||
|
// 可通过 localStorage.setItem('LOG_LEVEL', 'debug') 开启详细日志
|
||||||
|
const LOG_LEVELS = {
|
||||||
|
error: 0,
|
||||||
|
warn: 1,
|
||||||
|
info: 2,
|
||||||
|
debug: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从 localStorage 读取日志级别(允许用户临时开启详细日志)
|
||||||
|
const getLogLevel = () => {
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) {
|
||||||
|
const level = localStorage.getItem('LOG_LEVEL');
|
||||||
|
if (level && LOG_LEVELS[level] !== undefined) {
|
||||||
|
return LOG_LEVELS[level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 默认只显示 warn 和 error
|
||||||
|
return LOG_LEVELS.warn;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否应该输出指定级别的日志
|
||||||
|
const shouldLogLevel = (level) => {
|
||||||
|
return LOG_LEVELS[level] <= getLogLevel();
|
||||||
|
};
|
||||||
|
|
||||||
// ========== 日志限流配置 ==========
|
// ========== 日志限流配置 ==========
|
||||||
const LOG_THROTTLE_TIME = 1000; // 1秒内相同日志只输出一次
|
const LOG_THROTTLE_TIME = 1000; // 1秒内相同日志只输出一次
|
||||||
const recentLogs = new Map(); // 日志缓存,用于去重
|
const recentLogs = new Map(); // 日志缓存,用于去重
|
||||||
@@ -148,13 +176,13 @@ export const logger = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调试日志(仅开发环境)
|
* 调试日志(仅开发环境 + LOG_LEVEL=debug)
|
||||||
* @param {string} component - 组件名称
|
* @param {string} component - 组件名称
|
||||||
* @param {string} message - 调试信息
|
* @param {string} message - 调试信息
|
||||||
* @param {object} data - 相关数据(可选)
|
* @param {object} data - 相关数据(可选)
|
||||||
*/
|
*/
|
||||||
debug: (component, message, data = {}) => {
|
debug: (component, message, data = {}) => {
|
||||||
if (isDevelopment && shouldLog(component, message)) {
|
if (isDevelopment && shouldLogLevel('debug') && shouldLog(component, message)) {
|
||||||
console.group(`🐛 Debug: ${component}`);
|
console.group(`🐛 Debug: ${component}`);
|
||||||
console.log('Message:', message);
|
console.log('Message:', message);
|
||||||
if (Object.keys(data).length > 0) {
|
if (Object.keys(data).length > 0) {
|
||||||
@@ -166,13 +194,13 @@ export const logger = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 信息日志(仅开发环境)
|
* 信息日志(仅开发环境 + LOG_LEVEL>=info)
|
||||||
* @param {string} component - 组件名称
|
* @param {string} component - 组件名称
|
||||||
* @param {string} message - 信息内容
|
* @param {string} message - 信息内容
|
||||||
* @param {object} data - 相关数据(可选)
|
* @param {object} data - 相关数据(可选)
|
||||||
*/
|
*/
|
||||||
info: (component, message, data = {}) => {
|
info: (component, message, data = {}) => {
|
||||||
if (isDevelopment && shouldLog(component, message)) {
|
if (isDevelopment && shouldLogLevel('info') && shouldLog(component, message)) {
|
||||||
console.group(`ℹ️ Info: ${component}`);
|
console.group(`ℹ️ Info: ${component}`);
|
||||||
console.log('Message:', message);
|
console.log('Message:', message);
|
||||||
if (Object.keys(data).length > 0) {
|
if (Object.keys(data).length > 0) {
|
||||||
@@ -181,6 +209,28 @@ export const logger = {
|
|||||||
console.log('Timestamp:', new Date().toISOString());
|
console.log('Timestamp:', new Date().toISOString());
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置日志级别(方便调试)
|
||||||
|
* @param {string} level - 日志级别 ('error' | 'warn' | 'info' | 'debug')
|
||||||
|
*/
|
||||||
|
setLevel: (level) => {
|
||||||
|
if (LOG_LEVELS[level] !== undefined) {
|
||||||
|
localStorage.setItem('LOG_LEVEL', level);
|
||||||
|
console.log(`[Logger] 日志级别已设置为: ${level}`);
|
||||||
|
console.log(`[Logger] 可用级别: error < warn < info < debug`);
|
||||||
|
} else {
|
||||||
|
console.error(`[Logger] 无效的日志级别: ${level}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前日志级别
|
||||||
|
*/
|
||||||
|
getLevel: () => {
|
||||||
|
const levelNum = getLogLevel();
|
||||||
|
return Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === levelNum) || 'warn';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user