feat:更新编辑菜谱功能
This commit is contained in:
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user