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

View File

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

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) {
return Center(
child: SizedBox(