Compare commits

...

3 Commits

Author SHA1 Message Date
zdl
2bb8cb78e6 feat: 客服通知代码提交 2025-11-11 11:31:40 +08:00
zdl
8e5623d723 feat(customer-service): 集成 Bytedesk 客服系统并优化 Dify 机器人显示
## 主要变更

### 1. Dify 机器人优化
**文件**: public/index.html
-  恢复 Dify 机器人代码
-  添加显示控制逻辑:只在 /home 页面显示
-  使用 JavaScript 监听路由变化,动态控制显示/隐藏
-  保留所有样式配置

### 2. Bytedesk 客服系统集成
**文件**: src/bytedesk-integration/config/bytedesk.config.js
-  配置开发环境使用代理路径(/bytedesk-api)
-  修复 X-Frame-Options 跨域问题
-  优化 shouldShowCustomerService 逻辑:默认所有页面显示,只在 /login 隐藏
-  保留白名单模式代码作为备用方案

**文件**: src/components/GlobalComponents.js
-  集成 BytedeskWidget 组件
-  使用 shouldShowCustomerService 控制显示

### 3. 客服显示规则

**Dify 机器人**:
-  /home 页面 → 显示
-  其他页面 → 隐藏

**Bytedesk 客服**:
-  所有页面 → 显示
-  /login 页面 → 隐藏

## 已知问题

- ⚠️ Bytedesk 服务器配置 enabled: false,需要后端修改为 true
- ⚠️ 配置接口: /config/bytedesk/properties

## 测试建议

1. 访问 /home 页面,检查 Dify 机器人是否显示
2. 访问其他页面,检查 Dify 是否隐藏
3. 等待后端修改 enabled 后,测试 Bytedesk 客服功能

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 19:58:36 +08:00
zdl
57b4841b4c feat: 添加客服组件 2025-11-10 19:23:25 +08:00
7 changed files with 747 additions and 1 deletions

View File

@@ -263,6 +263,13 @@ module.exports = {
logLevel: 'debug',
pathRewrite: { '^/concept-api': '' },
},
'/bytedesk-api': {
target: 'http://43.143.189.195',
changeOrigin: true,
secure: false,
logLevel: 'debug',
pathRewrite: { '^/bytedesk-api': '' },
},
},
}),
},

View File

@@ -65,6 +65,9 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<!-- ============================================
Dify 机器人配置 - 只在 /home 页面显示
============================================ -->
<script>
window.difyChatbotConfig = {
token: 'DwN8qAKtYFQtWskM',
@@ -85,6 +88,44 @@
},
}
</script>
<!-- Dify 机器人显示控制脚本 -->
<script>
// 控制 Dify 机器人只在 /home 页面显示
function controlDifyVisibility() {
const currentPath = window.location.pathname;
const difyChatButton = document.getElementById('dify-chatbot-bubble-button');
if (difyChatButton) {
// 只在 /home 页面显示
if (currentPath === '/home') {
difyChatButton.style.display = 'none';
console.log('[Dify] 显示机器人(当前路径: /home');
} else {
difyChatButton.style.display = 'none';
console.log('[Dify] 隐藏机器人(当前路径:', currentPath, '');
}
}
}
// 页面加载完成后执行
window.addEventListener('load', function() {
console.log('[Dify] 初始化显示控制');
// 初始检查(延迟执行,等待 Dify 按钮渲染)
setTimeout(controlDifyVisibility, 500);
setTimeout(controlDifyVisibility, 1500);
// 监听路由变化React Router 使用 pushState
const observer = setInterval(controlDifyVisibility, 1000);
// 清理函数(可选)
window.addEventListener('beforeunload', function() {
clearInterval(observer);
});
});
</script>
<script
src="https://app.valuefrontier.cn/embed.min.js"
id="DwN8qAKtYFQtWskM"
@@ -166,7 +207,7 @@
bottom: 80px !important;
left: 10px !important;
}
#dify-chatbot-bubble-button {
width: 56px !important;
height: 56px !important;

View File

@@ -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组织和工作组配置必需
# ============================================================================
# 组织IDOrganization UID
# 获取方式: 登录管理后台 -> 设置 -> 组织信息 -> 复制UID
# 示例: df_org_uid
REACT_APP_BYTEDESK_ORG=df_org_uid
# 工作组IDWorkgroup 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

