From 5d156d3a0239ce9369e1327d7cc2ece590761ff0 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Tue, 22 Jul 2025 22:48:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=96=B0=E8=AE=B0=E5=BD=95UI?= =?UTF-8?q?=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/recipe.dart | 50 +++++++---------- lib/api/session.dart | 2 +- lib/config/app_config.dart | 7 +++ lib/layout/index.dart | 31 +++------- lib/models/layout.dart | 16 ++++++ lib/utils/http_util.dart | 9 +-- lib/views/home.dart | 44 +++++++++++++-- lib/widgets/recipe/recipe_calendar.dart | 7 +-- lib/widgets/recipe/recipe_card.dart | 3 +- lib/widgets/recipe/recipe_list.dart | 7 +-- lib/widgets/recipe/recipe_timeline.dart | 75 ++++++++++++------------- 11 files changed, 135 insertions(+), 116 deletions(-) create mode 100644 lib/config/app_config.dart create mode 100644 lib/models/layout.dart diff --git a/lib/api/recipe.dart b/lib/api/recipe.dart index 1ec5c9d..396f3ff 100644 --- a/lib/api/recipe.dart +++ b/lib/api/recipe.dart @@ -4,43 +4,40 @@ import 'package:food_hub_app/utils/index.dart'; Future queryRecipeByIdApi(int id) { return HttpUtil().get( - "/food-service/food/recipe/$id", + "/food/recipe/$id", converter: (data) => Recipe.fromJson(data), ); } Future addRecipeApi(Recipe recipe) { - return HttpUtil().post("/food-service/food/food/recipe", data: recipe); + return HttpUtil().post("/food/food/recipe", data: recipe); } Future updateRecipeApi(int id, Recipe recipe) { - return HttpUtil().put( - "/food-service/food/food/recipe/$id", - data: recipe, - ); + return HttpUtil().put("/food/food/recipe/$id", data: recipe); } Future deleteRecipeApi(int id) { - return HttpUtil().delete("/food-service/food/food/recipe/$id"); + return HttpUtil().delete("/food/food/recipe/$id"); } Future> queryRecipeByUserApi(int id) { return HttpUtil().get>( - "/food-service/food/recipe/user/$id", + "/food/recipe/user/$id", converter: (data) => convertListResponse(data, Recipe.fromJson), ); } Future> queryRecipeUserFavouriteApi() { return HttpUtil().get>( - "/food-service/food/recipe/user/favourite", + "/food/recipe/user/favourite", converter: (data) => convertListResponse(data, Recipe.fromJson), ); } Future> queryRecipeApi(RecipeQuery recipeQuery) { return HttpUtil().get>( - "/food-service/food/recipe", + "/food/recipe", queryParameters: recipeQuery.toJson(), converter: (data) => convertListResponse(data, Recipe.fromJson), ); @@ -48,61 +45,56 @@ Future> queryRecipeApi(RecipeQuery recipeQuery) { Future> queryFoodNameListApi() { return HttpUtil().get>( - "/food-service/food/recipe/name", + "/food/recipe/name", converter: (data) => convertListResponse(data, (json) => json.toString()), ); } Future addRecordApi(Record record) { - return HttpUtil().post("/food-service/food/record", data: record); + return HttpUtil().post("/food/record", data: record); } Future updateRecordApi(int id, Record record) { - return HttpUtil().put("/food-service/food/record/$id", data: record); + return HttpUtil().put("/food/record/$id", data: record); } Future deleteRecordApi(int id) { - return HttpUtil().delete("/food-service/food/record/$id"); + return HttpUtil().delete("/food/record/$id"); } Future addRecipeCommentApi(int id, String content) { - return HttpUtil().post( - "/food-service/food/recipe/$id/comment", - data: content, - ); + return HttpUtil().post("/food/recipe/$id/comment", data: content); } Future addRecipeLikeApi(int id) { - return HttpUtil().post("/food-service/food/recipe/$id/like"); + return HttpUtil().post("/food/recipe/$id/like"); } Future deleteRecipeLikeApi(int id) { - return HttpUtil().delete("/food-service/food/recipe/$id/like"); + return HttpUtil().delete("/food/recipe/$id/like"); } Future addRecipeFavouriteApi(int id) { - return HttpUtil().post("/food-service/food/recipe/$id/favourite"); + return HttpUtil().post("/food/recipe/$id/favourite"); } Future deleteRecipeFavouriteApi(int id) { - return HttpUtil().delete("/food-service/food/recipe/$id/like"); + return HttpUtil().delete("/food/recipe/$id/like"); } Future> queryRecordApi(String startDate, String endDate) { return HttpUtil().get>( - "/food-service/food/record", - queryParameters: { - "startDate": startDate, - "endDate": endDate - }, + "/food/record", + queryParameters: {"startDate": startDate, "endDate": endDate}, converter: (data) => convertListResponse(data, Record.fromJson), ); } Future> queryCategoryApi() { return HttpUtil().get>( - "/food-service/food/category", - converter: (data) => convertListResponse(data, (json) => json.toString()), + "/food/category", + converter: + (data) => convertListResponse(data, (json) => json.toString()), ); } diff --git a/lib/api/session.dart b/lib/api/session.dart index 22cd8c0..616dc25 100644 --- a/lib/api/session.dart +++ b/lib/api/session.dart @@ -3,7 +3,7 @@ import 'package:food_hub_app/utils/http_util.dart'; Future loginApi(String username, String password) { return HttpUtil().post( - "/food-service/session", + "/session", queryParameters: {"username": username, "password": password}, converter: (data) => Session.fromJson(data), ); diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart new file mode 100644 index 0000000..570bf25 --- /dev/null +++ b/lib/config/app_config.dart @@ -0,0 +1,7 @@ +/// 应用信息 +class AppConfig { + // 网络配置 + // http://192.168.1.3:8100 + // http://14.103.235.151:81/food-service + static const String baseApiUrl = "http://14.103.235.151:81/food-service"; +} \ No newline at end of file diff --git a/lib/layout/index.dart b/lib/layout/index.dart index 44fd1a4..ae7dc8f 100644 --- a/lib/layout/index.dart +++ b/lib/layout/index.dart @@ -2,32 +2,15 @@ import 'package:flutter/material.dart'; class NavBar extends StatelessWidget { final int currentIndex; + final List navItems; final Function(int) onTap; - static const List navItems = [ - BottomNavigationBarItem( - icon: Icon(Icons.home_outlined), - activeIcon: Icon(Icons.home), - label: "记录", - ), - BottomNavigationBarItem( - icon: Icon(Icons.pie_chart_outline), - activeIcon: Icon(Icons.pie_chart), - label: "统计", - ), - BottomNavigationBarItem( - icon: Icon(Icons.group_outlined), - activeIcon: Icon(Icons.group), - label: "朋友圈", - ), - BottomNavigationBarItem( - icon: Icon(Icons.account_circle_outlined), - activeIcon: Icon(Icons.account_circle), - label: "我的", - ), - ]; - - const NavBar({super.key, required this.currentIndex, required this.onTap}); + const NavBar({ + super.key, + required this.currentIndex, + required this.onTap, + required this.navItems, + }); @override Widget build(BuildContext context) { diff --git a/lib/models/layout.dart b/lib/models/layout.dart new file mode 100644 index 0000000..066f5aa --- /dev/null +++ b/lib/models/layout.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +/// 导航栏 +class NavItem { + final String label; + final IconData icon; + final IconData activeIcon; + final Widget page; + + const NavItem({ + required this.label, + required this.icon, + required this.activeIcon, + required this.page, + }); +} diff --git a/lib/utils/http_util.dart b/lib/utils/http_util.dart index 19d754d..36feda0 100644 --- a/lib/utils/http_util.dart +++ b/lib/utils/http_util.dart @@ -1,5 +1,5 @@ import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; +import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/utils/sp_util.dart'; import 'package:food_hub_app/widgets/common/index.dart'; @@ -11,7 +11,6 @@ class HttpUtil { factory HttpUtil() => _instance; late Dio _dio; - String baseUrl = kDebugMode ? "http://172.29.101.108:8100" : "http://14.103.235.151:81"; // 请求头配置 Map headers = { @@ -25,7 +24,7 @@ class HttpUtil { HttpUtil._internal() { // 初始化Dio实例 BaseOptions options = BaseOptions( - baseUrl: baseUrl, + baseUrl: AppConfig.baseApiUrl, connectTimeout: Duration(seconds: timeout), receiveTimeout: Duration(seconds: timeout), headers: headers, @@ -81,10 +80,6 @@ class HttpUtil { T Function(dynamic data)? converter, }) async { try { - if (kDebugMode) { - path = path.replaceFirst('/food-service', ''); - } - Response response = await _dio.request( path, data: data, diff --git a/lib/views/home.dart b/lib/views/home.dart index 5ef27a1..7e481a4 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -1,5 +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'; @@ -15,13 +16,43 @@ class HomePage extends StatefulWidget { class _HomePage extends State { int _currentIndex = 0; - final List _tabPages = const [ - RecordPage(), - StatsPage(), - MomentPage(), - ProfilePage(), + 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(); + bool isShow(int index) { return index == 0 || index == 2; } @@ -43,10 +74,11 @@ class _HomePage extends State { ), backgroundColor: Color(0xFFF5F5F5), endDrawer: const SettingsDrawer(), - body: _tabPages[_currentIndex], + body: tabPages[_currentIndex], floatingActionButton: isShow(_currentIndex) ? addItemButton(context) : null, bottomNavigationBar: NavBar( + navItems: bottomNavItems, currentIndex: _currentIndex, onTap: (index) { setState(() { diff --git a/lib/widgets/recipe/recipe_calendar.dart b/lib/widgets/recipe/recipe_calendar.dart index 4c00c45..cb5a470 100644 --- a/lib/widgets/recipe/recipe_calendar.dart +++ b/lib/widgets/recipe/recipe_calendar.dart @@ -171,9 +171,9 @@ class _RecipeCalendarState extends State { decoration: BoxDecoration( color: isSelected - ? Colors.blue + ? Theme.of(context).primaryColor : isToday - ? Colors.grey[200] + ? Colors.grey[300] : Colors.transparent, shape: BoxShape.circle, ), @@ -184,7 +184,7 @@ class _RecipeCalendarState extends State { // 日期数字 Text( day.day.toString(), - style: TextStyle(color: isSelected ? Colors.white : Colors.black87), + style: TextStyle(color: isSelected ? Colors.white : Colors.black), ), // 底部红点 - 只在指定日期显示 if (shouldShowRedDot) @@ -207,7 +207,6 @@ class _RecipeCalendarState extends State { return Column( children: [ Card(elevation: 0, color: Colors.white, child: recipeCalendar()), - const SizedBox(height: 10), Expanded(child: dailyItem()), ], ); diff --git a/lib/widgets/recipe/recipe_card.dart b/lib/widgets/recipe/recipe_card.dart index f701501..581caa5 100644 --- a/lib/widgets/recipe/recipe_card.dart +++ b/lib/widgets/recipe/recipe_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.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/index.dart'; @@ -19,7 +20,7 @@ class RecipeCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.network( - 'http://172.29.101.108:8100/${recipe.recordList[0].imageUrl}', + '${AppConfig.baseApiUrl}/${recipe.recordList[0].imageUrl}', height: 200, width: double.infinity, fit: BoxFit.contain, diff --git a/lib/widgets/recipe/recipe_list.dart b/lib/widgets/recipe/recipe_list.dart index 1f3a025..d0f06ef 100644 --- a/lib/widgets/recipe/recipe_list.dart +++ b/lib/widgets/recipe/recipe_list.dart @@ -34,14 +34,11 @@ class _RecipeListState extends State { if (recipeList.isEmpty) { return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); } else { - return ListView.separated( + return ListView.builder( itemCount: recipeList.length, itemBuilder: (context, index) { return RecipeCard(recipe: recipeList[index]); - }, - separatorBuilder: (context, index) { - return const SizedBox(height: 10); - }, + } ); } } diff --git a/lib/widgets/recipe/recipe_timeline.dart b/lib/widgets/recipe/recipe_timeline.dart index 24eb0fe..76abe53 100644 --- a/lib/widgets/recipe/recipe_timeline.dart +++ b/lib/widgets/recipe/recipe_timeline.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/api/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/index.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; @@ -18,11 +19,11 @@ class _RecipeTimeline extends State { @override void initState() { super.initState(); - refreshRecord(); + refreshRecord(DateTime.now().year); } - Future refreshRecord() async { - final result = await queryRecordApi("2025-01-01", "2025-07-08"); + Future refreshRecord(int year) async { + final result = await queryRecordApi("$year-01-01", "$year-12-31"); setState(() { recordList = result; }); @@ -36,18 +37,15 @@ class _RecipeTimeline extends State { initialYear: DateTime.now().year, minYear: 2000, maxYear: 2100, - onYearChanged: (year) { - print('选中的年份: $year'); - }, - accentColor: Colors.blue, + onYearChanged: (year) => refreshRecord(year) ), - Expanded(child: TimelineContainer(recordList)) + Expanded(child: timelineContainer(recordList)), ], ); } } -Widget TimelineContainer(List recordList) { +Widget timelineContainer(List recordList) { if (recordList.isEmpty) { return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); } else { @@ -76,6 +74,30 @@ class TimelineCard extends StatelessWidget { const TimelineCard({super.key, required this.record}); + Widget cardContent() { + 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), + Image.network( + '${AppConfig.baseApiUrl}/${record.imageUrl}', + width: double.infinity, + height: 250, + fit: BoxFit.contain, + errorBuilder: + (context, error, stackTrace) => errorImageContainer(250), + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { return SizedBox( @@ -89,28 +111,7 @@ class TimelineCard extends StatelessWidget { record.date, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), - 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), - Image.network( - 'http://172.29.101.108:8100/${record.imageUrl}', - width: double.infinity, - height: 250, - fit: BoxFit.contain, - errorBuilder: - (context, error, stackTrace) => - errorImageContainer(250), - ), - ], - ), - ), - ), + cardContent(), ], ), ), @@ -123,7 +124,6 @@ class YearSelector extends StatefulWidget { final int? minYear; final int? maxYear; final Function(int) onYearChanged; - final Color? accentColor; const YearSelector({ super.key, @@ -131,17 +131,16 @@ class YearSelector extends StatefulWidget { required this.onYearChanged, this.minYear, this.maxYear, - this.accentColor, }); @override State createState() => _YearSelectorState(); } +// SingleTickerProviderStateMixin 动画控制器 class _YearSelectorState extends State with SingleTickerProviderStateMixin { late int _currentYear; - late Color _accentColor; // 用于动画效果 late AnimationController _animationController; @@ -151,9 +150,6 @@ class _YearSelectorState extends State void initState() { super.initState(); _currentYear = widget.initialYear; - _accentColor = widget.accentColor ?? Theme - .of(context) - .primaryColor; // 初始化动画控制器 _animationController = AnimationController( @@ -228,12 +224,13 @@ class _YearSelectorState extends State child: Text( '$_currentYear', style: TextStyle( - fontSize: 28, + fontSize: 24, fontWeight: FontWeight.bold, - color: _accentColor, + color: Theme.of(context).primaryColor, ), ), ), + SizedBox(width: 10), TDButton( icon: Icons.chevron_right, size: TDButtonSize.small,