feat:增加Minio工具类

This commit is contained in:
2025-11-18 20:03:25 +08:00
parent 9a8e1a7419
commit b6995d8dd1
15 changed files with 332 additions and 194 deletions

View File

@@ -71,7 +71,7 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('菜谱信息')),
backgroundColor: Color(0xFFF4F4F4),
backgroundColor: Color(0xFFF5F5F5),
body: SingleChildScrollView(
child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()),
),

View File

@@ -1,10 +1,17 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/utils/minio_utils.dart';
import 'package:food_hub_app/widgets/common/form.dart';
import 'package:image_picker/image_picker.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';
class RecordFormPage extends StatefulWidget {
const RecordFormPage({super.key});
@@ -17,9 +24,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
final _focusNode = FocusNode();
static const String _nameField = 'name';
static const String _dateField = 'date';
final ImagePicker _picker = ImagePicker();
File? imageUrl; // 改为单张图片变量
File? imageFile;
@override
void dispose() {
@@ -29,12 +34,12 @@ class _RecordFormPageState extends State<RecordFormPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('新增记录', style: TextStyle(color: Colors.white)),
backgroundColor: theme.primaryColor,
backgroundColor: colors.primary,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
@@ -43,24 +48,16 @@ class _RecordFormPageState extends State<RecordFormPage> {
backgroundColor: const Color(0xFFF5F5F5),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(5),
child: Card(
elevation: 0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: _buildFormBuilder(),
),
),
padding: EdgeInsets.all(5),
child: buildCard(context: context, child: _buildFormBuilder()),
),
),
);
}
Widget _buildFormBuilder() {
final foodProvider = context.watch<FoodProvider>();
return FormBuilder(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
@@ -82,7 +79,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
_buildImageUploadArea(),
const SizedBox(height: 10),
buildFormButtonGroup(context: context, onConfirm: () => _submitForm),
buildFormButtonGroup(
context: context,
onConfirm: () => _submitForm(),
),
],
),
);
@@ -93,7 +93,11 @@ class _RecordFormPageState extends State<RecordFormPage> {
return FormBuilderTextField(
name: _nameField,
focusNode: _focusNode,
decoration: buildInputDecoration(context: context, hintText: '请输入菜谱名称'),
decoration: buildInputDecoration(
context: context,
hintText: '请输入菜谱名称',
prefixIcon: const Icon(Icons.title, size: 20, color: Color(0xFF86909C)),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入菜谱名称';
@@ -132,10 +136,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageUrl != null)
if (imageFile != null)
_buildImagePreviewItem()
else
_buildImageUploadButton(),
buildImageUploadButton(onPickImage: _pickImage),
],
);
}
@@ -147,109 +151,8 @@ class _RecordFormPageState extends State<RecordFormPage> {
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
GestureDetector(
onTap: () => _showImagePreview(),
child: Image(
image: FileImage(imageUrl!),
width: 120,
height: 120,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: 120,
height: 120,
color: Colors.grey[100],
child: const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
);
},
),
),
Positioned(
top: 6,
right: 6,
child: GestureDetector(
onTap: _removeImage,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 2,
spreadRadius: 0,
),
],
),
child: const Icon(Icons.close, color: Colors.white, size: 16),
),
),
),
],
),
),
);
}
void _showImagePreview() {
showDialog(
context: context,
barrierColor: Colors.black87, // 半透明黑色背景
builder:
(context) => Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: const EdgeInsets.all(16),
child: GestureDetector(
onTap: () => Navigator.pop(context), // 点击空白处关闭
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
// 预览图添加轻微阴影
boxShadow: [BoxShadow(color: Colors.black38, blurRadius: 10)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.file(
imageUrl!,
fit: BoxFit.contain, // 保持图片比例
height: MediaQuery.of(context).size.height * 0.7, // 限制最大高度
),
),
),
),
),
);
}
/// 图片上传按钮
Widget _buildImageUploadButton() {
return InkWell(
onTap: _pickImage,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFDCDFE6), width: 1),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add, color: Color(0xFF86909C), size: 24),
SizedBox(height: 6),
Text(
'添加图片',
style: TextStyle(color: Color(0xFF86909C), fontSize: 13),
),
buildFileImage(context, imageFile!),
buildDeleteImage(onRemoveImage: _removeImage),
],
),
),
@@ -263,19 +166,6 @@ class _RecordFormPageState extends State<RecordFormPage> {
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder:
(context, child) => Theme(
data: ThemeData.light().copyWith(
primaryColor: Theme.of(context).primaryColor,
colorScheme: ColorScheme.light(
primary: Theme.of(context).primaryColor,
),
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
),
),
child: child!,
),
);
if (picked != null) {
@@ -287,10 +177,17 @@ class _RecordFormPageState extends State<RecordFormPage> {
/// 选择图片(限制单张)
Future<void> _pickImage() async {
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png'],
allowMultiple: false,
);
if (result != null) {
PlatformFile file = result.files.first;
final fileName = await MinIOHelper().uploadFile(file: file);
setState(() {
imageUrl = File(image.path); // 直接覆盖现有图片
// imageFile = File(image.path);
});
}
}
@@ -298,14 +195,14 @@ class _RecordFormPageState extends State<RecordFormPage> {
/// 移除图片
void _removeImage() {
setState(() {
imageUrl = null;
imageFile = null;
});
}
/// 提交表单
void _submitForm() {
if (_formKey.currentState?.saveAndValidate() ?? false) {
if (imageUrl == null) {
if (imageFile == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请上传图片'),
@@ -318,7 +215,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
final formData = {
..._formKey.currentState!.value,
'imageUrl': imageUrl?.path, // 单张图片路径
'imageUrl': imageFile?.path,
};
ScaffoldMessenger.of(context).showSnackBar(