feat:更新上传记录表单

This commit is contained in:
2025-11-18 23:18:24 +08:00
parent b6995d8dd1
commit 7ede1c2d89
4 changed files with 101 additions and 102 deletions

View File

@@ -1,10 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:food_hub_app/models/layout.dart'; import 'package:food_hub_app/models/layout.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/views/moment.dart'; import 'package:food_hub_app/views/moment.dart';
import 'package:food_hub_app/views/profile.dart'; import 'package:food_hub_app/views/profile.dart';
import 'package:food_hub_app/views/record.dart'; import 'package:food_hub_app/views/record.dart';
import 'package:food_hub_app/views/stats.dart'; import 'package:food_hub_app/views/stats.dart';
import 'package:provider/provider.dart';
final List<PageInfo> pageInfos = [ final List<PageInfo> pageInfos = [
PageInfo( PageInfo(
label: "记录", label: "记录",
@@ -69,6 +73,7 @@ void buildBottomSheet(BuildContext context) {
Widget buildBottomSheetBody(BuildContext context) { Widget buildBottomSheetBody(BuildContext context) {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
final provider = context.watch<FoodProvider>();
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -91,6 +96,7 @@ Widget buildBottomSheetBody(BuildContext context) {
leading: Icon(Icons.note_add, color: colors.primary), leading: Icon(Icons.note_add, color: colors.primary),
title: const Text('新增记录'), title: const Text('新增记录'),
onTap: () { onTap: () {
provider.resetRecordForm();
Navigator.pop(context); Navigator.pop(context);
Navigator.pushNamed(context, "/recordForm"); Navigator.pushNamed(context, "/recordForm");
}, },

View File

@@ -12,7 +12,7 @@ class MinIOHelper {
final String rustfsIp = '14.103.235.151'; final String rustfsIp = '14.103.235.151';
final String rustfsFileUrl = 'http://14.103.235.151:9100'; final String rustfsFileUrl = 'http://14.103.235.151:9100';
final String bucketName = 'flisp'; final String bucketName = 'food';
MinIOHelper._internal() { MinIOHelper._internal() {
_minio = Minio( _minio = Minio(
@@ -26,6 +26,8 @@ class MinIOHelper {
late Minio _minio; late Minio _minio;
String get imageBaseUrl => '$rustfsFileUrl/$bucketName/';
Future<String> uploadFile({ Future<String> uploadFile({
required PlatformFile file, required PlatformFile file,
Function(double)? onProgress, Function(double)? onProgress,

View File

@@ -6,9 +6,7 @@ import 'package:food_hub_app/utils/minio_utils.dart';
import 'package:food_hub_app/widgets/common/form.dart'; import 'package:food_hub_app/widgets/common/form.dart';
import 'package:food_hub_app/widgets/common/image.dart'; import 'package:food_hub_app/widgets/common/image.dart';
import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/common/index.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'dart:io';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -24,7 +22,6 @@ class _RecordFormPageState extends State<RecordFormPage> {
final _focusNode = FocusNode(); final _focusNode = FocusNode();
static const String _nameField = 'name'; static const String _nameField = 'name';
static const String _dateField = 'date'; static const String _dateField = 'date';
File? imageFile;
@override @override
void dispose() { void dispose() {
@@ -56,7 +53,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
} }
Widget _buildFormBuilder() { Widget _buildFormBuilder() {
final foodProvider = context.watch<FoodProvider>(); final provider = context.watch<FoodProvider>();
return FormBuilder( return FormBuilder(
key: _formKey, key: _formKey,
@@ -66,37 +63,22 @@ class _RecordFormPageState extends State<RecordFormPage> {
children: [ children: [
buildFormLabel('菜谱名称', required: true), buildFormLabel('菜谱名称', required: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildNameTextField(), FormBuilderTextField(
const SizedBox(height: 10),
buildFormLabel('完成时间', required: true),
const SizedBox(height: 8),
_buildDateTextField(),
const SizedBox(height: 10),
buildFormLabel('上传图片', required: true),
const SizedBox(height: 10),
_buildImageUploadArea(),
const SizedBox(height: 10),
buildFormButtonGroup(
context: context,
onConfirm: () => _submitForm(),
),
],
),
);
}
/// 菜谱名称输入框
Widget _buildNameTextField() {
return FormBuilderTextField(
name: _nameField, name: _nameField,
focusNode: _focusNode, focusNode: _focusNode,
onChanged: (value) {
setState(() {
provider.recordFormItem.name = value!;
});
},
decoration: buildInputDecoration( decoration: buildInputDecoration(
context: context, context: context,
hintText: '请输入菜谱名称', hintText: '请输入菜谱名称',
prefixIcon: const Icon(Icons.title, size: 20, color: Color(0xFF86909C)), prefixIcon: const Icon(
Icons.title,
size: 20,
color: Color(0xFF86909C),
),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@@ -104,12 +86,12 @@ class _RecordFormPageState extends State<RecordFormPage> {
} }
return null; return null;
}, },
); ),
} const SizedBox(height: 10),
/// 完成时间选择框 buildFormLabel('完成时间', required: true),
Widget _buildDateTextField() { const SizedBox(height: 8),
return FormBuilderTextField( FormBuilderTextField(
name: _dateField, name: _dateField,
readOnly: true, readOnly: true,
decoration: buildInputDecoration( decoration: buildInputDecoration(
@@ -121,46 +103,45 @@ class _RecordFormPageState extends State<RecordFormPage> {
color: Color(0xFF86909C), color: Color(0xFF86909C),
), ),
), ),
onTap: () => _onSelectDate(), onTap: () => _onSelectDate(provider),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return '请选择完成时间'; return '请选择完成时间';
} }
return null; return null;
}, },
); ),
} const SizedBox(height: 10),
/// 图片上传区域(预览+上传按钮) buildFormLabel('上传图片', required: true),
Widget _buildImageUploadArea() { const SizedBox(height: 10),
return Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (imageFile != null) if (provider.recordFormItem.imageUrl.isEmpty)
_buildImagePreviewItem() buildImageUploadButton(onPickImage: () => _pickImage(provider))
else else
buildImageUploadButton(onPickImage: _pickImage), buildImagePreviewItem(
], context: context,
); imageUrl:
} '${MinIOHelper().imageBaseUrl}${provider.recordFormItem.imageUrl}',
onRemoveImage: () => _removeImage(provider),
Widget _buildImagePreviewItem() { ),
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
buildFileImage(context, imageFile!),
buildDeleteImage(onRemoveImage: _removeImage),
], ],
), ),
const SizedBox(height: 10),
buildFormButtonGroup(
context: context,
onConfirm: () => _submitForm(provider),
),
],
), ),
); );
} }
/// 选择日期 /// 选择日期
Future<void> _onSelectDate() async { Future<void> _onSelectDate(FoodProvider provider) async {
final DateTime? picked = await showDatePicker( final DateTime? picked = await showDatePicker(
context: context, context: context,
initialDate: DateTime.now(), initialDate: DateTime.now(),
@@ -169,14 +150,16 @@ class _RecordFormPageState extends State<RecordFormPage> {
); );
if (picked != null) { if (picked != null) {
_formKey.currentState?.fields[_dateField]?.didChange( final String date = DateFormat('yyyy-MM-dd').format(picked);
DateFormat('yyyy-MM-dd').format(picked), _formKey.currentState?.fields[_dateField]?.didChange(date);
); setState(() {
provider.recordFormItem.date = date;
});
} }
} }
/// 选择图片(限制单张) /// 选择图片(限制单张)
Future<void> _pickImage() async { Future<void> _pickImage(FoodProvider provider) async {
FilePickerResult? result = await FilePicker.platform.pickFiles( FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom, type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png'], allowedExtensions: ['jpg', 'jpeg', 'png'],
@@ -187,22 +170,22 @@ class _RecordFormPageState extends State<RecordFormPage> {
PlatformFile file = result.files.first; PlatformFile file = result.files.first;
final fileName = await MinIOHelper().uploadFile(file: file); final fileName = await MinIOHelper().uploadFile(file: file);
setState(() { setState(() {
// imageFile = File(image.path); provider.recordFormItem.imageUrl = fileName;
}); });
} }
} }
/// 移除图片 /// 移除图片
void _removeImage() { void _removeImage(FoodProvider provider) {
setState(() { setState(() {
imageFile = null; provider.recordFormItem.imageUrl = '';
}); });
} }
/// 提交表单 /// 提交表单
void _submitForm() { void _submitForm(FoodProvider provider) {
if (_formKey.currentState?.saveAndValidate() ?? false) { if (_formKey.currentState?.saveAndValidate() ?? false) {
if (imageFile == null) { if (provider.recordFormItem.imageUrl.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('请上传图片'), content: Text('请上传图片'),
@@ -213,18 +196,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
return; return;
} }
final formData = { print(provider.recordFormItem.imageUrl);
..._formKey.currentState!.value,
'imageUrl': imageFile?.path,
};
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('提交成功!'),
behavior: SnackBarBehavior.floating,
),
);
print('表单数据: $formData');
} }
} }
} }

View File

@@ -126,6 +126,25 @@ Widget buildNetworkImage(BuildContext context, String url) {
); );
} }
Widget buildImagePreviewItem({
required BuildContext context,
required String imageUrl,
required VoidCallback onRemoveImage,
}) {
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
buildNetworkImage(context, imageUrl),
buildDeleteImage(onRemoveImage: onRemoveImage),
],
),
),
);
}
Widget buildImageLoadingIndicator(ImageChunkEvent? loadingProgress) { Widget buildImageLoadingIndicator(ImageChunkEvent? loadingProgress) {
return Center( return Center(
child: SizedBox( child: SizedBox(