Merge branch 'feature_2025/251117_pref' of https://git.valuefrontier.cn/vf/vf_react into feature_2025/251117_pref
This commit is contained in:
@@ -18,10 +18,3 @@ REACT_APP_ENABLE_MOCK=false
|
|||||||
|
|
||||||
# 开发环境标识
|
# 开发环境标识
|
||||||
REACT_APP_ENV=development
|
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
|
|
||||||
|
|||||||
11
.env.mock
11
.env.mock
@@ -35,14 +35,3 @@ REACT_APP_ENABLE_MOCK=true
|
|||||||
|
|
||||||
# Mock 环境标识
|
# Mock 环境标识
|
||||||
REACT_APP_ENV=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
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
# ========================================
|
# ========================================
|
||||||
# 生产环境配置
|
# 生产环境配置
|
||||||
# ========================================
|
# ========================================
|
||||||
# 使用方式: npm run build
|
|
||||||
#
|
|
||||||
# 工作原理:
|
|
||||||
# 1. 此文件专门用于生产环境构建
|
|
||||||
# 2. 构建时会将环境变量嵌入到打包文件中
|
|
||||||
# 3. 确保 PostHog 等服务使用正确的生产配置
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
# 环境标识
|
# 环境标识
|
||||||
REACT_APP_ENV=production
|
REACT_APP_ENV=production
|
||||||
@@ -17,13 +10,8 @@ NODE_ENV=production
|
|||||||
REACT_APP_ENABLE_MOCK=false
|
REACT_APP_ENABLE_MOCK=false
|
||||||
|
|
||||||
# 🔧 调试模式(生产环境临时调试用)
|
# 🔧 调试模式(生产环境临时调试用)
|
||||||
# 开启后会在全局暴露 window.__DEBUG__ 和 window.__TEST_NOTIFICATION__ 调试 API
|
# 开启后会在全局暴露 window.__DEBUG__
|
||||||
# ⚠️ 警告: 调试模式会记录所有 API 请求/响应,调试完成后请立即关闭!
|
REACT_APP_ENABLE_DEBUG=false
|
||||||
# 使用方法:
|
|
||||||
# 1. 设置为 true 并重新构建
|
|
||||||
# 2. 在浏览器控制台使用 window.__DEBUG__.help() 查看命令
|
|
||||||
# 3. 调试完成后设置为 false 并重新构建
|
|
||||||
REACT_APP_ENABLE_DEBUG=true
|
|
||||||
|
|
||||||
# 后端 API 地址(生产环境)
|
# 后端 API 地址(生产环境)
|
||||||
REACT_APP_API_URL=http://49.232.185.254:5001
|
REACT_APP_API_URL=http://49.232.185.254:5001
|
||||||
@@ -49,20 +37,3 @@ TSC_COMPILE_ON_ERROR=true
|
|||||||
IMAGE_INLINE_SIZE_LIMIT=10000
|
IMAGE_INLINE_SIZE_LIMIT=10000
|
||||||
# Node.js 内存限制(适用于大型项目)
|
# Node.js 内存限制(适用于大型项目)
|
||||||
NODE_OPTIONS=--max_old_space_size=4096
|
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
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -48,6 +48,8 @@ Thumbs.db
|
|||||||
*.md
|
*.md
|
||||||
!README.md
|
!README.md
|
||||||
!CLAUDE.md
|
!CLAUDE.md
|
||||||
!docs/**/*.md
|
|
||||||
|
# 忽略 docs 目录(开发文档不提交到 Git)
|
||||||
|
docs/
|
||||||
|
|
||||||
src/assets/img/original-backup/
|
src/assets/img/original-backup/
|
||||||
|
|||||||
918
docs/BYTEDESK_INTEGRATION_GUIDE.md
Normal file
918
docs/BYTEDESK_INTEGRATION_GUIDE.md
Normal file
@@ -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 (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的原有代码保持不变 */}
|
||||||
|
|
||||||
|
{/* 3. 添加客服Widget(在return的JSX最后添加) */}
|
||||||
|
<BytedeskWidget
|
||||||
|
config={bytedeskConfig}
|
||||||
|
autoLoad={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的页面内容 */}
|
||||||
|
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式二: 按页面显示
|
||||||
|
|
||||||
|
**适用场景**: 只在特定页面显示客服(如排除登录页、支付页)
|
||||||
|
|
||||||
|
```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 (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的页面内容 */}
|
||||||
|
|
||||||
|
{showBytedesk && (
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
自定义页面规则(修改`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 (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的页面内容 */}
|
||||||
|
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
用户信息格式:
|
||||||
|
```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 (
|
||||||
|
<div className="App">
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 条件性显示
|
||||||
|
|
||||||
|
根据用户登录状态或角色显示客服:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
const { user } = useContext(AuthContext);
|
||||||
|
const bytedeskConfig = getBytedeskConfig();
|
||||||
|
|
||||||
|
// 只为普通用户显示客服(管理员不显示)
|
||||||
|
const showBytedesk = user && user.role === 'customer';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
{showBytedesk && (
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 事件回调
|
||||||
|
|
||||||
|
监听客服系统的加载状态:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
const bytedeskConfig = getBytedeskConfig();
|
||||||
|
|
||||||
|
const handleLoad = (bytedesk) => {
|
||||||
|
console.log('客服系统加载成功', bytedesk);
|
||||||
|
// 可以在这里执行自定义逻辑
|
||||||
|
// 例如: 发送统计事件
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error) => {
|
||||||
|
console.error('客服系统加载失败', error);
|
||||||
|
// 可以在这里显示降级方案
|
||||||
|
// 例如: 显示备用联系方式
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<BytedeskWidget
|
||||||
|
config={bytedeskConfig}
|
||||||
|
autoLoad={true}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
onError={handleError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 自定义触发按钮
|
||||||
|
|
||||||
|
隐藏默认图标,使用自定义按钮:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [showBytedesk, setShowBytedesk] = useState(false);
|
||||||
|
|
||||||
|
// 隐藏默认图标
|
||||||
|
const bytedeskConfig = {
|
||||||
|
...getBytedeskConfig(),
|
||||||
|
bubbleConfig: {
|
||||||
|
show: false, // 隐藏默认图标
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
{/* 自定义按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowBytedesk(true)}
|
||||||
|
className="custom-service-btn"
|
||||||
|
>
|
||||||
|
联系客服
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showBytedesk && (
|
||||||
|
<BytedeskWidget config={bytedeskConfig} autoLoad={true} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的页面内容 */}
|
||||||
|
|
||||||
|
{loadBytedesk && (
|
||||||
|
<BytedeskWidget config={getBytedeskConfig()} autoLoad={true} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Lazy Import
|
||||||
|
|
||||||
|
使用React.lazy延迟导入组件:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
|
const BytedeskWidget = lazy(() => import('./bytedesk-integration/components/BytedeskWidget'));
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
{/* 您的页面内容 */}
|
||||||
|
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<BytedeskWidget config={getBytedeskConfig()} autoLoad={true} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="
|
||||||
|
default-src 'self';
|
||||||
|
script-src 'self' https://www.weiyuai.cn;
|
||||||
|
connect-src 'self' http://43.143.189.195;
|
||||||
|
img-src 'self' data: http://43.143.189.195;
|
||||||
|
"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 # 本手册(参考)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**祝您集成顺利!**
|
||||||
|
|
||||||
|
如有任何问题,请随时联系技术支持。
|
||||||
@@ -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个核心页面**。
|
|
||||||
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
public/og-image.jpg
Normal file
BIN
public/og-image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -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 (
|
|
||||||
<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已在后端配置(允许您的前端域名)
|
|
||||||
* - 在管理后台配置正确的工作组ID(sid)
|
|
||||||
*/
|
|
||||||
@@ -1,27 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Bytedesk客服配置文件
|
* Bytedesk客服配置文件
|
||||||
* 通过代理访问 Bytedesk 服务器(解决 HTTPS 混合内容问题)
|
* 通过代理访问 Bytedesk 服务器(解决 HTTPS 混合内容问题)
|
||||||
*
|
- iframe 使用完整域名:https://valuefrontier.cn/bytedesk/chat/
|
||||||
* 环境变量配置(.env文件):
|
- 使用 HTTPS 协议,解决生产环境 Mixed Content 错误
|
||||||
* REACT_APP_BYTEDESK_ORG=df_org_uid
|
- 生产:前端 Nginx 代理 /bytedesk → 43.143.189.195
|
||||||
* REACT_APP_BYTEDESK_SID=df_wg_uid
|
- baseUrl 保持官方 CDN(用于加载 SDK 外部模块)
|
||||||
*
|
|
||||||
* 架构说明:
|
|
||||||
* - 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客服基础配置
|
|
||||||
*/
|
*/
|
||||||
export const bytedeskConfig = {
|
export const bytedeskConfig = {
|
||||||
// API服务地址(如果 SDK 需要调用 API)
|
// API服务地址(如果 SDK 需要调用 API)
|
||||||
@@ -61,9 +44,9 @@ export const bytedeskConfig = {
|
|||||||
|
|
||||||
// 聊天配置(必需)
|
// 聊天配置(必需)
|
||||||
chatConfig: {
|
chatConfig: {
|
||||||
org: BYTEDESK_ORG, // 组织ID
|
org: df_org_uid, // 组织ID
|
||||||
t: '1', // 类型: 1=人工客服, 2=机器人
|
t: '1', // 类型: 1=人工客服, 2=机器人
|
||||||
sid: BYTEDESK_SID, // 工作组ID
|
sid: df_wg_uid, // 工作组ID
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,45 +94,8 @@ export const getBytedeskConfigWithUser = (user) => {
|
|||||||
return config;
|
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 {
|
export default {
|
||||||
bytedeskConfig,
|
bytedeskConfig,
|
||||||
getBytedeskConfig,
|
getBytedeskConfig,
|
||||||
getBytedeskConfigWithUser,
|
getBytedeskConfigWithUser,
|
||||||
shouldShowCustomerService,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import ScrollToTop from './ScrollToTop';
|
|||||||
|
|
||||||
// Bytedesk客服组件
|
// Bytedesk客服组件
|
||||||
import BytedeskWidget from '../bytedesk-integration/components/BytedeskWidget';
|
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 包装组件
|
* ConnectionStatusBar 包装组件
|
||||||
@@ -74,7 +74,6 @@ function ConnectionStatusBarWrapper() {
|
|||||||
*/
|
*/
|
||||||
export function GlobalComponents() {
|
export function GlobalComponents() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const showBytedesk = shouldShowCustomerService(location.pathname);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -91,12 +90,10 @@ export function GlobalComponents() {
|
|||||||
<NotificationContainer />
|
<NotificationContainer />
|
||||||
|
|
||||||
{/* Bytedesk在线客服 - 根据路径条件性显示 */}
|
{/* Bytedesk在线客服 - 根据路径条件性显示 */}
|
||||||
{showBytedesk && (
|
|
||||||
<BytedeskWidget
|
<BytedeskWidget
|
||||||
config={getBytedeskConfig()}
|
config={getBytedeskConfig()}
|
||||||
autoLoad={true}
|
autoLoad={true}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ let isInitialized = false;
|
|||||||
* Should be called once when the app starts
|
* Should be called once when the app starts
|
||||||
*/
|
*/
|
||||||
export const initPostHog = () => {
|
export const initPostHog = () => {
|
||||||
|
// 开发环境禁用 PostHog(减少日志噪音,仅生产环境启用)
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 防止重复初始化
|
// 防止重复初始化
|
||||||
if (isInitializing || isInitialized) {
|
if (isInitializing || isInitialized) {
|
||||||
console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用');
|
console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用');
|
||||||
@@ -33,79 +38,68 @@ export const initPostHog = () => {
|
|||||||
posthog.init(apiKey, {
|
posthog.init(apiKey, {
|
||||||
api_host: apiHost,
|
api_host: apiHost,
|
||||||
|
|
||||||
// Pageview tracking - auto-capture for DAU/MAU analytics
|
// 📄 页面浏览追踪
|
||||||
capture_pageview: true, // Auto-capture all page views (required for DAU tracking)
|
capture_pageview: true, // 自动捕获页面浏览事件
|
||||||
capture_pageleave: true, // Auto-capture when user leaves page
|
capture_pageleave: true, // 自动捕获用户离开页面事件
|
||||||
|
|
||||||
// Session Recording Configuration
|
// 📹 会话录制配置(Session Recording)
|
||||||
session_recording: {
|
session_recording: {
|
||||||
enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true',
|
enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true',
|
||||||
|
|
||||||
// Privacy: Mask sensitive input fields
|
// 🔒 隐私保护:遮蔽敏感输入字段(录制时会自动打码)
|
||||||
maskInputOptions: {
|
maskInputOptions: {
|
||||||
password: true,
|
password: true, // 遮蔽密码输入框
|
||||||
email: true,
|
email: true, // 遮蔽邮箱输入框
|
||||||
phone: true,
|
phone: true, // 遮蔽手机号输入框
|
||||||
'data-sensitive': true, // Custom attribute for sensitive fields
|
'data-sensitive': true, // 遮蔽带有 data-sensitive 属性的字段(可在 HTML 中自定义)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Record canvas for charts/graphs
|
// 📊 录制 Canvas 画布内容(用于记录图表、图形等可视化内容)
|
||||||
recordCanvas: true,
|
recordCanvas: true,
|
||||||
|
|
||||||
// Network payload capture (useful for debugging API issues)
|
// 🌐 网络请求数据捕获(用于调试 API 问题)
|
||||||
networkPayloadCapture: {
|
networkPayloadCapture: {
|
||||||
recordHeaders: true,
|
recordHeaders: true, // 捕获请求头
|
||||||
recordBody: true,
|
recordBody: true, // 捕获请求体
|
||||||
// Don't record sensitive endpoints
|
// 🚫 敏感接口黑名单(不记录以下接口的数据)
|
||||||
urlBlocklist: [
|
urlBlocklist: [
|
||||||
'/api/auth/session',
|
'/api/auth/session', // 会话接口
|
||||||
'/api/auth/login',
|
'/api/auth/login', // 登录接口
|
||||||
'/api/auth/register',
|
'/api/auth/register', // 注册接口
|
||||||
'/api/payment',
|
'/api/payment', // 支付接口
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Performance optimization
|
// ⚡ 性能优化:批量发送事件
|
||||||
batch_size: 10, // Send events in batches of 10
|
batch_size: 10, // 每 10 个事件发送一次
|
||||||
batch_interval_ms: 3000, // Or every 3 seconds
|
batch_interval_ms: 3000, // 或每 3 秒发送一次(两个条件满足其一即发送)
|
||||||
|
|
||||||
// Privacy settings
|
// 🔐 隐私设置
|
||||||
respect_dnt: true, // Respect Do Not Track browser setting
|
respect_dnt: true, // 尊重浏览器的"禁止追踪"(Do Not Track)设置
|
||||||
persistence: 'localStorage+cookie', // Use both for reliability
|
persistence: 'localStorage+cookie', // 同时使用 localStorage 和 Cookie 存储(提高可靠性)
|
||||||
|
|
||||||
// Feature flags (for A/B testing)
|
// 🚩 功能开关(Feature Flags)- 用于 A/B 测试和灰度发布
|
||||||
bootstrap: {
|
bootstrap: {
|
||||||
featureFlags: {},
|
featureFlags: {}, // 初始功能开关配置(可从服务端动态加载)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Autocapture settings
|
// 🖱️ 自动捕获设置(Autocapture)
|
||||||
autocapture: {
|
autocapture: {
|
||||||
// Automatically capture clicks on buttons, links, etc.
|
// 自动捕获用户交互事件(点击、提交、修改等)
|
||||||
dom_event_allowlist: ['click', 'submit', 'change'],
|
dom_event_allowlist: ['click', 'submit', 'change'],
|
||||||
|
|
||||||
// Capture additional element properties
|
// 捕获额外的元素属性
|
||||||
capture_copied_text: false, // Don't capture copied text (privacy)
|
capture_copied_text: false, // 不捕获用户复制的文本(隐私保护)
|
||||||
},
|
|
||||||
|
|
||||||
// Development debugging
|
|
||||||
loaded: (posthogInstance) => {
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
console.log('✅ PostHog initialized successfully');
|
|
||||||
// posthogInstance.debug(); // 已关闭:减少控制台日志噪音
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
console.log('📊 PostHog Analytics initialized');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 忽略 AbortError(通常由热重载或快速导航引起)
|
// 忽略 AbortError(通常由热重载或快速导航引起)
|
||||||
if (error.name === 'AbortError') {
|
if (error.name === 'AbortError') {
|
||||||
console.log('⚠️ PostHog 初始化请求被中断(可能是热重载),这是正常的');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error('❌ PostHog initialization failed:', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
isInitializing = false;
|
isInitializing = false;
|
||||||
}
|
}
|
||||||
@@ -142,8 +136,6 @@ export const identifyUser = (userId, userProperties = {}) => {
|
|||||||
last_login: new Date().toISOString(),
|
last_login: new Date().toISOString(),
|
||||||
...userProperties,
|
...userProperties,
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log('👤 User identified:', userId); // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ User identification failed:', error);
|
console.error('❌ User identification failed:', error);
|
||||||
}
|
}
|
||||||
@@ -158,7 +150,6 @@ export const identifyUser = (userId, userProperties = {}) => {
|
|||||||
export const setUserProperties = (properties) => {
|
export const setUserProperties = (properties) => {
|
||||||
try {
|
try {
|
||||||
posthog.people.set(properties);
|
posthog.people.set(properties);
|
||||||
// console.log('📝 User properties updated'); // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to update user properties:', error);
|
console.error('❌ Failed to update user properties:', error);
|
||||||
}
|
}
|
||||||
@@ -176,10 +167,6 @@ export const trackEvent = (eventName, properties = {}) => {
|
|||||||
...properties,
|
...properties,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (process.env.NODE_ENV === 'development') {
|
|
||||||
// console.log('📍 Event tracked:', eventName, properties);
|
|
||||||
// } // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Event tracking failed:', error);
|
console.error('❌ Event tracking failed:', error);
|
||||||
}
|
}
|
||||||
@@ -225,9 +212,6 @@ export const trackPageView = (pagePath, properties = {}) => {
|
|||||||
...properties,
|
...properties,
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (process.env.NODE_ENV === 'development') {
|
|
||||||
// console.log('📄 Page view tracked:', pagePath);
|
|
||||||
// } // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Page view tracking failed:', error);
|
console.error('❌ Page view tracking failed:', error);
|
||||||
}
|
}
|
||||||
@@ -240,7 +224,6 @@ export const trackPageView = (pagePath, properties = {}) => {
|
|||||||
export const resetUser = () => {
|
export const resetUser = () => {
|
||||||
try {
|
try {
|
||||||
posthog.reset();
|
posthog.reset();
|
||||||
// console.log('🔄 User session reset'); // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Session reset failed:', error);
|
console.error('❌ Session reset failed:', error);
|
||||||
}
|
}
|
||||||
@@ -252,7 +235,6 @@ export const resetUser = () => {
|
|||||||
export const optOut = () => {
|
export const optOut = () => {
|
||||||
try {
|
try {
|
||||||
posthog.opt_out_capturing();
|
posthog.opt_out_capturing();
|
||||||
// console.log('🚫 User opted out of tracking'); // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Opt-out failed:', error);
|
console.error('❌ Opt-out failed:', error);
|
||||||
}
|
}
|
||||||
@@ -264,7 +246,6 @@ export const optOut = () => {
|
|||||||
export const optIn = () => {
|
export const optIn = () => {
|
||||||
try {
|
try {
|
||||||
posthog.opt_in_capturing();
|
posthog.opt_in_capturing();
|
||||||
// console.log('✅ User opted in to tracking'); // 已关闭:减少日志
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Opt-in failed:', error);
|
console.error('❌ Opt-in failed:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user