diff --git a/src/bytedesk-integration/.env.bytedesk.example b/src/bytedesk-integration/.env.bytedesk.example new file mode 100644 index 00000000..8bd1a900 --- /dev/null +++ b/src/bytedesk-integration/.env.bytedesk.example @@ -0,0 +1,156 @@ +################################################################################ +# Bytedesk客服系统环境变量配置示例 +# +# 使用方法: +# 1. 复制本文件到vf_react项目根目录(与package.json同级) +# cp bytedesk-integration/.env.bytedesk.example .env.local +# +# 2. 根据实际部署环境修改配置值 +# +# 3. 重启开发服务器使配置生效 +# npm start +# +# 注意事项: +# - .env.local文件不应提交到Git(已在.gitignore中) +# - 开发环境和生产环境应使用不同的配置文件 +# - 所有以REACT_APP_开头的变量会被打包到前端代码中 +################################################################################ + +# ============================================================================ +# Bytedesk服务器配置(必需) +# ============================================================================ + +# Bytedesk后端服务地址(生产环境) +# 格式: http://IP地址 或 https://域名 +# 示例: http://43.143.189.195 或 https://kefu.yourdomain.com +REACT_APP_BYTEDESK_API_URL=http://43.143.189.195 + +# ============================================================================ +# Bytedesk组织和工作组配置(必需) +# ============================================================================ + +# 组织ID(Organization UID) +# 获取方式: 登录管理后台 -> 设置 -> 组织信息 -> 复制UID +# 示例: df_org_uid +REACT_APP_BYTEDESK_ORG=df_org_uid + +# 工作组ID(Workgroup SID) +# 获取方式: 登录管理后台 -> 客服管理 -> 工作组 -> 复制工作组ID +# 示例: df_wg_aftersales (售后服务组) +REACT_APP_BYTEDESK_SID=df_wg_aftersales + +# ============================================================================ +# 可选配置 +# ============================================================================ + +# 客服类型 +# 2 = 人工客服(默认) +# 1 = 机器人客服 +# REACT_APP_BYTEDESK_TYPE=2 + +# 语言设置 +# zh-cn = 简体中文(默认) +# en = 英语 +# ja = 日语 +# ko = 韩语 +# REACT_APP_BYTEDESK_LOCALE=zh-cn + +# 客服图标位置 +# bottom-right = 右下角(默认) +# bottom-left = 左下角 +# top-right = 右上角 +# top-left = 左上角 +# REACT_APP_BYTEDESK_PLACEMENT=bottom-right + +# 客服图标边距(像素) +# REACT_APP_BYTEDESK_MARGIN_BOTTOM=20 +# REACT_APP_BYTEDESK_MARGIN_SIDE=20 + +# 主题模式 +# system = 跟随系统(默认) +# light = 亮色模式 +# dark = 暗色模式 +# REACT_APP_BYTEDESK_THEME_MODE=system + +# 主题色(十六进制颜色) +# REACT_APP_BYTEDESK_THEME_COLOR=#0066FF + +# 是否自动弹出客服窗口(不推荐) +# true = 页面加载后自动弹出 +# false = 需用户点击图标弹出(默认) +# REACT_APP_BYTEDESK_AUTO_POPUP=false + +# ============================================================================ +# 开发环境专用配置 +# ============================================================================ + +# 开发环境可以使用不同的服务器地址 +# 取消注释以下行使用本地或测试服务器 +# REACT_APP_BYTEDESK_API_URL_DEV=http://localhost:9003 + +# ============================================================================ +# 配置示例 - 不同部署场景 +# ============================================================================ + +# ---------- 示例1: 生产环境(域名访问) ---------- +# REACT_APP_BYTEDESK_API_URL=https://kefu.yourdomain.com +# REACT_APP_BYTEDESK_ORG=prod_org_12345 +# REACT_APP_BYTEDESK_SID=prod_wg_sales + +# ---------- 示例2: 测试环境(IP访问) ---------- +# REACT_APP_BYTEDESK_API_URL=http://192.168.1.100 +# REACT_APP_BYTEDESK_ORG=test_org_abc +# REACT_APP_BYTEDESK_SID=test_wg_support + +# ---------- 示例3: 本地开发环境 ---------- +# REACT_APP_BYTEDESK_API_URL=http://localhost:9003 +# REACT_APP_BYTEDESK_ORG=dev_org_local +# REACT_APP_BYTEDESK_SID=dev_wg_test + +# ============================================================================ +# 故障排查 +# ============================================================================ + +# 问题1: 客服图标不显示 +# 解决方案: +# - 检查REACT_APP_BYTEDESK_API_URL是否可访问 +# - 确认.env文件在项目根目录 +# - 重启开发服务器(npm start) +# - 查看浏览器控制台是否有错误 + +# 问题2: 连接不上后端服务 +# 解决方案: +# - 确认后端服务已启动(docker ps查看bytedesk-prod容器) +# - 检查CORS配置(后端.env.production中的BYTEDESK_CORS_ALLOWED_ORIGINS) +# - 确认防火墙未阻止80/443端口 + +# 问题3: ORG或SID配置错误 +# 解决方案: +# - 登录管理后台http://43.143.189.195/admin/ +# - 导航到"设置" -> "组织信息"获取ORG +# - 导航到"客服管理" -> "工作组"获取SID +# - 确保复制的ID没有多余空格 + +# 问题4: 多工作组场景 +# 解决方案: +# - 可以为不同页面配置不同的SID +# - 在bytedesk.config.js中使用条件判断 +# - 示例: 售后页面用售后组SID,销售页面用销售组SID + +# ============================================================================ +# 安全提示 +# ============================================================================ + +# 1. 不要在代码中硬编码API地址和ID +# 2. .env.local文件不应提交到Git仓库 +# 3. 生产环境建议使用HTTPS +# 4. 定期更新后端服务器的安全补丁 +# 5. 不要在公开的代码库中暴露组织ID和工作组ID + +# ============================================================================ +# 更多信息 +# ============================================================================ + +# Bytedesk官方文档: https://docs.bytedesk.com +# 技术支持: 访问http://43.143.189.195/chat/联系在线客服 +# GitHub: https://github.com/Bytedesk/bytedesk diff --git a/src/bytedesk-integration/App.jsx.example b/src/bytedesk-integration/App.jsx.example new file mode 100644 index 00000000..b7bccc9d --- /dev/null +++ b/src/bytedesk-integration/App.jsx.example @@ -0,0 +1,237 @@ +/** + * vf_react App.jsx集成示例 + * + * 本文件展示如何在vf_react项目中集成Bytedesk客服系统 + * + * 集成步骤: + * 1. 将bytedesk-integration文件夹复制到src/目录 + * 2. 在App.jsx中导入BytedeskWidget和配置 + * 3. 添加BytedeskWidget组件(代码如下) + * 4. 配置.env文件(参考.env.bytedesk.example) + */ + +import React, { useState, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; // 如果使用react-router +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig, shouldShowCustomerService } from './bytedesk-integration/config/bytedesk.config'; + +// ============================================================================ +// 方案一: 全局集成(推荐) +// 适用场景: 客服系统需要在所有页面显示 +// ============================================================================ + +function App() { + // ========== vf_react原有代码保持不变 ========== + // 这里是您原有的App.jsx代码 + // 例如: const [user, setUser] = useState(null); + // 例如: const [theme, setTheme] = useState('light'); + // ... 保持原有逻辑不变 ... + + // ========== Bytedesk集成代码开始 ========== + + const location = useLocation(); // 获取当前路径 + const [showBytedesk, setShowBytedesk] = useState(false); + + // 根据页面路径决定是否显示客服 + useEffect(() => { + const shouldShow = shouldShowCustomerService(location.pathname); + setShowBytedesk(shouldShow); + }, [location.pathname]); + + // 获取Bytedesk配置 + const bytedeskConfig = getBytedeskConfig(); + + // 客服加载成功回调 + const handleBytedeskLoad = (bytedesk) => { + console.log('[App] Bytedesk客服系统加载成功', bytedesk); + }; + + // 客服加载失败回调 + const handleBytedeskError = (error) => { + console.error('[App] Bytedesk客服系统加载失败', error); + }; + + // ========== Bytedesk集成代码结束 ========== + + return ( +
+ {/* ========== vf_react原有内容保持不变 ========== */} + {/* 这里是您原有的App.jsx JSX代码 */} + {/* 例如:
*/} + {/* 例如: ... */} + {/* ... 保持原有结构不变 ... */} + + {/* ========== Bytedesk客服Widget ========== */} + {showBytedesk && ( + + )} +
+ ); +} + +export default App; + + +// ============================================================================ +// 方案二: 带用户信息集成 +// 适用场景: 需要将登录用户信息传递给客服端 +// ============================================================================ + +/* +import React, { useState, useEffect, useContext } from 'react'; +import { useLocation } from 'react-router-dom'; +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfigWithUser, shouldShowCustomerService } from './bytedesk-integration/config/bytedesk.config'; +import { AuthContext } from './contexts/AuthContext'; // 假设您有用户认证Context + +function App() { + // 获取登录用户信息 + const { user } = useContext(AuthContext); + + const location = useLocation(); + const [showBytedesk, setShowBytedesk] = useState(false); + + useEffect(() => { + const shouldShow = shouldShowCustomerService(location.pathname); + setShowBytedesk(shouldShow); + }, [location.pathname]); + + // 根据用户信息生成配置 + const bytedeskConfig = user + ? getBytedeskConfigWithUser(user) + : getBytedeskConfig(); + + return ( +
+ // ... 您的原有代码 ... + + {showBytedesk && ( + + )} +
+ ); +} + +export default App; +*/ + + +// ============================================================================ +// 方案三: 条件性加载 +// 适用场景: 只在特定条件下显示客服(如用户已登录、特定用户角色等) +// ============================================================================ + +/* +import React, { useState, useEffect } from 'react'; +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + const [user, setUser] = useState(null); + const [showBytedesk, setShowBytedesk] = useState(false); + + useEffect(() => { + // 只有在用户登录且为普通用户时显示客服 + if (user && user.role === 'customer') { + setShowBytedesk(true); + } else { + setShowBytedesk(false); + } + }, [user]); + + const bytedeskConfig = getBytedeskConfig(); + + return ( +
+ // ... 您的原有代码 ... + + {showBytedesk && ( + + )} +
+ ); +} + +export default App; +*/ + + +// ============================================================================ +// 方案四: 动态控制显示/隐藏 +// 适用场景: 需要通过按钮或其他交互控制客服显示 +// ============================================================================ + +/* +import React, { useState } from 'react'; +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + const [showBytedesk, setShowBytedesk] = useState(false); + const bytedeskConfig = getBytedeskConfig(); + + const toggleBytedesk = () => { + setShowBytedesk(prev => !prev); + }; + + return ( +
+ // ... 您的原有代码 ... + + {/* 自定义客服按钮 *\/} + + + {/* 客服Widget *\/} + {showBytedesk && ( + + )} +
+ ); +} + +export default App; +*/ + + +// ============================================================================ +// 重要提示 +// ============================================================================ + +/** + * 1. CSS样式兼容性 + * - Bytedesk Widget使用Shadow DOM,不会影响您的全局样式 + * - Widget的样式可通过config中的theme配置调整 + * + * 2. 性能优化 + * - Widget脚本采用异步加载,不会阻塞页面渲染 + * - 建议在非关键页面(如登录、支付页)隐藏客服 + * + * 3. 错误处理 + * - 如果客服脚本加载失败,不会影响主应用 + * - 建议添加onError回调进行错误监控 + * + * 4. 调试模式 + * - 查看浏览器控制台的[Bytedesk]前缀日志 + * - 检查Network面板确认脚本加载成功 + * + * 5. 生产部署 + * - 确保.env文件配置正确(特别是REACT_APP_BYTEDESK_API_URL) + * - 确保CORS已在后端配置(允许您的前端域名) + * - 在管理后台配置正确的工作组ID(sid) + */ diff --git a/src/bytedesk-integration/components/BytedeskWidget.jsx b/src/bytedesk-integration/components/BytedeskWidget.jsx new file mode 100644 index 00000000..2d0a831b --- /dev/null +++ b/src/bytedesk-integration/components/BytedeskWidget.jsx @@ -0,0 +1,140 @@ +/** + * 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'); + + // 移除脚本 + if (scriptRef.current && document.body.contains(scriptRef.current)) { + document.body.removeChild(scriptRef.current); + } + + // 移除Widget DOM元素 + const widgetElements = document.querySelectorAll('[class*="bytedesk"], [id*="bytedesk"]'); + widgetElements.forEach(el => { + if (el && el.parentNode) { + el.parentNode.removeChild(el); + } + }); + + // 清理全局对象 + if (window.BytedeskWeb) { + delete window.BytedeskWeb; + } + }; + }, [config, autoLoad, onLoad, onError]); + + // 不渲染任何可见元素(Widget会自动插入到body) + return
; +}; + +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; diff --git a/src/bytedesk-integration/config/bytedesk.config.js b/src/bytedesk-integration/config/bytedesk.config.js new file mode 100644 index 00000000..e73aae49 --- /dev/null +++ b/src/bytedesk-integration/config/bytedesk.config.js @@ -0,0 +1,157 @@ +/** + * Bytedesk客服配置文件 + * 指向43.143.189.195服务器 + * + * 环境变量配置(.env文件): + * REACT_APP_BYTEDESK_API_URL=http://43.143.189.195 + * REACT_APP_BYTEDESK_ORG=df_org_uid + * REACT_APP_BYTEDESK_SID=df_wg_aftersales + */ + +// 从环境变量读取配置 +const BYTEDESK_API_URL = process.env.REACT_APP_BYTEDESK_API_URL || 'http://43.143.189.195'; +const BYTEDESK_ORG = process.env.REACT_APP_BYTEDESK_ORG || 'df_org_uid'; +const BYTEDESK_SID = process.env.REACT_APP_BYTEDESK_SID || 'df_wg_aftersales'; + +/** + * Bytedesk客服基础配置 + */ +export const bytedeskConfig = { + // API服务地址 + apiUrl: BYTEDESK_API_URL, + // 聊天页面地址 + htmlUrl: `${BYTEDESK_API_URL}/chat/`, + + // 客服图标位置 + placement: 'bottom-right', // bottom-right | bottom-left | top-right | top-left + + // 边距设置(像素) + marginBottom: 20, + marginSide: 20, + + // 自动弹出(不推荐) + autoPopup: false, + + // 语言设置 + locale: 'zh-cn', // zh-cn | en | ja | ko + + // 客服图标配置 + bubbleConfig: { + show: true, // 是否显示客服图标 + icon: '💬', // 图标(emoji或图片URL) + title: '在线客服', // 鼠标悬停标题 + subtitle: '点击咨询', // 副标题 + }, + + // 主题配置 + theme: { + mode: 'system', // light | dark | system + backgroundColor: '#0066FF', // 主题色 + textColor: '#ffffff', // 文字颜色 + }, + + // 聊天配置(必需) + chatConfig: { + org: BYTEDESK_ORG, // 组织ID + t: '2', // 类型: 2=客服, 1=机器人 + sid: BYTEDESK_SID, // 工作组ID + }, +}; + +/** + * 获取Bytedesk配置(根据环境自动切换) + * + * @returns {Object} Bytedesk配置对象 + */ +export const getBytedeskConfig = () => { + // 开发环境配置 + if (process.env.NODE_ENV === 'development') { + return { + ...bytedeskConfig, + apiUrl: 'http://43.143.189.195', // 指向测试服务器 + htmlUrl: 'http://43.143.189.195/chat/', + }; + } + + // 生产环境配置 + return bytedeskConfig; +}; + +/** + * 获取带用户信息的配置 + * 用于已登录用户,自动传递用户信息到客服端 + * + * @param {Object} user - 用户对象 + * @param {string} user.id - 用户ID + * @param {string} user.name - 用户名 + * @param {string} user.email - 用户邮箱 + * @param {string} user.mobile - 用户手机号 + * @returns {Object} 带用户信息的Bytedesk配置 + */ +export const getBytedeskConfigWithUser = (user) => { + const config = getBytedeskConfig(); + + if (user && user.id) { + return { + ...config, + chatConfig: { + ...config.chatConfig, + // 传递用户信息(可选) + customParams: { + userId: user.id, + userName: user.name || 'Guest', + userEmail: user.email || '', + userMobile: user.mobile || '', + source: 'web', // 来源标识 + }, + }, + }; + } + + return config; +}; + +/** + * 根据页面路径判断是否显示客服 + * + * @param {string} pathname - 当前页面路径 + * @returns {boolean} 是否显示客服 + */ +export const shouldShowCustomerService = (pathname) => { + // 在以下页面显示客服 + const allowedPages = [ + '/', // 首页 + '/home', // 主页 + '/products', // 产品页 + '/pricing', // 价格页 + '/contact', // 联系我们 + // 添加其他需要显示客服的页面... + ]; + + // 在以下页面隐藏客服 + const blockedPages = [ + '/login', // 登录页 + '/register', // 注册页 + '/payment', // 支付页 + // 添加其他不需要客服的页面... + ]; + + // 检查是否在阻止列表 + if (blockedPages.some(page => pathname.startsWith(page))) { + return false; + } + + // 检查是否在允许列表(如果列表为空,默认全部显示) + if (allowedPages.length === 0) { + return true; + } + + return allowedPages.some(page => pathname.startsWith(page)); +}; + +export default { + bytedeskConfig, + getBytedeskConfig, + getBytedeskConfigWithUser, + shouldShowCustomerService, +};