/** * Bytedesk客服Widget组件 * 用于vf_react项目集成 * * 使用方法: * import BytedeskWidget from './components/BytedeskWidget'; * import { getBytedeskConfig } from './config/bytedesk.config'; * * */ 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初始化成功'); 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; // 清理函数 return () => { console.log('[Bytedesk] 清理Widget'); try { // 调用Widget的destroy方法(如果存在) if (widgetRef.current && typeof widgetRef.current.destroy === 'function') { console.log('[Bytedesk] 调用Widget.destroy()'); widgetRef.current.destroy(); } } catch (error) { console.warn('[Bytedesk] Widget.destroy()失败:', error.message); } try { // 移除脚本 if (scriptRef.current) { if (document.body.contains(scriptRef.current)) { document.body.removeChild(scriptRef.current); } scriptRef.current = null; } } catch (error) { console.warn('[Bytedesk] 移除脚本失败:', error.message); } try { // 移除Widget DOM元素(使用更安全的remove()方法) const widgetElements = document.querySelectorAll('[class*="bytedesk"], [id*="bytedesk"]'); widgetElements.forEach(el => { try { if (el && el.parentNode) { // 优先使用remove()方法(更现代、更安全) if (typeof el.remove === 'function') { el.remove(); } else { el.parentNode.removeChild(el); } } } catch (removeError) { console.warn('[Bytedesk] 移除DOM元素失败:', el, removeError.message); } }); } catch (error) { console.warn('[Bytedesk] 清理DOM元素失败:', error.message); } try { // 清理全局对象 if (window.BytedeskWeb) { delete window.BytedeskWeb; } } catch (error) { console.warn('[Bytedesk] 清理全局对象失败:', error.message); } console.log('[Bytedesk] Widget清理完成'); }; }, [config, autoLoad, onLoad, onError]); // 不渲染任何元素(Widget会自动插入DOM到body) // 返回null避免React DOM管理冲突 return null; }; 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;