diff --git a/.env.development b/.env.development index 7ff74018..d17b1104 100644 --- a/.env.development +++ b/.env.development @@ -18,10 +18,3 @@ REACT_APP_ENABLE_MOCK=false # 开发环境标识 REACT_APP_ENV=development - -# PostHog 配置(开发环境) -# 留空 = 仅控制台 debug -# 填入 Key = 控制台 + PostHog Cloud 双模式 -REACT_APP_POSTHOG_KEY= -REACT_APP_POSTHOG_HOST=https://app.posthog.com -REACT_APP_ENABLE_SESSION_RECORDING=false diff --git a/.env.mock b/.env.mock index d185935c..6888e0d3 100644 --- a/.env.mock +++ b/.env.mock @@ -35,14 +35,3 @@ REACT_APP_ENABLE_MOCK=true # Mock 环境标识 REACT_APP_ENV=mock - -# PostHog 配置(Mock 环境) -# 留空 = 仅控制台 debug -# 填入 Key = 控制台 + PostHog Cloud 双模式 -REACT_APP_POSTHOG_KEY=phc_xKlRyG69Bx7hgOdFeCeLUvQWvSjw18ZKFgCwCeYezWF -REACT_APP_POSTHOG_HOST=https://app.posthog.com -REACT_APP_ENABLE_SESSION_RECORDING=false - -# PostHog Debug 模式(Mock 环境永久启用) -# 在浏览器 Console 中打印详细的事件追踪日志 -REACT_APP_POSTHOG_DEBUG=true diff --git a/.env.production b/.env.production index 272cb4db..3eb056e9 100644 --- a/.env.production +++ b/.env.production @@ -1,13 +1,6 @@ # ======================================== # 生产环境配置 # ======================================== -# 使用方式: npm run build -# -# 工作原理: -# 1. 此文件专门用于生产环境构建 -# 2. 构建时会将环境变量嵌入到打包文件中 -# 3. 确保 PostHog 等服务使用正确的生产配置 -# ======================================== # 环境标识 REACT_APP_ENV=production @@ -17,13 +10,8 @@ NODE_ENV=production REACT_APP_ENABLE_MOCK=false # 🔧 调试模式(生产环境临时调试用) -# 开启后会在全局暴露 window.__DEBUG__ 和 window.__TEST_NOTIFICATION__ 调试 API -# ⚠️ 警告: 调试模式会记录所有 API 请求/响应,调试完成后请立即关闭! -# 使用方法: -# 1. 设置为 true 并重新构建 -# 2. 在浏览器控制台使用 window.__DEBUG__.help() 查看命令 -# 3. 调试完成后设置为 false 并重新构建 -REACT_APP_ENABLE_DEBUG=true +# 开启后会在全局暴露 window.__DEBUG__ +REACT_APP_ENABLE_DEBUG=false # 后端 API 地址(生产环境) REACT_APP_API_URL=http://49.232.185.254:5001 @@ -49,20 +37,3 @@ TSC_COMPILE_ON_ERROR=true IMAGE_INLINE_SIZE_LIMIT=10000 # Node.js 内存限制(适用于大型项目) NODE_OPTIONS=--max_old_space_size=4096 - -# ======================================== -# Bytedesk 客服系统配置 -# ======================================== -# Bytedesk 服务器地址(使用相对路径,通过 Nginx 代理) -# ⚠️ 重要:生产环境必须使用相对路径,避免 Mixed Content 错误 -# Nginx 配置:location /bytedesk-api/ { proxy_pass http://43.143.189.195/; } -REACT_APP_BYTEDESK_API_URL=/bytedesk-api - -# 组织 UUID(从管理后台 -> 设置 -> 组织信息 -> 组织UUID) -REACT_APP_BYTEDESK_ORG=df_org_uid - -# 工作组 UUID(从管理后台 -> 客服管理 -> 工作组 -> 工作组UUID) -REACT_APP_BYTEDESK_SID=df_wg_uid - -# 客服类型(2=人工客服, 1=机器人) -REACT_APP_BYTEDESK_TYPE=2 diff --git a/.gitignore b/.gitignore index b1a59569..6141ca49 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ Thumbs.db *.md !README.md !CLAUDE.md -!docs/**/*.md + +# 忽略 docs 目录(开发文档不提交到 Git) +docs/ src/assets/img/original-backup/ diff --git a/docs/BYTEDESK_INTEGRATION_GUIDE.md b/docs/BYTEDESK_INTEGRATION_GUIDE.md new file mode 100644 index 00000000..c179b9ff --- /dev/null +++ b/docs/BYTEDESK_INTEGRATION_GUIDE.md @@ -0,0 +1,918 @@ +# Bytedesk客服系统 - 前端工程师集成手册 + +**版本**: v1.0 +**最后更新**: 2025-01-07 +**适用项目**: vf_react +**后端服务器**: http://43.143.189.195 + +--- + +## 📋 目录 + +- [1. 集成概述](#1-集成概述) +- [2. 快速开始(5分钟集成)](#2-快速开始5分钟集成) +- [3. 详细集成步骤](#3-详细集成步骤) +- [4. 配置说明](#4-配置说明) +- [5. 高级功能](#5-高级功能) +- [6. 样式定制](#6-样式定制) +- [7. 故障排查](#7-故障排查) +- [8. 常见问题FAQ](#8-常见问题faq) +- [9. 性能优化](#9-性能优化) +- [10. 安全注意事项](#10-安全注意事项) + +--- + +## 1. 集成概述 + +### 1.1 什么是Bytedesk客服系统? + +Bytedesk是一个开源的在线客服系统,为您的网站提供实时客户服务功能。本手册将指导您将Bytedesk客服Widget集成到vf_react项目中。 + +### 1.2 集成架构 + +``` +┌────────────────────────────────────────────────────────────┐ +│ vf_react前端项目 │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ App.jsx │ │ +│ │ ┌──────────────────────────────────────────────┐ │ │ +│ │ │ BytedeskWidget组件 │ │ │ +│ │ │ - 动态加载客服脚本 │ │ │ +│ │ │ - 显示悬浮客服图标 │ │ │ +│ │ │ - 处理用户交互 │ │ │ +│ │ └──────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────┘ + │ + │ HTTP/WebSocket + ↓ +┌────────────────────────────────────────────────────────────┐ +│ Bytedesk后端服务 (43.143.189.195) │ +│ - API接口: :9003 │ +│ - WebSocket: :9885 │ +│ - Nginx反向代理: :80 │ +└────────────────────────────────────────────────────────────┘ +``` + +### 1.3 集成特点 + +- ✅ **零侵入**: 不修改vf_react原有代码逻辑 +- ✅ **即插即用**: 复制文件 + 修改配置即可使用 +- ✅ **样式隔离**: 使用Shadow DOM,不影响全局样式 +- ✅ **异步加载**: 不阻塞页面渲染 +- ✅ **跨页面**: 在所有页面显示客服图标 +- ✅ **响应式**: 自动适配移动端和PC端 + +--- + +## 2. 快速开始(5分钟集成) + +### 步骤1: 复制集成文件 + +将`bytedesk-integration`文件夹复制到vf_react项目的`src/`目录下: + +```bash +# 在vf_react项目根目录执行 +cd D:\【Git】\vf_react +cp -r bytedesk-integration src/ +``` + +文件结构: +``` +vf_react/ +├── src/ +│ ├── bytedesk-integration/ # 客服集成文件夹 +│ │ ├── components/ +│ │ │ └── BytedeskWidget.jsx # 客服Widget组件 +│ │ ├── config/ +│ │ │ └── bytedesk.config.js # 配置文件 +│ │ ├── App.jsx.example # 集成示例代码 +│ │ ├── .env.bytedesk.example # 环境变量示例 +│ │ └── 前端工程师集成手册.md # 本手册 +│ ├── App.jsx # 您的主App文件 +│ └── ... +└── package.json +``` + +### 步骤2: 配置环境变量 + +复制环境变量模板到项目根目录并配置: + +```bash +# 复制模板 +cp src/bytedesk-integration/.env.bytedesk.example .env.local + +# 编辑配置文件 +vim .env.local +``` + +**必需配置项**(在.env.local中): +```bash +# Bytedesk服务器地址 +REACT_APP_BYTEDESK_API_URL=http://43.143.189.195 + +# 组织ID(由管理员提供) +REACT_APP_BYTEDESK_ORG=df_org_uid + +# 工作组ID(由管理员提供) +REACT_APP_BYTEDESK_SID=df_wg_aftersales +``` + +> **注意**: ORG和SID需要从管理员处获取,或登录后台http://43.143.189.195/admin/查看。 + +### 步骤3: 集成到App.jsx + +打开`src/App.jsx`,参考`App.jsx.example`添加以下代码: + +```jsx +// 1. 导入组件和配置(在文件顶部添加) +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + // 2. 获取配置 + const bytedeskConfig = getBytedeskConfig(); + + return ( +
+ {/* 您的原有代码保持不变 */} + + {/* 3. 添加客服Widget(在return的JSX最后添加) */} + +
+ ); +} + +export default App; +``` + +### 步骤4: 启动项目测试 + +```bash +# 安装依赖(如果需要) +npm install + +# 启动开发服务器 +npm start +``` + +打开浏览器,您应该在页面右下角看到客服图标(💬)。 + +--- + +## 3. 详细集成步骤 + +### 3.1 文件说明 + +#### BytedeskWidget.jsx +React组件,负责加载和管理Bytedesk客服Widget。 + +**主要功能**: +- 动态加载客服脚本(https://www.weiyuai.cn/embed/bytedesk-web.js) +- 初始化客服Widget +- 生命周期管理(加载、卸载、清理) +- 错误处理 + +**Props**: +```typescript +interface BytedeskWidgetProps { + config: Object; // 配置对象(必需) + autoLoad?: boolean; // 是否自动加载(默认true) + onLoad?: (bytedesk) => void; // 加载成功回调 + onError?: (error) => void; // 加载失败回调 +} +``` + +#### bytedesk.config.js +配置文件,包含客服系统的所有配置项。 + +**主要函数**: +- `getBytedeskConfig()`: 获取基础配置 +- `getBytedeskConfigWithUser(user)`: 获取带用户信息的配置 +- `shouldShowCustomerService(pathname)`: 判断是否在当前页面显示客服 + +### 3.2 集成方式选择 + +根据您的需求,选择合适的集成方式: + +#### 方式一: 全局集成(推荐) + +**适用场景**: 所有页面都需要客服功能 + +```jsx +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + const bytedeskConfig = getBytedeskConfig(); + + return ( +
+ {/* 您的页面内容 */} + + +
+ ); +} +``` + +#### 方式二: 按页面显示 + +**适用场景**: 只在特定页面显示客服(如排除登录页、支付页) + +```jsx +import { useLocation } from 'react-router-dom'; +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfig, shouldShowCustomerService } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + const location = useLocation(); + const bytedeskConfig = getBytedeskConfig(); + const showBytedesk = shouldShowCustomerService(location.pathname); + + return ( +
+ {/* 您的页面内容 */} + + {showBytedesk && ( + + )} +
+ ); +} +``` + +自定义页面规则(修改`bytedesk.config.js`): + +```javascript +export const shouldShowCustomerService = (pathname) => { + // 在以下页面显示客服 + const allowedPages = [ + '/', + '/home', + '/products', + '/pricing', + ]; + + // 在以下页面隐藏客服 + const blockedPages = [ + '/login', + '/register', + '/payment', + ]; + + if (blockedPages.some(page => pathname.startsWith(page))) { + return false; + } + + return allowedPages.some(page => pathname.startsWith(page)); +}; +``` + +#### 方式三: 带用户信息集成 + +**适用场景**: 需要将登录用户信息传递给客服端 + +```jsx +import { useContext } from 'react'; +import BytedeskWidget from './bytedesk-integration/components/BytedeskWidget'; +import { getBytedeskConfigWithUser } from './bytedesk-integration/config/bytedesk.config'; +import { AuthContext } from './contexts/AuthContext'; + +function App() { + const { user } = useContext(AuthContext); + const bytedeskConfig = getBytedeskConfigWithUser(user); + + return ( +
+ {/* 您的页面内容 */} + + +
+ ); +} +``` + +用户信息格式: +```javascript +const user = { + id: '12345', // 用户ID(必需) + name: '张三', // 用户名 + email: 'user@example.com', // 邮箱 + mobile: '13800138000', // 手机号 +}; +``` + +--- + +## 4. 配置说明 + +### 4.1 环境变量配置 + +在`.env.local`文件中配置(项目根目录): + +```bash +# ========== 必需配置 ========== + +# 后端服务地址 +REACT_APP_BYTEDESK_API_URL=http://43.143.189.195 + +# 组织ID +REACT_APP_BYTEDESK_ORG=df_org_uid + +# 工作组ID +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 + +# 自动弹出(不推荐) +REACT_APP_BYTEDESK_AUTO_POPUP=false +``` + +### 4.2 代码配置 + +在`bytedesk.config.js`中直接修改: + +```javascript +export const bytedeskConfig = { + // API服务地址 + apiUrl: 'http://43.143.189.195', + htmlUrl: 'http://43.143.189.195/chat/', + + // 客服图标位置 + placement: 'bottom-right', + + // 边距设置 + marginBottom: 20, + marginSide: 20, + + // 自动弹出 + autoPopup: false, + + // 语言设置 + locale: 'zh-cn', + + // 客服图标配置 + bubbleConfig: { + show: true, + icon: '💬', // 可以使用emoji或图片URL + title: '在线客服', + subtitle: '点击咨询', + }, + + // 主题配置 + theme: { + mode: 'system', // light | dark | system + backgroundColor: '#0066FF', + textColor: '#ffffff', + }, + + // 聊天配置 + chatConfig: { + org: 'df_org_uid', + t: '2', // 2=人工客服, 1=机器人 + sid: 'df_wg_aftersales', + }, +}; +``` + +--- + +## 5. 高级功能 + +### 5.1 多工作组支持 + +根据页面显示不同工作组的客服: + +```javascript +// bytedesk.config.js +export const getBytedeskConfigByPath = (pathname) => { + const config = getBytedeskConfig(); + + // 根据路径选择工作组 + if (pathname.startsWith('/sales')) { + return { + ...config, + chatConfig: { + ...config.chatConfig, + sid: 'df_wg_sales', // 销售组 + }, + }; + } else if (pathname.startsWith('/support')) { + return { + ...config, + chatConfig: { + ...config.chatConfig, + sid: 'df_wg_support', // 技术支持组 + }, + }; + } + + return config; // 默认售后组 +}; +``` + +使用示例: +```jsx +import { useLocation } from 'react-router-dom'; +import { getBytedeskConfigByPath } from './bytedesk-integration/config/bytedesk.config'; + +function App() { + const location = useLocation(); + const bytedeskConfig = getBytedeskConfigByPath(location.pathname); + + return ( +
+ +
+ ); +} +``` + +### 5.2 条件性显示 + +根据用户登录状态或角色显示客服: + +```jsx +function App() { + const { user } = useContext(AuthContext); + const bytedeskConfig = getBytedeskConfig(); + + // 只为普通用户显示客服(管理员不显示) + const showBytedesk = user && user.role === 'customer'; + + return ( +
+ {showBytedesk && ( + + )} +
+ ); +} +``` + +### 5.3 事件回调 + +监听客服系统的加载状态: + +```jsx +function App() { + const bytedeskConfig = getBytedeskConfig(); + + const handleLoad = (bytedesk) => { + console.log('客服系统加载成功', bytedesk); + // 可以在这里执行自定义逻辑 + // 例如: 发送统计事件 + }; + + const handleError = (error) => { + console.error('客服系统加载失败', error); + // 可以在这里显示降级方案 + // 例如: 显示备用联系方式 + }; + + return ( +
+ +
+ ); +} +``` + +### 5.4 自定义触发按钮 + +隐藏默认图标,使用自定义按钮: + +```jsx +import { useState } from 'react'; + +function App() { + const [showBytedesk, setShowBytedesk] = useState(false); + + // 隐藏默认图标 + const bytedeskConfig = { + ...getBytedeskConfig(), + bubbleConfig: { + show: false, // 隐藏默认图标 + }, + }; + + return ( +
+ {/* 自定义按钮 */} + + + {showBytedesk && ( + + )} +
+ ); +} +``` + +--- + +## 6. 样式定制 + +### 6.1 修改主题色 + +在配置中修改主题色: + +```javascript +// bytedesk.config.js +theme: { + mode: 'light', + backgroundColor: '#FF6600', // 您的品牌色 + textColor: '#ffffff', +}, +``` + +### 6.2 修改图标位置 + +```javascript +// bytedesk.config.js +placement: 'bottom-left', // 左下角 +marginBottom: 30, // 距底部30px +marginSide: 30, // 距左侧30px +``` + +### 6.3 使用自定义图标 + +使用图片URL替换emoji: + +```javascript +// bytedesk.config.js +bubbleConfig: { + show: true, + icon: 'https://yourdomain.com/images/service-icon.png', + title: '在线客服', + subtitle: '点击咨询', +}, +``` + +### 6.4 样式不冲突 + +Bytedesk Widget使用Shadow DOM技术,样式完全隔离,不会影响您的全局CSS。 + +--- + +## 7. 故障排查 + +### 7.1 客服图标不显示 + +**可能原因**: +1. 环境变量未配置 +2. 配置文件路径错误 +3. 后端服务未启动 +4. 脚本加载失败 + +**解决方案**: +```bash +# 1. 检查.env.local文件是否存在 +ls -la .env.local + +# 2. 检查环境变量是否加载 +console.log(process.env.REACT_APP_BYTEDESK_API_URL); + +# 3. 检查后端服务状态 +curl http://43.143.189.195/api/health + +# 4. 查看浏览器控制台错误 +# 打开浏览器开发者工具 -> Console标签页 +``` + +### 7.2 连接不上后端 + +**检查清单**: +```bash +# 1. 后端服务是否运行 +# 联系后端工程师确认docker容器状态 + +# 2. 防火墙是否开放 +# 确认80端口可访问 + +# 3. CORS配置 +# 后端需要在.env.production中添加您的前端地址: +# BYTEDESK_CORS_ALLOWED_ORIGINS=http://your-frontend-domain.com +``` + +### 7.3 ORG或SID错误 + +**获取正确配置**: +1. 登录管理后台: http://43.143.189.195/admin/ +2. 导航到"设置" -> "组织信息",复制`组织UID` +3. 导航到"客服管理" -> "工作组",复制`工作组ID` +4. 更新`.env.local`文件 +5. 重启开发服务器: `npm start` + +### 7.4 开发环境正常,生产环境异常 + +**检查清单**: +```bash +# 1. 确认生产环境的环境变量 +# 查看构建时的配置 + +# 2. 检查CORS配置 +# 后端需要添加生产域名到CORS白名单 + +# 3. 检查HTTPS/HTTP +# 如果前端使用HTTPS,后端也应使用HTTPS + +# 4. 查看生产环境日志 +npm run build +# 检查构建产物中的配置 +``` + +--- + +## 8. 常见问题FAQ + +### Q1: 客服系统会影响页面性能吗? + +**A**: 不会。客服脚本采用异步加载,不会阻塞页面渲染。Widget总大小约50KB(gzip后),首次加载后会被浏览器缓存。 + +### Q2: 可以在移动端使用吗? + +**A**: 可以。Bytedesk Widget完全响应式,自动适配移动端和PC端。 + +### Q3: 是否支持离线消息? + +**A**: 支持。用户在客服离线时发送的消息会被保存,客服上线后可以查看。 + +### Q4: 可以集成到React Native吗? + +**A**: BytedeskWidget是为Web设计的。React Native需要使用Bytedesk的原生SDK(另外提供)。 + +### Q5: 如何隐藏特定页面的客服? + +**A**: 使用`shouldShowCustomerService`函数(见3.2节"方式二")。 + +### Q6: 可以同时配置多个工作组吗? + +**A**: 可以。参考5.1节"多工作组支持"。 + +### Q7: 用户信息是否安全? + +**A**: 是的。所有通信使用WebSocket加密传输,用户信息不会被第三方获取。建议生产环境使用HTTPS。 + +### Q8: 是否需要付费? + +**A**: Bytedesk社区版(当前使用)完全免费,License有效期至2040年12月31日。 + +--- + +## 9. 性能优化 + +### 9.1 按需加载 + +只在需要时加载客服系统: + +```jsx +import { useState, useEffect } from 'react'; + +function App() { + const [loadBytedesk, setLoadBytedesk] = useState(false); + + // 延迟5秒加载(页面渲染完成后) + useEffect(() => { + const timer = setTimeout(() => { + setLoadBytedesk(true); + }, 5000); + + return () => clearTimeout(timer); + }, []); + + return ( +
+ {/* 您的页面内容 */} + + {loadBytedesk && ( + + )} +
+ ); +} +``` + +### 9.2 Lazy Import + +使用React.lazy延迟导入组件: + +```jsx +import { lazy, Suspense } from 'react'; + +const BytedeskWidget = lazy(() => import('./bytedesk-integration/components/BytedeskWidget')); + +function App() { + return ( +
+ {/* 您的页面内容 */} + + + + +
+ ); +} +``` + +### 9.3 缓存优化 + +客服脚本会自动被浏览器缓存,无需额外配置。 + +--- + +## 10. 安全注意事项 + +### 10.1 环境变量安全 + +```bash +# ❌ 错误: 不要在代码中硬编码配置 +const config = { + apiUrl: 'http://43.143.189.195', + org: 'df_org_uid', +}; + +# ✅ 正确: 使用环境变量 +const config = { + apiUrl: process.env.REACT_APP_BYTEDESK_API_URL, + org: process.env.REACT_APP_BYTEDESK_ORG, +}; +``` + +### 10.2 敏感信息保护 + +```javascript +// ❌ 不要传递敏感信息 +const user = { + id: '12345', + password: 'user-password', // 不要传递密码 + creditCard: '1234-5678', // 不要传递信用卡 +}; + +// ✅ 只传递必要信息 +const user = { + id: '12345', + name: '张三', + email: 'user@example.com', +}; +``` + +### 10.3 HTTPS使用 + +生产环境强烈建议使用HTTPS: + +```bash +# 开发环境 +REACT_APP_BYTEDESK_API_URL=http://43.143.189.195 + +# 生产环境 +REACT_APP_BYTEDESK_API_URL=https://kefu.yourdomain.com +``` + +### 10.4 内容安全策略(CSP) + +如果您的项目使用CSP,需要允许以下域名: + +```html + +``` + +--- + +## 11. 获取帮助 + +### 11.1 联系方式 + +- **技术支持**: 访问 http://43.143.189.195/chat/ 在线咨询 +- **管理员**: 联系您的项目管理员获取ORG和SID +- **后端工程师**: 联系后端团队确认服务器状态 + +### 11.2 日志查看 + +```javascript +// 在浏览器控制台查看Bytedesk日志 +// 日志前缀为 [Bytedesk] + +// 示例: +[Bytedesk] 开始加载客服Widget... +[Bytedesk] Widget脚本加载成功 +[Bytedesk] 初始化Widget +[Bytedesk] Widget初始化成功 +``` + +### 11.3 调试技巧 + +```javascript +// 1. 检查配置是否正确 +console.log('Bytedesk配置:', getBytedeskConfig()); + +// 2. 检查环境变量 +console.log('API URL:', process.env.REACT_APP_BYTEDESK_API_URL); +console.log('ORG:', process.env.REACT_APP_BYTEDESK_ORG); +console.log('SID:', process.env.REACT_APP_BYTEDESK_SID); + +// 3. 检查Widget是否加载 +console.log('BytedeskWeb对象:', window.BytedeskWeb); +``` + +--- + +## 12. 版本历史 + +| 版本 | 日期 | 更新内容 | +|------|------|---------| +| v1.0 | 2025-01-07 | 初始版本,支持基础集成功能 | + +--- + +## 13. 附录 + +### 13.1 完整配置示例 + +```javascript +// bytedesk.config.js - 完整配置 +export const bytedeskConfig = { + apiUrl: 'http://43.143.189.195', + htmlUrl: 'http://43.143.189.195/chat/', + placement: 'bottom-right', + marginBottom: 20, + marginSide: 20, + autoPopup: false, + locale: 'zh-cn', + bubbleConfig: { + show: true, + icon: '💬', + title: '在线客服', + subtitle: '点击咨询', + }, + theme: { + mode: 'system', + backgroundColor: '#0066FF', + textColor: '#ffffff', + }, + chatConfig: { + org: 'df_org_uid', + t: '2', + sid: 'df_wg_aftersales', + }, +}; +``` + +### 13.2 文件清单 + +集成所需的所有文件: + +``` +bytedesk-integration/ +├── components/ +│ └── BytedeskWidget.jsx # React组件(必需) +├── config/ +│ └── bytedesk.config.js # 配置文件(必需) +├── App.jsx.example # 集成示例(参考) +├── .env.bytedesk.example # 环境变量示例(参考) +└── 前端工程师集成手册.md # 本手册(参考) +``` + +--- + +**祝您集成顺利!** + +如有任何问题,请随时联系技术支持。 diff --git a/docs/POSTHOG_EVENT_TRACKING.md b/docs/POSTHOG_EVENT_TRACKING.md deleted file mode 100644 index efe908b7..00000000 --- a/docs/POSTHOG_EVENT_TRACKING.md +++ /dev/null @@ -1,841 +0,0 @@ -# PostHog 事件追踪实施总结 - -## ✅ 已完成的追踪 - -### 1. Home 页面(首页/落地页) - -**已实施的追踪事件**: - -#### 📄 页面浏览 -- **事件**: `LANDING_PAGE_VIEWED` -- **触发时机**: 页面加载 -- **属性**: - - `timestamp` - 访问时间 - - `is_authenticated` - 是否已登录 - - `user_id` - 用户ID(如果已登录) - -#### 🎯 功能卡片点击 -- **事件**: `FEATURE_CARD_CLICKED` -- **触发时机**: 用户点击任何功能卡片 -- **属性**: - - `feature_id` - 功能ID(news-catalyst, concepts, stocks, etc.) - - `feature_title` - 功能标题 - - `feature_url` - 目标URL - - `is_featured` - 是否为推荐功能(新闻中心为 true) - - `link_type` - 链接类型(internal/external) - -**追踪的6个核心功能**: -1. **新闻中心** (`news-catalyst`) - 推荐功能,黄色边框 -2. **概念中心** (`concepts`) -3. **个股信息汇总** (`stocks`) -4. **涨停板块分析** (`limit-analyse`) -5. **个股罗盘** (`company`) -6. **模拟盘交易** (`trading-simulation`) - ---- - -### 2. StockOverview 页面(个股中心)✅ 已完成 - -**注意**:个股中心页面已完全实现 PostHog 追踪,通过 `src/views/StockOverview/hooks/useStockOverviewEvents.js` Hook。 - -**已实施的追踪事件**: - -#### 📄 页面浏览 -- **事件**: `STOCK_OVERVIEW_VIEWED` -- **触发时机**: 页面加载 -- **属性**: - - `timestamp` - 访问时间 - -#### 📊 市场统计数据查看 -- **事件**: `STOCK_LIST_VIEWED` -- **触发时机**: 加载市场统计数据 -- **属性**: - - `total_market_cap` - 总市值 - - `total_volume` - 总成交量 - - `rising_stocks` - 上涨股票数 - - `falling_stocks` - 下跌股票数 - - `data_date` - 数据日期 - -#### 🔍 搜索追踪 -- **事件**: `SEARCH_INITIATED` / `STOCK_SEARCHED` -- **触发时机**: 用户输入搜索、完成搜索 -- **属性**: - - `query` - 搜索关键词 - - `result_count` - 搜索结果数量 - - `has_results` - 是否有结果 - - `context` - 固定为 'stock_overview' - -#### 🎯 搜索结果点击 -- **事件**: `SEARCH_RESULT_CLICKED` -- **触发时机**: 用户点击搜索结果 -- **属性**: - - `stock_code` - 股票代码 - - `stock_name` - 股票名称 - - `exchange` - 交易所 - - `position` - 在搜索结果中的位置 - - `context` - 固定为 'stock_overview' - -#### 🔥 概念卡片点击 -- **事件**: `CONCEPT_CLICKED` -- **触发时机**: 用户点击热门概念卡片 -- **属性**: - - `concept_name` - 概念名称 - - `concept_code` - 概念代码 - - `change_percent` - 涨跌幅 - - `stock_count` - 股票数量 - - `rank` - 排名 - - `source` - 固定为 'daily_hot_concepts' - -#### 🏷️ 概念股票标签点击 -- **事件**: `CONCEPT_STOCK_CLICKED` -- **触发时机**: 点击概念下的股票标签 -- **属性**: - - `stock_code` - 股票代码 - - `stock_name` - 股票名称 - - `concept_name` - 所属概念 - - `source` - 固定为 'daily_hot_concepts_tag' - -#### 📊 热力图股票点击 -- **事件**: `STOCK_CLICKED` -- **触发时机**: 点击热力图中的股票 -- **属性**: - - `stock_code` - 股票代码 - - `stock_name` - 股票名称 - - `change_percent` - 涨跌幅 - - `market_cap_range` - 市值区间 - - `source` - 固定为 'market_heatmap' - -#### 📅 日期选择变化 -- **事件**: `SEARCH_FILTER_APPLIED` -- **触发时机**: 用户选择不同的交易日期 -- **属性**: - - `filter_type` - 固定为 'date' - - `filter_value` - 新选择的日期 - - `previous_value` - 之前的日期 - - `context` - 固定为 'stock_overview' - -**实施方式**: Custom Hook (`useStockOverviewEvents.js`) 已集成 - ---- - -### 3. Concept 页面(概念中心) - -**已实施的追踪事件**: - -#### 📄 页面浏览 -- **事件**: `CONCEPT_CENTER_VIEWED` -- **触发时机**: 页面加载 -- **属性**: - - `timestamp` - 访问时间 - -#### 🔍 搜索查询 -- **事件**: `SEARCH_QUERY_SUBMITTED` -- **触发时机**: 用户搜索概念 -- **属性**: - - `query` - 搜索关键词 - - `category` - 固定为 'concept' - - `result_count` - 搜索结果数量 - - `has_results` - 是否有结果 - -#### 🎚️ 筛选追踪 -- **事件**: `SEARCH_FILTER_APPLIED` -- **触发时机**: 用户更改筛选条件 -- **属性**: - - `filter_type` - 筛选类型(sort/date) - - `filter_value` - 筛选值 - - `previous_value` - 之前的值 - - `context` - 固定为 'concept_center' - -**支持的筛选类型**: -1. **排序** (`sort`): 涨跌幅/相关度/股票数量/概念名称 -2. **日期范围** (`date`): 选择交易日期 - -#### 🎯 概念卡片点击 -- **事件**: `CONCEPT_CLICKED` -- **触发时机**: 用户点击概念卡片 -- **属性**: - - `concept_id` - 概念ID - - `concept_name` - 概念名称 - - `change_percent` - 涨跌幅 - - `stock_count` - 股票数量 - - `position` - 在列表中的位置 - - `source` - 固定为 'concept_center_list' - -#### 👀 查看个股 -- **事件**: `CONCEPT_STOCKS_VIEWED` -- **触发时机**: 用户点击"查看个股"按钮 -- **属性**: - - `concept_name` - 概念名称 - - `stock_count` - 股票数量 - - `source` - 固定为 'concept_center' - -#### 🏷️ 概念股票点击 -- **事件**: `CONCEPT_STOCK_CLICKED` -- **触发时机**: 点击概念股票表格中的股票 -- **属性**: - - `stock_code` - 股票代码 - - `stock_name` - 股票名称 - - `concept_name` - 所属概念 - - `source` - 固定为 'concept_center_stock_table' - -#### 📊 历史时间轴查看 -- **事件**: `CONCEPT_TIMELINE_VIEWED` -- **触发时机**: 用户点击"历史时间轴"按钮 -- **属性**: - - `concept_id` - 概念ID - - `concept_name` - 概念名称 - - `source` - 固定为 'concept_center' - -#### 📄 翻页追踪 -- **事件**: `NEWS_LIST_VIEWED` -- **触发时机**: 用户翻页 -- **属性**: - - `page` - 页码 - - `filters` - 当前筛选条件 - - `sort` - 排序方式 - - `has_query` - 是否有搜索词 - - `date` - 日期 - - `context` - 固定为 'concept_center' - -#### 🔄 视图模式切换 -- **事件**: `VIEW_MODE_CHANGED` -- **触发时机**: 用户切换网格/列表视图 -- **属性**: - - `view_mode` - 新视图模式(grid/list) - - `previous_mode` - 之前的模式 - - `context` - 固定为 'concept_center' - ---- - -### 4. Company 页面(公司详情/个股罗盘) - -**已实施的追踪事件**: - -#### 📄 页面浏览 -- **事件**: `COMPANY_PAGE_VIEWED` -- **触发时机**: 页面加载 -- **属性**: - - `timestamp` - 访问时间 - - `stock_code` - 当前查看的股票代码 - -#### 🔍 股票搜索 -- **事件**: `STOCK_SEARCHED` -- **触发时机**: 用户输入股票代码并查询 -- **属性**: - - `query` - 搜索的股票代码 - - `stock_code` - 股票代码 - - `previous_stock_code` - 之前查看的股票代码 - - `context` - 固定为 'company_page' - -#### 🔄 Tab 切换 -- **事件**: `TAB_CHANGED` -- **触发时机**: 用户切换不同的 Tab -- **属性**: - - `tab_index` - Tab 索引(0-3) - - `tab_name` - Tab 名称(公司概览/股票行情/财务全景/盈利预测) - - `previous_tab_index` - 之前的 Tab 索引 - - `stock_code` - 当前股票代码 - - `context` - 固定为 'company_page' - -**支持的 Tab**: -1. **公司概览** (index 0): 公司基本信息 -2. **股票行情** (index 1): 实时行情数据 -3. **财务全景** (index 2): 财务报表分析 -4. **盈利预测** (index 3): 盈利预测数据 - -#### ⭐ 自选股管理 -- **事件**: `WATCHLIST_ADDED` / `WATCHLIST_REMOVED` -- **触发时机**: 用户添加/移除自选股 -- **属性**: - - `stock_code` - 股票代码 - - `source` - 固定为 'company_page' - ---- - -### 5. Community 页面(新闻催化分析) - -**已实施的追踪事件**: - -#### 📄 页面浏览 -- **事件**: `COMMUNITY_PAGE_VIEWED` -- **触发时机**: 页面加载 -- **属性**: - - `timestamp` - 访问时间 - - `has_hot_events` - 是否有热点事件 - - `has_keywords` - 是否有热门关键词 - -#### 🔍 搜索追踪 -- **事件**: `SEARCH_QUERY_SUBMITTED` -- **触发时机**: 用户输入搜索关键词 -- **属性**: - - `query` - 搜索关键词 - - `category` - 分类(固定为 'news') - - `previous_query` - 上一次搜索词 - -#### 🎚️ 筛选追踪 -- **事件**: `SEARCH_FILTER_APPLIED` -- **触发时机**: 用户更改筛选条件 -- **属性**: - - `filter_type` - 筛选类型(sort/importance/date_range/industry) - - `filter_value` - 筛选值 - - `previous_value` - 上一次的值 - -**支持的筛选类型**: -1. **排序** (`sort`): 最新/最热/重要性 -2. **重要性** (`importance`): 全部/高/中/低 -3. **时间范围** (`date_range`): 今天/近7天/近30天 -4. **行业** (`industry`): 各行业代码 - -#### 🗞️ 新闻点击追踪 -- **事件**: `NEWS_ARTICLE_CLICKED` -- **触发时机**: 用户点击新闻事件 -- **属性**: - - `event_id` - 事件ID - - `event_title` - 事件标题 - - `importance` - 重要性等级 - - `source` - 来源(固定为 'community_page') - - `has_stocks` - 是否包含相关股票 - - `has_concepts` - 是否包含相关概念 - -#### 📖 详情查看追踪 -- **事件**: `NEWS_DETAIL_OPENED` -- **触发时机**: 用户点击"查看详情" -- **属性**: - - `event_id` - 事件ID - - `source` - 来源(固定为 'community_page') - -#### 📄 翻页追踪 -- **事件**: `NEWS_LIST_VIEWED` -- **触发时机**: 用户翻页 -- **属性**: - - `page` - 页码 - - `filters` - 当前筛选条件 - - `sort` - 排序方式 - - `importance` - 重要性 - - `has_query` - 是否有搜索词 - ---- - -## 🛠️ 实施方式 - -### 方案:Custom Hook 集成(推荐) - -**优势**: -- ✅ 集中管理,易于维护 -- ✅ 自动追踪,无需修改组件 -- ✅ 符合关注点分离原则 -- ✅ 便于测试和调试 - -### 修改的文件 - -#### 0. `src/views/StockOverview/hooks/useStockOverviewEvents.js` ✅ - -**文件已存在**,无需修改。已完整实现个股中心的所有追踪事件。 - -#### 1. `src/views/Concept/hooks/useConceptEvents.js` - -**新建 Hook 文件**: -```javascript -import { usePostHogTrack } from '../../../hooks/usePostHogRedux'; -import { RETENTION_EVENTS } from '../../../lib/constants'; -``` - -**提供的追踪函数**: -- `trackConceptSearched()` - 搜索概念 -- `trackFilterApplied()` - 筛选变化 -- `trackConceptClicked()` - 概念点击 -- `trackConceptStocksViewed()` - 查看个股 -- `trackConceptStockClicked()` - 点击概念股票 -- `trackConceptTimelineViewed()` - 历史时间轴 -- `trackPageChange()` - 翻页 -- `trackViewModeChanged()` - 视图切换 - -#### 2. `src/views/Company/hooks/useCompanyEvents.js` - -**新建 Hook 文件**: -```javascript -import { usePostHogTrack } from '../../../hooks/usePostHogRedux'; -import { RETENTION_EVENTS } from '../../../lib/constants'; -``` - -**提供的追踪函数**: -- `trackStockSearched()` - 股票搜索 -- `trackTabChanged()` - Tab 切换 -- `trackWatchlistAdded()` - 加入自选 -- `trackWatchlistRemoved()` - 移除自选 - -#### 3. `src/views/Company/index.js` - -**添加的导入**: -```javascript -import { useCompanyEvents } from './hooks/useCompanyEvents'; -``` - -**添加的 Hook**: -```javascript -const { - trackStockSearched, - trackTabChanged, - trackWatchlistAdded, - trackWatchlistRemoved, -} = useCompanyEvents({ stockCode }); -``` - -**添加的 State**: -```javascript -const [currentTabIndex, setCurrentTabIndex] = useState(0); -``` - -**修改的函数**: -1. **`handleSearch`**: 追踪股票搜索 -2. **`handleWatchlistToggle`**: 追踪自选股添加/移除 -3. **Tabs `onChange`**: 追踪 Tab 切换 - -#### 4. `src/views/Concept/index.js` - -**添加的导入**: -```javascript -import { useConceptEvents } from './hooks/useConceptEvents'; -``` - -**添加的 Hook**: -```javascript -const { - trackConceptSearched, - trackFilterApplied, - trackConceptClicked, - trackConceptStocksViewed, - trackConceptStockClicked, - trackConceptTimelineViewed, - trackPageChange, - trackViewModeChanged, -} = useConceptEvents({ navigate }); -``` - -**修改的函数**: -1. **`handleSearch`**: 追踪搜索查询 -2. **`handleSortChange`**: 追踪排序变化 -3. **`handleDateChange`**: 追踪日期变化 -4. **`handlePageChange`**: 追踪翻页 -5. **`handleConceptClick`**: 追踪概念点击 -6. **`handleViewStocks`**: 追踪查看个股 -7. **`handleViewContent`**: 追踪历史时间轴 -8. **视图切换按钮**: 追踪网格/列表切换 - -#### 3. `src/views/Home/HomePage.js` - -**添加的导入**: -```javascript -import { usePostHogTrack } from '../../hooks/usePostHogRedux'; -import { ACQUISITION_EVENTS } from '../../lib/constants'; -``` - -**添加的 Hook**: -```javascript -const { track } = usePostHogTrack(); -``` - -**添加的 useEffect**(页面浏览追踪): -```javascript -useEffect(() => { - track(ACQUISITION_EVENTS.LANDING_PAGE_VIEWED, { - timestamp: new Date().toISOString(), - is_authenticated: isAuthenticated, - user_id: user?.id || null, - }); -}, [track, isAuthenticated, user?.id]); -``` - -**修改的函数**: -- **`handleProductClick`**: 从接收 URL 改为接收完整 feature 对象,添加追踪逻辑 - -**修改后的代码**: -```javascript -const handleProductClick = useCallback((feature) => { - // 🎯 PostHog 追踪:功能卡片点击 - track(ACQUISITION_EVENTS.FEATURE_CARD_CLICKED, { - feature_id: feature.id, - feature_title: feature.title, - feature_url: feature.url, - is_featured: feature.featured || false, - link_type: feature.url.startsWith('http') ? 'external' : 'internal', - }); - - // 原有导航逻辑 - if (feature.url.startsWith('http')) { - window.open(feature.url, '_blank'); - } else { - navigate(feature.url); - } -}, [track, navigate]); -``` - -**更新的 onClick 事件**: -```javascript -// 从 -onClick={() => handleProductClick(coreFeatures[0].url)} - -// 改为 -onClick={() => handleProductClick(coreFeatures[0])} -``` - -#### 1. `src/views/Community/hooks/useEventFilters.js` - -**添加的导入**: -```javascript -import { usePostHogTrack } from '../../../hooks/usePostHogRedux'; -import { RETENTION_EVENTS } from '../../../lib/constants'; -``` - -**添加的Hook**: -```javascript -const { track } = usePostHogTrack(); -``` - -**修改的函数**: -1. **`updateFilters`**: 追踪搜索和筛选 -2. **`handlePageChange`**: 追踪翻页 -3. **`handleEventClick`**: 追踪新闻点击 -4. **`handleViewDetail`**: 追踪详情查看 - -#### 2. `src/views/Community/index.js` - -**添加的导入**: -```javascript -import { usePostHogTrack } from '../../hooks/usePostHogRedux'; -import { RETENTION_EVENTS } from '../../lib/constants'; -``` - -**添加的Hook**: -```javascript -const { track } = usePostHogTrack(); -``` - -**添加的useEffect**: -```javascript -useEffect(() => { - track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, { - timestamp: new Date().toISOString(), - has_hot_events: hotEvents && hotEvents.length > 0, - has_keywords: popularKeywords && popularKeywords.length > 0, - }); -}, [track]); -``` - ---- - -## 📊 追踪效果示例 - -### 用户行为路径示例 - -**首页转化路径**: -``` -1. 游客访问首页 - → 触发: LANDING_PAGE_VIEWED - → 属性: { is_authenticated: false, user_id: null } - -2. 点击"新闻中心"功能卡片 - → 触发: FEATURE_CARD_CLICKED - → 属性: { feature_id: "news-catalyst", feature_title: "新闻中心", is_featured: true, link_type: "internal" } - -3. 进入 Community 页面 - → 触发: COMMUNITY_PAGE_VIEWED -``` - -**Community 页面行为路径**: -``` -1. 用户进入 Community 页面 - → 触发: COMMUNITY_PAGE_VIEWED - -2. 用户搜索 "人工智能" - → 触发: SEARCH_QUERY_SUBMITTED - → 属性: { query: "人工智能", category: "news" } - -3. 用户筛选 "重要性:高" - → 触发: SEARCH_FILTER_APPLIED - → 属性: { filter_type: "importance", filter_value: "high" } - -4. 用户点击第一条新闻 - → 触发: NEWS_ARTICLE_CLICKED - → 属性: { event_id: "123", event_title: "...", importance: "high", source: "community_page" } - -5. 用户翻到第2页 - → 触发: NEWS_LIST_VIEWED - → 属性: { page: 2, filters: { sort: "new", importance: "high", has_query: true } } - -6. 用户点击"查看详情" - → 触发: NEWS_DETAIL_OPENED - → 属性: { event_id: "456", source: "community_page" } -``` - ---- - -## 🧪 测试方法 - -### 1. 使用 Redux DevTools - -1. 打开应用:`npm start` -2. 打开浏览器 Redux DevTools -3. 筛选 `posthog/trackEvent` actions -4. 执行各种操作 -5. 查看追踪的事件和属性 - -### 2. 控制台日志 - -开发环境下,PostHog 会自动输出日志: - -``` -📍 Event tracked: Community Page Viewed { timestamp: "...", has_hot_events: true } -📍 Event tracked: Search Query Submitted { query: "人工智能", category: "news" } -📍 Event tracked: Search Filter Applied { filter_type: "importance", filter_value: "high" } -``` - -### 3. PostHog Dashboard - -1. 登录 PostHog 后台 -2. 查看 "Events" 页面 -3. 筛选 Community 相关事件: - - `Community Page Viewed` - - `Search Query Submitted` - - `Search Filter Applied` - - `News Article Clicked` - - `News List Viewed` - ---- - -## 📈 数据分析建议 - -### 1. 搜索行为分析 - -**问题**: 用户最常搜索什么? - -**方法**: -- 筛选 `SEARCH_QUERY_SUBMITTED` 事件 -- 按 `query` 属性分组 -- 查看 Top 关键词 - -### 2. 筛选偏好分析 - -**问题**: 用户更喜欢什么排序方式? - -**方法**: -- 筛选 `SEARCH_FILTER_APPLIED` 事件 -- 按 `filter_type: "sort"` 筛选 -- 按 `filter_value` 分组统计 - -### 3. 新闻热度分析 - -**问题**: 哪些新闻最受欢迎? - -**方法**: -- 筛选 `NEWS_ARTICLE_CLICKED` 事件 -- 按 `event_id` 分组 -- 统计点击次数 - -### 4. 用户旅程分析 - -**问题**: 用户从搜索到点击的转化率? - -**方法**: -- 创建漏斗: - 1. `COMMUNITY_PAGE_VIEWED` - 2. `SEARCH_QUERY_SUBMITTED` - 3. `NEWS_ARTICLE_CLICKED` -- 分析每一步的流失率 - ---- - -## 🔧 扩展计划 - -### 下一步:其他页面追踪 - -按优先级排序: - -1. **Concept(概念中心)** ⭐⭐⭐ - - 搜索概念 - - 点击概念卡片 - - 查看概念详情 - - 点击概念内股票 - -2. **StockOverview(个股中心)** ⭐⭐⭐ - - 搜索股票 - - 点击股票卡片 - - 查看股票详情 - - 切换 Tab - -3. **LimitAnalyse(涨停分析)** ⭐⭐ - - 进入页面 - - 点击涨停板块 - - 展开板块详情 - - 点击涨停个股 - -4. **TradingSimulation(模拟盘)** ⭐⭐ - - 进入模拟盘 - - 下单操作 - - 查看持仓 - - 查看历史 - -5. **Company(公司详情)** ⭐ - - 查看公司概览 - - 查看财务全景 - - 查看盈利预测 - - Tab 切换 - ---- - -## 💡 最佳实践 - -### 1. 属性命名规范 - -- 使用 **snake_case** 命名(与 PostHog 推荐一致) -- 属性名要 **描述性强**,易于理解 -- 使用 **布尔值** 表示是/否(has_xxx, is_xxx) -- 使用 **枚举值** 表示类别(filter_type: "sort") - -### 2. 事件追踪原则 - -- **追踪用户意图**,而不仅仅是点击 -- **添加上下文**,帮助分析(previous_value, source) -- **保持一致性**,相似事件使用相似属性 -- **避免敏感信息**,不追踪用户隐私数据 - -### 3. 性能优化 - -- 使用 **`usePostHogTrack`** 而不是 `usePostHogRedux` - - 更轻量,只订阅追踪功能 - - 避免不必要的重渲染 -- 在 **Custom Hooks** 中集成,而不是每个组件 - - 集中管理,易于维护 - - 减少重复代码 - ---- - -## ⚠️ 注意事项 - -### 1. 依赖管理 - -确保 `useCallback` 的依赖数组包含 `track`: - -```javascript -// ✅ 正确 -const handleClick = useCallback(() => { - track(EVENT_NAME, { ... }); -}, [track]); - -// ❌ 错误(缺少 track) -const handleClick = useCallback(() => { - track(EVENT_NAME, { ... }); -}, []); -``` - -### 2. 事件去重 - -避免重复追踪相同事件: - -```javascript -// ✅ 正确(只在值变化时追踪) -if (newFilters.sort !== filters.sort) { - track(SEARCH_FILTER_APPLIED, { ... }); -} - -// ❌ 错误(每次都追踪) -track(SEARCH_FILTER_APPLIED, { ... }); -``` - -### 3. 空值处理 - -使用安全的属性访问: - -```javascript -// ✅ 正确 -has_stocks: !!(event.related_stocks && event.related_stocks.length > 0) - -// ❌ 错误(可能报错) -has_stocks: event.related_stocks.length > 0 -``` - ---- - -## 📚 参考资料 - -- **PostHog Events 文档**: https://posthog.com/docs/data/events -- **PostHog Properties 文档**: https://posthog.com/docs/data/properties -- **Redux PostHog 集成**: `POSTHOG_REDUX_INTEGRATION.md` -- **事件常量定义**: `src/lib/constants.js` - ---- - -## 🎉 总结 - -### 已实现的功能 - -- ✅ Home 页面追踪(2个事件) -- ✅ StockOverview 页面完整追踪(10个事件)✨ 已完成 -- ✅ Concept 页面完整追踪(9个事件) -- ✅ Company 页面完整追踪(5个事件) -- ✅ Community 页面完整追踪(7个事件) -- ✅ Custom Hook 集成方案 -- ✅ Redux DevTools 调试支持 -- ✅ 详细的事件属性 - -### 追踪的用户行为 - -**Home 页面**: -1. **页面访问** - 了解流量来源、登录转化率 -2. **功能卡片点击** - 识别最受欢迎的功能 -3. **推荐功能效果** - 分析特色功能(新闻中心)的点击率 - -**StockOverview 页面** ✨: -1. **页面访问** - 了解个股中心流量 -2. **搜索行为** - 股票搜索、搜索结果点击 -3. **概念交互** - 热门概念点击、概念股票标签点击 -4. **热力图交互** - 热力图中股票点击 -5. **数据筛选** - 日期选择变化 -6. **市场统计** - 市场数据查看 - -**Concept 页面**: -1. **页面访问** - 了解概念中心流量 -2. **搜索行为** - 概念搜索、搜索结果数量 -3. **筛选偏好** - 排序方式、日期选择 -4. **概念交互** - 概念点击、位置追踪 -5. **个股查看** - 查看个股、股票点击 -6. **时间轴查看** - 历史时间轴 -7. **翻页行为** - 优化分页逻辑 -8. **视图切换** - 网格/列表偏好 - -**Company 页面**: -1. **页面访问** - 了解公司详情页流量 -2. **股票搜索** - 用户查询哪些股票 -3. **Tab 切换** - 用户最关注哪个 Tab(概览/行情/财务/预测) -4. **自选股管理** - 自选股添加/移除行为 -5. **股票切换** - 分析用户查看股票的路径 - -**Community 页面**: -1. **页面访问** - 了解流量来源 -2. **搜索行为** - 了解用户需求 -3. **筛选偏好** - 优化默认设置 -4. **内容点击** - 识别热门内容 -5. **详情查看** - 分析用户兴趣 -6. **翻页行为** - 优化分页逻辑 - -### 下一步计划 - -1. ~~在关键页面实施追踪(Home, StockOverview, Concept, Company, Community)~~ ✅ 已完成 -2. **下一步**:其他页面追踪 - - LimitAnalyse(涨停分析)⭐⭐ - - TradingSimulation(模拟盘)⭐⭐ -3. 创建 PostHog Dashboard 和 Insights -4. 设置用户行为漏斗分析 -5. 配置 Feature Flags 进行 A/B 测试 - ---- - -**Home, StockOverview, Concept, Company, Community 页面追踪全部完成!** 🚀 - -现在你可以在 PostHog 后台看到完整的用户行为数据: -- **首页** → **个股中心/概念中心/公司详情/新闻中心** 的完整转化路径 -- **搜索行为**、**筛选偏好**、**内容点击** 的详细数据 -- **Tab 切换**、**视图切换**、**翻页行为** 的用户习惯分析 -- **自选股管理** 的用户行为追踪 - -共追踪 **33个事件**,覆盖 **5个核心页面**。 diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 00000000..32e4fda1 Binary files /dev/null and b/public/logo.png differ diff --git a/public/og-image.jpg b/public/og-image.jpg new file mode 100644 index 00000000..cf500b1a Binary files /dev/null and b/public/og-image.jpg differ diff --git a/src/bytedesk-integration/App.jsx.example b/src/bytedesk-integration/App.jsx.example deleted file mode 100644 index b7bccc9d..00000000 --- a/src/bytedesk-integration/App.jsx.example +++ /dev/null @@ -1,237 +0,0 @@ -/** - * 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/config/bytedesk.config.js b/src/bytedesk-integration/config/bytedesk.config.js index 2a433f4b..727123ff 100644 --- a/src/bytedesk-integration/config/bytedesk.config.js +++ b/src/bytedesk-integration/config/bytedesk.config.js @@ -1,27 +1,10 @@ /** * Bytedesk客服配置文件 * 通过代理访问 Bytedesk 服务器(解决 HTTPS 混合内容问题) - * - * 环境变量配置(.env文件): - * REACT_APP_BYTEDESK_ORG=df_org_uid - * REACT_APP_BYTEDESK_SID=df_wg_uid - * - * 架构说明: - * - iframe 使用完整域名:https://valuefrontier.cn/bytedesk/chat/ - * - 使用 HTTPS 协议,解决生产环境 Mixed Content 错误 - * - 本地:CRACO 代理 /bytedesk → valuefrontier.cn/bytedesk - * - 生产:前端 Nginx 代理 /bytedesk → 43.143.189.195 - * - baseUrl 保持官方 CDN(用于加载 SDK 外部模块) - * - * ⚠️ 注意:需要前端 Nginx 配置 /bytedesk/ 代理规则 - */ - -// 从环境变量读取配置 -const BYTEDESK_ORG = process.env.REACT_APP_BYTEDESK_ORG || 'df_org_uid'; -const BYTEDESK_SID = process.env.REACT_APP_BYTEDESK_SID || 'df_wg_uid'; - -/** - * Bytedesk客服基础配置 + - iframe 使用完整域名:https://valuefrontier.cn/bytedesk/chat/ + - 使用 HTTPS 协议,解决生产环境 Mixed Content 错误 + - 生产:前端 Nginx 代理 /bytedesk → 43.143.189.195 + - baseUrl 保持官方 CDN(用于加载 SDK 外部模块) */ export const bytedeskConfig = { // API服务地址(如果 SDK 需要调用 API) @@ -61,9 +44,9 @@ export const bytedeskConfig = { // 聊天配置(必需) chatConfig: { - org: BYTEDESK_ORG, // 组织ID + org: df_org_uid, // 组织ID t: '1', // 类型: 1=人工客服, 2=机器人 - sid: BYTEDESK_SID, // 工作组ID + sid: df_wg_uid, // 工作组ID }, }; @@ -111,45 +94,8 @@ export const getBytedeskConfigWithUser = (user) => { 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, }; diff --git a/src/components/GlobalComponents.js b/src/components/GlobalComponents.js index f6231c23..8ca4e954 100644 --- a/src/components/GlobalComponents.js +++ b/src/components/GlobalComponents.js @@ -14,7 +14,7 @@ import ScrollToTop from './ScrollToTop'; // Bytedesk客服组件 import BytedeskWidget from '../bytedesk-integration/components/BytedeskWidget'; -import { getBytedeskConfig, shouldShowCustomerService } from '../bytedesk-integration/config/bytedesk.config'; +import { getBytedeskConfig } from '../bytedesk-integration/config/bytedesk.config'; /** * ConnectionStatusBar 包装组件 @@ -74,7 +74,6 @@ function ConnectionStatusBarWrapper() { */ export function GlobalComponents() { const location = useLocation(); - const showBytedesk = shouldShowCustomerService(location.pathname); return ( <> @@ -91,12 +90,10 @@ export function GlobalComponents() { {/* Bytedesk在线客服 - 根据路径条件性显示 */} - {showBytedesk && ( - - )} + ); } diff --git a/src/lib/posthog.js b/src/lib/posthog.js index 5650b50f..3176a5cd 100644 --- a/src/lib/posthog.js +++ b/src/lib/posthog.js @@ -10,6 +10,11 @@ let isInitialized = false; * Should be called once when the app starts */ export const initPostHog = () => { + // 开发环境禁用 PostHog(减少日志噪音,仅生产环境启用) + if (process.env.NODE_ENV === 'development') { + return; + } + // 防止重复初始化 if (isInitializing || isInitialized) { console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用'); @@ -33,79 +38,68 @@ export const initPostHog = () => { posthog.init(apiKey, { api_host: apiHost, - // Pageview tracking - auto-capture for DAU/MAU analytics - capture_pageview: true, // Auto-capture all page views (required for DAU tracking) - capture_pageleave: true, // Auto-capture when user leaves page + // 📄 页面浏览追踪 + capture_pageview: true, // 自动捕获页面浏览事件 + capture_pageleave: true, // 自动捕获用户离开页面事件 - // Session Recording Configuration + // 📹 会话录制配置(Session Recording) session_recording: { enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true', - // Privacy: Mask sensitive input fields + // 🔒 隐私保护:遮蔽敏感输入字段(录制时会自动打码) maskInputOptions: { - password: true, - email: true, - phone: true, - 'data-sensitive': true, // Custom attribute for sensitive fields + password: true, // 遮蔽密码输入框 + email: true, // 遮蔽邮箱输入框 + phone: true, // 遮蔽手机号输入框 + 'data-sensitive': true, // 遮蔽带有 data-sensitive 属性的字段(可在 HTML 中自定义) }, - // Record canvas for charts/graphs + // 📊 录制 Canvas 画布内容(用于记录图表、图形等可视化内容) recordCanvas: true, - // Network payload capture (useful for debugging API issues) + // 🌐 网络请求数据捕获(用于调试 API 问题) networkPayloadCapture: { - recordHeaders: true, - recordBody: true, - // Don't record sensitive endpoints + recordHeaders: true, // 捕获请求头 + recordBody: true, // 捕获请求体 + // 🚫 敏感接口黑名单(不记录以下接口的数据) urlBlocklist: [ - '/api/auth/session', - '/api/auth/login', - '/api/auth/register', - '/api/payment', + '/api/auth/session', // 会话接口 + '/api/auth/login', // 登录接口 + '/api/auth/register', // 注册接口 + '/api/payment', // 支付接口 ], }, }, - // Performance optimization - batch_size: 10, // Send events in batches of 10 - batch_interval_ms: 3000, // Or every 3 seconds + // ⚡ 性能优化:批量发送事件 + batch_size: 10, // 每 10 个事件发送一次 + batch_interval_ms: 3000, // 或每 3 秒发送一次(两个条件满足其一即发送) - // Privacy settings - respect_dnt: true, // Respect Do Not Track browser setting - persistence: 'localStorage+cookie', // Use both for reliability + // 🔐 隐私设置 + respect_dnt: true, // 尊重浏览器的"禁止追踪"(Do Not Track)设置 + persistence: 'localStorage+cookie', // 同时使用 localStorage 和 Cookie 存储(提高可靠性) - // Feature flags (for A/B testing) + // 🚩 功能开关(Feature Flags)- 用于 A/B 测试和灰度发布 bootstrap: { - featureFlags: {}, + featureFlags: {}, // 初始功能开关配置(可从服务端动态加载) }, - // Autocapture settings + // 🖱️ 自动捕获设置(Autocapture) autocapture: { - // Automatically capture clicks on buttons, links, etc. + // 自动捕获用户交互事件(点击、提交、修改等) dom_event_allowlist: ['click', 'submit', 'change'], - // Capture additional element properties - capture_copied_text: false, // Don't capture copied text (privacy) - }, - - // Development debugging - loaded: (posthogInstance) => { - if (process.env.NODE_ENV === 'development') { - console.log('✅ PostHog initialized successfully'); - // posthogInstance.debug(); // 已关闭:减少控制台日志噪音 - } + // 捕获额外的元素属性 + capture_copied_text: false, // 不捕获用户复制的文本(隐私保护) }, }); isInitialized = true; - console.log('📊 PostHog Analytics initialized'); } catch (error) { // 忽略 AbortError(通常由热重载或快速导航引起) if (error.name === 'AbortError') { - console.log('⚠️ PostHog 初始化请求被中断(可能是热重载),这是正常的'); return; } - console.error('❌ PostHog initialization failed:', error); } finally { isInitializing = false; } @@ -142,8 +136,6 @@ export const identifyUser = (userId, userProperties = {}) => { last_login: new Date().toISOString(), ...userProperties, }); - - // console.log('👤 User identified:', userId); // 已关闭:减少日志 } catch (error) { console.error('❌ User identification failed:', error); } @@ -158,7 +150,6 @@ export const identifyUser = (userId, userProperties = {}) => { export const setUserProperties = (properties) => { try { posthog.people.set(properties); - // console.log('📝 User properties updated'); // 已关闭:减少日志 } catch (error) { console.error('❌ Failed to update user properties:', error); } @@ -176,10 +167,6 @@ export const trackEvent = (eventName, properties = {}) => { ...properties, timestamp: new Date().toISOString(), }); - - // if (process.env.NODE_ENV === 'development') { - // console.log('📍 Event tracked:', eventName, properties); - // } // 已关闭:减少日志 } catch (error) { console.error('❌ Event tracking failed:', error); } @@ -225,9 +212,6 @@ export const trackPageView = (pagePath, properties = {}) => { ...properties, }); - // if (process.env.NODE_ENV === 'development') { - // console.log('📄 Page view tracked:', pagePath); - // } // 已关闭:减少日志 } catch (error) { console.error('❌ Page view tracking failed:', error); } @@ -240,7 +224,6 @@ export const trackPageView = (pagePath, properties = {}) => { export const resetUser = () => { try { posthog.reset(); - // console.log('🔄 User session reset'); // 已关闭:减少日志 } catch (error) { console.error('❌ Session reset failed:', error); } @@ -252,7 +235,6 @@ export const resetUser = () => { export const optOut = () => { try { posthog.opt_out_capturing(); - // console.log('🚫 User opted out of tracking'); // 已关闭:减少日志 } catch (error) { console.error('❌ Opt-out failed:', error); } @@ -264,7 +246,6 @@ export const optOut = () => { export const optIn = () => { try { posthog.opt_in_capturing(); - // console.log('✅ User opted in to tracking'); // 已关闭:减少日志 } catch (error) { console.error('❌ Opt-in failed:', error); }