feat:更新菜谱显示模块

This commit is contained in:
2025-11-18 15:57:00 +08:00
parent df26790389
commit 9a8e1a7419
9 changed files with 244 additions and 221 deletions

View File

@@ -32,14 +32,11 @@ final List<PageInfo> pageInfos = [
),
];
class NavBar extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;
const NavBar({super.key, required this.currentIndex, required this.onTap});
List<BottomNavigationBarItem> get items =>
Widget buildBottomNavBar({
required int currentIndex,
required Function(int) onTap,
}) {
final List<BottomNavigationBarItem> items =
pageInfos
.map(
(item) => BottomNavigationBarItem(
@@ -50,37 +47,78 @@ class NavBar extends StatelessWidget {
)
.toList();
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Theme.of(context).primaryColor)),
),
child: BottomNavigationBar(
return BottomNavigationBar(
currentIndex: currentIndex,
iconSize: 25,
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.white,
items: items,
onTap: onTap,
),
);
}
}
List<StatelessWidget> homeActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.search, color: Colors.white),
onPressed: () {
// 搜索功能
void buildBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => buildBottomSheetBody(context),
);
}
Widget buildBottomSheetBody(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
const Text(
'请选择操作',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
ListTile(
leading: Icon(Icons.book, color: colors.primary),
title: const Text('新增菜谱'),
onTap: () {
Navigator.pop(context);
_handleAddRecipe(context);
},
),
// IconButton(
// icon: Icon(Icons.add, color: Colors.white),
// onPressed: () {
// Navigator.pushNamed(context, '/recordForm');
// },
// ),
];
ListTile(
leading: Icon(Icons.note_add, color: colors.primary),
title: const Text('新增记录'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, "/recordForm");
},
),
ListTile(
leading: Icon(Icons.group, color: colors.primary),
title: const Text('发布朋友圈'),
onTap: () {
Navigator.pop(context);
_handleAddRecord(context);
},
),
],
);
}
// 处理添加菜谱
void _handleAddRecipe(BuildContext context) {
// 这里添加跳转或处理添加菜谱的逻辑
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('添加菜谱功能')));
}
// 处理添加记录
void _handleAddRecord(BuildContext context) {
// 这里添加跳转或处理添加记录的逻辑
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('添加记录功能')));
}

View File

@@ -14,79 +14,6 @@ class _HomePage extends State<HomePage> {
List<Widget> get tabPages => pageInfos.map((item) => item.page).toList();
// 显示底部弹窗
void _showBottomSheet() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
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),
),
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('添加菜谱功能')));
}
// 处理添加记录
void _handleAddRecord() {
// 这里添加跳转或处理添加记录的逻辑
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('添加记录功能')));
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -103,23 +30,27 @@ class _HomePage extends State<HomePage> {
);
},
),
actions: homeActions(context),
actions: [
IconButton(
icon: Icon(Icons.search, color: Colors.white),
onPressed: () {
// 搜索功能
},
),
],
),
backgroundColor: Color(0xFFF5F5F5),
drawer: SettingsDrawer(),
body: Padding(
padding: EdgeInsets.all(4),
child: tabPages[_currentIndex],
),
body: Padding(padding: EdgeInsets.all(4), child: tabPages[_currentIndex]),
floatingActionButton: FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor,
onPressed: _showBottomSheet,
backgroundColor: Theme.of(context).colorScheme.primary,
onPressed: () => buildBottomSheet(context),
shape: const CircleBorder(),
mini: true,
child: const Icon(Icons.add, color: Colors.white, size: 30),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: NavBar(
bottomNavigationBar: buildBottomNavBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {

View File

@@ -43,8 +43,7 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
Future<void> refreshRecipeDetail() async {
// 获取参数
final args = ModalRoute.of(context)?.settings.arguments as Map;
final recipeId = args['id'];
final result = await queryRecipeByIdApi(recipeId);
final result = await queryRecipeByIdApi(args['id']);
setState(() {
recipe = result;
@@ -71,13 +70,8 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('菜谱信息'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
appBar: AppBar(title: Text('菜谱信息')),
backgroundColor: Color(0xFFF4F4F4),
body: SingleChildScrollView(
child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()),
),
@@ -89,12 +83,11 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildTitleSection(),
SizedBox(height: 10),
_buildInfoCard(
context,
icon: Icons.info,
title: "基础信息",
content: _buildTag(context, title: recipe.category),
content: buildTag(context, recipe.category),
),
_buildInfoCard(
context,
@@ -131,20 +124,6 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
);
}
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,
@@ -165,16 +144,14 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
),
if (materials.isEmpty)
_buildTag(context, title: '')
buildTag(context, '')
else
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children:
materials
.map(
(m) => _buildTag(context, title: '${m.name} ${m.amount}'),
)
.map((m) => buildTag(context, '${m.name} ${m.amount}'))
.toList(),
),
],
@@ -229,7 +206,7 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
children: [
Row(
children: [
Icon(icon, color: Theme.of(context).primaryColor),
Icon(icon, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 5),
Text(
title,
@@ -240,7 +217,7 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
SizedBox(height: 5),
content,
],
)
),
);
}
}

View File

