feat:更新菜谱表单

This commit is contained in:
2026-06-23 20:02:17 +08:00
parent ed0ceb29d3
commit a5e788eee6
7 changed files with 846 additions and 35 deletions

View File

@@ -65,8 +65,8 @@ class AppActionsState extends State<AppActions> {
void _handleAddRecipe(FoodProvider provider) { void _handleAddRecipe(FoodProvider provider) {
// provider.resetRecordForm(); // provider.resetRecordForm();
// Navigator.pop(context); Navigator.pop(context);
// Navigator.pushNamed(context, "/recordForm"); Navigator.pushNamed(context, "/recipeForm");
} }
void _handleAddRecord(FoodProvider provider) { void _handleAddRecord(FoodProvider provider) {

View File

@@ -12,6 +12,7 @@ import 'package:food_hub_app/views/moment.dart';
import 'package:food_hub_app/views/moment_form.dart'; import 'package:food_hub_app/views/moment_form.dart';
import 'package:food_hub_app/views/moment_user.dart'; import 'package:food_hub_app/views/moment_user.dart';
import 'package:food_hub_app/views/profile_user.dart'; import 'package:food_hub_app/views/profile_user.dart';
import 'package:food_hub_app/views/recipe_form.dart';
import 'package:food_hub_app/views/record_form.dart'; import 'package:food_hub_app/views/record_form.dart';
import 'package:food_hub_app/views/recipe_detail.dart'; import 'package:food_hub_app/views/recipe_detail.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
@@ -71,6 +72,7 @@ class MyApp extends StatelessWidget {
routes: { routes: {
'/home': (context) => HomePage(), '/home': (context) => HomePage(),
'/recordForm': (context) => RecordFormPage(), '/recordForm': (context) => RecordFormPage(),
'/recipeForm': (context) => RecipeFormPage(),
'/recipeDetail': (context) => RecipeDetailPage(), '/recipeDetail': (context) => RecipeDetailPage(),
'/momentForm': (context) => MomentFormPage(), '/momentForm': (context) => MomentFormPage(),
'/moment': (context) => MomentPage(), '/moment': (context) => MomentPage(),

View File

@@ -5,11 +5,13 @@ part 'recipe.g.dart';
/// 食材信息 /// 食材信息
@JsonSerializable() @JsonSerializable()
class RecipeMaterial { class RecipeMaterial {
int id;
String type; String type;
String name; String name;
String amount; String amount;
RecipeMaterial({ RecipeMaterial({
required this.id,
required this.type, required this.type,
required this.name, required this.name,
required this.amount, required this.amount,
@@ -24,11 +26,13 @@ class RecipeMaterial {
/// 步骤信息 /// 步骤信息
@JsonSerializable() @JsonSerializable()
class RecipeStep { class RecipeStep {
int id;
int sort; int sort;
String content; String content;
String imageUrl; String imageUrl;
RecipeStep({ RecipeStep({
required this.id,
required this.sort, required this.sort,
required this.content, required this.content,
required this.imageUrl, required this.imageUrl,

View File

@@ -8,6 +8,7 @@ part of 'recipe.dart';
RecipeMaterial _$RecipeMaterialFromJson(Map<String, dynamic> json) => RecipeMaterial _$RecipeMaterialFromJson(Map<String, dynamic> json) =>
RecipeMaterial( RecipeMaterial(
id: (json['id'] as num).toInt(),
type: json['type'] as String, type: json['type'] as String,
name: json['name'] as String, name: json['name'] as String,
amount: json['amount'] as String, amount: json['amount'] as String,
@@ -15,12 +16,14 @@ RecipeMaterial _$RecipeMaterialFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$RecipeMaterialToJson(RecipeMaterial instance) => Map<String, dynamic> _$RecipeMaterialToJson(RecipeMaterial instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id,
'type': instance.type, 'type': instance.type,
'name': instance.name, 'name': instance.name,
'amount': instance.amount, 'amount': instance.amount,
}; };
RecipeStep _$RecipeStepFromJson(Map<String, dynamic> json) => RecipeStep( RecipeStep _$RecipeStepFromJson(Map<String, dynamic> json) => RecipeStep(
id: (json['id'] as num).toInt(),
sort: (json['sort'] as num).toInt(), sort: (json['sort'] as num).toInt(),
content: json['content'] as String, content: json['content'] as String,
imageUrl: json['imageUrl'] as String, imageUrl: json['imageUrl'] as String,
@@ -28,6 +31,7 @@ RecipeStep _$RecipeStepFromJson(Map<String, dynamic> json) => RecipeStep(
Map<String, dynamic> _$RecipeStepToJson(RecipeStep instance) => Map<String, dynamic> _$RecipeStepToJson(RecipeStep instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id,
'sort': instance.sort, 'sort': instance.sort,
'content': instance.content, 'content': instance.content,
'imageUrl': instance.imageUrl, 'imageUrl': instance.imageUrl,

783
lib/views/recipe_form.dart Normal file
View File

@@ -0,0 +1,783 @@
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<RecipeFormPage> createState() => _RecipeFormPageState();
}
class _RecipeFormPageState extends State<RecipeFormPage> {
final _formKey = GlobalKey<FormBuilderState>();
int _currentStep = 0;
List<RecipeMaterial> _materials = [];
List<RecipeStep> _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<String> _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(),
],
),
),
),
);
}
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'index.dart';
/// 通用输入框样式 /// 通用输入框样式
InputDecoration buildInputDecoration({ InputDecoration buildInputDecoration({
required BuildContext context, required BuildContext context,
@@ -87,40 +89,19 @@ Widget buildFormButtonGroup({
/// 取消按钮 /// 取消按钮
Widget _buildCancelButton(BuildContext context) { Widget _buildCancelButton(BuildContext context) {
return ElevatedButton( return buildInfoButton(
onPressed: () => Navigator.pop(context), // 直接使用传入的context返回 context: context,
style: ElevatedButton.styleFrom( text: '取消',
backgroundColor: Theme.of(context).colorScheme.surface, onPressed: () => Navigator.pop(context),
foregroundColor: const Color(0xFF4E5969),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: const Text('取消'),
); );
} }
/// 提交按钮 /// 提交按钮
Widget _buildSubmitButton(BuildContext context, VoidCallback onConfirm) { Widget _buildSubmitButton(BuildContext context, VoidCallback onConfirm) {
return ElevatedButton( return buildPrimaryButton(context: context, text: '提交', onPressed: onConfirm);
onPressed: () => onConfirm(),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: const Text('提交', style: TextStyle(color: Colors.white)),
);
} }
/// 删除按钮 /// 删除按钮
Widget _buildDeleteButton(BuildContext context, VoidCallback onDelete) { Widget _buildDeleteButton(BuildContext context, VoidCallback onDelete) {
return ElevatedButton( return buildErrorButton(text: '删除', onPressed: onDelete);
onPressed: () => onDelete(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: const Text('删除', style: TextStyle(color: Colors.white)),
);
} }

View File

@@ -92,13 +92,50 @@ Widget buildTag(BuildContext context, String title) {
); );
} }
/// 确认按钮样式 Widget buildPrimaryButton({
ButtonStyle primaryButtonStyle() { required BuildContext context,
return ButtonStyle( required String text,
backgroundColor: WidgetStateProperty.all(Colors.green), required VoidCallback onPressed,
shape: WidgetStateProperty.all( }) {
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), return ElevatedButton(
onPressed: () => onPressed(),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
), ),
child: Text(text, style: TextStyle(color: Colors.white)),
);
}
Widget buildErrorButton({
required String text,
required VoidCallback onPressed,
}) {
return ElevatedButton(
onPressed: () => onPressed(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: Text(text, style: TextStyle(color: Colors.white)),
);
}
Widget buildInfoButton({
required BuildContext context,
required String text,
required VoidCallback onPressed,
}) {
return ElevatedButton(
onPressed: () => onPressed(),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: Text(text, style: TextStyle(color: Colors.white)),
); );
} }