492 lines
10 KiB
Markdown
492 lines
10 KiB
Markdown
# Quick Start: 声明式埋点上报系统
|
|
|
|
**Feature**: 005-declarative-tracking
|
|
**Audience**: 开发者
|
|
**Time**: 5-10 分钟
|
|
|
|
## Overview
|
|
|
|
本指南帮助您快速上手使用声明式埋点系统,从基础的手动埋点到高级的自动埋点功能。
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js 18+
|
|
- 项目已集成 Umami Analytics
|
|
- 了解基本的 React 和 TypeScript
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
### 1. 确认 Umami 已配置
|
|
|
|
检查 `apps/web/index.html` 或应用入口是否有 Umami 脚本:
|
|
|
|
```html
|
|
<!-- 应该已存在 -->
|
|
<script
|
|
defer
|
|
src="https://your-umami-domain/script.js"
|
|
data-website-id="your-website-id"
|
|
></script>
|
|
```
|
|
|
|
### 2. 安装依赖
|
|
|
|
```bash
|
|
# 项目依赖已包含,无需额外安装
|
|
npm install
|
|
```
|
|
|
|
---
|
|
|
|
## Basic Usage (基础埋点)
|
|
|
|
### Step 1: 在 Drawnix 中启用埋点插件
|
|
|
|
修改 `packages/drawnix/src/drawnix.tsx`:
|
|
|
|
```typescript
|
|
import { withTracking } from './plugins/tracking';
|
|
|
|
// 在其他插件后添加 withTracking
|
|
const editor = withMind(
|
|
withDraw(
|
|
withFreehand(
|
|
withTracking(
|
|
// 其他插件...
|
|
)
|
|
)
|
|
)
|
|
);
|
|
```
|
|
|
|
### Step 2: 添加手动埋点属性
|
|
|
|
在任意 React 组件中使用 `track` 属性:
|
|
|
|
```tsx
|
|
// 示例: UnifiedToolbar.tsx
|
|
export const UnifiedToolbar = () => {
|
|
return (
|
|
<div className="toolbar">
|
|
{/* 基础埋点 */}
|
|
<Button track="toolbar_click_pen">
|
|
<PenIcon />
|
|
</Button>
|
|
|
|
{/* 带参数的埋点 */}
|
|
<Button
|
|
track="toolbar_click_shape"
|
|
track-params='{"shape": "rectangle"}'
|
|
>
|
|
<RectIcon />
|
|
</Button>
|
|
|
|
{/* 不埋点 */}
|
|
<Button>
|
|
Normal Button
|
|
</Button>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
### Step 3: 验证埋点
|
|
|
|
1. 启动开发服务器: `npm start`
|
|
2. 打开浏览器控制台(F12)
|
|
3. 点击添加了 `track` 属性的按钮
|
|
4. 查看控制台日志(开发模式下会输出):
|
|
```
|
|
[Tracking] Event tracked: toolbar_click_pen
|
|
[Tracking] Batching event (1/10)
|
|
```
|
|
5. 等待 5 秒或点击 10 个事件后,检查网络请求:
|
|
```
|
|
POST https://your-umami-domain/api/send
|
|
Payload: { name: "toolbar_click_pen", data: { version: "1.2.3", url: "..." } }
|
|
```
|
|
|
|
---
|
|
|
|
## Advanced Usage (高级功能)
|
|
|
|
### 1. 自动埋点模式
|
|
|
|
启用自动埋点,无需手动添加 `track` 属性:
|
|
|
|
```typescript
|
|
// packages/drawnix/src/drawnix.tsx
|
|
const editor = withTracking(
|
|
// ... other plugins
|
|
{
|
|
autoTrack: true // 启用自动埋点
|
|
}
|
|
);
|
|
```
|
|
|
|
**效果**:
|
|
- 所有 `<button>`, `<a>`, `<input type="button">` 等交互元素自动埋点
|
|
- 事件名自动生成,基于元素特征(如按钮文本、ID、aria-label)
|
|
- 排除导航栏、页脚、工具栏等区域
|
|
|
|
**示例**:
|
|
```tsx
|
|
// 无需 track 属性
|
|
<Button id="save-btn">保存</Button>
|
|
|
|
// 自动生成事件名: auto_click_save-btn 或 auto_click_保存
|
|
```
|
|
|
|
### 2. 排除特定元素
|
|
|
|
使用 `data-track-ignore` 属性排除埋点:
|
|
|
|
```tsx
|
|
<nav data-track-ignore>
|
|
<Button>导航按钮</Button> {/* 不会自动埋点 */}
|
|
</nav>
|
|
|
|
<Button data-track-ignore>
|
|
临时不埋点的按钮
|
|
</Button>
|
|
```
|
|
|
|
### 3. 支持其他事件类型
|
|
|
|
除了 click,还支持 hover、focus 等事件:
|
|
|
|
```tsx
|
|
// Hover 埋点
|
|
<Card track-hover="card_hover_features">
|
|
Feature Card
|
|
</Card>
|
|
|
|
// Focus 埋点
|
|
<Input track-focus="input_focus_search" />
|
|
|
|
// 同时支持多种事件
|
|
<Element
|
|
track="element_click"
|
|
track-hover="element_hover"
|
|
track-focus="element_focus"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration (配置)
|
|
|
|
### 全局配置
|
|
|
|
创建 `packages/drawnix/src/config/tracking.config.ts`:
|
|
|
|
```typescript
|
|
import type { TrackConfig } from '../types/tracking.types';
|
|
|
|
export const trackingConfig: Partial<TrackConfig> = {
|
|
autoTrack: false, // 默认关闭自动埋点
|
|
debounceTime: 500, // 防抖 500ms
|
|
logLevel: 'error', // 生产环境只记录错误
|
|
batchConfig: {
|
|
enabled: true,
|
|
batchSize: 10, // 10 个事件批量上报
|
|
batchTimeout: 5000 // 或 5 秒超时
|
|
},
|
|
excludedSelectors: [
|
|
'nav',
|
|
'header',
|
|
'footer',
|
|
'[data-track-ignore]',
|
|
'.no-track' // 自定义排除类
|
|
]
|
|
};
|
|
```
|
|
|
|
应用配置:
|
|
|
|
```typescript
|
|
// drawnix.tsx
|
|
import { trackingConfig } from './config/tracking.config';
|
|
|
|
const editor = withTracking(
|
|
// ... plugins
|
|
trackingConfig
|
|
);
|
|
```
|
|
|
|
### 环境变量配置
|
|
|
|
在 `vite.config.ts` 中注入版本号:
|
|
|
|
```typescript
|
|
// vite.config.ts
|
|
import { defineConfig } from 'vite';
|
|
import packageJson from './package.json';
|
|
|
|
export default defineConfig({
|
|
define: {
|
|
'import.meta.env.VITE_APP_VERSION': JSON.stringify(packageJson.version)
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Testing (测试)
|
|
|
|
### 开发环境测试
|
|
|
|
启用调试模式:
|
|
|
|
```typescript
|
|
const editor = withTracking(
|
|
// ... plugins
|
|
{
|
|
devMode: true, // 启用开发模式
|
|
logLevel: 'debug' // 输出详细日志
|
|
}
|
|
);
|
|
```
|
|
|
|
控制台输出示例:
|
|
```
|
|
[Tracking] Service initialized
|
|
[Tracking] Event captured: button_click_save
|
|
[Tracking] Debounce check passed
|
|
[Tracking] Event queued (1/10)
|
|
[Tracking] Batch timeout started (5s)
|
|
[Tracking] Batch uploading (10 events)
|
|
[Tracking] Upload successful
|
|
```
|
|
|
|
### 单元测试
|
|
|
|
```typescript
|
|
// packages/drawnix/src/services/tracking/__tests__/tracking-service.test.ts
|
|
import { TrackingService } from '../tracking-service';
|
|
|
|
describe('TrackingService', () => {
|
|
let service: TrackingService;
|
|
|
|
beforeEach(() => {
|
|
service = new TrackingService(mockConfig);
|
|
});
|
|
|
|
it('should track event', async () => {
|
|
await service.track('test_event', { param: 'value' });
|
|
|
|
expect(service.getStats().totalEvents).toBe(1);
|
|
});
|
|
|
|
it('should debounce duplicate events', async () => {
|
|
const element = document.createElement('button');
|
|
|
|
await service.track('test_event', {}, element);
|
|
await service.track('test_event', {}, element); // 应被防抖
|
|
|
|
expect(service.getStats().totalEvents).toBe(1);
|
|
});
|
|
});
|
|
```
|
|
|
|
### E2E 测试
|
|
|
|
```typescript
|
|
// tests/e2e/tracking/declarative-tracking.spec.ts
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test('should track button click', async ({ page }) => {
|
|
// 监听网络请求
|
|
const requests: any[] = [];
|
|
page.on('request', request => {
|
|
if (request.url().includes('/api/send')) {
|
|
requests.push(request.postDataJSON());
|
|
}
|
|
});
|
|
|
|
// 访问页面
|
|
await page.goto('http://localhost:7200');
|
|
|
|
// 点击埋点按钮
|
|
await page.click('[track="button_click_save"]');
|
|
|
|
// 等待批量上报(最多 5 秒)
|
|
await page.waitForTimeout(5500);
|
|
|
|
// 验证请求
|
|
expect(requests.length).toBeGreaterThan(0);
|
|
expect(requests[0].name).toBe('button_click_save');
|
|
expect(requests[0].data.version).toBeDefined();
|
|
expect(requests[0].data.url).toContain('localhost:7200');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting (问题排查)
|
|
|
|
### 1. 埋点不生效
|
|
|
|
**症状**: 点击元素后没有上报事件
|
|
|
|
**排查步骤**:
|
|
1. 检查 Umami SDK 是否加载:
|
|
```javascript
|
|
console.log(window.umami); // 应该是一个对象
|
|
```
|
|
2. 检查插件是否启用:
|
|
```typescript
|
|
// drawnix.tsx 中是否调用了 withTracking
|
|
```
|
|
3. 检查元素是否被排除:
|
|
```typescript
|
|
// 元素是否在 nav/header/footer 内?
|
|
// 元素是否有 data-track-ignore 属性?
|
|
```
|
|
4. 检查防抖:
|
|
```typescript
|
|
// 是否在 500ms 内重复点击?
|
|
```
|
|
|
|
### 2. 批量上报不触发
|
|
|
|
**症状**: 事件队列一直累积,不上报
|
|
|
|
**排查步骤**:
|
|
1. 检查批量配置:
|
|
```typescript
|
|
batchConfig: {
|
|
enabled: true // 是否启用?
|
|
}
|
|
```
|
|
2. 检查网络连接:
|
|
```bash
|
|
curl https://your-umami-domain/api/send
|
|
```
|
|
3. 查看控制台错误:
|
|
```javascript
|
|
// 是否有 CORS 错误?
|
|
// 是否有 401/403 认证错误?
|
|
```
|
|
|
|
### 3. 缓存不工作
|
|
|
|
**症状**: 离线时事件丢失,未缓存
|
|
|
|
**排查步骤**:
|
|
1. 检查 IndexedDB 是否可用:
|
|
```javascript
|
|
console.log(window.indexedDB); // 应该存在
|
|
```
|
|
2. 检查 localforage 初始化:
|
|
```typescript
|
|
import localforage from 'localforage';
|
|
const cache = await localforage.getItem('tracking_cache');
|
|
console.log(cache);
|
|
```
|
|
|
|
### 4. 版本号不显示
|
|
|
|
**症状**: Umami 面板中 `version` 字段为空
|
|
|
|
**排查步骤**:
|
|
1. 检查环境变量:
|
|
```javascript
|
|
console.log(import.meta.env.VITE_APP_VERSION);
|
|
```
|
|
2. 检查 vite.config.ts 配置
|
|
3. 重新构建项目:
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices (最佳实践)
|
|
|
|
### 1. 事件命名规范
|
|
|
|
**推荐**:
|
|
```typescript
|
|
// 使用 snake_case,包含动作和对象
|
|
track="button_click_save"
|
|
track="card_hover_feature"
|
|
track="input_focus_search"
|
|
```
|
|
|
|
**不推荐**:
|
|
```typescript
|
|
// 太简短,不明确
|
|
track="click"
|
|
track="save"
|
|
|
|
// 太长,冗余
|
|
track="user_clicked_the_save_button_in_the_toolbar"
|
|
```
|
|
|
|
### 2. 参数设计
|
|
|
|
**推荐**:
|
|
```tsx
|
|
<!-- 有意义的结构化参数 -->
|
|
<Button
|
|
track="toolbar_click_shape"
|
|
track-params='{"shape": "rectangle", "color": "red"}'
|
|
/>
|
|
```
|
|
|
|
**不推荐**:
|
|
```tsx
|
|
<!-- 参数过多或无用 -->
|
|
<Button
|
|
track="click"
|
|
track-params='{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, ...}'
|
|
/>
|
|
```
|
|
|
|
### 3. 性能优化
|
|
|
|
- ✅ 使用批量上报(减少网络请求)
|
|
- ✅ 启用防抖(避免重复上报)
|
|
- ✅ 合理配置排除区域(减少无用埋点)
|
|
- ❌ 不要在高频事件上埋点(如 mousemove)
|
|
|
|
### 4. 隐私保护
|
|
|
|
- ✅ 不在 `track-params` 中包含敏感信息(密码、信用卡号)
|
|
- ✅ 过滤用户输入的文本内容
|
|
- ✅ 遵守 GDPR/CCPA 合规要求
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
- 📖 阅读 [data-model.md](./data-model.md) 了解数据结构
|
|
- 📖 阅读 [contracts/umami-api.md](./contracts/umami-api.md) 了解 API 集成
|
|
- 🛠️ 查看 [tasks.md](./tasks.md) 了解实现细节(待生成)
|
|
- 🧪 运行测试: `npm test packages/drawnix/src/services/tracking`
|
|
|
|
---
|
|
|
|
## FAQ
|
|
|
|
**Q: 自动埋点会影响性能吗?**
|
|
A: 性能开销 <2%,使用事件委托和防抖机制优化。
|
|
|
|
**Q: 可以在生产环境禁用埋点吗?**
|
|
A: 可以,设置 `logLevel: 'silent'` 并在配置中禁用所有埋点。
|
|
|
|
**Q: 支持移动端吗?**
|
|
A: 支持,touch 事件会映射到 click 事件。
|
|
|
|
**Q: 如何自定义事件名生成规则?**
|
|
A: 修改 `tracking-utils.ts` 中的 `generateAutoEventName()` 函数。
|
|
|
|
---
|
|
|
|
**Happy Tracking! 🎉**
|