From 26c55934d7c696173db5f8e2546a544269c08c29 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 24 Nov 2025 20:43:24 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(usage):=20=E6=B7=BB=E5=8A=A0AI?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E7=A7=AF=E5=88=86=E7=BB=9F=E8=AE=A1=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增AI聊天积分统计视图、控制器及相关绑定配置 在底部导航栏添加AI聊天入口 实现积分数据显示及刷新功能 --- lib/modules/usage/bindings/usage_binding.dart | 13 ++-- .../controllers/aichat_stats_controller.dart | 10 +++ .../usage/controllers/usage_controller.dart | 2 + .../usage/views/aichat_stats_view.dart | 74 +++++++++++++++++++ lib/modules/usage/views/usage_view.dart | 8 +- 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 lib/modules/usage/controllers/aichat_stats_controller.dart create mode 100644 lib/modules/usage/views/aichat_stats_view.dart diff --git a/lib/modules/usage/bindings/usage_binding.dart b/lib/modules/usage/bindings/usage_binding.dart index 1d3145df9..e03af7554 100644 --- a/lib/modules/usage/bindings/usage_binding.dart +++ b/lib/modules/usage/bindings/usage_binding.dart @@ -2,6 +2,7 @@ import 'package:get/get.dart'; import '../controllers/usage_controller.dart'; import '../controllers/meeting_stats_controller.dart'; import '../controllers/translation_stats_controller.dart'; +import '../controllers/aichat_stats_controller.dart'; import '../../../data/services/usage_stats_service.dart'; class UsageBinding implements Bindings { @@ -10,11 +11,13 @@ class UsageBinding implements Bindings { if (!Get.isRegistered()) { Get.put(UsageStatsService(), permanent: true); } - if (!Get.isRegistered()) { - Get.lazyPut(() => TranslationStatsController()); - } - Get.lazyPut(() => MeetingStatsController()); + Get.lazyPut(() => UsageController()); + + Get.lazyPut(() => AiChatStatsController()); + + Get.lazyPut(() => TranslationStatsController()); + + Get.lazyPut(() => MeetingStatsController()); } } - diff --git a/lib/modules/usage/controllers/aichat_stats_controller.dart b/lib/modules/usage/controllers/aichat_stats_controller.dart new file mode 100644 index 000000000..efc285a6c --- /dev/null +++ b/lib/modules/usage/controllers/aichat_stats_controller.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../../../data/models/user_Info.dart'; + +class AiChatStatsController extends GetxController { + final isLoading = false.obs; + + int get currentIntegral => User.instance.aichatintegral; + int get totalIntegral => User.instance.aichattotalintegral; +} + diff --git a/lib/modules/usage/controllers/usage_controller.dart b/lib/modules/usage/controllers/usage_controller.dart index bf2c5a4be..d8ecbbecd 100644 --- a/lib/modules/usage/controllers/usage_controller.dart +++ b/lib/modules/usage/controllers/usage_controller.dart @@ -2,12 +2,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../views/translation_stats_view.dart'; import '../views/meeting_stats_view.dart'; +import '../views/aichat_stats_view.dart'; class UsageController extends GetxController { late PageController pageController; final RxInt pageIndex = 0.obs; final List pageList = const [ + AiChatStatsView(), TranslationStatsView(), MeetingStatsView(), ]; diff --git a/lib/modules/usage/views/aichat_stats_view.dart b/lib/modules/usage/views/aichat_stats_view.dart new file mode 100644 index 000000000..584ce5ee1 --- /dev/null +++ b/lib/modules/usage/views/aichat_stats_view.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import '../../../core/theme/app_colors.dart'; +import '../controllers/aichat_stats_controller.dart'; +import '../../../data/models/user_Info.dart'; + +class AiChatStatsView extends GetView { + const AiChatStatsView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], + appBar: _buildAppBar(isDarkMode), + body: _buildBody(isDarkMode), + ); + } + + PreferredSizeWidget _buildAppBar(bool isDarkMode) { + return AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios, color: isDarkMode ? Colors.white : Colors.black, size: 20.sp), + onPressed: () => Get.back(), + ), + backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.white, + elevation: 0.5, + title: Text( + 'AI聊天', + style: TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + ), + centerTitle: true, + ); + } + + Widget _buildBody(bool isDarkMode) { + return Obx(() { + final _ = User.refreshTick.value; + final u = User.instance; + return Padding( + padding: EdgeInsets.all(16.w), + child: Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode ? AppColors.cardBackground : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2)), + ], + ), + child: Row( + children: [ + Icon(Icons.chat_bubble_outline, color: AppColors.primary, size: 20.sp), + SizedBox(width: 8.w), + Expanded( + child: Text( + 'AI聊天积分', + style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + ), + ), + Text( + '${u.aichatintegral}/${u.aichattotalintegral}', + style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + ), + ], + ), + ), + ); + }); + } +} + diff --git a/lib/modules/usage/views/usage_view.dart b/lib/modules/usage/views/usage_view.dart index efa1b02e8..393884869 100644 --- a/lib/modules/usage/views/usage_view.dart +++ b/lib/modules/usage/views/usage_view.dart @@ -21,6 +21,10 @@ class UsageView extends GetView { bottomNavigationBar: Obx( () => BottomNavigationBar( items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.chat_bubble_outline), + label: 'AI聊天', + ), BottomNavigationBarItem( icon: const Icon(Icons.translate), label: 'interpretation'.tr, @@ -32,7 +36,8 @@ class UsageView extends GetView { ], backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFB), currentIndex: controller.pageIndex.value, - unselectedItemColor: isDarkMode ? Colors.grey[500] : const Color(0xFF6B7280), + unselectedItemColor: + isDarkMode ? Colors.grey[500] : const Color(0xFF6B7280), selectedItemColor: const Color(0xFF3B82F6), unselectedFontSize: 10.sp, selectedFontSize: 10.sp, @@ -47,4 +52,3 @@ class UsageView extends GetView { ); } } - From d1b6c6de8bd0e04d535e5ffce225d037322eb0ee Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Tue, 25 Nov 2025 15:29:48 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(usage):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=BB=9F=E8=AE=A1=E6=A8=A1=E5=9D=97=E7=9A=84?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E5=92=8C=E5=AF=BC=E8=88=AA=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将导航栏配置提取为常量并集中管理颜色配置 - 优化底部导航栏的响应式设计和动画效果 - 统一各统计页面的积分卡片显示位置和样式 - 移除翻译统计页中多余的积分行 - 将AI聊天页面标题改为英文 --- .../usage/views/aichat_stats_view.dart | 29 +++- .../usage/views/meeting_stats_view.dart | 147 ++++++++++++++++-- .../usage/views/translation_stats_view.dart | 18 +-- lib/modules/usage/views/usage_view.dart | 102 ++++++++---- 4 files changed, 230 insertions(+), 66 deletions(-) diff --git a/lib/modules/usage/views/aichat_stats_view.dart b/lib/modules/usage/views/aichat_stats_view.dart index 584ce5ee1..7ac2d806b 100644 --- a/lib/modules/usage/views/aichat_stats_view.dart +++ b/lib/modules/usage/views/aichat_stats_view.dart @@ -22,14 +22,18 @@ class AiChatStatsView extends GetView { PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( leading: IconButton( - icon: Icon(Icons.arrow_back_ios, color: isDarkMode ? Colors.white : Colors.black, size: 20.sp), + icon: Icon(Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, size: 20.sp), onPressed: () => Get.back(), ), backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.white, elevation: 0.5, title: Text( - 'AI聊天', - style: TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + 'AI chat', + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), ), centerTitle: true, ); @@ -47,22 +51,32 @@ class AiChatStatsView extends GetView { color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2)), + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 2)), ], ), child: Row( children: [ - Icon(Icons.chat_bubble_outline, color: AppColors.primary, size: 20.sp), + Icon(Icons.chat_bubble_outline, + color: AppColors.primary, size: 20.sp), SizedBox(width: 8.w), Expanded( child: Text( 'AI聊天积分', - style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), ), ), Text( '${u.aichatintegral}/${u.aichattotalintegral}', - style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), ), ], ), @@ -71,4 +85,3 @@ class AiChatStatsView extends GetView { }); } } - diff --git a/lib/modules/usage/views/meeting_stats_view.dart b/lib/modules/usage/views/meeting_stats_view.dart index e648dc14f..de27ba356 100644 --- a/lib/modules/usage/views/meeting_stats_view.dart +++ b/lib/modules/usage/views/meeting_stats_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../../../core/theme/app_colors.dart'; import '../controllers/meeting_stats_controller.dart'; +import '../../../data/models/user_Info.dart'; class MeetingStatsView extends GetView { const MeetingStatsView({Key? key}) : super(key: key); @@ -23,19 +24,24 @@ class MeetingStatsView extends GetView { PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( leading: IconButton( - icon: Icon(Icons.arrow_back_ios, color: isDarkMode ? Colors.white : Colors.black, size: 20.sp), + icon: Icon(Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, size: 20.sp), onPressed: () => Get.back(), ), backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.white, elevation: 0.5, title: Text( '会议统计', - style: TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), ), centerTitle: true, actions: [ IconButton( - icon: Icon(Icons.refresh, color: isDarkMode ? Colors.white : Colors.black), + icon: Icon(Icons.refresh, + color: isDarkMode ? Colors.white : Colors.black), onPressed: controller.refreshData, ), ], @@ -51,6 +57,8 @@ class MeetingStatsView extends GetView { padding: EdgeInsets.all(16.w), child: Column( children: [ + _buildIntegralCard(isDarkMode), + SizedBox(height: 16.h), _buildOverviewCard(isDarkMode), ], ), @@ -67,7 +75,10 @@ class MeetingStatsView extends GetView { color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2)), + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 2)), ], ), child: Obx(() => Row( @@ -99,7 +110,9 @@ class MeetingStatsView extends GetView { style: TextStyle( fontSize: 14.sp, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, - color: isSelected ? Colors.white : (isDarkMode ? Colors.white70 : Colors.black54), + color: isSelected + ? Colors.white + : (isDarkMode ? Colors.white70 : Colors.black54), ), ), ), @@ -115,7 +128,10 @@ class MeetingStatsView extends GetView { color: isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2)), + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 2)), ], ), child: Column( @@ -127,22 +143,45 @@ class MeetingStatsView extends GetView { SizedBox(width: 8.w), Obx(() => Text( '${controller.selectedTimeRange.value}概览', - style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), )), ], ), SizedBox(height: 16.h), Row( children: [ - Expanded(child: _buildStatItem('ASR调用次数', controller.formatNumber(stats.allAsrCalls), Icons.api, isDarkMode)), - Expanded(child: _buildStatItem('音频总时长', controller.formatDuration(stats.allAudioSeconds), Icons.mic, isDarkMode)), + Expanded( + child: _buildStatItem( + 'ASR调用次数', + controller.formatNumber(stats.allAsrCalls), + Icons.api, + isDarkMode)), + Expanded( + child: _buildStatItem( + '音频总时长', + controller.formatDuration(stats.allAudioSeconds), + Icons.mic, + isDarkMode)), ], ), SizedBox(height: 12.h), Row( children: [ - Expanded(child: _buildStatItem('音频文件大小', controller.formatBytes(stats.allAudioFileBytes), Icons.folder, isDarkMode)), - Expanded(child: _buildStatItem('转写字节', controller.formatNumber(stats.allTranscriptBytes), Icons.text_snippet, isDarkMode)), + Expanded( + child: _buildStatItem( + '音频文件大小', + controller.formatBytes(stats.allAudioFileBytes), + Icons.folder, + isDarkMode)), + Expanded( + child: _buildStatItem( + '转写字节', + controller.formatNumber(stats.allTranscriptBytes), + Icons.text_snippet, + isDarkMode)), ], ), ], @@ -150,22 +189,100 @@ class MeetingStatsView extends GetView { ); } - Widget _buildStatItem(String label, String value, IconData icon, bool isDarkMode) { + Widget _buildStatItem( + String label, String value, IconData icon, bool isDarkMode) { return Column( children: [ Icon(icon, color: AppColors.primary, size: 24.sp), SizedBox(height: 8.h), Text( value, - style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: isDarkMode ? Colors.white : Colors.black), + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black), ), SizedBox(height: 4.h), Text( label, - style: TextStyle(fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.black54), + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54), ), ], ); } -} + Widget _buildIntegralCard(bool isDarkMode) { + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode ? AppColors.cardBackground : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.stacked_line_chart, + color: AppColors.primary, size: 20.sp), + SizedBox(width: 8.w), + Text( + '积分统计', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + ], + ), + SizedBox(height: 16.h), + Obx(() { + final _ = User.refreshTick.value; + final u = User.instance; + return _buildIntegralRow( + '会议', + '${controller.formatNumber(u.meetintegral)}/${controller.formatNumber(u.meettotalintegral)}', + Icons.people, + isDarkMode, + ); + }), + ], + ), + ); + } + + Widget _buildIntegralRow( + String label, String value, IconData icon, bool isDarkMode) { + return Row( + children: [ + Icon(icon, color: AppColors.primary, size: 18.sp), + SizedBox(width: 12.w), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black), + ), + ), + Text( + value, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), + ), + ], + ); + } +} diff --git a/lib/modules/usage/views/translation_stats_view.dart b/lib/modules/usage/views/translation_stats_view.dart index 30d0501a6..4c98c9493 100644 --- a/lib/modules/usage/views/translation_stats_view.dart +++ b/lib/modules/usage/views/translation_stats_view.dart @@ -95,13 +95,13 @@ class TranslationStatsView extends GetView { padding: EdgeInsets.all(16.w), child: Column( children: [ + _buildIntegralCard(isDarkMode), + SizedBox(height: 16.h), if (controller.selectedTabIndex.value == 0) _buildRealtimeStatsCard(isDarkMode), SizedBox(height: 16.h), _buildOverviewCard(isDarkMode), SizedBox(height: 16.h), - _buildIntegralCard(isDarkMode), - SizedBox(height: 16.h), _buildApiCallsCard(isDarkMode), SizedBox(height: 16.h), _buildDataVolumeCard(isDarkMode), @@ -465,26 +465,12 @@ class TranslationStatsView extends GetView { final u = User.instance; return Column( children: [ - _buildIntegralRow( - 'AI聊天', - '${controller.formatNumber(u.aichatintegral)}/${controller.formatNumber(u.aichattotalintegral)}', - Icons.chat_bubble_outline, - isDarkMode, - ), - SizedBox(height: 12.h), _buildIntegralRow( '翻译', '${controller.formatNumber(u.tradeintegral)}/${controller.formatNumber(u.tradetotalintegral)}', Icons.translate, isDarkMode, ), - SizedBox(height: 12.h), - _buildIntegralRow( - '会议', - '${controller.formatNumber(u.meetintegral)}/${controller.formatNumber(u.meettotalintegral)}', - Icons.groups, - isDarkMode, - ), ], ); }), diff --git a/lib/modules/usage/views/usage_view.dart b/lib/modules/usage/views/usage_view.dart index 393884869..26531868f 100644 --- a/lib/modules/usage/views/usage_view.dart +++ b/lib/modules/usage/views/usage_view.dart @@ -7,6 +7,21 @@ import '../controllers/usage_controller.dart'; class UsageView extends GetView { const UsageView({super.key}); + // 1. 将导航栏配置提取为常量,提高可维护性 + static const _navItems = [ + _NavItem(Icons.chat_bubble_outline, 'AI chat'), + _NavItem(Icons.translate, 'interpretation'), + _NavItem(Icons.people, 'meeting'), + ]; + + // 2. 颜色配置集中管理 + Color _getBgColor(bool isDark) => + isDark ? Colors.black : const Color(0xFFF9FAFB); + Color _getUnselectedColor(bool isDark) => + isDark ? Colors.grey[500]! : const Color(0xFF6B7280); + Color _getSelectedColor(bool isDark) => + isDark ? Colors.blue[200]! : const Color(0xFF3B82F6); + @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -19,36 +34,69 @@ class UsageView extends GetView { children: controller.pageList, ), bottomNavigationBar: Obx( - () => BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.chat_bubble_outline), - label: 'AI聊天', - ), - BottomNavigationBarItem( - icon: const Icon(Icons.translate), - label: 'interpretation'.tr, + () { + final currentIndex = controller.pageIndex.value; + + return Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: isDarkMode ? Colors.grey[800]! : Colors.grey[200]!, + width: 0.5, + )), ), - BottomNavigationBarItem( - icon: const Icon(Icons.groups), - label: 'meeting'.tr, + // 3. 添加安全区域适配(针对刘海屏) + child: BottomNavigationBar( + // 4. 使用生成的导航项 + items: _navItems + .map((item) => BottomNavigationBarItem( + icon: Icon(item.icon), + activeIcon: Icon(item.icon, + color: _getSelectedColor(isDarkMode)), // 5. 激活态图标 + label: item.label.tr, // 统一处理国际化 + )) + .toList(), + + backgroundColor: _getBgColor(isDarkMode), + currentIndex: currentIndex, + unselectedItemColor: _getUnselectedColor(isDarkMode), + selectedItemColor: _getSelectedColor(isDarkMode), + unselectedFontSize: 10.sp, + selectedFontSize: 10.sp, + + // 6. 添加动画过渡效果 + onTap: (index) => _onItemTapped(index, currentIndex), + + type: BottomNavigationBarType.fixed, + // 7. 增加图标和文字的间距 + iconSize: 22.w, // 响应式图标尺寸 + selectedLabelStyle: TextStyle(height: 1.5), // 文字垂直间距 + unselectedLabelStyle: TextStyle(height: 1.5), ), - ], - backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFB), - currentIndex: controller.pageIndex.value, - unselectedItemColor: - isDarkMode ? Colors.grey[500] : const Color(0xFF6B7280), - selectedItemColor: const Color(0xFF3B82F6), - unselectedFontSize: 10.sp, - selectedFontSize: 10.sp, - onTap: (int index) { - if (index == controller.pageIndex.value) return; - controller.pageIndex.value = index; - controller.pageController.jumpToPage(index); - }, - type: BottomNavigationBarType.fixed, - ), + ); + }, ), ); } + + // 9. 抽离点击逻辑 + void _onItemTapped(int index, int currentIndex) { + if (index == currentIndex) return; + + // 10. 添加页面切换动画 + controller.pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + controller.pageIndex.value = index; + } +} + +// 11. 使用数据类管理导航项配置 +class _NavItem { + final IconData icon; + final String label; + + const _NavItem(this.icon, this.label); }