From 703a5a6aeb80c8075130358aae61f272ed81e79e Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Tue, 11 Nov 2025 18:27:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E9=97=AA=E7=81=B5?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9B=BE=E7=89=87=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/flisp_page.dart | 2 +- lib/utils/minio_utils.dart | 57 +++++++++++++++++++ lib/widgets/flisp_form.dart | 39 ++++++++++++- lib/widgets/flisp_widget.dart | 56 +++++++++++++++--- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 50 +++++++++++++++- pubspec.yaml | 20 ++++++- 7 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 lib/utils/minio_utils.dart diff --git a/lib/pages/flisp_page.dart b/lib/pages/flisp_page.dart index ba965a8..66eac02 100644 --- a/lib/pages/flisp_page.dart +++ b/lib/pages/flisp_page.dart @@ -25,7 +25,7 @@ class FlispPageState extends State { late List _flisps; FlispTab _currentTab = FlispTab.all; - // 获取过滤后的待办事项 + // 获取过滤后的闪灵事项 List get _activeFlisps => getActiveFlisps(_currentTab, _flisps); void showAddDialog() { diff --git a/lib/utils/minio_utils.dart b/lib/utils/minio_utils.dart new file mode 100644 index 0000000..ad6f7e7 --- /dev/null +++ b/lib/utils/minio_utils.dart @@ -0,0 +1,57 @@ +import 'dart:io'; +import 'package:crypto/crypto.dart'; + +import 'package:file_picker/file_picker.dart'; +import 'package:minio/io.dart'; +import 'package:minio/minio.dart'; + +class MinIOHelper { + static final MinIOHelper _instance = MinIOHelper._internal(); + + factory MinIOHelper() => _instance; + + final String rustfsIp = '14.103.235.151'; + final String rustfsFileUrl = 'http://14.103.235.151:9100'; + final String bucketName = 'flisp'; + + MinIOHelper._internal() { + _minio = Minio( + endPoint: rustfsIp, + port: 9100, + accessKey: "tHSFfcDW8qpCzKa2Xg6Y", + secretKey: "oq79EeYJ4jdczRp2IHUMCnbKtSw58NgDlG3sOkvX", + useSSL: false, + ); + } + + late Minio _minio; + + Future uploadFile({ + required PlatformFile file, + Function(double)? onProgress, + }) async { + try { + String hashName = await _generateMD5HashName(file.path!); + String fileName = '$hashName${_getFileExtension(file.name)}'; + + await _minio.fPutObject(bucketName, fileName, file.path!); + return fileName; + } catch (e) { + throw Exception('文件上传失败: $e'); + } + } + + String _getFileExtension(String fileName) { + if (fileName.contains('.')) { + return '.${fileName.split('.').last.toLowerCase()}'; + } + return ''; + } + + Future _generateMD5HashName(String filePath) async { + final file = File(filePath); + final bytes = await file.readAsBytes(); + final hash = md5.convert(bytes); + return hash.toString(); + } +} diff --git a/lib/widgets/flisp_form.dart b/lib/widgets/flisp_form.dart index 04393fc..79c4f59 100644 --- a/lib/widgets/flisp_form.dart +++ b/lib/widgets/flisp_form.dart @@ -1,5 +1,7 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flisp_app/models/flisp.dart'; import 'package:flisp_app/provider/flisp_provider.dart'; +import 'package:flisp_app/utils/minio_utils.dart'; import 'package:flisp_app/widgets/flisp_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; @@ -45,6 +47,21 @@ class _FlispFormState extends State { }); } + void _selectImage(String fileName) { + final provider = Provider.of(context, listen: false); + setState(() { + provider.formItem.imageUrl = + '${MinIOHelper().rustfsFileUrl}/${MinIOHelper().bucketName}/$fileName'; + }); + } + + void _deleteImage() { + final provider = Provider.of(context, listen: false); + setState(() { + provider.formItem.imageUrl = ''; + }); + } + @override Widget build(BuildContext context) { final int maxContentCount = 100; @@ -105,15 +122,35 @@ class _FlispFormState extends State { SizedBox(height: 8), buildFlispTag(provider.formItem), SizedBox(height: 12), + if (provider.formItem.imageUrl.isNotEmpty) + buildFlispImage( + flisp: provider.formItem, + showDelete: true, + onDelete: _deleteImage, + ), ], ), ), ); } + void selectImage() async { + 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); + _selectImage(fileName); + } + } + Widget buildSelectImageButton() { return GestureDetector( - onTap: () {}, + onTap: () => selectImage(), child: Container( width: 36, height: 36, diff --git a/lib/widgets/flisp_widget.dart b/lib/widgets/flisp_widget.dart index c0f4f36..145f7d1 100644 --- a/lib/widgets/flisp_widget.dart +++ b/lib/widgets/flisp_widget.dart @@ -98,26 +98,64 @@ Widget _buildFlispContent(Flisp flisp) { ); } -Widget _buildFlispImage(Flisp flisp) { +Widget _buildDeleteImage(VoidCallback? onDelete) { + return Positioned( + top: -8, + right: 0, + child: GestureDetector( + onTap: onDelete, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(10), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Icon(Icons.close, color: Colors.white, size: 16), + ), + ), + ); +} + +Widget buildFlispImage({ + required Flisp flisp, + bool showDelete = false, + VoidCallback? onDelete, +}) { return Container( - height: 150, + height: 200, margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage( image: NetworkImage(flisp.imageUrl), - fit: BoxFit.cover, + fit: BoxFit.contain, ), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - color: Colors.black.withOpacity(0.3), + color: Colors.black.withAlpha(10), ), - alignment: Alignment.topRight, - child: const Padding( - padding: EdgeInsets.all(6.0), - child: Icon(Icons.image, color: Colors.white, size: 20), + child: Stack( + clipBehavior: Clip.none, + children: [ + Align( + alignment: Alignment.topRight, + child: const Padding( + padding: EdgeInsets.all(6.0), + child: Icon(Icons.image, color: Colors.white, size: 20), + ), + ), + if (showDelete) _buildDeleteImage(onDelete), + ], ), ), ); @@ -253,7 +291,7 @@ Widget _buildFlispItem({ children: [ _buildFlispContent(flisp), const SizedBox(height: 8), - if (hasImage) _buildFlispImage(flisp), + if (hasImage) buildFlispImage(flisp: flisp, showDelete: false), const SizedBox(height: 4), if (hasVideo) _buildFlispVideo(flisp), const SizedBox(height: 4), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index be9e5b6..a6cc016 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import file_picker import flutter_local_notifications import path_provider_foundation import rive_native import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 206ce7c..0936bee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -54,6 +54,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.3" build: dependency: transitive description: @@ -166,8 +174,16 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" - crypto: + cross_file: dependency: transitive + description: + name: cross_file + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" + crypto: + dependency: "direct main" description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf @@ -222,6 +238,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.3.3" fixnum: dependency: transitive description: @@ -280,6 +304,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.31" flutter_test: dependency: "direct dev" description: flutter @@ -490,6 +522,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + minio: + dependency: "direct main" + description: + name: minio + sha256: ee2ce47766e46c7d164f960f2f5ed6a9a82844d877f6b82574f6876ec50c56d1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.5.8" nested: dependency: transitive description: @@ -863,6 +903,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.13.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a0578a0..d05e74d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,19 +35,35 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - provider: ^6.1.1 # 状态管理 - shared_preferences: ^2.2.2 # 本地存储 + # 状态管理 + provider: ^6.1.1 + # 本地存储 + shared_preferences: ^2.2.2 + # 对话框组件 awesome_dialog: ^3.3.0 + # 切换开关组件 toggle_switch: ^2.3.0 + # 轻量级提示 fluttertoast: ^8.2.2 + # 表单构建器 flutter_form_builder: ^10.0.0 + # 表单验证器 form_builder_validators: ^10.0.0 + # 国际化支持 intl: ^0.19.0 + # 轻量级NoSQL数据库 hive: ^2.2.3 + # Hive的Flutter集成 hive_flutter: ^1.1.0 + # 路径提供器 path_provider: ^2.1.1 + # 本地通知 flutter_local_notifications: ^18.0.0 + # 时区支持 timezone: ^0.10.1 + file_picker: ^10.3.3 + minio: ^3.5.8 + crypto: ^3.0.7 dev_dependencies: flutter_test: