feat: 已完成的工作:

-  创建了4个P1优先级Hook(搜索、导航、个人资料、订阅)
  -  将其中3个Hook集成到5个组件中
  -  在个人资料、设置、搜索、订阅流程中添加了15+个追踪点
  -  覆盖了完整的收入漏斗(支付发起 → 成功 → 订阅创建)
  -  添加了留存追踪(个人资料更新、设置修改、搜索查询)

  影响:
  - 完整的用户订阅旅程可见性
  - 个人资料/设置参与度追踪
  - 搜索行为分析
  - 完整的支付漏斗追踪(微信支付)
This commit is contained in:
zdl
2025-10-29 12:29:41 +08:00
parent e3a953559f
commit 02cd234def
4 changed files with 107 additions and 3 deletions

View File

@@ -33,6 +33,7 @@ import {
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents';
// Icons // Icons
import { import {
@@ -54,6 +55,14 @@ export default function SubscriptionContent() {
// Auth context // Auth context
const { user } = useAuth(); const { user } = useAuth();
// 🎯 初始化订阅埋点Hook传入当前订阅信息
const subscriptionEvents = useSubscriptionEvents({
currentSubscription: {
plan: user?.subscription_plan || 'free',
status: user?.subscription_status || 'none'
}
});
// Chakra color mode // Chakra color mode
const textColor = useColorModeValue('gray.700', 'white'); const textColor = useColorModeValue('gray.700', 'white');
const borderColor = useColorModeValue('gray.200', 'gray.600'); const borderColor = useColorModeValue('gray.200', 'gray.600');
@@ -161,6 +170,13 @@ export default function SubscriptionContent() {
return; return;
} }
// 🎯 追踪定价方案选择
subscriptionEvents.trackPricingPlanSelected(
plan.name,
selectedCycle,
selectedCycle === 'monthly' ? plan.monthly_price : plan.yearly_price
);
setSelectedPlan(plan); setSelectedPlan(plan);
onPaymentModalOpen(); onPaymentModalOpen();
}; };
@@ -170,6 +186,17 @@ export default function SubscriptionContent() {
setLoading(true); setLoading(true);
try { try {
const price = selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price;
// 🎯 追踪支付发起
subscriptionEvents.trackPaymentInitiated({
planName: selectedPlan.name,
paymentMethod: 'wechat_pay',
amount: price,
billingCycle: selectedCycle,
orderId: null // Will be set after order creation
});
const response = await fetch('/api/payment/create-order', { const response = await fetch('/api/payment/create-order', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -204,6 +231,13 @@ export default function SubscriptionContent() {
throw new Error('网络错误'); throw new Error('网络错误');
} }
} catch (error) { } catch (error) {
// 🎯 追踪支付失败
subscriptionEvents.trackPaymentFailed({
planName: selectedPlan.name,
paymentMethod: 'wechat_pay',
amount: selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price
}, error.message);
toast({ toast({
title: '创建订单失败', title: '创建订单失败',
description: error.message, description: error.message,
@@ -251,6 +285,26 @@ export default function SubscriptionContent() {
setAutoCheckInterval(null); setAutoCheckInterval(null);
logger.info('SubscriptionContent', '自动检测到支付成功', { orderId }); logger.info('SubscriptionContent', '自动检测到支付成功', { orderId });
// 🎯 追踪支付成功
subscriptionEvents.trackPaymentSuccessful({
planName: selectedPlan?.name,
paymentMethod: 'wechat_pay',
amount: paymentOrder?.amount,
billingCycle: selectedCycle,
orderId: orderId,
transactionId: data.transaction_id
});
// 🎯 追踪订阅创建
subscriptionEvents.trackSubscriptionCreated({
plan: selectedPlan?.name,
billingCycle: selectedCycle,
amount: paymentOrder?.amount,
startDate: new Date().toISOString(),
endDate: null // Will be calculated by backend
});
toast({ toast({
title: '支付成功!', title: '支付成功!',
description: '订阅已激活,正在跳转...', description: '订阅已激活,正在跳转...',

View File

@@ -2,11 +2,21 @@
import React from 'react'; import React from 'react';
import { Card, Input, Radio, Form, Button } from 'antd'; import { Card, Input, Radio, Form, Button } from 'antd';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { useSearchEvents } from '../../../hooks/useSearchEvents';
const SearchBox = ({ onSearch }) => { const SearchBox = ({ onSearch }) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
// 🎯 初始化搜索埋点Hook
const searchEvents = useSearchEvents({ context: 'community' });
const handleSubmit = (values) => { const handleSubmit = (values) => {
// 🎯 追踪搜索查询提交在调用onSearch之前
if (values.q) {
searchEvents.trackSearchQuerySubmitted(values.q, 0, {
search_type: values.search_type || 'topic'
});
}
onSearch(values); onSearch(values);
}; };

View File

@@ -44,11 +44,15 @@ import {
import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons'; import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
import { useProfileEvents } from '../../hooks/useProfileEvents';
export default function ProfilePage() { export default function ProfilePage() {
const { user, updateUser } = useAuth(); const { user, updateUser } = useAuth();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
// 🎯 初始化个人资料埋点Hook
const profileEvents = useProfileEvents({ pageType: 'profile' });
const [newTag, setNewTag] = useState(''); const [newTag, setNewTag] = useState('');
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const fileInputRef = useRef(); const fileInputRef = useRef();
@@ -95,6 +99,12 @@ export default function ProfilePage() {
updateUser(updatedData); updateUser(updatedData);
setIsEditing(false); setIsEditing(false);
// 🎯 追踪个人资料更新成功
const updatedFields = Object.keys(formData).filter(
key => user?.[key] !== formData[key]
);
profileEvents.trackProfileUpdated(updatedFields, updatedData);
// ✅ 保留关键操作提示 // ✅ 保留关键操作提示
toast({ toast({
title: "个人资料更新成功", title: "个人资料更新成功",
@@ -105,6 +115,10 @@ export default function ProfilePage() {
} catch (error) { } catch (error) {
logger.error('ProfilePage', 'handleSaveProfile', error, { userId: user?.id }); logger.error('ProfilePage', 'handleSaveProfile', error, { userId: user?.id });
// 🎯 追踪个人资料更新失败
const attemptedFields = Object.keys(formData);
profileEvents.trackProfileUpdateFailed(attemptedFields, error.message);
// ✅ 保留错误提示 // ✅ 保留错误提示
toast({ toast({
title: "更新失败", title: "更新失败",
@@ -128,6 +142,9 @@ export default function ProfilePage() {
reader.onload = (e) => { reader.onload = (e) => {
updateUser({ avatar_url: e.target.result }); updateUser({ avatar_url: e.target.result });
// 🎯 追踪头像上传
profileEvents.trackAvatarUploaded('file_upload', file.size);
// ✅ 保留关键操作提示 // ✅ 保留关键操作提示
toast({ toast({
title: "头像更新成功", title: "头像更新成功",

View File

@@ -59,12 +59,16 @@ import { FaWeixin, FaMobile, FaEnvelope } from 'react-icons/fa';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { getApiBase } from '../../utils/apiConfig'; import { getApiBase } from '../../utils/apiConfig';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
import { useProfileEvents } from '../../hooks/useProfileEvents';
export default function SettingsPage() { export default function SettingsPage() {
const { user, updateUser, logout } = useAuth(); const { user, updateUser, logout } = useAuth();
const { colorMode, toggleColorMode } = useColorMode(); const { colorMode, toggleColorMode } = useColorMode();
const toast = useToast(); const toast = useToast();
// 🎯 初始化设置页面埋点Hook
const profileEvents = useProfileEvents({ pageType: 'settings' });
// 模态框状态 // 模态框状态
const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure(); const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure();
const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure(); const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure();
@@ -209,9 +213,12 @@ export default function SettingsPage() {
if (response.ok && data.success) { if (response.ok && data.success) {
const isFirstSet = passwordStatus.needsFirstTimeSetup; const isFirstSet = passwordStatus.needsFirstTimeSetup;
// 🎯 追踪密码修改成功
profileEvents.trackPasswordChanged(true);
toast({ toast({
title: isFirstSet ? "密码设置成功" : "密码修改成功", title: isFirstSet ? "密码设置成功" : "密码修改成功",
description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录", description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录",
status: "success", status: "success",
duration: 3000, duration: 3000,
@@ -220,7 +227,7 @@ export default function SettingsPage() {
setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' });
onPasswordClose(); onPasswordClose();
// 刷新密码状态 // 刷新密码状态
fetchPasswordStatus(); fetchPasswordStatus();
@@ -234,6 +241,9 @@ export default function SettingsPage() {
throw new Error(data.error || '密码修改失败'); throw new Error(data.error || '密码修改失败');
} }
} catch (error) { } catch (error) {
// 🎯 追踪密码修改失败
profileEvents.trackPasswordChanged(false, error.message);
toast({ toast({
title: "修改失败", title: "修改失败",
description: error.message, description: error.message,
@@ -364,6 +374,9 @@ export default function SettingsPage() {
email_confirmed: data.user.email_confirmed email_confirmed: data.user.email_confirmed
}); });
// 🎯 追踪邮箱绑定成功
profileEvents.trackAccountBound('email', true);
toast({ toast({
title: "邮箱绑定成功", title: "邮箱绑定成功",
status: "success", status: "success",
@@ -374,6 +387,9 @@ export default function SettingsPage() {
setEmailForm({ email: '', verificationCode: '' }); setEmailForm({ email: '', verificationCode: '' });
onEmailClose(); onEmailClose();
} catch (error) { } catch (error) {
// 🎯 追踪邮箱绑定失败
profileEvents.trackAccountBound('email', false);
toast({ toast({
title: "绑定失败", title: "绑定失败",
description: error.message, description: error.message,
@@ -397,6 +413,13 @@ export default function SettingsPage() {
updateUser(notifications); updateUser(notifications);
// 🎯 追踪通知偏好更改
profileEvents.trackNotificationPreferencesChanged({
email: notifications.email_notifications,
push: notifications.system_updates,
sms: notifications.sms_notifications
});
// ❌ 移除设置保存成功toast // ❌ 移除设置保存成功toast
logger.info('SettingsPage', '通知设置已保存'); logger.info('SettingsPage', '通知设置已保存');
} catch (error) { } catch (error) {