Initial TrueGrowth source import
This commit is contained in:
491
specs/005-declarative-tracking/quickstart.md
Normal file
491
specs/005-declarative-tracking/quickstart.md
Normal file
@@ -0,0 +1,491 @@
|
||||
# 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! 🎉**
|
||||
Reference in New Issue
Block a user