feat:增加食谱详细信息模块

This commit is contained in:
2025-07-28 22:57:45 +08:00
parent 8923d9e63a
commit b5dd0ab749
5 changed files with 252 additions and 3085 deletions

View File

@@ -32,6 +32,25 @@ class RecipeCard extends StatelessWidget {
);
}
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),
@@ -43,22 +62,7 @@ class RecipeCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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,
),
),
buildRecipeCarousel(),
Divider(
height: 1,
thickness: 1,
@@ -100,7 +104,7 @@ class RecipeCard extends StatelessWidget {
circleIconButton(
context: context,
icon: Icons.book,
onPressed: () => {},
onPressed: () => Navigator.pushNamed(context, '/recipeDetail'),
),
],
),

View File

@@ -0,0 +1,227 @@
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<RecipeDetail> createState() => _RecipeDetailState();
}
class _RecipeDetailState extends State<RecipeDetail> {
@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,
],
),
),
);
}
}

View File

@@ -33,14 +33,11 @@ class _RecipeListState extends State<RecipeList> {
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: (BuildContext context, int index) {
return SizedBox(height: 10);
},
}
);
}
}