From 504ee073ceb2cb4a7c5e98aabc81a93acee4b32a Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Thu, 31 Jul 2025 22:55:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E8=8F=9C=E8=B0=B1?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E4=BF=A1=E6=81=AF=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/recipe.dart | 12 +- lib/main.dart | 6 +- lib/models/recipe.dart | 156 ++++++++--- lib/models/recipe.g.dart | 191 ++++++++++---- lib/views/moment.dart | 12 +- lib/views/recipe_detail.dart | 247 ++++++++++++++++++ .../{recordForm.dart => record_form.dart} | 0 lib/widgets/common/index.dart | 14 + lib/widgets/recipe/card.dart | 9 +- lib/widgets/recipe/detail.dart | 227 ---------------- lib/widgets/recipe/list.dart | 10 +- lib/widgets/recipe/timeline.dart | 24 +- 12 files changed, 556 insertions(+), 352 deletions(-) create mode 100644 lib/views/recipe_detail.dart rename lib/views/{recordForm.dart => record_form.dart} (100%) delete mode 100644 lib/widgets/recipe/detail.dart diff --git a/lib/api/recipe.dart b/lib/api/recipe.dart index 396f3ff..7ed5e48 100644 --- a/lib/api/recipe.dart +++ b/lib/api/recipe.dart @@ -2,10 +2,10 @@ import 'package:food_hub_app/models/recipe.dart'; import 'package:food_hub_app/utils/http_util.dart'; import 'package:food_hub_app/utils/index.dart'; -Future queryRecipeByIdApi(int id) { - return HttpUtil().get( +Future queryRecipeByIdApi(int id) { + return HttpUtil().get( "/food/recipe/$id", - converter: (data) => Recipe.fromJson(data), + converter: (data) => RecipeDetail.fromJson(data), ); } @@ -35,11 +35,11 @@ Future> queryRecipeUserFavouriteApi() { ); } -Future> queryRecipeApi(RecipeQuery recipeQuery) { - return HttpUtil().get>( +Future> queryRecipeApi(RecipeQuery recipeQuery) { + return HttpUtil().get>( "/food/recipe", queryParameters: recipeQuery.toJson(), - converter: (data) => convertListResponse(data, Recipe.fromJson), + converter: (data) => convertListResponse(data, RecipeSummary.fromJson), ); } diff --git a/lib/main.dart b/lib/main.dart index f3c8e65..a50733b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,8 +3,8 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:food_hub_app/utils/sp_util.dart'; import 'package:food_hub_app/views/home.dart'; import 'package:food_hub_app/views/login.dart'; -import 'package:food_hub_app/views/recordForm.dart'; -import 'package:food_hub_app/widgets/recipe/detail.dart'; +import 'package:food_hub_app/views/record_form.dart'; +import 'package:food_hub_app/views/recipe_detail.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -47,7 +47,7 @@ class MyApp extends StatelessWidget { routes: { '/home': (context) => HomePage(), '/recordForm': (context) => RecordFormPage(), - '/recipeDetail': (context) => RecipeDetail(), + '/recipeDetail': (context) => RecipeDetailPage(), }, ); } diff --git a/lib/models/recipe.dart b/lib/models/recipe.dart index 795216f..704f795 100644 --- a/lib/models/recipe.dart +++ b/lib/models/recipe.dart @@ -4,43 +4,43 @@ part 'recipe.g.dart'; /// 食材信息 @JsonSerializable() -class Material { +class RecipeMaterial { String type; String name; String amount; - Material({required this.type, required this.name, required this.amount}); + RecipeMaterial({required this.type, required this.name, required this.amount}); - factory Material.fromJson(Map json) => - _$MaterialFromJson(json); + factory RecipeMaterial.fromJson(Map json) => + _$RecipeMaterialFromJson(json); - Map toJson() => _$MaterialToJson(this); + Map toJson() => _$RecipeMaterialToJson(this); } /// 步骤信息 @JsonSerializable() -class Step { +class RecipeStep { int sort; String content; String imageUrl; - Step({required this.sort, required this.content, required this.imageUrl}); + RecipeStep({required this.sort, required this.content, required this.imageUrl}); - factory Step.fromJson(Map json) => _$StepFromJson(json); + factory RecipeStep.fromJson(Map json) => _$RecipeStepFromJson(json); - Map toJson() => _$StepToJson(this); + Map toJson() => _$RecipeStepToJson(this); } /// 评论信息 @JsonSerializable() -class Comment { +class RecipeComment { int id; String username; String avatar; String content; String date; - Comment({ + RecipeComment({ required this.id, required this.username, required this.avatar, @@ -48,10 +48,10 @@ class Comment { required this.date, }); - factory Comment.fromJson(Map json) => - _$CommentFromJson(json); + factory RecipeComment.fromJson(Map json) => + _$RecipeCommentFromJson(json); - Map toJson() => _$CommentToJson(this); + Map toJson() => _$RecipeCommentToJson(this); } /// 成果信息 @@ -94,44 +94,44 @@ class RecipeQuery { /// 菜谱信息 @JsonSerializable() class Recipe { - int? id; + int id; String name; String category; double recommendRate; - String? remark; + String remark; bool isShare; - int? userId; - String? username; - String? avatar; - List? materialList; - List? stepList; + int userId; + String username; + String avatar; + List materialList; + List stepList; List recordList; - List? likeList; - int? likeCount; - List? favouriteList; - int? favouriteCount; - List? commentList; - int? commentCount; + List likeList; + int likeCount; + List favouriteList; + int favouriteCount; + List commentList; + int commentCount; Recipe({ - this.id, + required this.id, required this.name, required this.category, required this.recommendRate, - this.remark, + required this.remark, required this.isShare, - this.userId, - this.username, - this.avatar, - this.materialList, - this.stepList, + required this.userId, + required this.username, + required this.avatar, + required this.materialList, + required this.stepList, required this.recordList, - this.likeList, - this.likeCount, - this.favouriteList, - this.favouriteCount, - this.commentList, - this.commentCount, + required this.likeList, + required this.likeCount, + required this.favouriteList, + required this.favouriteCount, + required this.commentList, + required this.commentCount, }); factory Recipe.fromJson(Map json) => _$RecipeFromJson(json); @@ -139,4 +139,80 @@ class Recipe { Map toJson() => _$RecipeToJson(this); } +@JsonSerializable() +class RecipeSummary { + int id; + String name; + String category; + double recommendRate; + bool isShare; + List recordList; + int likeCount; + int favouriteCount; + int commentCount; + int userId; + String username; + String avatar; + + RecipeSummary({ + required this.id, + required this.name, + required this.category, + required this.recommendRate, + required this.isShare, + required this.userId, + required this.username, + required this.avatar, + required this.recordList, + required this.likeCount, + required this.favouriteCount, + required this.commentCount, + }); + + factory RecipeSummary.fromJson(Map json) => _$RecipeSummaryFromJson(json); + + Map toJson() => _$RecipeSummaryToJson(this); +} + +@JsonSerializable() +class RecipeDetail { + int id; + String name; + String category; + double recommendRate; + String remark; + bool isShare; + int userId; + String username; + String avatar; + List materialList; + List stepList; + List recordList; + List likeList; + List favouriteList; + List commentList; + + RecipeDetail({ + required this.id, + required this.name, + required this.category, + required this.recommendRate, + required this.remark, + required this.isShare, + required this.userId, + required this.username, + required this.avatar, + required this.materialList, + required this.stepList, + required this.recordList, + required this.likeList, + required this.favouriteList, + required this.commentList, + }); + + factory RecipeDetail.fromJson(Map json) => _$RecipeDetailFromJson(json); + + Map toJson() => _$RecipeDetailToJson(this); +} + enum ViewType { recipe, calendar, timeline } diff --git a/lib/models/recipe.g.dart b/lib/models/recipe.g.dart index 21c5fc5..3891b61 100644 --- a/lib/models/recipe.g.dart +++ b/lib/models/recipe.g.dart @@ -6,45 +6,50 @@ part of 'recipe.dart'; // JsonSerializableGenerator // ************************************************************************** -Material _$MaterialFromJson(Map json) => Material( - type: json['type'] as String, - name: json['name'] as String, - amount: json['amount'] as String, -); +RecipeMaterial _$RecipeMaterialFromJson(Map json) => + RecipeMaterial( + type: json['type'] as String, + name: json['name'] as String, + amount: json['amount'] as String, + ); -Map _$MaterialToJson(Material instance) => { - 'type': instance.type, - 'name': instance.name, - 'amount': instance.amount, -}; +Map _$RecipeMaterialToJson(RecipeMaterial instance) => + { + 'type': instance.type, + 'name': instance.name, + 'amount': instance.amount, + }; -Step _$StepFromJson(Map json) => Step( +RecipeStep _$RecipeStepFromJson(Map json) => RecipeStep( sort: (json['sort'] as num).toInt(), content: json['content'] as String, imageUrl: json['imageUrl'] as String, ); -Map _$StepToJson(Step instance) => { - 'sort': instance.sort, - 'content': instance.content, - 'imageUrl': instance.imageUrl, -}; +Map _$RecipeStepToJson(RecipeStep instance) => + { + 'sort': instance.sort, + 'content': instance.content, + 'imageUrl': instance.imageUrl, + }; -Comment _$CommentFromJson(Map json) => Comment( - id: (json['id'] as num).toInt(), - username: json['username'] as String, - avatar: json['avatar'] as String, - content: json['content'] as String, - date: json['date'] as String, -); +RecipeComment _$RecipeCommentFromJson(Map json) => + RecipeComment( + id: (json['id'] as num).toInt(), + username: json['username'] as String, + avatar: json['avatar'] as String, + content: json['content'] as String, + date: json['date'] as String, + ); -Map _$CommentToJson(Comment instance) => { - 'id': instance.id, - 'username': instance.username, - 'avatar': instance.avatar, - 'content': instance.content, - 'date': instance.date, -}; +Map _$RecipeCommentToJson(RecipeComment instance) => + { + 'id': instance.id, + 'username': instance.username, + 'avatar': instance.avatar, + 'content': instance.content, + 'date': instance.date, + }; Record _$RecordFromJson(Map json) => Record( id: (json['id'] as num?)?.toInt(), @@ -71,42 +76,42 @@ Map _$RecipeQueryToJson(RecipeQuery instance) => {'category': instance.category}; Recipe _$RecipeFromJson(Map json) => Recipe( - id: (json['id'] as num?)?.toInt(), + id: (json['id'] as num).toInt(), name: json['name'] as String, category: json['category'] as String, recommendRate: (json['recommendRate'] as num).toDouble(), - remark: json['remark'] as String?, + remark: json['remark'] as String, isShare: json['isShare'] as bool, - userId: (json['userId'] as num?)?.toInt(), - username: json['username'] as String?, - avatar: json['avatar'] as String?, + userId: (json['userId'] as num).toInt(), + username: json['username'] as String, + avatar: json['avatar'] as String, materialList: - (json['materialList'] as List?) - ?.map((e) => Material.fromJson(e as Map)) + (json['materialList'] as List) + .map((e) => RecipeMaterial.fromJson(e as Map)) .toList(), stepList: - (json['stepList'] as List?) - ?.map((e) => Step.fromJson(e as Map)) + (json['stepList'] as List) + .map((e) => RecipeStep.fromJson(e as Map)) .toList(), recordList: (json['recordList'] as List) .map((e) => Record.fromJson(e as Map)) .toList(), likeList: - (json['likeList'] as List?) - ?.map((e) => (e as num).toInt()) + (json['likeList'] as List) + .map((e) => (e as num).toInt()) .toList(), - likeCount: (json['likeCount'] as num?)?.toInt(), + likeCount: (json['likeCount'] as num).toInt(), favouriteList: - (json['favouriteList'] as List?) - ?.map((e) => (e as num).toInt()) + (json['favouriteList'] as List) + .map((e) => (e as num).toInt()) .toList(), - favouriteCount: (json['favouriteCount'] as num?)?.toInt(), + favouriteCount: (json['favouriteCount'] as num).toInt(), commentList: - (json['commentList'] as List?) - ?.map((e) => Comment.fromJson(e as Map)) + (json['commentList'] as List) + .map((e) => RecipeComment.fromJson(e as Map)) .toList(), - commentCount: (json['commentCount'] as num?)?.toInt(), + commentCount: (json['commentCount'] as num).toInt(), ); Map _$RecipeToJson(Recipe instance) => { @@ -129,3 +134,93 @@ Map _$RecipeToJson(Recipe instance) => { 'commentList': instance.commentList, 'commentCount': instance.commentCount, }; + +RecipeSummary _$RecipeSummaryFromJson(Map json) => + RecipeSummary( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + category: json['category'] as String, + recommendRate: (json['recommendRate'] as num).toDouble(), + isShare: json['isShare'] as bool, + userId: (json['userId'] as num).toInt(), + username: json['username'] as String, + avatar: json['avatar'] as String, + recordList: + (json['recordList'] as List) + .map((e) => Record.fromJson(e as Map)) + .toList(), + likeCount: (json['likeCount'] as num).toInt(), + favouriteCount: (json['favouriteCount'] as num).toInt(), + commentCount: (json['commentCount'] as num).toInt(), + ); + +Map _$RecipeSummaryToJson(RecipeSummary instance) => + { + 'id': instance.id, + 'name': instance.name, + 'category': instance.category, + 'recommendRate': instance.recommendRate, + 'isShare': instance.isShare, + 'recordList': instance.recordList, + 'likeCount': instance.likeCount, + 'favouriteCount': instance.favouriteCount, + 'commentCount': instance.commentCount, + 'userId': instance.userId, + 'username': instance.username, + 'avatar': instance.avatar, + }; + +RecipeDetail _$RecipeDetailFromJson(Map json) => RecipeDetail( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + category: json['category'] as String, + recommendRate: (json['recommendRate'] as num).toDouble(), + remark: json['remark'] as String, + isShare: json['isShare'] as bool, + userId: (json['userId'] as num).toInt(), + username: json['username'] as String, + avatar: json['avatar'] as String, + materialList: + (json['materialList'] as List) + .map((e) => RecipeMaterial.fromJson(e as Map)) + .toList(), + stepList: + (json['stepList'] as List) + .map((e) => RecipeStep.fromJson(e as Map)) + .toList(), + recordList: + (json['recordList'] as List) + .map((e) => Record.fromJson(e as Map)) + .toList(), + likeList: + (json['likeList'] as List) + .map((e) => (e as num).toInt()) + .toList(), + favouriteList: + (json['favouriteList'] as List) + .map((e) => (e as num).toInt()) + .toList(), + commentList: + (json['commentList'] as List) + .map((e) => RecipeComment.fromJson(e as Map)) + .toList(), +); + +Map _$RecipeDetailToJson(RecipeDetail instance) => + { + 'id': instance.id, + 'name': instance.name, + 'category': instance.category, + 'recommendRate': instance.recommendRate, + 'remark': instance.remark, + 'isShare': instance.isShare, + 'userId': instance.userId, + 'username': instance.username, + 'avatar': instance.avatar, + 'materialList': instance.materialList, + 'stepList': instance.stepList, + 'recordList': instance.recordList, + 'likeList': instance.likeList, + 'favouriteList': instance.favouriteList, + 'commentList': instance.commentList, + }; diff --git a/lib/views/moment.dart b/lib/views/moment.dart index ef314a8..8f6bbb3 100644 --- a/lib/views/moment.dart +++ b/lib/views/moment.dart @@ -14,11 +14,10 @@ class MomentPage extends StatefulWidget { class _MomentPageState extends State { List momentList = []; - - // 分页相关变量 - int _page = 1; + int _currentPage = 1; final int _pageSize = 3; bool _hasMore = true; + // 初始化EasyRefresh控制器 final EasyRefreshController _freshController = EasyRefreshController( controlFinishRefresh: true, @@ -49,11 +48,10 @@ class _MomentPageState extends State { try { // 如果是刷新,重置页码 if (isRefresh) { - _page = 1; + _currentPage = 1; } - // 调用API获取数据,传入页码和页大小 - final result = await queryMomentByPageApi(_page, _pageSize); + final result = await queryMomentByPageApi(_currentPage, _pageSize); setState(() { if (isRefresh) { @@ -68,7 +66,7 @@ class _MomentPageState extends State { _hasMore = result.current < result.pages; // 如果有更多数据,准备加载下一页 if (_hasMore) { - _page++; + _currentPage++; } }); } catch (e) { diff --git a/lib/views/recipe_detail.dart b/lib/views/recipe_detail.dart new file mode 100644 index 0000000..650fea5 --- /dev/null +++ b/lib/views/recipe_detail.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:food_hub_app/api/recipe.dart'; +import 'package:food_hub_app/models/recipe.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; + +class RecipeDetailPage extends StatefulWidget { + const RecipeDetailPage({super.key}); + + @override + State createState() => _RecipeDetailState(); +} + +class _RecipeDetailState extends State { + RecipeDetail recipe = RecipeDetail( + id: 0, + name: '', + category: '', + recommendRate: 0, + remark: '', + isShare: false, + userId: 0, + username: '', + avatar: '', + materialList: [], + stepList: [], + recordList: [], + likeList: [], + favouriteList: [], + commentList: [], + ); + + // 定义三个分类列表 + List mainMaterialList = []; // 主料 + List auxiliaryMaterialList = []; // 配料 + List accessoryMaterialList = []; // 辅料 + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + refreshRecipeDetail(); + } + + Future refreshRecipeDetail() async { + // 获取参数 + final args = ModalRoute.of(context)?.settings.arguments as Map; + final recipeId = args['id']; + final result = await queryRecipeByIdApi(recipeId); + + setState(() { + recipe = result; + getRecipeMaterial(); + }); + } + + void getRecipeMaterial() { + for (var material in recipe.materialList) { + switch (material.type) { + case '主料': + mainMaterialList.add(material); + break; + case '配料': + auxiliaryMaterialList.add(material); + break; + case '辅料': + accessoryMaterialList.add(material); + break; + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('菜谱信息', style: TextStyle(color: Colors.white)), + backgroundColor: Theme.of(context).primaryColor, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + ), + backgroundColor: Color(0xFFF5F5F5), + body: SingleChildScrollView( + child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()), + ), + ); + } + + Widget _buildRecipeDetail() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildTitleSection(), + SizedBox(height: 10), + _buildInfoCard( + context, + icon: Icons.info, + title: "基础信息", + content: _buildTag(context, title: recipe.category), + ), + _buildInfoCard( + context, + icon: Icons.shopping_cart, + title: "食材信息", + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildMaterialSection(context, '主料', mainMaterialList), + _buildMaterialSection(context, '辅料', auxiliaryMaterialList), + _buildMaterialSection(context, '调料', accessoryMaterialList), + ], + ), + ), + _buildInfoCard( + context, + icon: Icons.list, + title: "步骤信息", + content: Column( + children: + recipe.stepList.map((step) => _buildStepSection(step)).toList(), + ), + ), + _buildInfoCard( + context, + icon: Icons.note, + title: "其他信息", + content: Text( + '备注:${recipe.remark.isEmpty ? '无' : recipe.remark}', + style: const TextStyle(height: 1.5), + ), + ), + ], + ); + } + + Widget _buildTag(BuildContext context, {required String title}) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Color.lerp(Theme.of(context).primaryColor, Colors.white, 0.8), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + title, + style: TextStyle(color: Theme.of(context).primaryColor), + ), + ); + } + + Widget _buildTitleSection() { + return Text( + recipe.name, + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ); + } + + Widget _buildMaterialSection( + BuildContext context, + String title, + List materials, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8, top: 12), + child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + ), + if (materials.isEmpty) + _buildTag(context, title: '无') + else + Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: + materials + .map( + (m) => _buildTag(context, title: '${m.name} ${m.amount}'), + ) + .toList(), + ), + ], + ); + } + + Widget _buildStepSection(RecipeStep step) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("第${step.sort + 1}步"), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + step.content, + style: const TextStyle( + height: 1.4, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + if (step.imageUrl.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + step.imageUrl, + width: double.infinity, + height: 180, + fit: BoxFit.contain, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildInfoCard( + BuildContext context, { + required IconData icon, + required String title, + required Widget content, + }) { + return cardContainer( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: Theme.of(context).primaryColor), + const SizedBox(width: 5), + Text( + title, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ], + ), + SizedBox(height: 5), + content, + ], + ), + ); + } +} diff --git a/lib/views/recordForm.dart b/lib/views/record_form.dart similarity index 100% rename from lib/views/recordForm.dart rename to lib/views/record_form.dart diff --git a/lib/widgets/common/index.dart b/lib/widgets/common/index.dart index 41d79a7..40a727d 100644 --- a/lib/widgets/common/index.dart +++ b/lib/widgets/common/index.dart @@ -27,6 +27,19 @@ InputDecoration formInputDecoration({ ); } +Widget cardContainer(Widget content) { + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: Padding( + padding: const EdgeInsets.all(10), + child: content, + ), + ); +} + +/// 圆形图标按钮 Widget circleIconButton({ required IconData icon, required VoidCallback onPressed, @@ -70,6 +83,7 @@ Text buttonText({required String text}) { return Text(text, style: TextStyle(color: Colors.white)); } +/// 图片错误显示 Widget errorImageContainer(double height) { return Container( height: 200, diff --git a/lib/widgets/recipe/card.dart b/lib/widgets/recipe/card.dart index 9b5d03d..7a45842 100644 --- a/lib/widgets/recipe/card.dart +++ b/lib/widgets/recipe/card.dart @@ -7,7 +7,7 @@ import 'package:food_hub_app/widgets/common/image.dart'; import 'package:food_hub_app/widgets/common/index.dart'; class RecipeCard extends StatelessWidget { - final Recipe recipe; + final RecipeSummary recipe; const RecipeCard({super.key, required this.recipe}); @@ -104,7 +104,12 @@ class RecipeCard extends StatelessWidget { circleIconButton( context: context, icon: Icons.book, - onPressed: () => Navigator.pushNamed(context, '/recipeDetail'), + onPressed: + () => Navigator.pushNamed( + context, + '/recipeDetail', + arguments: {'id': recipe.id}, + ), ), ], ), diff --git a/lib/widgets/recipe/detail.dart b/lib/widgets/recipe/detail.dart deleted file mode 100644 index 03fd099..0000000 --- a/lib/widgets/recipe/detail.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:food_hub_app/models/recipe.dart' as recipeModel; - -class RecipeDetail extends StatefulWidget { - const RecipeDetail({super.key}); - - @override - State createState() => _RecipeDetailState(); -} - -class _RecipeDetailState extends State { - @override - void initState() { - super.initState(); - } - - recipeModel.Recipe recipe = recipeModel.Recipe( - name: '雪糕', - category: '冷饮', - recommendRate: 4, - isShare: true, - recordList: [], - stepList: [ - recipeModel.Step( - sort: 1, - content: '将牛奶倒入锅中,小火加热至微沸', - imageUrl: 'https://picsum.photos/400/300?step=1', // 步骤图片URL - ), - recipeModel.Step(sort: 2, content: '你好', imageUrl: ''), - ], - materialList: [], - ); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('菜谱信息', style: TextStyle(color: Colors.white)), - backgroundColor: Theme.of(context).primaryColor, - leading: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - ), - backgroundColor: Color(0xFFF5F5F5), - body: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - recipe.name, - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - // 基础信息卡片 - _buildInfoCard( - context, - icon: Icons.info, - title: "基础信息", - content: Column( - children: [_buildInfoRow("类别", recipe.category)], - ), - ), - - // 食材信息卡片 - _buildInfoCard( - context, - icon: Icons.shopping_cart, - title: "食材信息", - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildMaterialSection("主料", 1), - _buildMaterialSection("配料", 2), - _buildMaterialSection("辅料", 3), - ], - ), - ), - - // 步骤信息卡片 - _buildInfoCard( - context, - icon: Icons.list, - title: "步骤信息", - content: Column( - children: - recipe.stepList?.map((step) { - return _buildStepSection(step); - }).toList() ?? - [], - ), - ), - - // 其他信息卡片 - if (recipe.remark != null && recipe.remark!.isNotEmpty) - _buildInfoCard( - context, - icon: Icons.note, - title: "其他信息", - content: Text( - recipe.remark!, - style: const TextStyle(height: 1.5), - ), - ), - - const SizedBox(height: 20), - ], - ), - ), - ), - ); - } - - // 构建食材分类部分 - Widget _buildMaterialSection(String title, int type) { - final materials = - recipe.materialList - ?.where((material) => material.type == type) - ?.toList() ?? - []; - - if (materials.isEmpty) return const SizedBox.shrink(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - ), - ...materials.map((material) { - return Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: Row( - children: [ - const Icon(Icons.circle, size: 6, color: Colors.grey), - const SizedBox(width: 8), - Text("${material.name} ${material.amount ?? ''}"), - ], - ), - ); - }).toList(), - ], - ); - } - - Widget _buildStepSection(recipeModel.Step step) { - return Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("第${step.sort}步"), - SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(step.content, style: const TextStyle(height: 1.4, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - if (step.imageUrl.isNotEmpty) - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.network( - step.imageUrl, - width: double.infinity, - height: 180, - fit: BoxFit.contain, - ), - ), - ], - ), - ), - ], - ), - ); - } - - // 构建信息行 - Widget _buildInfoRow(String label, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text(label), Text(value)], - ), - ); - } - - // 通用卡片组件 - Widget _buildInfoCard( - BuildContext context, { - required IconData icon, - required String title, - required Widget content, - }) { - return Card( - elevation: 0, - color: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: Padding( - padding: const EdgeInsets.all(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(icon, color: Theme.of(context).primaryColor), - const SizedBox(width: 8), - Text( - title, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ], - ), - SizedBox(height: 5), - content, - ], - ), - ), - ); - } -} diff --git a/lib/widgets/recipe/list.dart b/lib/widgets/recipe/list.dart index bd35396..fcdbce8 100644 --- a/lib/widgets/recipe/list.dart +++ b/lib/widgets/recipe/list.dart @@ -12,7 +12,7 @@ class RecipeList extends StatefulWidget { } class _RecipeListState extends State { - List recipeList = []; + List recipeSummaryList = []; @override void initState() { @@ -24,19 +24,19 @@ class _RecipeListState extends State { final result = await queryRecipeApi(RecipeQuery(category: "")); setState(() { - recipeList = result; + recipeSummaryList = result; }); } @override Widget build(BuildContext context) { - if (recipeList.isEmpty) { + if (recipeSummaryList.isEmpty) { return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); } else { return ListView.builder( - itemCount: recipeList.length, + itemCount: recipeSummaryList.length, itemBuilder: (context, index) { - return RecipeCard(recipe: recipeList[index]); + return RecipeCard(recipe: recipeSummaryList[index]); } ); } diff --git a/lib/widgets/recipe/timeline.dart b/lib/widgets/recipe/timeline.dart index b664584..34e8263 100644 --- a/lib/widgets/recipe/timeline.dart +++ b/lib/widgets/recipe/timeline.dart @@ -33,20 +33,16 @@ class _RecipeTimeline extends State { @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(10), - child: Column( - children: [ - YearSelector( - initialYear: DateTime.now().year, - minYear: 2000, - maxYear: 2100, - onYearChanged: (year) => refreshRecord(year), - ), - SizedBox(height: 10), - Expanded(child: timelineContainer(recordList)), - ], - ), + return Column( + children: [ + YearSelector( + initialYear: DateTime.now().year, + minYear: 2000, + maxYear: 2100, + onYearChanged: (year) => refreshRecord(year), + ), + Expanded(child: timelineContainer(recordList)), + ], ); } }