Files
vf_react/src/bytedesk-integration/components/BytedeskWidget.jsx

173 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Bytedesk客服Widget组件
* 用于vf_react项目集成
*
* 使用方法:
* import BytedeskWidget from './components/BytedeskWidget';
* import { getBytedeskConfig } from './config/bytedesk.config';
*
* <BytedeskWidget
* config={getBytedeskConfig()}
* autoLoad={true}
* />
*/
import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
const BytedeskWidget = ({
config,
autoLoad = true,
onLoad,
onError
}) => {
const scriptRef = useRef(null);
const widgetRef = useRef(null);
useEffect(() => {
// 如果不自动加载或配置未设置,跳过
if (!autoLoad || !config) {
if (!config) {
console.warn('[Bytedesk] 配置未设置,客服组件未加载');
}
return;
}
console.log('[Bytedesk] 开始加载客服Widget...', config);
// 加载Bytedesk Widget脚本
const script = document.createElement('script');
script.src = 'https://www.weiyuai.cn/embed/bytedesk-web.js';
script.async = true;
script.id = 'bytedesk-web-script';
script.onload = () => {
console.log('[Bytedesk] Widget脚本加载成功');
try {
if (window.BytedeskWeb) {
console.log('[Bytedesk] 初始化Widget');
const bytedesk = new window.BytedeskWeb(config);
bytedesk.init();
widgetRef.current = bytedesk;
console.log('[Bytedesk] Widget初始化成功');
// ⚡ 屏蔽 STOMP WebSocket 错误日志(不影响功能)
// Bytedesk SDK 内部的 /stomp WebSocket 连接失败不影响核心客服功能
// SDK 会自动降级使用 HTTP 轮询
const originalConsoleError = console.error;
console.error = function(...args) {
const errorMsg = args.join(' ');
// 忽略 /stomp 和 STOMP 相关错误
if (errorMsg.includes('/stomp') ||
errorMsg.includes('stomp onWebSocketError') ||
(errorMsg.includes('WebSocket connection to') && errorMsg.includes('/stomp'))) {
return; // 不输出日志
}
originalConsoleError.apply(console, args);
};
if (onLoad) {
onLoad(bytedesk);
}
} else {
throw new Error('BytedeskWeb对象未定义');
}
} catch (error) {
console.error('[Bytedesk] Widget初始化失败:', error);
if (onError) {
onError(error);
}
}
};
script.onerror = (error) => {
console.error('[Bytedesk] Widget脚本加载失败:', error);
if (onError) {
onError(error);
}
};
// 添加脚本到页面
document.body.appendChild(script);
scriptRef.current = script;
// 清理函数 - 增强错误处理,防止 React 18 StrictMode 双重清理报错
return () => {
console.log('[Bytedesk] 清理Widget');
// 移除脚本
try {
if (scriptRef.current && scriptRef.current.parentNode) {
scriptRef.current.parentNode.removeChild(scriptRef.current);
}
scriptRef.current = null;
} catch (error) {
console.warn('[Bytedesk] 移除脚本失败(可能已被移除):', error.message);
}
// 移除Widget DOM元素
try {
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]);
// 不渲染任何可见元素Widget会自动插入到body
return <div id="bytedesk-widget-container" style={{ display: 'none' }} />;
};
BytedeskWidget.propTypes = {
config: PropTypes.shape({
apiUrl: PropTypes.string.isRequired,
htmlUrl: PropTypes.string.isRequired,
placement: PropTypes.oneOf(['bottom-right', 'bottom-left', 'top-right', 'top-left']),
marginBottom: PropTypes.number,
marginSide: PropTypes.number,
autoPopup: PropTypes.bool,
locale: PropTypes.string,
bubbleConfig: PropTypes.shape({
show: PropTypes.bool,
icon: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
}),
theme: PropTypes.shape({
mode: PropTypes.oneOf(['light', 'dark', 'system']),
backgroundColor: PropTypes.string,
textColor: PropTypes.string,
}),
chatConfig: PropTypes.shape({
org: PropTypes.string.isRequired,
t: PropTypes.string.isRequired,
sid: PropTypes.string.isRequired,
}).isRequired,
}),
autoLoad: PropTypes.bool,
onLoad: PropTypes.func,
onError: PropTypes.func,
};
export default BytedeskWidget;