feat:更新编辑菜谱功能

This commit is contained in:
2026-06-30 23:26:49 +08:00
parent 613e50b0da
commit 9d3a3f2e80
9 changed files with 282 additions and 331 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/provider/food_provider.dart';
@@ -21,10 +22,6 @@ class RecipeForm extends StatefulWidget {
class _RecipeFormState extends State<RecipeForm> {
final _formKey = GlobalKey<FormBuilderState>();
int _currentStep = 0;
List<RecipeMaterial> _materials = [];
List<RecipeMaterial> _materialZhuliaos = [];
List<RecipeMaterial> _materialFuliaos = [];
List<RecipeStep> _steps = [];
// 表单字段名称常量
static const String _nameField = 'name';
@@ -54,18 +51,18 @@ class _RecipeFormState extends State<RecipeForm> {
Widget _buildStepContent(FoodProvider provider) {
switch (_currentStep) {
case 0:
return _buildBasicInfoStep(provider);
return _buildInfo(provider);
case 1:
return _buildMaterialsStep();
return _buildMaterials(provider);
case 2:
return _buildStepsStep();
return _buildSteps(provider);
default:
return _buildBasicInfoStep(provider);
return _buildInfo(provider);
}
}
// 第一步:基本信息
Widget _buildBasicInfoStep(FoodProvider provider) {
Widget _buildInfo(FoodProvider provider) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -80,6 +77,9 @@ class _RecipeFormState extends State<RecipeForm> {
hintText: '请输入菜谱名称',
prefixIcon: Icons.restaurant_menu,
),
onChanged: (value) {
provider.updateRecipeFormItem(name: value ?? '');
},
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(errorText: '菜谱名称不能为空'),
FormBuilderValidators.maxLength(50, errorText: '名称不能超过50个字符'),
@@ -106,6 +106,9 @@ class _RecipeFormState extends State<RecipeForm> {
),
)
.toList(),
onChanged: (value) {
provider.updateRecipeFormItem(category: value ?? '');
},
validator: FormBuilderValidators.required(errorText: '请选择分类'),
),
const SizedBox(height: 16),
@@ -120,6 +123,9 @@ class _RecipeFormState extends State<RecipeForm> {
divisions: 4,
activeColor: Theme.of(context).colorScheme.primary,
decoration: const InputDecoration(border: InputBorder.none),
onChanged: (value) {
provider.updateRecipeFormItem(recommendRate: value ?? 0);
},
),
const SizedBox(height: 16),
@@ -133,6 +139,9 @@ class _RecipeFormState extends State<RecipeForm> {
context: context,
hintText: '请输入菜谱的特别说明或小贴士',
),
onChanged: (value) {
provider.updateRecipeFormItem(remark: value ?? '');
},
),
const SizedBox(height: 16),
@@ -140,6 +149,9 @@ class _RecipeFormState extends State<RecipeForm> {
name: _isShareField,
initialValue: provider.recipeFormItem.isShare,
title: const Text('公开分享此菜谱'),
onChanged: (value) {
provider.updateRecipeFormItem(isShare: value ?? false);
},
),
],
),
@@ -147,7 +159,9 @@ class _RecipeFormState extends State<RecipeForm> {
}
// 第二步:食材清单
Widget _buildMaterialsStep() {
Widget _buildMaterials(FoodProvider provider) {
final materials = provider.recipeFormItem.materialList;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -161,12 +175,12 @@ class _RecipeFormState extends State<RecipeForm> {
Icons.add,
color: Theme.of(context).colorScheme.primary,
),
onPressed: _addMaterial,
onPressed: () => provider.addRecipeMaterial(),
tooltip: '添加食材',
),
],
),
if (_materials.isEmpty)
if (materials.isEmpty)
Container(
color: Theme.of(context).colorScheme.surface,
margin: EdgeInsets.only(bottom: 10),
@@ -185,24 +199,28 @@ class _RecipeFormState extends State<RecipeForm> {
else
Column(
children:
_materials.asMap().entries.map((entry) {
final index = entry.key;
final material = entry.value;
materials.asMap().entries.map((entry) {
final index = entry.key;
final material = entry.value;
return Column(
children: [
_buildMaterialItem(index, material),
const SizedBox(height: 8)
],
);
}).toList(),
return Column(
children: [
_buildMaterialItem(index, material, provider),
const SizedBox(height: 8),
],
);
}).toList(),
),
],
),
);
}
Widget _buildMaterialItem(int index, RecipeMaterial material) {
Widget _buildMaterialItem(
int index,
RecipeMaterial material,
FoodProvider provider,
) {
return Column(
key: ValueKey(material.id),
children: [
@@ -217,7 +235,7 @@ class _RecipeFormState extends State<RecipeForm> {
prefixIcon: Icons.content_paste,
),
onChanged: (value) {
_updateMaterial(index, name: value);
provider.updateRecipeMaterial(index, name: value);
},
),
),
@@ -231,13 +249,13 @@ class _RecipeFormState extends State<RecipeForm> {
prefixIcon: Icons.scale,
),
onChanged: (value) {
_updateMaterial(index, amount: value);
provider.updateRecipeMaterial(index, amount: value);
},
),
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
onPressed: () => _removeMaterial(index),
onPressed: () => provider.removeRecipeMaterial(index),
),
],
),
@@ -245,44 +263,10 @@ class _RecipeFormState extends State<RecipeForm> {
);
}
void _addMaterial() {
setState(() {
_materials.add(
RecipeMaterial(
id: DateTime.now().microsecondsSinceEpoch,
type: '主料',
name: '',
amount: '',
),
);
});
}
void _removeMaterial(int index) {
setState(() {
_materials.removeAt(index);
});
}
void _updateMaterial(
int index, {
String? type,
String? name,
String? amount,
}) {
setState(() {
final material = _materials[index];
_materials[index] = RecipeMaterial(
id: material.id,
type: type ?? material.type,
name: name ?? material.name,
amount: amount ?? material.amount,
);
});
}
// 第三步:制作步骤
Widget _buildStepsStep() {
Widget _buildSteps(FoodProvider provider) {
final steps = provider.recipeFormItem.stepList;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -296,12 +280,12 @@ class _RecipeFormState extends State<RecipeForm> {
Icons.add,
color: Theme.of(context).colorScheme.primary,
),
onPressed: _addStep,
onPressed: () => provider.addRecipeStep(),
tooltip: '添加步骤',
),
],
),
if (_steps.isEmpty)
if (steps.isEmpty)
Container(
color: Theme.of(context).colorScheme.surface,
margin: EdgeInsets.only(bottom: 10),
@@ -320,24 +304,24 @@ class _RecipeFormState extends State<RecipeForm> {
else
Column(
children:
_steps.asMap().entries.map((entry) {
final index = entry.key;
final step = entry.value;
steps.asMap().entries.map((entry) {
final index = entry.key;
final step = entry.value;
return Column(
children: [
_buildStepItem(index, step),
const SizedBox(height: 8)
],
);
}).toList(),
return Column(
children: [
_buildStepItem(index, step, provider),
const SizedBox(height: 8),
],
);
}).toList(),
),
],
),
);
}
Widget _buildStepItem(int index, RecipeStep step) {
Widget _buildStepItem(int index, RecipeStep step, FoodProvider provider) {
return Row(
key: ValueKey(step.id),
crossAxisAlignment: CrossAxisAlignment.center,
@@ -369,81 +353,22 @@ class _RecipeFormState extends State<RecipeForm> {
decoration: buildInputDecoration(
context: context,
hintText: '请输入内容',
prefixIcon: Icons.content_paste
prefixIcon: Icons.content_paste,
),
onChanged: (value) {
_updateStep(index, content: value);
provider.updateRecipeStep(index, content: value);
},
),
),
// 删除按钮
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _removeStep(index),
onPressed: () => provider.removeRecipeStep(index),
),
],
);
}
void _addStep() {
setState(() {
_steps.add(
RecipeStep(
id: DateTime.now().microsecondsSinceEpoch,
sort: _steps.length,
content: '',
imageUrl: '',
),
);
});
}
void _removeStep(int index) {
setState(() {
_steps.removeAt(index);
// // 重新排序
// for (int i = 0; i < _steps.length; i++) {
// _steps[i] = RecipeStep(
// id: DateTime.now().microsecond,
// sort: i,
// content: _steps[i].content,
// imageUrl: _steps[i].imageUrl,
// );
// }
});
}
void _reorderStep(int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final RecipeStep item = _steps.removeAt(oldIndex);
_steps.insert(newIndex, item);
// 更新排序序号
for (int i = 0; i < _steps.length; i++) {
_steps[i] = RecipeStep(
id: DateTime.now().microsecond,
sort: i,
content: _steps[i].content,
imageUrl: _steps[i].imageUrl,
);
}
});
}
void _updateStep(int index, {String? content, String? imageUrl}) {
setState(() {
final step = _steps[index];
_steps[index] = RecipeStep(
id: step.id,
sort: step.sort,
content: content ?? step.content,
imageUrl: imageUrl ?? step.imageUrl,
);
});
}
// 步骤指示器
Widget _buildStepIndicator() {
return Container(
@@ -545,7 +470,7 @@ class _RecipeFormState extends State<RecipeForm> {
}
// 导航按钮
Widget _buildNavigationButtons() {
Widget _buildNavigationButtons(FoodProvider provider) {
final isLastStep = _currentStep == 2;
final isFirstStep = _currentStep == 0;
@@ -563,15 +488,15 @@ class _RecipeFormState extends State<RecipeForm> {
Expanded(
child: buildPrimaryButton(
context: context,
text: isLastStep ? '完成创建' : '下一步',
onPressed: _nextStep,
text: isLastStep ? '提交' : '下一步',
onPressed: () => _nextStep(provider),
),
),
],
);
}
void _nextStep() {
void _nextStep(FoodProvider provider) {
if (_currentStep < 2) {
if (_formKey.currentState?.saveAndValidate() ?? false) {
setState(() {
@@ -579,7 +504,7 @@ class _RecipeFormState extends State<RecipeForm> {
});
}
} else {
_submitForm();
_submitForm(provider);
}
}
@@ -591,112 +516,37 @@ class _RecipeFormState extends State<RecipeForm> {
}
}
bool _validateCurrentStep() {
return true;
switch (_currentStep) {
case 0:
// 验证基本信息
final name =
_formKey.currentState?.fields[_nameField]?.value?.toString() ?? '';
final category =
_formKey.currentState?.fields[_categoryField]?.value?.toString() ??
'';
if (name.isEmpty) {
ToastUtil.error('请输入菜谱名称');
return false;
}
if (category.isEmpty) {
ToastUtil.error('请选择分类');
return false;
}
return true;
case 1:
// 验证食材清单
if (_materials.isEmpty) {
ToastUtil.error('请至少添加一个食材');
return false;
}
for (var material in _materials) {
if (material.name.isEmpty || material.amount.isEmpty) {
ToastUtil.error('请完善食材信息');
return false;
}
}
return true;
case 2:
// 验证制作步骤
if (_steps.isEmpty) {
ToastUtil.error('请至少添加一个制作步骤');
return false;
}
for (var step in _steps) {
if (step.content.isEmpty) {
ToastUtil.error('请完善步骤说明');
return false;
}
}
return true;
default:
return true;
}
}
void _submitForm() {
void _submitForm(FoodProvider provider) async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
if (_validateCurrentStep()) {
final formData = _formKey.currentState!.value;
LoadingDialog.show(context, message: '提交中');
final result = await provider.handleRecipe();
if (result == true) {
LoadingDialog.hide(context);
// 构建完整的RecipeDetail对象
// final recipe = RecipeDetail(
// id: widget.initialData?.id ?? 0,
// name: formData[_nameField],
// category: formData[_categoryField],
// recommendRate: formData[_recommendRateField] ?? 3.0,
// remark: formData[_remarkField] ?? '',
// isShare: formData[_isShareField] ?? false,
// userId: widget.initialData?.userId ?? 0,
// username: widget.initialData?.username ?? '',
// avatar: widget.initialData?.avatar ?? '',
// materialList: _materials,
// stepList: _steps,
// recordList: widget.initialData?.recordList ?? [],
// likeList: widget.initialData?.likeList ?? [],
// favouriteList: widget.initialData?.favouriteList ?? [],
// commentList: widget.initialData?.commentList ?? [],
// );
//
// _saveRecipe(recipe);
if (provider.isEditing) {
showSuccessTip(context, '更新菜谱成功');
} else {
showSuccessTip(context, '新增菜谱成功');
}
Future.delayed(Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.pop(context);
}
});
} else {
LoadingDialog.hide(context);
if (provider.isEditing) {
showErrorTip(context, '更新菜谱失败');
} else {
showErrorTip(context, '新增菜谱失败');
}
}
} else {
ToastUtil.error('请检查表单填写是否正确');
}
}
void _saveRecipe(RecipeDetail recipe) {
LoadingDialog.show(context, message: '提交中');
// 模拟保存操作
// Future.delayed(const Duration(seconds: 2), () {
// LoadingDialog.hide(context);
//
// if (mounted) {
// ToastUtil.success(widget.initialData == null ? '菜谱创建成功!' : '菜谱更新成功!');
//
// Future.delayed(const Duration(milliseconds: 1500), () {
// if (mounted) {
// Navigator.pop(context);
// }
// });
// }
// });
}
@override
Widget build(BuildContext context) {
final provider = context.watch<FoodProvider>();
@@ -713,7 +563,7 @@ class _RecipeFormState extends State<RecipeForm> {
children: [
_buildStepIndicator(),
FormBuilder(key: _formKey, child: _buildStepContent(provider)),
_buildNavigationButtons(),
_buildNavigationButtons(provider),
],
),
),