import 'package:flutter/material.dart'; import 'package:flutter_common/widget/common_widget.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:food_hub_app/models/recipe.dart'; import 'package:food_hub_app/widgets/common/form.dart'; import 'package:food_hub_app/widgets/common/index.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:flutter_common/utils/toast_util.dart'; import 'package:flutter_common/widget/loading_widget.dart'; class RecipeFormPage extends StatefulWidget { final RecipeDetail? initialData; const RecipeFormPage({super.key, this.initialData}); @override State createState() => _RecipeFormPageState(); } class _RecipeFormPageState extends State { final _formKey = GlobalKey(); int _currentStep = 0; List _materials = []; List _steps = []; // 表单字段名称常量 static const String _nameField = 'name'; static const String _categoryField = 'category'; static const String _recommendRateField = 'recommendRate'; static const String _remarkField = 'remark'; static const String _isShareField = 'isShare'; // 分类选项 final List _categoryOptions = [ '家常菜', '川菜', '粤菜', '湘菜', '西餐', '甜品', '汤羹', '其他', ]; @override void initState() { super.initState(); _initializeFormData(); } void _initializeFormData() { if (widget.initialData != null) { _materials = List.from(widget.initialData!.materialList); _steps = List.from(widget.initialData!.stepList); } } // 构建步骤内容 Widget _buildStepContent() { switch (_currentStep) { case 0: return _buildBasicInfoStep(); case 1: return _buildMaterialsStep(); case 2: return _buildStepsStep(); default: return _buildBasicInfoStep(); } } // 第一步:基本信息 Widget _buildBasicInfoStep() { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFormLabel(context: context, text: '菜谱名称', isRequired: true), const SizedBox(height: 8), FormBuilderTextField( name: _nameField, initialValue: widget.initialData?.name ?? '', decoration: buildInputDecoration( context: context, hintText: '请输入菜谱名称', prefixIcon: Icons.restaurant_menu, ), validator: FormBuilderValidators.compose([ FormBuilderValidators.required(errorText: '菜谱名称不能为空'), FormBuilderValidators.maxLength(50, errorText: '名称不能超过50个字符'), ]), ), const SizedBox(height: 16), buildFormLabel(context: context, text: '分类', isRequired: true), const SizedBox(height: 8), FormBuilderDropdown( name: _categoryField, initialValue: widget.initialData?.category ?? '家常菜', decoration: buildInputDecoration( context: context, hintText: '请选择分类', prefixIcon: Icons.widgets, ), items: _categoryOptions .map( (category) => DropdownMenuItem( value: category, child: Text(category), ), ) .toList(), validator: FormBuilderValidators.required(errorText: '请选择分类'), ), const SizedBox(height: 16), buildFormLabel(context: context, text: '推荐评分', isRequired: false), const SizedBox(height: 8), FormBuilderSlider( name: _recommendRateField, initialValue: widget.initialData?.recommendRate ?? 3.0, min: 0, max: 5, divisions: 10, activeColor: Colors.orange, decoration: const InputDecoration(border: InputBorder.none), ), const SizedBox(height: 16), buildFormLabel(context: context, text: '备注', isRequired: false), const SizedBox(height: 8), FormBuilderTextField( name: _remarkField, initialValue: widget.initialData?.remark ?? '', maxLines: 3, decoration: buildInputDecoration( context: context, hintText: '请输入菜谱的特别说明或小贴士' ) ), const SizedBox(height: 16), FormBuilderCheckbox( name: _isShareField, initialValue: widget.initialData?.isShare ?? false, title: const Text('公开分享此菜谱'), ), ], ), ); } // 第二步:食材清单 Widget _buildMaterialsStep() { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('食材清单', style: Theme.of(context).textTheme.headlineSmall), const Spacer(), IconButton( icon: const Icon(Icons.add, color: Colors.blue), onPressed: _addMaterial, tooltip: '添加食材', ), ], ), const SizedBox(height: 16), if (_materials.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ Icon(Icons.inventory_2, size: 48, color: Colors.grey[400]), const SizedBox(height: 16), Text( '暂无食材,请点击添加按钮添加食材', style: TextStyle(color: Colors.grey[600]), ), ], ), ) else ..._materials.asMap().entries.map( (entry) => _buildMaterialItem(entry.key, entry.value), ), ], ), ); } Widget _buildMaterialItem(int index, RecipeMaterial material) { return Card( key: ValueKey(material.id), margin: const EdgeInsets.only(bottom: 12), child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4, ), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), ), child: Text( '食材 ${index + 1}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.blue, ), ), ), const Spacer(), IconButton( icon: const Icon(Icons.delete, color: Colors.red, size: 20), onPressed: () => _removeMaterial(index), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextFormField( initialValue: material.type, decoration: const InputDecoration( labelText: '类型', border: OutlineInputBorder(), hintText: '如:主料、辅料', ), onChanged: (value) { _updateMaterial(index, type: value); }, ), ), const SizedBox(width: 12), Expanded( flex: 2, child: TextFormField( initialValue: material.name, decoration: const InputDecoration( labelText: '食材名称', border: OutlineInputBorder(), hintText: '如:鸡蛋、西红柿', ), onChanged: (value) { _updateMaterial(index, name: value); }, ), ), const SizedBox(width: 12), Expanded( child: TextFormField( initialValue: material.amount, decoration: const InputDecoration( labelText: '用量', border: OutlineInputBorder(), hintText: '如:2个、100g', ), onChanged: (value) { _updateMaterial(index, amount: value); }, ), ), ], ), ], ), ), ); } 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() { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('制作步骤', style: Theme.of(context).textTheme.headlineSmall), const Spacer(), IconButton( icon: const Icon(Icons.add, color: Colors.blue), onPressed: _addStep, tooltip: '添加步骤', ), ], ), const SizedBox(height: 16), if (_steps.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ Icon(Icons.list_alt, size: 48, color: Colors.grey[400]), const SizedBox(height: 16), Text( '暂无步骤,请点击添加按钮添加制作步骤', style: TextStyle(color: Colors.grey[600]), ), ], ), ) else ReorderableListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _steps.length, itemBuilder: (context, index) => _buildStepItem(index, _steps[index]), onReorder: (oldIndex, newIndex) => _reorderStep(oldIndex, newIndex), ), ], ), ); } Widget _buildStepItem(int index, RecipeStep step) { return Card( key: Key('step_$index'), margin: const EdgeInsets.only(bottom: 12), child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(12), ), child: Text( '步骤 ${index + 1}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.green, ), ), ), const Spacer(), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _removeStep(index), ), ], ), const SizedBox(height: 12), TextFormField( initialValue: step.content, maxLines: 3, decoration: const InputDecoration( labelText: '步骤说明', border: OutlineInputBorder(), hintText: '详细描述此步骤的操作方法', ), onChanged: (value) { _updateStep(index, content: value); }, ), const SizedBox(height: 12), TextFormField( initialValue: step.imageUrl, decoration: const InputDecoration( labelText: '步骤图片URL(可选)', border: OutlineInputBorder(), hintText: '添加步骤演示图片链接', prefixIcon: Icon(Icons.image), ), onChanged: (value) { _updateStep(index, imageUrl: value); }, ), ], ), ), ); } void _addStep() { setState(() { _steps.add( RecipeStep( id: DateTime.now().microsecond, 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: DateTime.now().microsecond, sort: step.sort, content: content ?? step.content, imageUrl: imageUrl ?? step.imageUrl, ); }); } // 步骤指示器 Widget _buildStepIndicator() { return Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildStepCircle(0), _buildStepConnector(0), _buildStepCircle(1), _buildStepConnector(1), _buildStepCircle(2), ], ), Container( margin: const EdgeInsets.only(top: 8), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildStepTitle(0, '基本信息'), SizedBox(width: 25), _buildStepTitle(1, '食材清单'), SizedBox(width: 25), _buildStepTitle(2, '制作步骤'), ], ), ), ], ), ); } // 只构建圆圈,不包含标题文字 Widget _buildStepCircle(int stepIndex) { final isActive = _currentStep == stepIndex; final isCompleted = _currentStep > stepIndex; final boxColor = isActive || isCompleted ? Theme.of(context).colorScheme.primary : Colors.grey; return Container( width: 32, height: 32, decoration: BoxDecoration( color: boxColor, shape: BoxShape.circle, ), child: Center( child: isCompleted ? const Icon(Icons.check, color: Colors.white, size: 16) : Text( '${stepIndex + 1}', style: TextStyle( color: isActive ? Colors.white : Colors.grey.shade700, fontWeight: FontWeight.bold, ), ), ), ); } // 箭头连接器 Widget _buildStepConnector(int stepIndex) { final isActive = _currentStep > stepIndex; return SizedBox( width: 40, child: Icon( Icons.arrow_forward, size: 20, color: isActive ? Theme.of(context).colorScheme.primary : Colors.grey, ), ); } Widget _buildStepTitle(int stepIndex, String title) { final isActive = _currentStep == stepIndex; final isCompleted = _currentStep > stepIndex; final textColor = isActive || isCompleted ? Theme.of(context).colorScheme.primary : Colors.grey; return Text( title, textAlign: TextAlign.center, style: TextStyle( fontWeight: isActive ? FontWeight.bold : FontWeight.normal, color: textColor, fontSize: 12, ), ); } // 导航按钮 Widget _buildNavigationButtons() { final isLastStep = _currentStep == 2; final isFirstStep = _currentStep == 0; return Row( children: [ if (!isFirstStep) Expanded( child: OutlinedButton( onPressed: _previousStep, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), side: BorderSide(color: Theme.of(context).colorScheme.primary), ), child: Text( '上一步', style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ), ), if (!isFirstStep) const SizedBox(width: 12), Expanded( child: buildPrimaryButton( context: context, text: isLastStep ? '完成创建' : '下一步', onPressed: _nextStep, ), ), ], ); } void _nextStep() { if (_currentStep < 2) { if (_formKey.currentState?.saveAndValidate() ?? false) { setState(() { _currentStep++; }); } } else { _submitForm(); } } void _previousStep() { if (_currentStep > 0) { setState(() { _currentStep--; }); } } 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() { if (_formKey.currentState?.saveAndValidate() ?? false) { if (_validateCurrentStep()) { final formData = _formKey.currentState!.value; // 构建完整的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); } } 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 colors = Theme.of(context).colorScheme; final title = widget.initialData == null ? '创建新菜谱' : '编辑菜谱'; return Scaffold( appBar: AppBar( title: Text(title, style: const TextStyle(color: Colors.white)), backgroundColor: colors.primary, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), ), body: Padding( padding: EdgeInsets.all(10), child: CommonCard( child: Column( children: [ _buildStepIndicator(), Expanded( child: FormBuilder( key: _formKey, child: Padding( padding: const EdgeInsets.all(16), child: _buildStepContent(), ), ), ), _buildNavigationButtons(), ], ), ), ), ); } }