feat(mining-app): 完善 telemetry 埋点 - 页面访问 + 用户行为

- 新增 TelemetryRouteObserver,挂载到 GoRouter 自动追踪全部页面访问
  无需每个页面手动调用 logPageView(),覆盖 27 个路由
- user_providers: login_success/login_failed(区分密码/短信)、logout
- trading_page: buy_shares、sell_shares(含价格/数量/结果)、transfer_shares(含方向/金额/结果)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-05 09:36:04 -08:00
parent a2a318e24c
commit 033d1cde42
4 changed files with 79 additions and 0 deletions

View File

@ -31,6 +31,7 @@ import '../../presentation/pages/profile/notification_inbox_page.dart';
import '../../presentation/widgets/main_shell.dart';
import '../../presentation/providers/user_providers.dart';
import 'routes.dart';
import 'telemetry_route_observer.dart';
/// 访
const _publicRoutes = {
@ -72,6 +73,7 @@ final appRouterProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: Routes.splash,
refreshListenable: authNotifier,
observers: [TelemetryRouteObserver()],
redirect: (context, state) {
// 使 authNotifier ref.read
final isLoggedIn = authNotifier.isLoggedIn;

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import '../telemetry/telemetry_service.dart';
///
///
/// GoRouter.observers访
/// logPageView()
class TelemetryRouteObserver extends NavigatorObserver {
static const _pageNames = {
'/': 'splash',
'/login': 'login',
'/register': 'register',
'/forgot-password': 'forgot_password',
'/change-password': 'change_password',
'/trade-password': 'trade_password',
'/contribution': 'contribution',
'/trading': 'trading',
'/asset': 'asset',
'/profile': 'profile',
'/edit-profile': 'edit_profile',
'/mining-records': 'mining_records',
'/batch-mining-records': 'batch_mining_records',
'/contribution-records': 'contribution_records',
'/planting-records': 'planting_records',
'/send-shares': 'send_shares',
'/receive-shares': 'receive_shares',
'/c2c-market': 'c2c_market',
'/c2c-publish': 'c2c_publish',
'/c2c-order-detail': 'c2c_order_detail',
'/my-team': 'my_team',
'/trading-records': 'trading_records',
'/transfer-records': 'transfer_records',
'/p2p-transfer-records': 'p2p_transfer_records',
'/notifications': 'notifications',
'/help-center': 'help_center',
'/about': 'about',
};
void _log(Route? route) {
if (route == null) return;
final name = route.settings.name;
if (name == null || name.isEmpty) return;
final path = name.split('?').first;
final pageName = _pageNames[path] ?? path.replaceAll('/', '').replaceAll('-', '_');
TelemetryService().logPageView(pageName);
}
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
_log(route);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
_log(newRoute);
}
}

View File

@ -19,6 +19,7 @@ import '../../widgets/shimmer_loading.dart';
import '../../widgets/kline_chart/kline_chart_widget.dart';
import '../../widgets/trade_password_dialog.dart';
import '../../../data/models/capability_model.dart';
import '../../../core/telemetry/telemetry_service.dart';
class TradingPage extends ConsumerStatefulWidget {
const TradingPage({super.key});
@ -1540,6 +1541,13 @@ class _TradingPageState extends ConsumerState<TradingPage> {
.sellShares(_priceController.text, _quantityController.text);
}
final action = isBuy ? 'buy_shares' : 'sell_shares';
TelemetryService().logUserAction(action, properties: {
'success': success,
'price': _priceController.text,
'quantity': _quantityController.text,
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -1960,6 +1968,11 @@ class _TransferBottomSheetState extends ConsumerState<_TransferBottomSheet> {
success = await ref.read(tradingNotifierProvider.notifier).transferOut(amount);
}
TelemetryService().logUserAction('transfer_shares', properties: {
'direction': _direction == 0 ? 'in' : 'out',
'amount': amount,
'success': success,
});
if (mounted) {
if (success) {
Navigator.pop(context);

View File

@ -178,9 +178,11 @@ class UserNotifier extends StateNotifier<UserState> {
TelemetryService().setUserId(result.user.accountSequence);
TelemetryService().setAccessToken(result.accessToken);
TelemetryService().resumeAfterLogin();
TelemetryService().logUserAction('login_success', properties: {'method': 'password'});
//
await fetchProfile();
} catch (e) {
TelemetryService().logUserAction('login_failed', properties: {'method': 'password', 'error': e.toString()});
state = state.copyWith(isLoading: false, error: e.toString());
rethrow;
}
@ -204,9 +206,11 @@ class UserNotifier extends StateNotifier<UserState> {
TelemetryService().setUserId(result.user.accountSequence);
TelemetryService().setAccessToken(result.accessToken);
TelemetryService().resumeAfterLogin();
TelemetryService().logUserAction('login_success', properties: {'method': 'sms'});
//
await fetchProfile();
} catch (e) {
TelemetryService().logUserAction('login_failed', properties: {'method': 'sms', 'error': e.toString()});
state = state.copyWith(isLoading: false, error: e.toString());
rethrow;
}
@ -218,6 +222,7 @@ class UserNotifier extends StateNotifier<UserState> {
await _authDataSource.logout(state.refreshToken!);
} catch (_) {}
}
TelemetryService().logUserAction('logout');
await TelemetryService().pauseForLogout();
TelemetryService().setAccessToken(null);
await _clearStorage();