@@ -19,6 +19,7 @@ class ImagePreviewPage extends StatefulWidget {
class _ImagePreviewPageState extends State<ImagePreviewPage> {
// 声明 PageController 并初始化初始索引
late PageController _pageController;
// 记录当前显示的图片索引(用于更新页码)
int _currentIndex = 0;
@@ -68,7 +69,8 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
centerTitle: true,
),
body: PhotoViewGallery(
pageOptions: widget.images.map((url) {
pageOptions:
widget.images.map((url) {
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(url),
minScale: PhotoViewComputedScale.contained,
@@ -86,29 +88,93 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
}
}
Widget buildNetworkImage(String url) {
Widget buildNetworkImage(BuildContext context, String url) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: GestureDetector(
onTap: () {
_showFullScreenImage(context, url);
},
child: Image.network(
url,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
// 加载中显示占位符
if (loadingProgress == null) return child;
return _buildLoadingIndicator(loadingProgress);
},
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
),
),
);
}
Widget _buildLoadingIndicator(ImageChunkEvent? loadingProgress) {
return Center(
child: SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
value:
loadingProgress?.expectedTotalBytes != null
? loadingProgress!.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
errorBuilder: (context, error, stackTrace) {
}
Widget _buildErrorImage() {
return Container(
color: Colors.grey[200],
child: const Icon(Icons.image, color: Colors.grey, size: 30),
);
}
Widget _buildPhotoView(String url) {
return PhotoView(
imageProvider: NetworkImage(url),
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 2,
initialScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(tag: url),
loadingBuilder: (context, event) => _buildLoadingIndicator(event),
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
);
}
Widget _buildCloseImage(BuildContext context) {
return Positioned(
top: MediaQuery.of(context).padding.top + 10,
right: 20,
child: IconButton(
icon: Icon(Icons.close, color: Colors.white, size: 30),
onPressed: () => Navigator.of(context).pop(),
),
);
}
void _showFullScreenImage(BuildContext context, String url) {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Scaffold(
backgroundColor: Colors.black.withAlpha(200),
body: Stack(
children: [
// 可缩放图片
Positioned.fill(child: _buildPhotoView(url)),
// 关闭按钮
_buildCloseImage(context),
],
),
);
},
),
);

View File

@@ -83,7 +83,7 @@ Widget circleIconButton({
required IconData icon,
required VoidCallback onPressed,
required BuildContext context,
bool? isSmall
bool? isSmall,
}) {
final double size = isSmall == true ? 24 : 36;
@@ -101,6 +101,19 @@ Widget circleIconButton({
);
}
Widget buildTag(BuildContext context, String title) {
final colors = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Color.lerp(colors.primary, Colors.white, 0.8),
borderRadius: BorderRadius.circular(10),
),
child: Text(title, style: TextStyle(color: colors.primary)),
);
}
/// 确认按钮样式
ButtonStyle primaryButtonStyle() {
return ButtonStyle(

View File

@@ -94,7 +94,6 @@ class _YearSelectorState extends State<YearSelector>
isSmall: true,
onPressed: () => _previousYear(),
),
SizedBox(width: 5),
// 年份显示
AnimatedBuilder(
animation: _scaleAnimation,
@@ -110,7 +109,6 @@ class _YearSelectorState extends State<YearSelector>
),
),
),
SizedBox(width: 5),
circleIconButton(
context: context,
icon: Icons.chevron_right,

View File

@@ -128,7 +128,7 @@ class MomentCard extends StatelessWidget {
// 点击图片时,跳转到预览页面
onTap: () => imageTapClick(index),
// 原图片组件
child: buildNetworkImage(imageUrls[index]),
child: buildNetworkImage(context, imageUrls[index]),
);
}),
);

View File

@@ -10,12 +10,23 @@ class RecipeCard extends StatelessWidget {
const RecipeCard({super.key, required this.recipe});
void navigatorToRecipeDetail(BuildContext context) {
Navigator.pushNamed(context, '/recipeDetail', arguments: {'id': recipe.id});
}
@override
Widget build(BuildContext context) {
final List<String> imageUrls =
recipe.recordList
.map((item) => '${AppConfig.baseApiUrl}/${item.imageUrl}')
.toList();
final String firstImageUrl = recipe.recordList.first.imageUrl;
Widget buildRecipeImage() {
return AspectRatio(
aspectRatio: 1.5,
child: buildNetworkImage(
context,
'${AppConfig.baseApiUrl}/$firstImageUrl',
),
);
}
Widget buildRecipeContent(BuildContext context) {
return Column(
@@ -32,26 +43,6 @@ class RecipeCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
),
Row(
children: [
circleIconButton(
context: context,
icon: Icons.edit,
onPressed: () => {},
),
SizedBox(width: 4),
circleIconButton(
context: context,
icon: Icons.book,
onPressed:
() => Navigator.pushNamed(
context,
'/recipeDetail',
arguments: {'id': recipe.id},
),
),
],
),
],
),
Row(
@@ -105,12 +96,12 @@ class RecipeCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 1.5,
child: buildNetworkImage(imageUrls.first),
),
buildRecipeImage(),
SizedBox(height: 8),
buildRecipeContent(context),
GestureDetector(
onTap: () => navigatorToRecipeDetail(context),
child: buildRecipeContent(context)
)
],
),
);
@@ -120,10 +111,10 @@ class RecipeCard extends StatelessWidget {
Widget _buildIconText({
required IconData icon,
required String text,
Color color = Colors.grey, // 默认灰色
Color color = Colors.grey,
double iconSize = 16,
double textSize = 16,
double spacing = 2, // 图标与文本间距
double spacing = 2,
}) {
return Row(
children: [

View File

@@ -48,19 +48,28 @@ class _RecipeTimeline extends State<RecipeTimeline> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.date_range, color: Theme.of(context).colorScheme.primary),
SizedBox(width: 5),
Text(
record.date,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
Column(
children: [
Text(record.name, style: TextStyle(fontSize: 16)),
Text(
record.name,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
GestureDetector(
onTap: () => imageTapClick(),
child: AspectRatio(
aspectRatio: 1.5,
child: buildNetworkImage(imageUrls.first),
child: buildNetworkImage(context, imageUrls.first),
),
),
],