rwadurian/frontend/mobile-app/lib/features/ranking/presentation/pages/ranking_page.dart

476 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// 排行榜类型枚举
enum RankingType { daily, weekly, monthly }
/// 筛选类型枚举
enum FilterType { all, previousDay, previousWeek, previousMonth }
/// 排行榜数据模型
class RankingItem {
final int rank;
final String name;
final String province;
final String city;
final int teamPlantingAmount;
final String? avatarUrl;
RankingItem({
required this.rank,
required this.name,
required this.province,
required this.city,
required this.teamPlantingAmount,
this.avatarUrl,
});
}
/// 龙虎榜页面 - 显示用户排行榜
/// 支持日榜、周榜、月榜切换,以及筛选功能
class RankingPage extends ConsumerStatefulWidget {
const RankingPage({super.key});
@override
ConsumerState<RankingPage> createState() => _RankingPageState();
}
class _RankingPageState extends ConsumerState<RankingPage> {
// 当前选中的排行榜类型
RankingType _selectedRankingType = RankingType.daily;
// 当前选中的筛选类型
FilterType _selectedFilterType = FilterType.all;
// 模拟排行榜数据
final List<RankingItem> _mockRankingData = [
RankingItem(
rank: 1,
name: '环保先锋',
province: '广东',
city: '深圳',
teamPlantingAmount: 1234567,
),
RankingItem(
rank: 2,
name: '绿色卫士',
province: '广东',
city: '深圳',
teamPlantingAmount: 1123456,
),
RankingItem(
rank: 3,
name: '低碳达人',
province: '广东',
city: '深圳',
teamPlantingAmount: 987654,
),
RankingItem(
rank: 4,
name: '节能小能手',
province: '广东',
city: '深圳',
teamPlantingAmount: 876543,
),
RankingItem(
rank: 5,
name: '分类高手',
province: '广东',
city: '深圳',
teamPlantingAmount: 765432,
),
];
/// 切换排行榜类型
void _selectRankingType(RankingType type) {
setState(() {
_selectedRankingType = type;
});
}
/// 切换筛选类型
void _selectFilterType(FilterType type) {
setState(() {
_selectedFilterType = type;
});
}
/// 格式化数字(添加千分位)
String _formatNumber(int number) {
return number.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6), // 浅米色
Color(0xFFEAE0C8), // 浅橙色
],
),
),
child: Column(
children: [
// 顶部标题和Tab栏
_buildHeader(),
// 筛选栏
_buildFilterBar(),
// 排行榜列表
Expanded(
child: _buildRankingList(),
),
],
),
),
);
}
/// 构建顶部标题和Tab栏
Widget _buildHeader() {
return Container(
color: const Color(0xCCFFF5E6),
child: SafeArea(
bottom: false,
child: Column(
children: [
// 标题
Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const Center(
child: Text(
'龙虎榜',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
),
),
),
// Tab栏
_buildTabBar(),
],
),
),
);
}
/// 构建Tab栏
Widget _buildTabBar() {
return Container(
height: 45,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Color(0x338B5A2B),
),
),
),
child: Row(
children: [
// 日榜
Expanded(
child: _buildTabItem(
title: '日榜',
isSelected: _selectedRankingType == RankingType.daily,
onTap: () => _selectRankingType(RankingType.daily),
),
),
// 周榜
Expanded(
child: _buildTabItem(
title: '周榜',
isSelected: _selectedRankingType == RankingType.weekly,
onTap: () => _selectRankingType(RankingType.weekly),
),
),
// 月榜
Expanded(
child: _buildTabItem(
title: '月榜',
isSelected: _selectedRankingType == RankingType.monthly,
onTap: () => _selectRankingType(RankingType.monthly),
),
),
],
),
);
}
/// 构建Tab项
Widget _buildTabItem({
required String title,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 44,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 3,
color: isSelected ? const Color(0xFFD4AF37) : Colors.transparent,
),
),
),
child: Center(
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.21,
color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B),
),
),
),
),
);
}
/// 构建筛选栏
Widget _buildFilterBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip(
title: '全部',
isSelected: _selectedFilterType == FilterType.all,
onTap: () => _selectFilterType(FilterType.all),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一日榜',
isSelected: _selectedFilterType == FilterType.previousDay,
onTap: () => _selectFilterType(FilterType.previousDay),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一周榜',
isSelected: _selectedFilterType == FilterType.previousWeek,
onTap: () => _selectFilterType(FilterType.previousWeek),
),
const SizedBox(width: 12),
_buildFilterChip(
title: '上一月榜',
isSelected: _selectedFilterType == FilterType.previousMonth,
onTap: () => _selectFilterType(FilterType.previousMonth),
),
],
),
),
);
}
/// 构建筛选标签
Widget _buildFilterChip({
required String title,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFD4AF37) : const Color(0x1A8B5A2B),
borderRadius: BorderRadius.circular(9999),
),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.5,
color: isSelected ? Colors.white : const Color(0xFF8B5A2B),
),
),
),
);
}
/// 构建排行榜列表
Widget _buildRankingList() {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: _mockRankingData.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: _buildRankingItem(_mockRankingData[index]),
);
},
);
}
/// 构建排行榜项
Widget _buildRankingItem(RankingItem item) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFFDF8), // 浅白色背景,与页面背景区分
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
// 排名
SizedBox(
width: 32,
child: _buildRankBadge(item.rank),
),
const SizedBox(width: 16),
// 头像
_buildAvatar(item),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFF5D4037),
),
),
Text(
'省份: ${item.province}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
Text(
'城市: ${item.city}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
),
// 团队认种量
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_formatNumber(item.teamPlantingAmount),
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
color: Color(0xFFD4AF37),
),
),
const Text(
'团队认种量',
style: TextStyle(
fontSize: 12,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
],
),
],
),
);
}
/// 构建排名徽章
Widget _buildRankBadge(int rank) {
// 前三名使用皇冠图标
if (rank <= 3) {
Color crownColor;
switch (rank) {
case 1:
crownColor = const Color(0xFFFFD700); // 金色
break;
case 2:
crownColor = const Color(0xFFC0C0C0); // 银色
break;
case 3:
crownColor = const Color(0xFFCD7F32); // 铜色
break;
default:
crownColor = const Color(0xFF8B5A2B);
}
return Icon(
Icons.workspace_premium,
size: 32,
color: crownColor,
);
}
// 其他排名显示数字
return Text(
rank.toString(),
style: const TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.4,
color: Color(0xFF8B5A2B),
),
textAlign: TextAlign.center,
);
}
/// 构建头像
Widget _buildAvatar(RankingItem item) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(28),
),
child: item.avatarUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.network(
item.avatarUrl!,
fit: BoxFit.cover,
),
)
: const Icon(
Icons.person,
size: 32,
color: Color(0xFF8B5A2B),
),
);
}
}