From 21ff0ad94f95895a4b616ce34fd1b656e1fbe66d Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Tue, 18 Nov 2025 11:01:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E9=87=8D=E6=9E=84UI=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .metadata | 15 -- android/app/src/main/AndroidManifest.xml | 2 +- lib/layout/{index.dart => drawer.dart} | 47 ----- lib/layout/nav_bar.dart | 86 +++++++++ lib/models/layout.dart | 5 +- lib/views/home.dart | 162 +++++++---------- lib/views/login.dart | 2 +- lib/views/moment.dart | 85 ++++----- lib/views/recipe_detail.dart | 13 +- lib/views/record.dart | 18 +- lib/views/stats.dart | 71 +++----- lib/widgets/common/image.dart | 2 +- lib/widgets/common/index.dart | 29 ++- lib/widgets/common/year_selector.dart | 4 +- lib/widgets/moment/card.dart | 56 +++--- lib/widgets/recipe/calendar.dart | 70 +++---- lib/widgets/recipe/card.dart | 221 +++++++++-------------- lib/widgets/recipe/list.dart | 11 +- lib/widgets/recipe/timeline.dart | 141 +++++++-------- lib/widgets/stats/card.dart | 66 ++++--- 20 files changed, 501 insertions(+), 605 deletions(-) rename lib/layout/{index.dart => drawer.dart} (65%) create mode 100644 lib/layout/nav_bar.dart diff --git a/.metadata b/.metadata index 9a674c6..8b01539 100644 --- a/.metadata +++ b/.metadata @@ -15,21 +15,6 @@ migration: - platform: root create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: android - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: ios - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: linux - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: macos - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: web - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - platform: windows create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 09a279a..95d7db5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ navItems; - final Function(int) onTap; - - const NavBar({ - super.key, - required this.currentIndex, - required this.onTap, - required this.navItems, - }); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - border: Border(top: BorderSide(color: Theme.of(context).primaryColor)), - ), - child: BottomNavigationBar( - currentIndex: currentIndex, - iconSize: 25, - type: BottomNavigationBarType.fixed, - backgroundColor: Colors.white, - items: navItems, - onTap: onTap, - ), - ); - } -} - class SettingsDrawer extends StatelessWidget { const SettingsDrawer({super.key}); @@ -97,20 +67,3 @@ class SettingsDrawer extends StatelessWidget { ); } } - -List homeActions(BuildContext context) { - return [ - IconButton( - icon: Icon(Icons.search, color: Colors.white), - onPressed: () { - // 搜索功能 - }, - ), - // IconButton( - // icon: Icon(Icons.add, color: Colors.white), - // onPressed: () { - // Navigator.pushNamed(context, '/recordForm'); - // }, - // ), - ]; -} diff --git a/lib/layout/nav_bar.dart b/lib/layout/nav_bar.dart new file mode 100644 index 0000000..e5f484a --- /dev/null +++ b/lib/layout/nav_bar.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:food_hub_app/models/layout.dart'; +import 'package:food_hub_app/views/moment.dart'; +import 'package:food_hub_app/views/profile.dart'; +import 'package:food_hub_app/views/record.dart'; +import 'package:food_hub_app/views/stats.dart'; + +final List pageInfos = [ + PageInfo( + label: "记录", + icon: Icons.home_outlined, + activeIcon: Icons.home, + page: RecordPage(), + ), + PageInfo( + label: "统计", + icon: Icons.pie_chart_outline, + activeIcon: Icons.pie_chart, + page: StatsPage(), + ), + PageInfo( + label: "朋友圈", + icon: Icons.group_outlined, + activeIcon: Icons.group, + page: MomentPage(), + ), + PageInfo( + label: "我的", + icon: Icons.account_circle_outlined, + activeIcon: Icons.account_circle, + page: ProfilePage(), + ), +]; + +class NavBar extends StatelessWidget { + final int currentIndex; + + final Function(int) onTap; + + const NavBar({super.key, required this.currentIndex, required this.onTap}); + + List get items => + pageInfos + .map( + (item) => BottomNavigationBarItem( + icon: Icon(item.icon), + activeIcon: Icon(item.activeIcon), + label: item.label, + ), + ) + .toList(); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border(top: BorderSide(color: Theme.of(context).primaryColor)), + ), + child: BottomNavigationBar( + currentIndex: currentIndex, + iconSize: 25, + type: BottomNavigationBarType.fixed, + backgroundColor: Colors.white, + items: items, + onTap: onTap, + ), + ); + } +} + +List homeActions(BuildContext context) { + return [ + IconButton( + icon: Icon(Icons.search, color: Colors.white), + onPressed: () { + // 搜索功能 + }, + ), + // IconButton( + // icon: Icon(Icons.add, color: Colors.white), + // onPressed: () { + // Navigator.pushNamed(context, '/recordForm'); + // }, + // ), + ]; +} diff --git a/lib/models/layout.dart b/lib/models/layout.dart index 066f5aa..7489143 100644 --- a/lib/models/layout.dart +++ b/lib/models/layout.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; -/// 导航栏 -class NavItem { +class PageInfo { final String label; final IconData icon; final IconData activeIcon; final Widget page; - const NavItem({ + const PageInfo({ required this.label, required this.icon, required this.activeIcon, diff --git a/lib/views/home.dart b/lib/views/home.dart index eba8d41..0562bc1 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -1,10 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:food_hub_app/layout/index.dart'; -import 'package:food_hub_app/models/layout.dart'; -import 'package:food_hub_app/views/moment.dart'; -import 'package:food_hub_app/views/profile.dart'; -import 'package:food_hub_app/views/record.dart'; -import 'package:food_hub_app/views/stats.dart'; +import 'package:food_hub_app/layout/drawer.dart'; +import 'package:food_hub_app/layout/nav_bar.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -16,112 +12,79 @@ class HomePage extends StatefulWidget { class _HomePage extends State { int _currentIndex = 0; - final List navItems = [ - NavItem( - label: "记录", - icon: Icons.home_outlined, - activeIcon: Icons.home, - page: RecordPage(), - ), - NavItem( - label: "统计", - icon: Icons.pie_chart_outline, - activeIcon: Icons.pie_chart, - page: StatsPage(), - ), - NavItem( - label: "朋友圈", - icon: Icons.group_outlined, - activeIcon: Icons.group, - page: MomentPage(), - ), - NavItem( - label: "我的", - icon: Icons.account_circle_outlined, - activeIcon: Icons.account_circle, - page: ProfilePage(), - ), - ]; - - List get bottomNavItems => - navItems - .map( - (item) => BottomNavigationBarItem( - icon: Icon(item.icon), - activeIcon: Icon(item.activeIcon), - label: item.label, - ), - ) - .toList(); - - List get tabPages => navItems.map((item) => item.page).toList(); + List get tabPages => pageInfos.map((item) => item.page).toList(); // 显示底部弹窗 void _showBottomSheet() { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(16), - ), + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (context) => Container( - padding: const EdgeInsets.all(10), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - '请选择操作', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + builder: + (context) => Container( + padding: const EdgeInsets.all(10), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + '请选择操作', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + ListTile( + leading: Icon( + Icons.book, + color: Theme.of(context).primaryColor, + ), + title: const Text('新增菜谱'), + onTap: () { + Navigator.pop(context); + _handleAddRecipe(); + }, + ), + ListTile( + leading: Icon( + Icons.note_add, + color: Theme.of(context).primaryColor, + ), + title: const Text('新增记录'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, "/recordForm"); + }, + ), + ListTile( + leading: Icon( + Icons.group, + color: Theme.of(context).primaryColor, + ), + title: const Text('发布朋友圈'), + onTap: () { + Navigator.pop(context); + _handleAddRecord(); + }, + ), + ], ), - const SizedBox(height: 10), - ListTile( - leading: Icon(Icons.book, color: Theme.of(context).primaryColor), - title: const Text('新增菜谱'), - onTap: () { - Navigator.pop(context); - _handleAddRecipe(); - }, - ), - ListTile( - leading: Icon(Icons.note_add, color: Theme.of(context).primaryColor), - title: const Text('新增记录'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, "/recordForm"); - }, - ), - ListTile( - leading: Icon(Icons.group, color: Theme.of(context).primaryColor), - title: const Text('发布朋友圈'), - onTap: () { - Navigator.pop(context); - _handleAddRecord(); - }, - ) - ], - ), - ), + ), ); } // 处理添加菜谱 void _handleAddRecipe() { // 这里添加跳转或处理添加菜谱的逻辑 - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('添加菜谱功能')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('添加菜谱功能'))); } // 处理添加记录 void _handleAddRecord() { // 这里添加跳转或处理添加记录的逻辑 - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('添加记录功能')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('添加记录功能'))); } @override @@ -129,8 +92,8 @@ class _HomePage extends State { return Scaffold( appBar: AppBar( centerTitle: true, - title: Text('Food Hub', style: TextStyle(color: Colors.white)), - backgroundColor: Theme.of(context).primaryColor, + title: Text('食光集', style: TextStyle(color: Colors.white)), + backgroundColor: Theme.of(context).colorScheme.primary, leading: Builder( builder: (context) { return IconButton( @@ -143,17 +106,20 @@ class _HomePage extends State { actions: homeActions(context), ), backgroundColor: Color(0xFFF5F5F5), - drawer: const SettingsDrawer(), - body: tabPages[_currentIndex], + drawer: SettingsDrawer(), + body: Padding( + padding: EdgeInsets.all(4), + child: tabPages[_currentIndex], + ), floatingActionButton: FloatingActionButton( backgroundColor: Theme.of(context).primaryColor, onPressed: _showBottomSheet, shape: const CircleBorder(), + mini: true, child: const Icon(Icons.add, color: Colors.white, size: 30), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: NavBar( - navItems: bottomNavItems, currentIndex: _currentIndex, onTap: (index) { setState(() { diff --git a/lib/views/login.dart b/lib/views/login.dart index 1dd0e18..f2e0ad7 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -17,7 +17,7 @@ class LoginPage extends StatefulWidget { class _LoginPage extends State { final GlobalKey _formKey = GlobalKey(); - String _username = "", _password = ""; + String _username = "Cxx0822", _password = "19940822Cxx"; bool _isRemember = false; bool _isObscure = true; Color _eyeColor = Colors.grey; diff --git a/lib/views/moment.dart b/lib/views/moment.dart index ad746d7..c6d6d9f 100644 --- a/lib/views/moment.dart +++ b/lib/views/moment.dart @@ -2,8 +2,8 @@ import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:food_hub_app/apis/moment.dart'; import 'package:food_hub_app/models/moment.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/moment/card.dart'; -import 'package:tdesign_flutter/tdesign_flutter.dart'; class MomentPage extends StatefulWidget { const MomentPage({super.key}); @@ -126,66 +126,61 @@ class _MomentPageState extends State { return EasyRefresh( controller: _freshController, onRefresh: _onRefresh, - child: const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'), + child: buildEmptyData(), ); } // 有数据时显示列表 return Stack( children: [ - Padding( - padding: const EdgeInsets.all(5), - child: EasyRefresh( - controller: _freshController, - header: ClassicHeader( - dragText: '下拉刷新', - armedText: '释放刷新', - readyText: '准备刷新', - processingText: '刷新中...', - processedText: '刷新完成', - failedText: '刷新失败', - noMoreText: '没有更多数据', - showText: true, - messageText: '更新于 %T', - showMessage: true, - ), - footer: ClassicFooter( - dragText: '上拉加载', - armedText: '释放加载', - readyText: '准备加载', - processingText: '加载中...', - processedText: '加载完成', - failedText: '加载失败', - noMoreText: '没有更多数据', - showText: true, - messageText: '更新于 %T', - showMessage: true, - ), - onRefresh: _onRefresh, - onLoad: _onLoad, - child: ListView.builder( - controller: _scrollController, - itemCount: momentList.length, - itemBuilder: (context, index) { - return MomentCard(moment: momentList[index]); - }, - ), + EasyRefresh( + controller: _freshController, + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '准备刷新', + processingText: '刷新中...', + processedText: '刷新完成', + failedText: '刷新失败', + noMoreText: '没有更多数据', + showText: true, + messageText: '更新于 %T', + showMessage: true, + ), + footer: ClassicFooter( + dragText: '上拉加载', + armedText: '释放加载', + readyText: '准备加载', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败', + noMoreText: '没有更多数据', + showText: true, + messageText: '更新于 %T', + showMessage: true, + ), + onRefresh: _onRefresh, + onLoad: _onLoad, + child: ListView.builder( + controller: _scrollController, + itemCount: momentList.length, + itemBuilder: (context, index) { + return MomentCard(moment: momentList[index]); + }, ), ), // 返回顶部按钮 if (_showScrollToTop) Positioned( - right: 10, - bottom: 20, + right: 0, + bottom: 0, child: FloatingActionButton( onPressed: _scrollToTop, - backgroundColor: Colors.white, + backgroundColor: Theme.of(context).colorScheme.primary, elevation: 5, mini: true, - child: const Icon( - Icons.arrow_upward - ), + child: const Icon(Icons.arrow_upward, color: Colors.white), ), ), ], diff --git a/lib/views/recipe_detail.dart b/lib/views/recipe_detail.dart index ccab5ba..3e583b3 100644 --- a/lib/views/recipe_detail.dart +++ b/lib/views/recipe_detail.dart @@ -72,14 +72,12 @@ class _RecipeDetailState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('菜谱信息', style: TextStyle(color: Colors.white)), - backgroundColor: Theme.of(context).primaryColor, + title: Text('菜谱信息'), leading: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), + icon: Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), ), - backgroundColor: Color(0xFFF5F5F5), body: SingleChildScrollView( child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()), ), @@ -224,8 +222,9 @@ class _RecipeDetailState extends State { required String title, required Widget content, }) { - return cardContainer( - Column( + return buildCard( + context: context, + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( @@ -241,7 +240,7 @@ class _RecipeDetailState extends State { SizedBox(height: 5), content, ], - ), + ) ); } } diff --git a/lib/views/record.dart b/lib/views/record.dart index 0cc0b5a..8169bb9 100644 --- a/lib/views/record.dart +++ b/lib/views/record.dart @@ -47,17 +47,15 @@ class _RecordPageState extends State backgroundColor: Colors.white, showIndicator: true, ), + SizedBox(height: 8), Expanded( - child: Padding( - padding: EdgeInsets.all(5), - child: TabBarView( - controller: _tabController, - children: [ - RecipeList(), - RecipeCalendar(), - RecipeTimeline(), - ], - ), + child: TabBarView( + controller: _tabController, + children: [ + RecipeList(), + RecipeCalendar(), + RecipeTimeline(), + ], ), ), ], diff --git a/lib/views/stats.dart b/lib/views/stats.dart index def3374..a26d305 100644 --- a/lib/views/stats.dart +++ b/lib/views/stats.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/apis/stats.dart'; import 'package:food_hub_app/models/stats.dart'; import 'package:food_hub_app/widgets/common/chart.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/stats/card.dart'; class StatsPage extends StatefulWidget { @@ -45,7 +46,10 @@ class _StatsPage extends State { rankStats = result4; if (recordStats.isNotEmpty) { - double sumValue = recordStats.fold(0.0, (sum, item) => sum + item.value); + double sumValue = recordStats.fold( + 0.0, + (sum, item) => sum + item.value, + ); averageRecordCount = sumValue / recordStats.length; } }); @@ -54,55 +58,22 @@ class _StatsPage extends State { @override Widget build(BuildContext context) { return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(5), - child: Column( - children: [ - _buildSummaryStats(), - SizedBox( - height: chartHeight, - child: Card( - color: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: const EdgeInsets.all(10), - child: _buildRecordStats(), - ), - ), - ), - SizedBox( - height: chartHeight, - child: Card( - color: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: const EdgeInsets.all(10), - child: _buildCategoryStats(), - ), - ), - ), - SizedBox( - height: chartHeight, - child: Card( - color: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: const EdgeInsets.all(10), - child: _buildRankStats(), - ), - ), - ), - ], - ), + child: Column( + children: [ + _buildSummaryStats(), + SizedBox( + height: chartHeight, + child: buildCard(context: context, child: _buildRecordStats()), + ), + SizedBox( + height: chartHeight, + child: buildCard(context: context, child: _buildCategoryStats()), + ), + SizedBox( + height: chartHeight, + child: buildCard(context: context, child: _buildRankStats()), + ), + ], ), ); } diff --git a/lib/widgets/common/image.dart b/lib/widgets/common/image.dart index 91e70d8..f2f3049 100644 --- a/lib/widgets/common/image.dart +++ b/lib/widgets/common/image.dart @@ -86,7 +86,7 @@ class _ImagePreviewPageState extends State { } } -Widget networkImage(String url) { +Widget buildNetworkImage(String url) { return ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( diff --git a/lib/widgets/common/index.dart b/lib/widgets/common/index.dart index 40a727d..1f06a9d 100644 --- a/lib/widgets/common/index.dart +++ b/lib/widgets/common/index.dart @@ -27,18 +27,29 @@ InputDecoration formInputDecoration({ ); } -Widget cardContainer(Widget content) { +Widget buildCard({required BuildContext context, required Widget child}) { + final colors = Theme.of(context).colorScheme; return Card( elevation: 0, - color: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: Padding( - padding: const EdgeInsets.all(10), - child: content, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.outline.withAlpha(50), width: 1), + ), + child: Padding(padding: EdgeInsets.all(8), child: child), ), ); } +Widget buildEmptyData() { + return Center( + child: Text('暂无数据', style: TextStyle(fontSize: 16, color: Colors.grey)), + ); +} + /// 圆形图标按钮 Widget circleIconButton({ required IconData icon, @@ -48,14 +59,14 @@ Widget circleIconButton({ return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - fixedSize: Size(32, 32), + fixedSize: Size(26, 26), shape: CircleBorder(), elevation: 0, - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: Theme.of(context).colorScheme.primary, padding: EdgeInsets.zero, minimumSize: const Size(0, 0), ), - child: Icon(icon, color: Colors.white, size: 18), + child: Icon(icon, color: Colors.white), ); } diff --git a/lib/widgets/common/year_selector.dart b/lib/widgets/common/year_selector.dart index 1c8b751..5c49296 100644 --- a/lib/widgets/common/year_selector.dart +++ b/lib/widgets/common/year_selector.dart @@ -93,6 +93,7 @@ class _YearSelectorState extends State icon: Icons.chevron_left, onPressed: () => _previousYear(), ), + SizedBox(width: 5), // 年份显示 AnimatedBuilder( animation: _scaleAnimation, @@ -102,12 +103,13 @@ class _YearSelectorState extends State child: Text( '$_currentYear', style: TextStyle( - fontSize: 24, + fontSize: 18, fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor, ), ), ), + SizedBox(width: 5), circleIconButton( context: context, icon: Icons.chevron_right, diff --git a/lib/widgets/moment/card.dart b/lib/widgets/moment/card.dart index 0c27409..3281d28 100644 --- a/lib/widgets/moment/card.dart +++ b/lib/widgets/moment/card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/models/moment.dart'; import 'package:food_hub_app/widgets/common/image.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; class MomentCard extends StatelessWidget { @@ -11,37 +12,32 @@ class MomentCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - elevation: 0, - color: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildAvatar(context), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildNickname(), - const SizedBox(height: 5), - _buildContent(), - if (moment.imageList.isNotEmpty) ...[ - const SizedBox(height: 8), - _buildPostImages(context), - ], - const SizedBox(height: 10), - _buildTime(), - const SizedBox(height: 5), - _buildActionButtons(), + return buildCard( + context: context, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAvatar(context), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildNickname(), + const SizedBox(height: 5), + _buildContent(), + if (moment.imageList.isNotEmpty) ...[ + const SizedBox(height: 8), + _buildPostImages(context), ], - ), + const SizedBox(height: 10), + _buildTime(), + const SizedBox(height: 5), + _buildActionButtons(), + ], ), - ], - ), + ), + ], ), ); } @@ -132,7 +128,7 @@ class MomentCard extends StatelessWidget { // 点击图片时,跳转到预览页面 onTap: () => imageTapClick(index), // 原图片组件 - child: networkImage(imageUrls[index]), + child: buildNetworkImage(imageUrls[index]), ); }), ); diff --git a/lib/widgets/recipe/calendar.dart b/lib/widgets/recipe/calendar.dart index 64ea802..e1ce1a7 100644 --- a/lib/widgets/recipe/calendar.dart +++ b/lib/widgets/recipe/calendar.dart @@ -5,7 +5,6 @@ import 'package:food_hub_app/utils/date_util.dart'; import 'package:food_hub_app/utils/index.dart'; import 'package:food_hub_app/widgets/common/index.dart'; import 'package:table_calendar/table_calendar.dart'; -import 'package:tdesign_flutter/tdesign_flutter.dart'; class RecipeCalendar extends StatefulWidget { const RecipeCalendar({super.key}); @@ -90,49 +89,50 @@ class _RecipeCalendarState extends State { ); } - Widget dailyItem() { - return Card( - elevation: 0, - color: Colors.white, - child: Padding( - padding: EdgeInsets.all(5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(formatDateTime(_selectedDay, 'MM月dd日 EEEE')), - if (selectRecordList.isEmpty) - TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据') - else - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: selectRecordList.length, - itemBuilder: (context, index) { - return recipeRecordItem(selectRecordList[index]); - }, - ), - ], - ), - ), + Widget dailyItem(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(formatDateTime(_selectedDay, 'MM月dd日 EEEE')), + if (selectRecordList.isEmpty) + buildEmptyData() + else + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: selectRecordList.length, + itemBuilder: (context, index) { + return recipeRecordItem(context, selectRecordList[index]); + }, + ), + ], ); } - Widget recipeRecordItem(Record record) { + Widget recipeRecordItem(BuildContext context, Record record) { + final colors = Theme.of(context).colorScheme; + return Card( elevation: 0, - color: Color(0xFFF5F5DC), + color: colors.inversePrimary, child: Padding( padding: EdgeInsets.all(10), child: Row( children: [ - TDAvatar( - size: TDAvatarSize.medium, - type: TDAvatarType.customText, - text: record.category[0], + CircleAvatar( + radius: 24, + backgroundColor: colors.primary, + child: Text( + record.category[0], + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), ), SizedBox(width: 10), Expanded( - // 使用Expanded让文本区域占据剩余空间 child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -149,7 +149,7 @@ class _RecipeCalendarState extends State { circleIconButton( context: context, icon: Icons.chevron_right, - onPressed: () => {} + onPressed: () => {}, ), ], ), @@ -205,8 +205,8 @@ class _RecipeCalendarState extends State { Widget build(BuildContext context) { return Column( children: [ - Card(elevation: 0, color: Colors.white, child: recipeCalendar()), - Expanded(child: dailyItem()), + buildCard(context: context, child: recipeCalendar()), + Expanded(child: buildCard(context: context, child: dailyItem(context))), ], ); } diff --git a/lib/widgets/recipe/card.dart b/lib/widgets/recipe/card.dart index 7a45842..9262b09 100644 --- a/lib/widgets/recipe/card.dart +++ b/lib/widgets/recipe/card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/models/recipe.dart'; @@ -18,150 +17,106 @@ class RecipeCard extends StatelessWidget { .map((item) => '${AppConfig.baseApiUrl}/${item.imageUrl}') .toList(); - Widget buildCarouselItem(String url) { - return Builder( - builder: (BuildContext context) { - return AspectRatio( - aspectRatio: 2, - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: networkImage(url), - ), - ); - }, + Widget buildRecipeContent(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + recipe.name, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Row( + children: [ + circleIconButton( + context: context, + icon: Icons.edit, + onPressed: () => {}, + ), + SizedBox(width: 8), + circleIconButton( + context: context, + icon: Icons.book, + onPressed: + () => Navigator.pushNamed( + context, + '/recipeDetail', + arguments: {'id': recipe.id}, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + _buildIconText( + icon: Icons.food_bank, + text: recipe.category, + color: Color(0xFF6A5ACD), + ), + const SizedBox(width: 12), + _buildIconText( + icon: Icons.thumb_up_alt_outlined, + text: recipe.likeCount.toString(), + color: Color(0xFFDC143C), + ), + const SizedBox(width: 12), + _buildIconText( + icon: Icons.star_outline, + text: recipe.favouriteCount.toString(), + color: Color(0xFF20B2AA), + ), + const SizedBox(width: 12), + _buildIconText( + icon: Icons.comment_outlined, + text: recipe.commentCount.toString(), + color: Color(0xFFDAC570), + ), + ], + ), + Row( + children: [ + _buildIconText( + icon: Icons.date_range, + text: recipe.recordList[0].date, + color: Color(0xFF32CD32), + ), + ], + ), + ], + ), + ], ); } - Widget buildRecipeCarousel() { - return FlutterCarousel( - // 轮播项 - items: - imageUrls.map((url) { - return buildCarouselItem(url); - }).toList(), - // 轮播配置 - options: FlutterCarouselOptions( - height: 300, - autoPlay: imageUrls.length > 1, - enableInfiniteScroll: imageUrls.length > 1, - autoPlayInterval: const Duration(seconds: 3), - viewportFraction: 0.9, - showIndicator: true, - ), - ); - } - - return Card( - shape: RoundedRectangleBorder( - side: BorderSide(color: Theme.of(context).primaryColor, width: 1.0), - borderRadius: BorderRadius.circular(10.0), - ), - elevation: 0, - color: Colors.white, - clipBehavior: Clip.antiAlias, + return buildCard( + context: context, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildRecipeCarousel(), - Divider( - height: 1, - thickness: 1, - color: Theme.of(context).primaryColor, - indent: 0, - endIndent: 0, - ), - Padding( - padding: const EdgeInsets.all(10), - child: _buildRecipeContent(context), + AspectRatio( + aspectRatio: 1.5, + child: buildNetworkImage(imageUrls.first), ), + SizedBox(height: 8), + buildRecipeContent(context), ], ), ); } - Widget _buildRecipeContent(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - recipe.name, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - maxLines: 1, // 限制单行,避免挤压按钮 - overflow: TextOverflow.ellipsis, - ), - ), - Row( - children: [ - circleIconButton( - context: context, - icon: Icons.edit, - onPressed: () => {}, - ), - circleIconButton( - context: context, - icon: Icons.book, - onPressed: - () => Navigator.pushNamed( - context, - '/recipeDetail', - arguments: {'id': recipe.id}, - ), - ), - ], - ), - ], - ), - const SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - children: [ - _buildIconText( - icon: Icons.food_bank, - text: recipe.category, - color: Theme.of(context).primaryColor, - ), - const SizedBox(width: 12), - _buildIconText( - icon: Icons.thumb_up_alt_outlined, - text: recipe.likeCount.toString(), - color: Colors.red, - ), - const SizedBox(width: 12), - _buildIconText( - icon: Icons.star_outline, - text: recipe.favouriteCount.toString(), - color: Colors.blue, - ), - const SizedBox(width: 12), - _buildIconText( - icon: Icons.comment_outlined, - text: recipe.commentCount.toString(), - color: Colors.deepOrange, - ), - ], - ), - Row( - children: [ - _buildIconText( - icon: Icons.date_range, - text: recipe.recordList[0].date, - color: Colors.red, - ), - ], - ), - ], - ), - ], - ); - } - // 通用图标文本组件 Widget _buildIconText({ required IconData icon, diff --git a/lib/widgets/recipe/list.dart b/lib/widgets/recipe/list.dart index 0059e05..ce7d7ee 100644 --- a/lib/widgets/recipe/list.dart +++ b/lib/widgets/recipe/list.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/apis/recipe.dart'; import 'package:food_hub_app/models/recipe.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/recipe/card.dart'; -import 'package:tdesign_flutter/tdesign_flutter.dart'; class RecipeList extends StatefulWidget { const RecipeList({super.key}); @@ -31,13 +31,16 @@ class _RecipeListState extends State { @override Widget build(BuildContext context) { if (recipeSummaryList.isEmpty) { - return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); + return buildEmptyData(); } else { - return ListView.builder( + return ListView.separated( itemCount: recipeSummaryList.length, itemBuilder: (context, index) { return RecipeCard(recipe: recipeSummaryList[index]); - } + }, + separatorBuilder: (context, index) { + return SizedBox(height: 10); + }, ); } } diff --git a/lib/widgets/recipe/timeline.dart b/lib/widgets/recipe/timeline.dart index 7fddec4..8284a0c 100644 --- a/lib/widgets/recipe/timeline.dart +++ b/lib/widgets/recipe/timeline.dart @@ -3,6 +3,7 @@ import 'package:food_hub_app/apis/recipe.dart'; import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/models/recipe.dart'; import 'package:food_hub_app/widgets/common/image.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/common/year_selector.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'package:timelines_plus/timelines_plus.dart'; @@ -30,55 +31,7 @@ class _RecipeTimeline extends State { }); } - @override - Widget build(BuildContext context) { - return Column( - children: [ - YearSelector( - initialYear: DateTime.now().year, - minYear: 2000, - maxYear: 2100, - onYearChanged: (year) => refreshRecord(year), - ), - Expanded(child: timelineContainer(recordList)), - ], - ); - } -} - -Widget timelineContainer(List recordList) { - if (recordList.isEmpty) { - return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); - } else { - return Timeline.tileBuilder( - theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0), - builder: TimelineTileBuilder.connected( - itemCount: recordList.length, - connectorBuilder: - (context, index, type) => Connector.solidLine( - thickness: 2, - color: Theme.of(context).primaryColor, - ), - indicatorBuilder: (context, index) { - return Indicator.dot( - size: 12.0, - color: Theme.of(context).primaryColor, - ); - }, - contentsBuilder: (context, index) { - return TimelineCard(record: recordList[index]); - }, - ), - ); - } -} - -class TimelineCard extends StatelessWidget { - final Record record; - - const TimelineCard({super.key, required this.record}); - - Widget cardContent(BuildContext context) { + Widget buildTimelineCard(BuildContext context, Record record) { final imageUrls = ['${AppConfig.baseApiUrl}/${record.imageUrl}']; void imageTapClick() { @@ -91,45 +44,73 @@ class TimelineCard extends StatelessWidget { ); } - return Card( - elevation: 0, - color: Colors.white, - child: Padding( - padding: EdgeInsets.all(10), - child: Column( - children: [ - Text(record.name, style: TextStyle(fontSize: 16)), - const SizedBox(height: 5), - GestureDetector( - onTap: () => imageTapClick(), - child: AspectRatio( - aspectRatio: 1.5, - child: networkImage(imageUrls[0]), + return buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + record.date, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + Column( + children: [ + Text(record.name, style: TextStyle(fontSize: 16)), + const SizedBox(height: 5), + GestureDetector( + onTap: () => imageTapClick(), + child: AspectRatio( + aspectRatio: 1.5, + child: buildNetworkImage(imageUrls.first), + ), ), - ), - ], - ), + ], + ), + ], ), ); } + Widget buildTimeline(BuildContext context, List recordList) { + if (recordList.isEmpty) { + return buildEmptyData(); + } else { + return Timeline.tileBuilder( + theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0), + builder: TimelineTileBuilder.connected( + itemCount: recordList.length, + connectorBuilder: + (context, index, type) => Connector.solidLine( + thickness: 2, + color: Theme.of(context).colorScheme.primary, + ), + indicatorBuilder: (context, index) { + return Indicator.dot( + size: 12.0, + color: Theme.of(context).colorScheme.primary, + ); + }, + contentsBuilder: (context, index) { + return buildTimelineCard(context, recordList[index]); + }, + ), + ); + } + } + @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.only(left: 10, bottom: 5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - record.date, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - cardContent(context), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + YearSelector( + initialYear: DateTime.now().year, + minYear: 2000, + maxYear: 2100, + onYearChanged: (year) => refreshRecord(year), ), - ), + Expanded(child: buildTimeline(context, recordList)), + ], ); } } diff --git a/lib/widgets/stats/card.dart b/lib/widgets/stats/card.dart index 0485d5e..e01a314 100644 --- a/lib/widgets/stats/card.dart +++ b/lib/widgets/stats/card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/models/stats.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; class StatisticCard extends StatelessWidget { final IconData icon; @@ -19,41 +20,36 @@ class StatisticCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - color: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 40, - height: 40, - child: Icon(icon, color: color, size: 40), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(title, style: TextStyle(fontSize: 20, color: color)), - const SizedBox(height: 4), - Row( - children: [ - Text( - value.toString(), - style: TextStyle(fontSize: 20, color: color), - ), - const SizedBox(width: 5), - Text(unit, style: TextStyle(fontSize: 20, color: color)), - ], - ), - ], - ), - ], - ), + return buildCard( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 40, + height: 40, + child: Icon(icon, color: color, size: 40), + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(title, style: TextStyle(fontSize: 20, color: color)), + const SizedBox(height: 4), + Row( + children: [ + Text( + value.toString(), + style: TextStyle(fontSize: 20, color: color), + ), + const SizedBox(width: 5), + Text(unit, style: TextStyle(fontSize: 20, color: color)), + ], + ), + ], + ), + ], ), ); }