import 'package:flutter/material.dart'; import '../../constants/app_constants.dart'; import '../../utils/responsive_layout.dart'; import '../../utils/http/http_client.dart'; import '../../models/poetry_model.dart'; import '../../controllers/load/locally.dart'; /// 时间: 2026-03-25 /// 功能: 热门页面 /// 介绍: 展示诗词排行榜,包括总榜、日榜、月榜 /// 最新变化: 新建热门页面,支持多种排行榜类型 class PopularPage extends StatefulWidget { const PopularPage({super.key}); @override State createState() => _PopularPageState(); } class _PopularPageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; final List _tabCategories = ['总榜', '日榜', '月榜']; List _rankList = []; bool _loading = false; String _errorMessage = ''; bool _showBottomIndicator = false; @override void initState() { super.initState(); _tabController = TabController(length: _tabCategories.length, vsync: this); _tabController.addListener(() { if (mounted && !_tabController.indexIsChanging) { _loadRankList(); } }); _loadRankList(); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: Column( children: [ // Tab栏 Container( padding: const EdgeInsets.symmetric(horizontal: 16), child: TabBar( controller: _tabController, tabs: _tabCategories .map((category) => Tab(text: category)) .toList(), labelColor: AppConstants.primaryColor, unselectedLabelColor: Colors.grey[600], indicatorColor: AppConstants.primaryColor, indicatorWeight: 3, labelStyle: const TextStyle(fontWeight: FontWeight.bold), ), ), // 内容区域 Expanded( child: TabBarView( controller: _tabController, children: _tabCategories.map((category) { return _buildRankContent(category); }).toList(), ), ), ], ), ); } Widget _buildRankContent(String category) { if (_loading) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('正在加载排行榜...'), ], ), ); } if (_errorMessage.isNotEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 64, color: Colors.grey), const SizedBox(height: 16), Text(_errorMessage, style: const TextStyle(color: Colors.grey)), const SizedBox(height: 16), ElevatedButton( onPressed: _loadRankList, child: const Text('🔄 重试'), ), ], ), ); } if (_rankList.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.bar_chart, size: 64, color: Colors.grey), SizedBox(height: 16), Text('暂无排行数据', style: TextStyle(color: Colors.grey)), SizedBox(height: 8), Text( '换个时间段试试吧', style: TextStyle(color: Colors.grey, fontSize: 12), ), ], ), ); } return RefreshIndicator( onRefresh: () async => await _loadRankList(forceRefresh: true), child: NotificationListener( onNotification: (scrollNotification) { if (scrollNotification is ScrollEndNotification && scrollNotification.metrics.extentAfter == 0) { _showBottomIndicatorMethod(); } return false; }, child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _rankList.length + (_showBottomIndicator ? 1 : 0), itemBuilder: (context, index) { if (index == _rankList.length) { return const Center( child: Padding( padding: EdgeInsets.all(16), child: Text('到底了', style: TextStyle(color: Colors.grey)), ), ); } return _buildRankItem(_rankList[index], index + 1); }, ), ), ); } Widget _buildRankItem(PoetryModel poetry, int index) { final rank = poetry.rank > 0 ? poetry.rank : index; // 优先使用API返回的rank final isTopThree = rank <= 3; return Card( margin: const EdgeInsets.only(bottom: 12), elevation: isTopThree ? 4 : 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isTopThree ? AppConstants.primaryColor : AppConstants.primaryColor.withValues(alpha: 0.2), width: isTopThree ? 2 : 1, ), ), child: InkWell( onTap: () => _onViewDetail(poetry), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // 排名徽章 Container( width: 40, height: 40, decoration: BoxDecoration( color: isTopThree ? AppConstants.primaryColor : AppConstants.primaryColor.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: isTopThree ? null : Border.all( color: AppConstants.primaryColor.withValues( alpha: 0.2, ), width: 1, ), ), child: Center( child: Text( rank.toString(), style: TextStyle( color: isTopThree ? Colors.white : AppConstants.primaryColor.withValues(alpha: 0.8), fontWeight: FontWeight.bold, fontSize: 16, ), ), ), ), const SizedBox(width: 12), // 内容区域 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题 Text( poetry.name, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // 朝代和作者 if (poetry.alias.isNotEmpty || poetry.url.isNotEmpty) Row( children: [ if (poetry.alias.isNotEmpty) ...[ Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: AppConstants.primaryColor.withValues( alpha: 0.1, ), borderRadius: BorderRadius.circular(8), ), child: Text( poetry.alias, style: TextStyle( fontSize: 10, color: AppConstants.primaryColor, ), ), ), const SizedBox(width: 8), ], if (poetry.url.isNotEmpty) Expanded( child: Text( poetry.url, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), // 统计数据 Row( children: [ _buildStatItem( '👁', poetry.hitsTotal.toString(), '总浏览', ), const SizedBox(width: 16), _buildStatItem('💖', poetry.like.toString(), '点赞'), const SizedBox(width: 16), if (_tabController.index == 1) // 日榜 _buildStatItem('📅', poetry.hitsDay.toString(), '今日'), if (_tabController.index == 2) // 月榜 _buildStatItem( '📊', poetry.hitsMonth.toString(), '本月', ), ], ), ], ), ), ], ), ), ), ); } Widget _buildStatItem(String icon, String value, String label) { return Row( mainAxisSize: MainAxisSize.min, children: [ Text(icon, style: const TextStyle(fontSize: 12)), const SizedBox(width: 4), Text( value, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), const SizedBox(width: 2), Text(label, style: TextStyle(fontSize: 10, color: Colors.grey[600])), ], ); } Future _loadRankList({bool forceRefresh = false}) async { setState(() { _loading = true; _errorMessage = ''; _showBottomIndicator = false; }); try { final tabIndex = _tabController.index; final type = tabIndex == 0 ? 'total' : tabIndex == 1 ? 'day' : 'month'; final isPreloadEnabled = await LocalCacheManager().isPreloadEnabled(); if (isPreloadEnabled && !forceRefresh) { print('预加载模式:尝试从本地缓存加载数据'); final cachedData = await LocalCacheManager().getCachedPopularList(type); if (cachedData != null && cachedData.isNotEmpty) { print('从本地缓存加载数据成功'); if (mounted) { setState(() { _rankList = cachedData .map((item) => PoetryModel.fromJson(item)) .toList(); _loading = false; }); } return; } print('本地缓存为空,从服务器加载'); } print('正在请求排行榜数据: type=$type, period=$type'); final response = await HttpClient.get( '/rlist.php', queryParameters: {'type': type, 'limit': '20'}, ); print('API响应状态: ${response.statusCode}'); print('API响应成功: ${response.isSuccess}'); print('API响应代码: ${response.code}'); print('API响应消息: ${response.message}'); print('API响应数据: ${response.data}'); if (response.isSuccess && response.code == 0) { final data = response.data; final rankData = data['list'] as List? ?? []; final rankDataList = rankData.cast>(); if (isPreloadEnabled) { print('保存数据到本地缓存'); await LocalCacheManager().cachePopularList(type, rankDataList); } if (mounted) { setState(() { _rankList = rankData .map((item) => PoetryModel.fromJson(item)) .toList(); _loading = false; }); } } else { if (mounted) { setState(() { _errorMessage = response.message.isNotEmpty == true ? response.message : '获取排行榜失败'; _loading = false; }); } } } catch (e) { if (mounted) { setState(() { _errorMessage = '网络请求失败,请检查网络连接'; _loading = false; }); } } } void _showBottomIndicatorMethod() { if (_rankList.isNotEmpty && !_loading && mounted) { setState(() { _showBottomIndicator = true; }); Future.delayed(const Duration(seconds: 3), () { if (mounted) { setState(() { _showBottomIndicator = false; }); } }); } } void _onViewDetail(PoetryModel poetry) { // TODO: 跳转到诗词详情页面 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('查看诗词: ${poetry.name}'), duration: const Duration(seconds: 1), ), ); } }