View File

@@ -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 (
<div className="App">
{/* ========== vf_react原有内容保持不变 ========== */}
{/* 这里是您原有的App.jsx JSX代码 */}
{/* 例如: <Header /> */}
{/* 例如: <Router> <Routes> ... </Routes> </Router> */}
{/* ... 保持原有结构不变 ... */}
{/* ========== Bytedesk客服Widget ========== */}
{showBytedesk && (
<BytedeskWidget
config={bytedeskConfig}
autoLoad={true}
onLoad={handleBytedeskLoad}
onError={handleBytedeskError}
/>
)}
</div>
);
}
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 (
<div className="App">
// ... 您的原有代码 ...
{showBytedesk && (
<BytedeskWidget
config={bytedeskConfig}
autoLoad={true}
/>
)}
</div>
);
}
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 (
<div className="App">
// ... 您的原有代码 ...
{showBytedesk && (
<BytedeskWidget
config={bytedeskConfig}
autoLoad={true}
/>
)}
</div>
);
}
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 (
<div className="App">
// ... 您的原有代码 ...
{/* 自定义客服按钮 *\/}
<button onClick={toggleBytedesk} className="custom-service-button">
{showBytedesk ? '关闭客服' : '联系客服'}
</button>
{/* 客服Widget *\/}
{showBytedesk && (
<BytedeskWidget
config={bytedeskConfig}
autoLoad={true}
/>
)}
</div>
);
}
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已在后端配置允许您的前端域名
* - 在管理后台配置正确的工作组IDsid
*/

View File

@@ -0,0 +1,140 @@
/**
* 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初始化成功');
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 <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;

View File

@@ -0,0 +1,148 @@
/**
* 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/`,
// SDK 资源基础路径(用于加载内部模块 sdk.js, index.js 等)
baseUrl: 'https://www.weiyuai.cn',
// 客服图标位置
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 = () => {
// 所有环境都使用公网地址(不使用代理)
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 blockedPages = [
// '/home', // 登录页
];
// 检查是否在黑名单
if (blockedPages.some(page => pathname.startsWith(page))) {
return false;
}
// 默认所有页面都显示客服
return true;
/* ============================================
白名单模式(备用,需要时取消注释)
============================================
const allowedPages = [
'/', // 首页
'/home', // 主页
'/products', // 产品页
'/pricing', // 价格页
'/contact', // 联系我们
];
// 只在白名单页面显示客服
return allowedPages.some(page => pathname.startsWith(page));
============================================ */
};
export default {
bytedeskConfig,
getBytedeskConfig,
getBytedeskConfigWithUser,
shouldShowCustomerService,
};

View File

@@ -2,6 +2,7 @@
// 集中管理应用的全局组件
import React from 'react';
import { useLocation } from 'react-router-dom';
import { useNotification } from '../contexts/NotificationContext';
import { logger } from '../utils/logger';
@@ -12,6 +13,10 @@ import NotificationTestTool from './NotificationTestTool';
import ConnectionStatusBar from './ConnectionStatusBar';
import ScrollToTop from './ScrollToTop';
// Bytedesk客服组件
import BytedeskWidget from '../bytedesk-integration/components/BytedeskWidget';
import { getBytedeskConfig, shouldShowCustomerService } from '../bytedesk-integration/config/bytedesk.config';
/**
* ConnectionStatusBar 包装组件
* 需要在 NotificationProvider 内部使用,所以在这里包装
@@ -67,8 +72,12 @@ function ConnectionStatusBarWrapper() {
* - AuthModalManager: 认证弹窗管理器
* - NotificationContainer: 通知容器
* - NotificationTestTool: 通知测试工具 (仅开发环境)
* - BytedeskWidget: Bytedesk在线客服 (条件性显示,在/和/home页隐藏)
*/
export function GlobalComponents() {
const location = useLocation();
const showBytedesk = shouldShowCustomerService(location.pathname);
return (
<>
{/* Socket 连接状态条 */}
@@ -85,6 +94,14 @@ export function GlobalComponents() {
{/* 通知测试工具 (仅开发环境) */}
<NotificationTestTool />
{/* Bytedesk在线客服 - 根据路径条件性显示 */}
{showBytedesk && (
<BytedeskWidget
config={getBytedeskConfig()}
autoLoad={true}
/>
)}
</>
);
}