173 lines
5.1 KiB
JavaScript
173 lines
5.1 KiB
JavaScript
/**
|
||
* 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;
|