Files
xianyan/lib/features/user_center/presentation/user_debug_page.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

718 lines
23 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — 用户调试信息页面
/// 创建时间: 2026-05-01
/// 更新时间: 2026-05-13
/// 作用: 调试页面 — 显示API接口返回的所有用户信息/面板/调试/统计/热力图/设备/金币数据
/// 上次更新: 增加设备列表+金币记录接口数据展示
/// ============================================================
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_typography.dart';
import '../../../core/theme/app_radius.dart';
import '../../../shared/widgets/adaptive/adaptive_back_button.dart';
import '../../../shared/widgets/containers/glass_container.dart';
import '../services/user_center_service.dart';
class UserDebugPage extends ConsumerStatefulWidget {
const UserDebugPage({super.key});
@override
ConsumerState<UserDebugPage> createState() => _UserDebugPageState();
}
class _UserDebugPageState extends ConsumerState<UserDebugPage> {
Map<String, dynamic>? _userInfoData;
Map<String, dynamic>? _debugData;
Map<String, dynamic>? _dashboardData;
Map<String, dynamic>? _statsData;
Map<String, dynamic>? _heatmapData;
Map<String, dynamic>? _signinCalendarData;
Map<String, dynamic>? _devicesData;
Map<String, dynamic>? _coinData;
bool _isLoading = true;
String? _error;
String _selectedTab = 'all';
@override
void initState() {
super.initState();
_loadAllData();
}
Future<void> _loadAllData() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final results = await Future.wait([
UserCenterService.getUserInfo(),
UserCenterService.getDebugInfo(),
UserCenterService.getDashboard(),
UserCenterService.getStats(),
UserCenterService.getHeatmap(),
UserCenterService.getSigninCalendar(),
UserCenterService.devices(action: 'list'),
UserCenterService.getCoinLog(),
]);
if (mounted) {
setState(() {
_userInfoData = results[0];
_debugData = results[1];
_dashboardData = results[2];
_statsData = results[3];
_heatmapData = results[4];
_signinCalendarData = results[5];
_devicesData = results[6];
_coinData = results[7];
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
return CupertinoPageScaffold(
backgroundColor: ext.bgPrimary,
navigationBar: CupertinoNavigationBar(
middle: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.ant_fill, size: 18, color: ext.textPrimary),
const SizedBox(width: 6),
Text(
'调试信息',
style: AppTypography.title3.copyWith(color: ext.textPrimary),
),
],
),
backgroundColor: ext.bgPrimary.withValues(alpha: 0.85),
border: null,
leading: const AdaptiveBackButton(),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: _isLoading ? null : _loadAllData,
child: _isLoading
? const CupertinoActivityIndicator()
: Icon(CupertinoIcons.refresh, color: ext.accent),
),
),
child: SafeArea(bottom: false, child: _buildBody(ext)),
);
}
Widget _buildBody(AppThemeExtension ext) {
if (_isLoading) {
return const Center(child: CupertinoActivityIndicator());
}
if (_error != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: GlassContainer(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.xmark_circle, size: 32, color: CupertinoColors.systemRed),
const SizedBox(height: AppSpacing.sm),
Text(
'加载失败',
style: AppTypography.title3.copyWith(color: ext.textPrimary),
),
const SizedBox(height: AppSpacing.xs),
Text(
_error!,
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: AppSpacing.md),
CupertinoButton(
color: ext.accent,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
onPressed: _loadAllData,
child: const Text('重试'),
),
],
),
),
),
);
}
return Column(
children: [
_buildTabBar(ext),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
children: _buildSections(ext),
),
),
],
);
}
Widget _buildTabBar(AppThemeExtension ext) {
final tabs = [
('all', '全部'),
('user', '用户'),
('dashboard', '面板'),
('stats', '统计'),
('calendar', '日历'),
('devices', '设备'),
('coin', '金币'),
];
return SizedBox(
height: 44,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
children: tabs.map((tab) {
final isSelected = _selectedTab == tab.$1;
return Padding(
padding: const EdgeInsets.only(right: AppSpacing.sm),
child: GestureDetector(
onTap: () => setState(() => _selectedTab = tab.$1),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: isSelected
? ext.accent.withValues(alpha: 0.15)
: ext.bgSecondary.withValues(alpha: 0.5),
borderRadius: AppRadius.pillBorder,
),
child: Center(
child: Text(
tab.$2,
style: AppTypography.caption1.copyWith(
color: isSelected ? ext.accent : ext.textSecondary,
fontWeight: isSelected
? FontWeight.w700
: FontWeight.w500,
),
),
),
),
),
);
}).toList(),
),
);
}
List<Widget> _buildSections(AppThemeExtension ext) {
final sections = <Widget>[];
if (_selectedTab == 'all' || _selectedTab == 'user') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.person,
title: '用户信息 (getUserInfo)',
endpoint: 'GET /api/user_center/index',
data: _userInfoData,
),
const SizedBox(height: AppSpacing.sm),
_buildSection(
ext,
icon: CupertinoIcons.ant_fill,
title: '调试信息 (getDebugInfo)',
endpoint: 'GET /api/user_center/debug',
data: _debugData,
),
const SizedBox(height: AppSpacing.sm),
_buildEditableFieldsInfo(ext),
const SizedBox(height: AppSpacing.sm),
]);
}
if (_selectedTab == 'all' || _selectedTab == 'dashboard') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.chart_bar,
title: '数据面板 (getDashboard)',
endpoint: 'GET /api/user_center/dashboard',
data: _dashboardData,
),
const SizedBox(height: AppSpacing.sm),
]);
}
if (_selectedTab == 'all' || _selectedTab == 'stats') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.chart_bar_square,
title: '学习统计 (getStats)',
endpoint: 'GET /api/user_center/stats?type=overview',
data: _statsData,
),
const SizedBox(height: AppSpacing.sm),
_buildSection(
ext,
icon: CupertinoIcons.flame,
title: '活跃热力图 (getHeatmap)',
endpoint: 'GET /api/user_center/heatmap',
data: _heatmapData,
),
const SizedBox(height: AppSpacing.sm),
]);
}
if (_selectedTab == 'all' || _selectedTab == 'calendar') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.calendar,
title: '签到日历 (getSigninCalendar)',
endpoint: 'GET /api/user_center/signin_calendar',
data: _signinCalendarData,
),
const SizedBox(height: AppSpacing.sm),
]);
}
if (_selectedTab == 'all' || _selectedTab == 'devices') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.device_laptop,
title: '设备列表 (devices)',
endpoint: 'POST /api/user_center/devices?action=list',
data: _devicesData,
),
const SizedBox(height: AppSpacing.sm),
]);
}
if (_selectedTab == 'all' || _selectedTab == 'coin') {
sections.addAll([
_buildSection(
ext,
icon: CupertinoIcons.money_dollar_circle,
title: '金币记录 (coin)',
endpoint: 'GET /api/user_center/coin',
data: _coinData,
),
const SizedBox(height: AppSpacing.sm),
]);
}
sections.add(const SizedBox(height: AppSpacing.xl));
return sections;
}
Widget _buildSection(
AppThemeExtension ext, {
required IconData icon,
required String title,
required String endpoint,
required Map<String, dynamic>? data,
}) {
return GlassContainer(
depth: GlassDepth.elevated,
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.md,
AppSpacing.md,
AppSpacing.md,
AppSpacing.xs,
),
child: Row(
children: [
Icon(icon, size: 18, color: ext.accent),
const SizedBox(width: 6),
Expanded(
child: Text(
title,
style: AppTypography.headline.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w700,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: 4,
),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.08),
borderRadius: AppRadius.smBorder,
),
child: Text(
endpoint,
style: AppTypography.caption2.copyWith(
color: ext.accent,
fontFamily: 'monospace',
),
),
),
),
const SizedBox(height: AppSpacing.sm),
if (data != null)
...data.entries.map((entry) => _buildDataRow(ext, entry))
else
Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Text(
'暂无数据',
style: AppTypography.subhead.copyWith(color: ext.textHint),
),
),
],
),
);
}
Widget _buildDataRow(AppThemeExtension ext, MapEntry<String, dynamic> entry) {
final value = entry.value;
final displayValue = value is Map || value is List
? const JsonEncoder.withIndent(' ').convert(value)
: value?.toString() ?? 'null';
final isComplex = value is Map || value is List;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
height: 0.5,
thickness: 0.5,
color: ext.textHint.withValues(alpha: 0.15),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 110,
child: Text(
entry.key,
style: AppTypography.caption1.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
fontFamily: 'monospace',
),
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: isComplex
? Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: ext.bgSecondary.withValues(alpha: 0.5),
borderRadius: AppRadius.smBorder,
),
child: Text(
displayValue,
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
fontFamily: 'monospace',
height: 1.4,
),
),
)
: Text(
displayValue,
style: AppTypography.caption1.copyWith(
color: ext.textPrimary,
),
),
),
],
),
),
],
),
);
}
Widget _buildEditableFieldsInfo(AppThemeExtension ext) {
return GlassContainer(
depth: GlassDepth.elevated,
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.md,
AppSpacing.md,
AppSpacing.md,
AppSpacing.xs,
),
child: Row(
children: [
Icon(CupertinoIcons.pencil, size: 18, color: ext.accent),
const SizedBox(width: 6),
Text(
'可编辑字段总览',
style: AppTypography.headline.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w700,
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: 4,
),
decoration: BoxDecoration(
color: CupertinoColors.systemGreen.withValues(alpha: 0.08),
borderRadius: AppRadius.smBorder,
),
child: Text(
'POST /api/user_center/profile',
style: AppTypography.caption2.copyWith(
color: CupertinoColors.systemGreen,
fontFamily: 'monospace',
),
),
),
),
const SizedBox(height: AppSpacing.sm),
_buildEditableRow(
ext,
'username',
'用户名',
'3-30字符字母/数字/下划线/中文',
'profile',
),
_buildEditableRow(ext, 'nickname', '昵称', '1-50字符', 'profile'),
_buildEditableRow(ext, 'bio', '个人简介', '0-500字符', 'profile'),
_buildEditableRow(ext, 'avatar', '头像URL', '1-500字符', 'profile'),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Divider(
height: 0.5,
thickness: 0.5,
color: ext.textHint.withValues(alpha: 0.15),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.md,
AppSpacing.sm,
AppSpacing.md,
AppSpacing.xs,
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: 4,
),
decoration: BoxDecoration(
color: CupertinoColors.systemOrange.withValues(alpha: 0.08),
borderRadius: AppRadius.smBorder,
),
child: Text(
'安全接口 /api/user_security/* (需回执验证)',
style: AppTypography.caption2.copyWith(
color: CupertinoColors.systemOrange,
fontFamily: 'monospace',
),
),
),
),
_buildEditableRow(
ext,
'email',
'邮箱',
'5-100字符需回执(changeemail)',
'security',
),
_buildEditableRow(
ext,
'mobile',
'手机号',
'11位需回执(changemobile)',
'security',
),
_buildEditableRow(
ext,
'password',
'密码',
'6-30字符需回执(changepwd)',
'security',
),
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.md,
AppSpacing.sm,
AppSpacing.md,
AppSpacing.md,
),
child: Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: CupertinoColors.systemBlue.withValues(alpha: 0.08),
borderRadius: AppRadius.smBorder,
),
child: Row(
children: [
const Icon(CupertinoIcons.lightbulb, size: 14, color: CupertinoColors.systemBlue),
const SizedBox(width: AppSpacing.xs),
Expanded(
child: Text(
'回执(Receipt)验证: HMAC-SHA256签名密钥Xy7kP9mL2qR4wS8v有效期300秒',
style: AppTypography.caption1.copyWith(
color: CupertinoColors.systemBlue,
),
),
),
],
),
),
),
],
),
);
}
Widget _buildEditableRow(
AppThemeExtension ext,
String field,
String label,
String desc,
String apiType,
) {
final isProfile = apiType == 'profile';
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
height: 0.5,
thickness: 0.5,
color: ext.textHint.withValues(alpha: 0.15),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: Row(
children: [
SizedBox(
width: 110,
child: Row(
children: [
Text(
field,
style: AppTypography.caption1.copyWith(
color: isProfile
? CupertinoColors.systemGreen
: CupertinoColors.systemOrange,
fontWeight: FontWeight.w600,
fontFamily: 'monospace',
),
),
const SizedBox(width: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 3,
vertical: 1,
),
decoration: BoxDecoration(
color:
(isProfile
? CupertinoColors.systemGreen
: CupertinoColors.systemOrange)
.withValues(alpha: 0.1),
borderRadius: AppRadius.pillBorder,
),
child: Text(
apiType,
style: AppTypography.caption2.copyWith(
color: isProfile
? CupertinoColors.systemGreen
: CupertinoColors.systemOrange,
fontSize: 8,
fontWeight: FontWeight.w700,
),
),
),
],
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppTypography.caption1.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
Text(
desc,
style: AppTypography.caption2.copyWith(
color: ext.textHint,
),
),
],
),
),
],
),
),
],
),
);
}
}