Files
chat/client/flutter/lib/pages/dashboard_page.dart

406 lines
11 KiB
Dart
Raw Normal View History

2026-04-25 16:36:34 +08:00
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sales_chat/providers/stats_provider.dart';
import 'package:sales_chat/widgets/user_avatar.dart';
import 'package:sales_chat/theme/app_theme.dart';
/// 看板页面 —— Twitter 风格
class DashboardPage extends StatefulWidget {
const DashboardPage({super.key});
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
Future<void> _loadData() async {
final statsProvider = context.read<StatsProvider>();
await statsProvider.loadMyStats();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('看板'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadData,
),
],
),
body: Consumer<StatsProvider>(
builder: (context, statsProvider, _) {
if (statsProvider.isLoading && statsProvider.myStats == null) {
return const Center(child: CircularProgressIndicator());
}
final stats = statsProvider.myStats;
return RefreshIndicator(
onRefresh: _loadData,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (stats != null) _buildStatsOverview(context, stats),
const SizedBox(height: 8),
_buildTodayStats(context, stats),
const SizedBox(height: 8),
_buildRankingSection(context, statsProvider),
],
),
),
);
},
),
);
}
/// 统计概览 —— 卡片区块2x2 网格
Widget _buildStatsOverview(BuildContext context, dynamic myStats) {
final total = myStats.total;
return Container(
width: double.infinity,
color: AppTheme.cardBackground,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'我的统计',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 2.0,
children: [
_StatGridItem(
value: _formatNumber(total.totalInvites),
label: '总邀请数',
icon: Icons.card_giftcard_outlined,
color: AppTheme.primaryColor,
),
_StatGridItem(
value: _formatNumber(total.totalJoins),
label: '总加入数',
icon: Icons.person_add_outlined,
color: AppTheme.successColor,
),
_StatGridItem(
value: '+${myStats.today.invitesCreated}',
label: '今日邀请',
icon: Icons.add_circle_outline,
color: AppTheme.warningColor,
),
_StatGridItem(
value: '+${myStats.today.joins}',
label: '今日加入',
icon: Icons.people,
color: AppTheme.infoColor,
),
],
),
],
),
);
}
/// 今日统计 —— 独立卡片区块
Widget _buildTodayStats(BuildContext context, dynamic myStats) {
if (myStats == null) return const SizedBox.shrink();
final today = myStats.today;
return Container(
width: double.infinity,
color: AppTheme.cardBackground,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'今日数据',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _TodayStatItem(
value: '${today.invitesCreated}',
label: '新建邀请',
icon: Icons.add_circle_outline,
color: AppTheme.primaryColor,
),
),
Container(
width: 0.5,
height: 40,
color: AppTheme.dividerColor,
),
Expanded(
child: _TodayStatItem(
value: '${today.joins}',
label: '新加入',
icon: Icons.person_add,
color: AppTheme.successColor,
),
),
],
),
],
),
);
}
/// 排行榜区块
Widget _buildRankingSection(BuildContext context, StatsProvider provider) {
return Container(
color: AppTheme.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'排行榜',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
GestureDetector(
onTap: () => provider.loadRanking(),
child: const Text(
'加载排行',
style: TextStyle(
fontSize: 13,
color: AppTheme.primaryColor,
),
),
),
],
),
),
if (provider.ranking.isEmpty)
Padding(
padding: const EdgeInsets.all(24),
child: Center(
child: Text(
'暂无排行数据',
style: TextStyle(color: AppTheme.textSecondary, fontSize: 14),
),
),
)
else
..._buildRankingItems(provider),
],
),
);
}
/// 构建排行列表项(带分隔线)
List<Widget> _buildRankingItems(StatsProvider provider) {
final rankings = provider.ranking.take(10).toList();
final items = <Widget>[];
for (int i = 0; i < rankings.length; i++) {
final entry = rankings[i];
items.add(Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
// 排名序号
SizedBox(
width: 28,
child: Text(
'${entry.rank}',
style: TextStyle(
fontSize: 16,
fontWeight: i < 3 ? FontWeight.bold : FontWeight.w500,
color: i < 3
? [const Color(0xFFFFC300), const Color(0xFFC0C0C0), const Color(0xFFCD7F32)][i]
: AppTheme.textHint,
),
textAlign: TextAlign.center,
),
),
const SizedBox(width: 12),
// 头像
UserAvatar(
displayName: entry.displayName,
radius: 16,
),
const SizedBox(width: 12),
// 名字
Expanded(
child: Text(
entry.displayName,
style: const TextStyle(
fontSize: 15,
color: AppTheme.textPrimary,
),
),
),
// 加入数
Text(
'${entry.totalJoins} 加入',
style: const TextStyle(
fontSize: 13,
color: AppTheme.textSecondary,
),
),
],
),
));
if (i < rankings.length - 1) {
items.add(const Padding(
padding: EdgeInsets.only(left: 56),
child: Divider(height: 0.5, thickness: 0.5),
));
}
}
return items;
}
/// 格式化数字
String _formatNumber(int number) {
if (number >= 10000) {
return '${(number / 10000).toStringAsFixed(1)}w';
} else if (number >= 1000) {
return '${(number / 1000).toStringAsFixed(1)}k';
}
return number.toString();
}
}
/// 统计网格项 —— 大数字 + 小标签 + 图标
class _StatGridItem extends StatelessWidget {
final String value;
final String label;
final IconData icon;
final Color color;
const _StatGridItem({
required this.value,
required this.label,
required this.icon,
required this.color,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.06),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Icon(icon, color: color, size: 18),
const SizedBox(width: 6),
Text(
value,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
],
),
);
}
}
/// 今日统计项
class _TodayStatItem extends StatelessWidget {
final String value;
final String label;
final IconData icon;
final Color color;
const _TodayStatItem({
required this.value,
required this.label,
required this.icon,
required this.color,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 18),
const SizedBox(width: 6),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
],
);
}
}