Browse Source

[feat]亲密度分析,初步对接文件上传接口

hezihao 7 months ago
parent
commit
ec3ccaff2e
29 changed files with 642 additions and 181 deletions
  1. 1 2
      lib/data/api/atmob_file_api.c.dart
  2. 10 5
      lib/data/api/atmob_file_api.dart
  3. 2 2
      lib/data/api/response/upload_result_bean.dart
  4. 2 2
      lib/data/api/response/upload_result_bean.g.dart
  5. 48 0
      lib/data/bean/upload_info.dart
  6. 29 0
      lib/data/bean/upload_info.g.dart
  7. 35 0
      lib/data/repository/file_upload_repository.dart
  8. 0 10
      lib/data/repository/intimacy_analyze_repository.dart
  9. 24 5
      lib/di/get_it.config.dart
  10. 27 21
      lib/di/network_module.dart
  11. 67 3
      lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart
  12. 75 66
      lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart
  13. 16 22
      lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_item_widget.dart
  14. 27 15
      lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_nine_grid.dart
  15. 3 4
      lib/module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_view.dart
  16. 4 0
      lib/module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_controller.dart
  17. 4 5
      lib/module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_view.dart
  18. 12 2
      lib/module/intimacy_analyse/widget/step/upload_step_card.dart
  19. 0 12
      lib/utils/fake_image_util.dart
  20. 25 0
      lib/utils/file_util.dart
  21. 19 0
      lib/utils/image_picker_util.dart
  22. 3 3
      lib/utils/prefix_util.dart
  23. 20 0
      lib/utils/upload/file_data_source_util.dart
  24. 117 0
      lib/utils/upload/upload_file_manager.dart
  25. 26 0
      lib/utils/upload/upload_scene_type.dart
  26. 26 0
      lib/utils/upload/upload_state.dart
  27. 12 0
      lib/utils/uuid_util.dart
  28. 2 2
      pubspec.lock
  29. 6 0
      pubspec.yaml

+ 1 - 2
lib/data/api/atmob_file_api.c.dart

@@ -24,8 +24,7 @@ class _AtmobFileApi implements AtmobFileApi {
     final _extra = <String, dynamic>{};
     final queryParameters = <String, dynamic>{};
     final _headers = <String, dynamic>{};
-    final _data = <String, dynamic>{};
-    _data.addAll(request.toJson());
+    final _data = FormData.fromMap(request.toJson());
     final _options = _setStreamType<BaseResponse<UploadResultBean>>(
       Options(
             method: 'POST',

+ 10 - 5
lib/data/api/atmob_file_api.dart

@@ -1,15 +1,21 @@
+import 'dart:convert';
+
 import 'package:dio/dio.dart';
 import 'package:keyboard/data/api/request/upload_request.dart';
 import 'package:keyboard/data/api/response/upload_result_bean.dart';
 import 'package:retrofit/error_logger.dart';
 import 'package:retrofit/http.dart';
 import '../../base/base_response.dart';
-import '../../di/network_module.dart';
-import '../consts/Constants.dart';
 
 part 'atmob_file_api.c.dart';
 // part 'atmob_file_api.g.dart';
 
+/// 注:每次生成前,要将.c改成.g,生成后,再重命名为.c
+/// 以及要修改文件上传的代码,将下面1)、2)的代码,改为3)的代码
+/// 1)final _data = <String, dynamic>{};
+/// 2)_data.addAll(request.toJson());
+/// 改为 => 3)final _data =FormData.fromMap(request.toJson());
+
 @RestApi()
 abstract class AtmobFileApi {
   factory AtmobFileApi(Dio dio, {String baseUrl}) = _AtmobFileApi;
@@ -18,7 +24,6 @@ abstract class AtmobFileApi {
   @MultiPart()
   @POST("/project/keyboard/v1/upload/image")
   Future<BaseResponse<UploadResultBean>> uploadImage(
-      @Body() UploadRequest request);
+    @Body() UploadRequest request,
+  );
 }
-
-final atmobFileApi = AtmobFileApi(fileDio, baseUrl: Constants.baseUrl);

+ 2 - 2
lib/data/api/response/upload_result_bean.dart

@@ -7,11 +7,11 @@ part 'upload_result_bean.g.dart';
 class UploadResultBean {
   /// 路径,用于提交给服务端
   @JsonKey(name: 'filePath')
-  String filePath;
+  String? filePath;
 
   /// cdn前缀
   @JsonKey(name: 'cdnPrefix')
-  int? cdnPrefix;
+  String? cdnPrefix;
 
   UploadResultBean({
     required this.filePath,

+ 2 - 2
lib/data/api/response/upload_result_bean.g.dart

@@ -8,8 +8,8 @@ part of 'upload_result_bean.dart';
 
 UploadResultBean _$UploadResultBeanFromJson(Map<String, dynamic> json) =>
     UploadResultBean(
-      filePath: json['filePath'] as String,
-      cdnPrefix: (json['cdnPrefix'] as num?)?.toInt(),
+      filePath: json['filePath'] as String?,
+      cdnPrefix: json['cdnPrefix'] as String?,
     );
 
 Map<String, dynamic> _$UploadResultBeanToJson(UploadResultBean instance) =>

+ 48 - 0
lib/data/bean/upload_info.dart

@@ -0,0 +1,48 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'upload_info.g.dart';
+
+/// 上传信息
+@JsonSerializable()
+class UploadInfo {
+  /// 唯一id
+  @JsonKey(name: "id")
+  String? id;
+
+  /// 文件名称
+  @JsonKey(name: "fileName")
+  String? fileName;
+
+  /// 文件的绝对路径
+  @JsonKey(name: "filePath")
+  String? filePath;
+
+  /// 文件场景类型
+  @JsonKey(name: "uploadSceneType")
+  String? uploadSceneType;
+
+  /// 上传状态
+  @JsonKey(name: "uploadState")
+  String? uploadState;
+
+  /// 文件的远程访问Url,上传成功后赋值
+  @JsonKey(name: "fileUrl")
+  String? fileUrl;
+
+  /// 文件的cdn前缀,上传成功后赋值
+  @JsonKey(name: "fileUrlCdnPrefix")
+  String? fileUrlCdnPrefix;
+
+  UploadInfo({
+    this.id,
+    this.fileName,
+    this.filePath,
+    this.uploadSceneType,
+    this.uploadState,
+  });
+
+  Map<String, dynamic> toJson() => _$UploadInfoToJson(this);
+
+  factory UploadInfo.fromJson(Map<String, dynamic> json) =>
+      _$UploadInfoFromJson(json);
+}

+ 29 - 0
lib/data/bean/upload_info.g.dart

@@ -0,0 +1,29 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'upload_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+UploadInfo _$UploadInfoFromJson(Map<String, dynamic> json) =>
+    UploadInfo(
+        id: json['id'] as String?,
+        fileName: json['fileName'] as String?,
+        filePath: json['filePath'] as String?,
+        uploadSceneType: json['uploadSceneType'] as String?,
+        uploadState: json['uploadState'] as String?,
+      )
+      ..fileUrl = json['fileUrl'] as String?
+      ..fileUrlCdnPrefix = json['fileUrlCdnPrefix'] as String?;
+
+Map<String, dynamic> _$UploadInfoToJson(UploadInfo instance) =>
+    <String, dynamic>{
+      'id': instance.id,
+      'fileName': instance.fileName,
+      'filePath': instance.filePath,
+      'uploadSceneType': instance.uploadSceneType,
+      'uploadState': instance.uploadState,
+      'fileUrl': instance.fileUrl,
+      'fileUrlCdnPrefix': instance.fileUrlCdnPrefix,
+    };

+ 35 - 0
lib/data/repository/file_upload_repository.dart

@@ -0,0 +1,35 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/utils/file_util.dart';
+
+import '../../utils/http_handler.dart';
+import '../api/atmob_file_api.dart';
+import '../api/request/upload_request.dart';
+import '../api/response/upload_result_bean.dart';
+
+/// 亲密度分析Repository层
+@LazySingleton()
+class FileUploadRepository {
+  final String tag = "FileUploadRepository";
+
+  /// 文件上传接口
+  AtmobFileApi atmobFileApi;
+
+  FileUploadRepository(this.atmobFileApi);
+
+  /// 上传图片
+  Future<UploadResultBean> uploadImage({required File file}) async {
+    // 包装为 MultipartFile
+    // MultipartFile multipartFile = await MultipartFile.fromFile(
+    //   // 文件路径
+    //   file.path,
+    //   // 文件名
+    //   filename: FileUtil.getFileName(file)
+    // );
+    return atmobFileApi
+        .uploadImage(UploadRequest(file: file))
+        .then(HttpHandler.handle(false));
+  }
+}

+ 0 - 10
lib/data/repository/intimacy_analyze_repository.dart

@@ -9,10 +9,7 @@ import '../../utils/async_util.dart';
 import '../../utils/atmob_log.dart';
 import '../../utils/http_handler.dart';
 import '../api/atmob_api.dart';
-import '../api/atmob_file_api.dart';
-import '../api/request/upload_request.dart';
 import '../api/response/intimacy_analyze_config_response.dart';
-import '../api/response/upload_result_bean.dart';
 
 /// 亲密度分析Repository层
 @LazySingleton()
@@ -44,13 +41,6 @@ class IntimacyAnalyzeRepository {
     });
   }
 
-  /// 上传图片
-  Future<UploadResultBean> uploadImage({required File? file}) {
-    return atmobFileApi
-        .uploadImage(UploadRequest(file: file))
-        .then(HttpHandler.handle(false));
-  }
-
   /// 获取亲密度配置
   Future<IntimacyAnalyzeConfigResponse> requestIntimacyAnalyzeConfig() {
     return atmobApi

+ 24 - 5
lib/di/get_it.config.dart

@@ -14,6 +14,7 @@ import 'package:get_it/get_it.dart' as _i174;
 import 'package:injectable/injectable.dart' as _i526;
 
 import '../data/api/atmob_api.dart' as _i243;
+import '../data/api/atmob_file_api.dart' as _i723;
 import '../data/api/atmob_stream_api.dart' as _i329;
 import '../data/bean/character_group_info.dart' as _i96;
 import '../data/bean/keyboard_info.dart' as _i497;
@@ -21,6 +22,7 @@ import '../data/repository/account_repository.dart' as _i83;
 import '../data/repository/characters_repository.dart' as _i421;
 import '../data/repository/chat_repository.dart' as _i425;
 import '../data/repository/config_repository.dart' as _i50;
+import '../data/repository/file_upload_repository.dart' as _i815;
 import '../data/repository/intimacy_analyze_repository.dart' as _i283;
 import '../data/repository/keyboard_repository.dart' as _i274;
 import '../data/repository/store_repository.dart' as _i987;
@@ -73,6 +75,7 @@ import '../module/store/suprise/goods_surprise_controller.dart' as _i935;
 import '../module/user_info/user_info_controller.dart' as _i866;
 import '../plugins/keyboard_method_handler.dart' as _i415;
 import '../utils/payment_status_manager.dart' as _i779;
+import '../utils/upload/upload_file_manager.dart' as _i428;
 import 'network_module.dart' as _i567;
 
 extension GetItInjectableX on _i174.GetIt {
@@ -133,14 +136,29 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i361.Dio>(instanceName: 'streamDio'),
       ),
     );
+    gh.singleton<_i361.Dio>(
+      () => networkModule.createFileDio(),
+      instanceName: 'fileDio',
+    );
     gh.factory<_i798.LoginDialogController>(
       () => _i798.LoginDialogController(gh<_i495.WechatLoginService>()),
     );
+    gh.singleton<_i723.AtmobFileApi>(
+      () => networkModule.provideAtmobFileApi(
+        gh<_i361.Dio>(instanceName: 'fileDio'),
+      ),
+    );
+    gh.lazySingleton<_i815.FileUploadRepository>(
+      () => _i815.FileUploadRepository(gh<_i723.AtmobFileApi>()),
+    );
     gh.singleton<_i243.AtmobApi>(
       () => networkModule.provideAtmobApi(
         gh<_i361.Dio>(instanceName: 'defaultDio'),
       ),
     );
+    gh.lazySingleton<_i428.UploadFileManager>(
+      () => _i428.UploadFileManager(gh<_i815.FileUploadRepository>()),
+    );
     gh.lazySingleton<_i83.AccountRepository>(
       () => _i83.AccountRepository(gh<_i243.AtmobApi>()),
     );
@@ -207,6 +225,12 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i161.KeyBoardController>(
       () => _i161.KeyBoardController(gh<_i274.KeyboardRepository>()),
     );
+    gh.factory<_i666.IntimacyAnalyseUploadController>(
+      () => _i666.IntimacyAnalyseUploadController(
+        gh<_i283.IntimacyAnalyzeRepository>(),
+        gh<_i428.UploadFileManager>(),
+      ),
+    );
     gh.factory<_i970.CharacterGroupContentController>(
       () => _i970.CharacterGroupContentController(
         gh<_i421.CharactersRepository>(),
@@ -271,11 +295,6 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i283.IntimacyAnalyzeRepository>(),
       ),
     );
-    gh.factory<_i666.IntimacyAnalyseUploadController>(
-      () => _i666.IntimacyAnalyseUploadController(
-        gh<_i283.IntimacyAnalyzeRepository>(),
-      ),
-    );
     gh.factory<_i1008.LoginController>(
       () => _i1008.LoginController(
         gh<_i83.AccountRepository>(),

+ 27 - 21
lib/di/network_module.dart

@@ -3,6 +3,7 @@ import 'package:injectable/injectable.dart';
 import 'package:keyboard/data/api/atmob_api.dart';
 import 'package:pretty_dio_logger/pretty_dio_logger.dart';
 
+import '../data/api/atmob_file_api.dart';
 import '../data/api/atmob_stream_api.dart';
 import '../data/consts/constants.dart';
 import '../utils/stream_dio_log_interceptor.dart';
@@ -29,6 +30,27 @@ abstract class NetworkModule {
   }
 
   @singleton
+  @Named("fileDio")
+  Dio createFileDio() {
+    Dio dio = Dio(BaseOptions(
+      sendTimeout: const Duration(seconds: 15),
+      receiveTimeout: const Duration(seconds: 15),
+    ));
+    dio.interceptors.add(
+      PrettyDioLogger(
+        requestHeader: true,
+        requestBody: true,
+        responseBody: true,
+        responseHeader: false,
+        error: true,
+        compact: true,
+        enabled: BuildConfig.isDebug,
+      ),
+    );
+    return dio;
+  }
+
+  @singleton
   @Named("streamDio")
   Dio createStreamDio() {
     Dio streamDio = Dio(
@@ -44,34 +66,18 @@ abstract class NetworkModule {
     return streamDio;
   }
 
-  static Dio _createFileDio() {
-    Dio dio = Dio(
-      BaseOptions(
-        sendTimeout: const Duration(seconds: 15),
-        receiveTimeout: const Duration(seconds: 15),
-      ),
-    );
-    dio.interceptors.add(
-      PrettyDioLogger(
-        requestHeader: true,
-        requestBody: true,
-        responseBody: true,
-        responseHeader: true,
-        enabled: BuildConfig.isDebug,
-      ),
-    );
-    return dio;
-  }
-
   @singleton
   AtmobApi provideAtmobApi(@Named("defaultDio") Dio dio) {
     return AtmobApi(dio, baseUrl: Constants.baseUrl);
   }
 
   @singleton
+  AtmobFileApi provideAtmobFileApi(@Named("fileDio") Dio dio) {
+    return AtmobFileApi(dio, baseUrl: Constants.baseUrl);
+  }
+
+  @singleton
   AtmobStreamApi provideAtmobStreamApi(@Named("streamDio") Dio streamDio) {
     return AtmobStreamApi(streamDio, baseUrl: Constants.baseUrl);
   }
 }
-
-final fileDio = NetworkModule._createFileDio();

+ 67 - 3
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart

@@ -1,7 +1,11 @@
+import 'dart:io';
+
 import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/data/bean/upload_info.dart';
+import 'package:keyboard/utils/upload/upload_scene_type.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 import '../../../data/api/response/intimacy_analyze_config_response.dart';
 import '../../../data/bean/option_select_config.dart';
@@ -10,21 +14,28 @@ import '../../../data/repository/intimacy_analyze_repository.dart';
 import '../../../router/app_page_arguments.dart';
 import '../../../utils/atmob_log.dart';
 import '../../../utils/image_picker_util.dart';
+import '../../../utils/upload/upload_file_manager.dart';
 
 /// 亲密度分析上传页Controller
 @injectable
 class IntimacyAnalyseUploadController extends BaseController {
   final String tag = "IntimacyAnalyseUploadController";
 
+  /// 上传场景
+  final UploadSceneType uploadSceneType = UploadSceneType.intimacyAnalyse;
+
   /// 亲密度分析Repository
   IntimacyAnalyzeRepository intimacyAnalyzeRepository;
 
+  /// 文件上传管理器
+  UploadFileManager uploadFileManager;
+
   /// 亲密度配置
   Rxn<IntimacyAnalyzeConfigResponse> get intimacyAnalyzeConfig =>
       intimacyAnalyzeRepository.intimacyAnalyzeConfig;
 
-  /// 已选择的图片列表
-  RxList<AssetEntity> selectedAssetList = <AssetEntity>[].obs;
+  /// 上传图片列表
+  RxList<UploadInfo> uploadInfoList = <UploadInfo>[].obs;
 
   /// Ai模型列表
   RxList<String> aiModelList = <String>[].obs;
@@ -69,7 +80,10 @@ class IntimacyAnalyseUploadController extends BaseController {
   /// 报告是否已解锁
   Rx<bool> isReportUnlock = true.obs;
 
-  IntimacyAnalyseUploadController(this.intimacyAnalyzeRepository);
+  IntimacyAnalyseUploadController(
+    this.intimacyAnalyzeRepository,
+    this.uploadFileManager,
+  );
 
   @override
   void onInit() {
@@ -130,6 +144,8 @@ class IntimacyAnalyseUploadController extends BaseController {
   void _initArgs() {
     final arguments = Get.arguments as Map<String, dynamic>?;
 
+    /// 当前选择的图片列表
+    List<AssetEntity> selectedAssetList = [];
     if (arguments?[AppPageArguments.selectedAssetList] == null) {
       AtmobLog.i(tag, '没有传递 selectedAssetList 参数');
     } else {
@@ -140,10 +156,58 @@ class IntimacyAnalyseUploadController extends BaseController {
         AtmobLog.i(tag, "selectedAssetList: $selectedAssetList");
       }
     }
+
+    _handleSelectedAssetUpload(selectedAssetList.toList());
+  }
+
+  /// 处理一进页面,就开始上传
+  void _handleSelectedAssetUpload(List<AssetEntity> selectedAssetList) async {
+    for (var entity in selectedAssetList) {
+      // 获取文件路径
+      File? file = await entity.file;
+      if (file == null) {
+        continue;
+      }
+      // 添加到上传列表
+      uploadInfoList.add(
+        uploadFileManager.uploadFile(
+          sceneType: uploadSceneType,
+          file: file,
+          onUploadInfoUpdateCallback: (uploadInfo) {
+            // 上传信息更新时回调,更新UI
+            _updateUploadInfo(uploadInfo);
+          },
+        ),
+      );
+      uploadInfoList.refresh();
+    }
+  }
+
+  /// 更新上传信息
+  void _updateUploadInfo(UploadInfo uploadInfo) {
+    // 更新上传信息
+    var newList = [...uploadInfoList];
+    newList.map((item) {
+      if (item.id == uploadInfo.id) {
+        return uploadInfo;
+      }
+      return item;
+    }).toList();
+    uploadInfoList.value = newList;
+  }
+
+  /// 删除上传信息
+  void deleteUploadInfo(UploadInfo uploadInfo) {
+    uploadFileManager.deleteUploadInfo(uploadInfo);
+    uploadInfoList.removeWhere((item) {
+      return item.id == uploadInfo.id;
+    });
+    uploadInfoList.refresh();
   }
 
   /// 返回上一页
   void clickBack() {
+    uploadFileManager.deleteUploadInfoBySceneType(uploadSceneType);
     Get.back();
   }
 

+ 75 - 66
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart

@@ -131,23 +131,30 @@ class IntimacyAnalyseUploadPage
 
   /// 上传步骤卡片
   Widget _buildUploadStepCard() {
-    return UploadStepCard(
-      topTitleWidget: StepTitleWidget(
-        stepLabel: StringFormatUtil.formatStr(
-          StringName.intimacyAnalyseStep,
-          1.toString(),
+    return Obx(() {
+      return UploadStepCard(
+        topTitleWidget: StepTitleWidget(
+          stepLabel: StringFormatUtil.formatStr(
+            StringName.intimacyAnalyseStep,
+            1.toString(),
+          ),
+          stepTitle: StringName.intimacyAnalyseStepTitleSelectImage,
+          stepDesc: StringName.intimacyAnalyseUploadCardTip,
         ),
-        stepTitle: StringName.intimacyAnalyseStepTitleSelectImage,
-        stepDesc: StringName.intimacyAnalyseUploadCardTip,
-      ),
-      // 底部,添加上当前的亲密关系
-      bottomChild: _buildRecommendIntimacy(),
-      onClickAddCallback: () {
-        if (Get.context != null) {
-          controller.clickUploadBtn(Get.context!);
-        }
-      },
-    );
+        // 底部,添加上当前的亲密关系
+        bottomChild: _buildRecommendIntimacy(),
+        onClickAddCallback: () {
+          if (Get.context != null) {
+            controller.clickUploadBtn(Get.context!);
+          }
+        },
+        // 上传信息列表
+        imageUploadInfoList: controller.uploadInfoList.value,
+        onClickDeleteCallback: (uploadInfo) {
+          controller.deleteUploadInfo(uploadInfo);
+        },
+      );
+    });
   }
 
   /// 预测方向步骤卡片
@@ -206,58 +213,60 @@ class IntimacyAnalyseUploadPage
 
   /// 分析结果卡片
   Widget _buildAnalysisResultCard() {
-    return StepCard(
-      bgImageProvider: Assets.images.bgIntimacyAnalyseUploadCard.provider(),
-      topTitleWidget: StepTitleWidget(
-        stepTitle: StringName.intimacyAnalyseAnalysisResult,
-      ),
-      topIconWidget: Assets.images.iconIntimacyAnalysisResultTop.image(
-        height: 63.h,
-        width: 103.w,
-      ),
-      contentWidget: Column(
-        children: [
-          // 图片九宫格
-          Container(
-            constraints: BoxConstraints(
-              // 最小高度
-              minHeight: 212.h
-            ),
-            margin: EdgeInsets.only(left: 12.w, right: 12.w),
-            padding: EdgeInsets.only(
-              left: 12.w,
-              top: 12.h,
-              right: 12.w,
-              bottom: 12.h,
-            ),
-            decoration: BoxDecoration(
-              color: ColorName.white,
-              borderRadius: BorderRadius.circular(16.r),
-            ),
-            child: UploadNineGrid(
-              mode: Mode.preview,
-              // 上传的图片列表
-              imageSrcList: [],
-              maxCount: 9,
-              spacing: 8.0,
+    return Obx(() {
+      return StepCard(
+        bgImageProvider: Assets.images.bgIntimacyAnalyseUploadCard.provider(),
+        topTitleWidget: StepTitleWidget(
+          stepTitle: StringName.intimacyAnalyseAnalysisResult,
+        ),
+        topIconWidget: Assets.images.iconIntimacyAnalysisResultTop.image(
+          height: 63.h,
+          width: 103.w,
+        ),
+        contentWidget: Column(
+          children: [
+            // 图片九宫格
+            Container(
+              constraints: BoxConstraints(
+                // 最小高度
+                minHeight: 212.h,
+              ),
+              margin: EdgeInsets.only(left: 12.w, right: 12.w),
+              padding: EdgeInsets.only(
+                left: 12.w,
+                top: 12.h,
+                right: 12.w,
+                bottom: 12.h,
+              ),
+              decoration: BoxDecoration(
+                color: ColorName.white,
+                borderRadius: BorderRadius.circular(16.r),
+              ),
+              child: UploadNineGrid(
+                mode: Mode.preview,
+                // 上传的图片列表
+                imageUploadInfoList: controller.uploadInfoList,
+                maxCount: 9,
+                spacing: 8.0,
+              ),
             ),
-          ),
-          SizedBox(height: 10.h),
-          Container(
-            margin: EdgeInsets.symmetric(horizontal: 12.w),
-            child: Row(
-              children: [
-                // 预测方向
-                _buildDirectionResult(),
-                SizedBox(width: 12.w),
-                // 模型
-                _buildAiModelResult(),
-              ],
+            SizedBox(height: 10.h),
+            Container(
+              margin: EdgeInsets.symmetric(horizontal: 12.w),
+              child: Row(
+                children: [
+                  // 预测方向
+                  _buildDirectionResult(),
+                  SizedBox(width: 12.w),
+                  // 模型
+                  _buildAiModelResult(),
+                ],
+              ),
             ),
-          ),
-        ],
-      ),
-    );
+          ],
+        ),
+      );
+    });
   }
 
   /// 分析报告

+ 16 - 22
lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_item_widget.dart

@@ -6,33 +6,24 @@ import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:keyboard/resource/assets.gen.dart';
 
+import '../../../../data/bean/upload_info.dart';
 import '../../../../resource/colors.gen.dart';
 import '../../../../resource/string.gen.dart';
 import '../../../../utils/prefix_util.dart';
+import '../../../../utils/upload/file_data_source_util.dart';
+import '../../../../utils/upload/upload_state.dart';
 import '../../../../widget/rotate_image.dart';
 
-/// 上传状态
-enum UploadState {
-  /// 上传成功
-  success,
-
-  /// 上传中
-  uploading,
-
-  /// 上传失败
-  fail,
-}
-
 /// 点击了条目时回调
-typedef OnClickItemCallback = void Function();
+typedef OnClickItemCallback = void Function(UploadInfo uploadInfo);
 
 /// 点击了删除按钮时回调
-typedef OnClickDeleteCallback = void Function();
+typedef OnClickDeleteCallback = void Function(UploadInfo uploadInfo);
 
 /// 上传项组件,有3种状态,上传成功、上传中、上传失败
 class UploadItemWidget extends StatelessWidget {
-  /// 图片路径
-  final String imageSrc;
+  /// 图片的上传信息
+  final UploadInfo imageUploadInfo;
 
   /// 是否有删除按钮
   final bool hasDeleteBtn;
@@ -51,7 +42,7 @@ class UploadItemWidget extends StatelessWidget {
 
   const UploadItemWidget({
     super.key,
-    required this.imageSrc,
+    required this.imageUploadInfo,
     this.hasDeleteBtn = false,
     required this.uploadState,
     this.uploadFailMaskWidget,
@@ -62,7 +53,9 @@ class UploadItemWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return GestureDetector(
-      onTap: onClickItemCallback,
+      onTap: () {
+        onClickItemCallback(imageUploadInfo);
+      },
       child: Container(
         // decoration: BoxDecoration(borderRadius: BorderRadius.circular(12.5.r)),
         child: Stack(
@@ -89,10 +82,11 @@ class UploadItemWidget extends StatelessWidget {
 
   /// 图片
   Widget _buildImage() {
+    String src = FileDataSourceUtil.getFileDataSourceSrc(imageUploadInfo);
     // 网络图片
-    if (PrefixUtil.isRemoteImage(imageSrc)) {
+    if (PrefixUtil.isRemoteImage(src)) {
       return CachedNetworkImage(
-        imageUrl: imageSrc,
+        imageUrl: src,
         // 宽高铺满父组件
         height: double.infinity,
         width: double.infinity,
@@ -101,7 +95,7 @@ class UploadItemWidget extends StatelessWidget {
     } else {
       // 本地图片
       return Image.file(
-        File(imageSrc),
+        File(src),
         height: double.infinity,
         width: double.infinity,
         fit: BoxFit.cover,
@@ -132,7 +126,7 @@ class UploadItemWidget extends StatelessWidget {
       onTap: () {
         // 执行删除
         if (onClickDeleteCallback != null) {
-          onClickDeleteCallback!();
+          onClickDeleteCallback!(imageUploadInfo);
         }
       },
       child: Container(

+ 27 - 15
lib/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_nine_grid.dart

@@ -3,7 +3,9 @@ import 'package:keyboard/data/bean/image_viewer_item.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_add_widget.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_upload/widget/upload_item_widget.dart';
 import 'package:keyboard/utils/prefix_util.dart';
-
+import 'package:keyboard/utils/upload/file_data_source_util.dart';
+import '../../../../data/bean/upload_info.dart';
+import '../../../../utils/upload/upload_state.dart';
 import '../../image_viewer/image_viewer_page.dart';
 
 /// 模式
@@ -21,7 +23,7 @@ class UploadNineGrid extends StatelessWidget {
   final Mode mode;
 
   /// 图片资源列表
-  final List<String> imageSrcList;
+  final List<UploadInfo> imageUploadInfoList;
 
   /// 最大显示数量
   final int maxCount;
@@ -32,13 +34,17 @@ class UploadNineGrid extends StatelessWidget {
   /// 点击上传按钮的回调
   final OnClickAddCallback? onClickAddCallback;
 
+  /// 点击删除时回调
+  final OnClickDeleteCallback? onClickDeleteCallback;
+
   const UploadNineGrid({
     super.key,
     required this.mode,
-    required this.imageSrcList,
+    required this.imageUploadInfoList,
     this.maxCount = 9,
     this.spacing = 2.0,
     this.onClickAddCallback,
+    this.onClickDeleteCallback,
   });
 
   @override
@@ -61,17 +67,17 @@ class UploadNineGrid extends StatelessWidget {
       itemCount: _getItemCount(),
       itemBuilder: (context, index) {
         // 添加图片的条目
-        if (index >= imageSrcList.length) {
+        if (index >= imageUploadInfoList.length) {
           return UploadAddWidget(
             // 剩余多少张图片,可以上传
-            residueCount: maxCount - imageSrcList.length,
+            residueCount: maxCount - imageUploadInfoList.length,
             // 点击上传图片
             onClickAddCallback: onClickAddCallback,
           );
         } else {
           // 上传项
-          String src = imageSrcList[index];
-          return _buildUploadItem(src, index);
+          UploadInfo info = imageUploadInfoList[index];
+          return _buildUploadItem(info, index);
         }
       },
     );
@@ -83,7 +89,7 @@ class UploadNineGrid extends StatelessWidget {
     if (mode == Mode.preview) {
       return false;
     }
-    return imageSrcList.length < maxCount;
+    return imageUploadInfoList.length < maxCount;
   }
 
   /// 获取宫格列表项的数量
@@ -91,30 +97,36 @@ class UploadNineGrid extends StatelessWidget {
     final int itemCount;
     // 如果图片数量小于最大数量,则总数量,要添加1个添加图片的条目
     if (_isNeedAddItem()) {
-      itemCount = imageSrcList.length + 1;
+      itemCount = imageUploadInfoList.length + 1;
     } else {
       // 满了最大数量,则直接显示所有图片
-      itemCount = imageSrcList.length;
+      itemCount = imageUploadInfoList.length;
     }
     return itemCount;
   }
 
   /// 上传项
-  Widget _buildUploadItem(String imageSrc, int index) {
+  Widget _buildUploadItem(UploadInfo info, int index) {
     return UploadItemWidget(
-      imageSrc: imageSrc,
+      imageUploadInfo: info,
       hasDeleteBtn: true,
       uploadState: UploadState.success,
-      onClickDeleteCallback: () {
+      onClickDeleteCallback: (UploadInfo uploadInfo) {
         // 删除图片
+        if (onClickDeleteCallback != null) {
+          onClickDeleteCallback!(uploadInfo);
+        }
       },
-      onClickItemCallback: () {
+      onClickItemCallback: (UploadInfo uploadInfo) {
         // 预览图片
         ImageViewerPage.start(
-          imageSrcList.map((src) {
+          imageUploadInfoList.map((info) {
+            String src = FileDataSourceUtil.getFileDataSourceSrc(info);
+            // 远程图片
             if (PrefixUtil.isRemoteImage(src)) {
               return ImageViewerItem.network(src);
             }
+            // 本地图片
             return ImageViewerItem.file(src);
           }).toList(),
           index: index,

+ 3 - 4
lib/module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_view.dart

@@ -1,8 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
-import 'package:get/get_core/src/get_main.dart';
-import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
 import 'package:keyboard/base/base_view.dart';
 import 'package:keyboard/widget/markdown/markdown_viewer.dart';
 
@@ -11,7 +9,6 @@ import '../../../../data/bean/option_select_item.dart';
 import '../../../../resource/assets.gen.dart';
 import '../../../../resource/colors.gen.dart';
 import '../../../../resource/string.gen.dart';
-import '../../../../utils/fake_image_util.dart';
 import '../../intimacy_analyse_upload/widget/upload_nine_grid.dart';
 import '../../widget/option_select_widget.dart';
 import '../../widget/step/upload_step_card.dart';
@@ -101,6 +98,8 @@ class ConversationAnalysisView
           controller.clickUploadBtn(Get.context!);
         }
       },
+      // TODO hezihao,上传信息列表
+      imageUploadInfoList: [],
     );
   }
 
@@ -201,7 +200,7 @@ class ConversationAnalysisView
             ),
             child: UploadNineGrid(
               mode: Mode.preview,
-              imageSrcList: [...FakeImageUtil.getImageUrlList()],
+              imageUploadInfoList: [],
               maxCount: 9,
               spacing: 8.0,
             ),

+ 4 - 0
lib/module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_controller.dart

@@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
 import 'package:get/get_rx/src/rx_types/rx_types.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/data/bean/upload_info.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
 import '../../../../data/bean/option_select_config.dart';
@@ -27,6 +28,9 @@ class ScanImageReplyController extends BaseController {
   /// 回复语气列表
   RxList<String> replyToneList = <String>["真好", "真好看", "真的很好看~"].obs;
 
+  /// 上传信息
+  Rx<UploadInfo> uploadInfo = UploadInfo().obs;
+
   @override
   void onReady() {
     super.onReady();

+ 4 - 5
lib/module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_view.dart

@@ -1,15 +1,14 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
-import 'package:get/get_core/src/get_main.dart';
-import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
 import 'package:keyboard/base/base_view.dart';
-
+import 'package:keyboard/data/bean/upload_info.dart';
 import '../../../../data/bean/option_select_config.dart';
 import '../../../../data/bean/option_select_item.dart';
 import '../../../../resource/assets.gen.dart';
 import '../../../../resource/colors.gen.dart';
 import '../../../../resource/string.gen.dart';
+import '../../../../utils/upload/upload_state.dart';
 import '../../../../widget/actionbtn/action_btn.dart';
 import '../../../../widget/gradient_text.dart';
 import '../../intimacy_analyse_upload/popup/reply_mode_select_popup.dart';
@@ -121,7 +120,7 @@ class ScanImageReplyView extends BaseView<ScanImageReplyController> {
     } else {
       // 上传
       imageWidget = UploadItemWidget(
-        imageSrc: "",
+        imageUploadInfo: controller.uploadInfo.value,
         hasDeleteBtn: false,
         uploadState: UploadState.fail,
         // 失败时的遮罩
@@ -140,7 +139,7 @@ class ScanImageReplyView extends BaseView<ScanImageReplyController> {
             ),
           ),
         ),
-        onClickItemCallback: () {
+        onClickItemCallback: (UploadInfo uploadInfo) {
           // 预览图片
         },
       );

+ 12 - 2
lib/module/intimacy_analyse/widget/step/upload_step_card.dart

@@ -1,15 +1,19 @@
 import 'package:flutter/cupertino.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 
+import '../../../../data/bean/upload_info.dart';
 import '../../../../resource/assets.gen.dart';
 import '../../../../resource/colors.gen.dart';
-import '../../../../utils/fake_image_util.dart';
 import '../../intimacy_analyse_upload/widget/upload_add_widget.dart';
+import '../../intimacy_analyse_upload/widget/upload_item_widget.dart';
 import '../../intimacy_analyse_upload/widget/upload_nine_grid.dart';
 import '../step_card.dart';
 
 /// 上传步骤卡片
 class UploadStepCard extends StatelessWidget {
+  /// 图片资源列表
+  final List<UploadInfo> imageUploadInfoList;
+
   /// 顶部标题区域的组件
   final Widget? topTitleWidget;
 
@@ -19,11 +23,16 @@ class UploadStepCard extends StatelessWidget {
   /// 点击上传按钮的回调
   final OnClickAddCallback? onClickAddCallback;
 
+  /// 点击删除时回调
+  final OnClickDeleteCallback? onClickDeleteCallback;
+
   const UploadStepCard({
     super.key,
+    required this.imageUploadInfoList,
     this.topTitleWidget,
     this.bottomChild,
     this.onClickAddCallback,
+    this.onClickDeleteCallback,
   });
 
   @override
@@ -57,10 +66,11 @@ class UploadStepCard extends StatelessWidget {
             // 图片九宫格
             child: UploadNineGrid(
               mode: Mode.edit,
-              imageSrcList: [...FakeImageUtil.getImageUrlList()],
+              imageUploadInfoList: imageUploadInfoList,
               maxCount: 9,
               spacing: 8.0,
               onClickAddCallback: onClickAddCallback,
+              onClickDeleteCallback: onClickDeleteCallback,
             ),
           ),
           SizedBox(height: 10.h),

+ 0 - 12
lib/utils/fake_image_util.dart

@@ -1,12 +0,0 @@
-class FakeImageUtil {
-  /// 测试图片列表
-  static List<String> getImageUrlList() {
-    return [
-      "https://img0.baidu.com/it/u=4227008132,2843866888&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1084",
-      "https://wx3.sinaimg.cn/mw690/88e90961ly1hwvqdknjo4j20u0140tav.jpg",
-      "https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750",
-      "https://pic.rmb.bdstatic.com/bjh/news/6792ab1e35c6a2a6cd10a5990bd033d0.png",
-      "http://t15.baidu.com/it/u=3292075640,1695839085&fm=224&app=112&f=JPEG?w=333&h=500"
-    ];
-  }
-}

+ 25 - 0
lib/utils/file_util.dart

@@ -0,0 +1,25 @@
+import 'dart:io';
+import 'package:path/path.dart' as path;
+
+/// 文件工具类
+class FileUtil {
+  /// 文件是否存在
+  static bool fileExists(File file) {
+    return file.existsSync();
+  }
+
+  /// 获取文件的绝对路径
+  static String getFilePath(File file) {
+    return file.path;
+  }
+
+  /// 获取文件名(带扩展名)
+  static String getFileName(File file) {
+    return path.basename(file.path);
+  }
+
+  /// 获取文件名(不带扩展名)
+  static String getFileNameWithoutExt(File file) {
+    return path.basenameWithoutExtension(file.path);
+  }
+}

+ 19 - 0
lib/utils/image_picker_util.dart

@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/cupertino.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
@@ -66,6 +68,23 @@ class ImagePickerUtil {
         [];
     return result;
   }
+
+  /// AssetEntity实体,转换为File列表
+  /// [assetsList] 图片、视频选择后的资源列表
+  static Future<List<File>> convertAssetToFile(
+    List<AssetEntity> assetsList,
+  ) async {
+    List<File> files = [];
+
+    for (var asset in assetsList) {
+      final file = await asset.file;
+      if (file != null) {
+        files.add(file);
+      }
+    }
+
+    return files;
+  }
 }
 
 /// 自定义按钮文字

+ 3 - 3
lib/utils/prefix_util.dart

@@ -1,10 +1,10 @@
 /// 前缀判断的工具类
 class PrefixUtil {
   /// 判断是否是远程图片
-  static bool isRemoteImage(String imageSrc) {
-    if (imageSrc.isEmpty) {
+  static bool isRemoteImage(String url) {
+    if (url.isEmpty) {
       return false;
     }
-    return imageSrc.startsWith('http') || imageSrc.startsWith('https');
+    return url.startsWith('http') || url.startsWith('https');
   }
 }

+ 20 - 0
lib/utils/upload/file_data_source_util.dart

@@ -0,0 +1,20 @@
+import '../../data/bean/upload_info.dart';
+
+/// 文件资源工具类
+class FileDataSourceUtil {
+  /// 获取文件数据源的src
+  static String getFileDataSourceSrc(UploadInfo info) {
+    String url = info.fileUrl ?? "";
+    String path = info.filePath ?? "";
+
+    String src;
+    // 优先使用远程Url,否则使用文件的绝对路径
+    if (url.isNotEmpty) {
+      src = url;
+    } else {
+      src = path;
+    }
+
+    return src;
+  }
+}

+ 117 - 0
lib/utils/upload/upload_file_manager.dart

@@ -0,0 +1,117 @@
+import 'dart:io';
+
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/utils/atmob_log.dart';
+import 'package:keyboard/utils/upload/upload_scene_type.dart';
+import 'package:keyboard/utils/upload/upload_state.dart';
+import 'package:keyboard/utils/uuid_util.dart';
+
+import '../../data/api/response/upload_result_bean.dart';
+import '../../data/bean/upload_info.dart';
+import '../../data/repository/file_upload_repository.dart';
+import '../file_util.dart';
+
+/// 上传信息更新回调
+typedef OnUploadInfoUpdateCallback = void Function(UploadInfo uploadInfo);
+
+/// 文件上传管理器
+@lazySingleton
+class UploadFileManager {
+  final String _tag = "UploadFileManager";
+
+  /// 文件上传Repository
+  FileUploadRepository fileUploadRepository;
+
+  /// 上传信息队列
+  final List<UploadInfo> _uploadInfoQueue = <UploadInfo>[];
+
+  UploadFileManager(this.fileUploadRepository);
+
+  /// 上传文件
+  UploadInfo uploadFile({
+    required UploadSceneType sceneType,
+    required File file,
+    OnUploadInfoUpdateCallback? onUploadInfoUpdateCallback,
+  }) {
+    // 创建一个上传记录,添加到队列中
+    UploadInfo uploadInfo = UploadInfo(
+      // 唯一id
+      id: UuidUtil.generateUuid(),
+      // 文件名称
+      fileName: FileUtil.getFileName(file),
+      // 文件路径
+      filePath: FileUtil.getFilePath(file),
+      // 上传场景类型
+      uploadSceneType: sceneType.name,
+      // 上传状态
+      uploadState: UploadState.uploading.name,
+    );
+    _uploadInfoQueue.add(uploadInfo);
+
+    // 执行上传任务
+    _doUploadFile(uploadInfo, onUploadInfoUpdateCallback);
+
+    return uploadInfo;
+  }
+
+  /// 根据场景类型,批量删除上传记录
+  void deleteUploadInfoBySceneType(UploadSceneType uploadSceneType) {
+    _uploadInfoQueue.removeWhere((item) {
+      return item.uploadSceneType == uploadSceneType.type;
+    });
+  }
+
+  /// 删除某一个上传记录
+  void deleteUploadInfo(UploadInfo uploadInfo) {
+    _uploadInfoQueue.remove(uploadInfo);
+  }
+
+  /// 根据场景类型,获取上传记录列表
+  List<UploadInfo> findUploadInfoList(UploadSceneType sceneType) {
+    return _uploadInfoQueue.where((uploadInfo) {
+      return uploadInfo.uploadSceneType == sceneType.name;
+    }).toList();
+  }
+
+  /// 通过id,查找上传记录
+  UploadInfo? findUploadInfo(String id) {
+    for (var uploadInfo in _uploadInfoQueue) {
+      if (uploadInfo.id == id) {
+        return uploadInfo;
+      }
+    }
+    return null;
+  }
+
+  /// 执行,上传文件
+  void _doUploadFile(
+    UploadInfo uploadInfo,
+    OnUploadInfoUpdateCallback? onUploadInfoUpdateCallback,
+  ) {
+    File file = File(uploadInfo.filePath ?? "");
+    fileUploadRepository
+        .uploadImage(file: file)
+        .then((UploadResultBean result) {
+          AtmobLog.d(
+            _tag,
+            "上传文件成功 => 访问地址:${result.cdnPrefix}${result.filePath}",
+          );
+          // 设置远程访问Url和Cdn前缀
+          uploadInfo.fileUrl = "${result.cdnPrefix}${result.filePath}";
+          uploadInfo.fileUrlCdnPrefix = result.cdnPrefix;
+          // 上传成功,更新状态为成功
+          uploadInfo.uploadState = UploadState.success.name;
+          if (onUploadInfoUpdateCallback != null) {
+            onUploadInfoUpdateCallback(uploadInfo);
+          }
+        })
+        .catchError((error) {
+          AtmobLog.d(_tag, "上传文件失败,原因: => $error");
+          // 上传失败,更新状态为失败
+          uploadInfo.uploadState = UploadState.fail.name;
+          if (onUploadInfoUpdateCallback != null) {
+            onUploadInfoUpdateCallback(uploadInfo);
+          }
+        });
+  }
+}

+ 26 - 0
lib/utils/upload/upload_scene_type.dart

@@ -0,0 +1,26 @@
+/// 上传场景枚举
+enum UploadSceneType {
+  /// 亲密度分析
+  intimacyAnalyse("intimacyAnalyse"),
+
+  /// 对话分析
+  conversationAnalysis("conversationAnalysis"),
+
+  /// 识图回复
+  scanImageReply("scanImageReply");
+
+  final String type;
+
+  const UploadSceneType(this.type);
+
+  /// 通过类型字符串,获取对应的类型枚举
+  static UploadSceneType fromString(String type) {
+    return values.firstWhere(
+          (e) => e.type == type,
+      orElse: () => throw ArgumentError('无效的类型值: $type'),
+    );
+  }
+
+  @override
+  String toString() => type;
+}

+ 26 - 0
lib/utils/upload/upload_state.dart

@@ -0,0 +1,26 @@
+/// 上传状态
+enum UploadState {
+  /// 上传成功
+  success("success"),
+
+  /// 上传中
+  uploading("uploading"),
+
+  /// 上传失败
+  fail("fail");
+
+  final String state;
+
+  const UploadState(this.state);
+
+  /// 通过状态字符串,获取对应的状态枚举
+  static UploadState fromString(String state) {
+    return values.firstWhere(
+      (e) => e.state == state,
+      orElse: () => throw ArgumentError('无效的状态值: $state'),
+    );
+  }
+
+  @override
+  String toString() => state;
+}

+ 12 - 0
lib/utils/uuid_util.dart

@@ -0,0 +1,12 @@
+import 'package:uuid/uuid.dart';
+
+/// Uuid工具类
+class UuidUtil {
+  static final Uuid _uuid = Uuid();
+
+  /// 生成 UUID 字符串
+  static String generateUuid() {
+    final String rawUuid = _uuid.v4();
+    return rawUuid.replaceAll('-', '');
+  }
+}

+ 2 - 2
pubspec.lock

@@ -988,7 +988,7 @@ packages:
     source: hosted
     version: "3.2.0"
   path:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: path
       sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
@@ -1465,7 +1465,7 @@ packages:
     source: hosted
     version: "3.1.4"
   uuid:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: uuid
       sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff

+ 6 - 0
pubspec.yaml

@@ -65,6 +65,9 @@ dependencies:
   # 图片预览
   photo_view: ^0.15.0
 
+  # 文件路径处理
+  path: ^1.8.3
+
   collection: ^1.19.1
 
   #虚线
@@ -94,6 +97,9 @@ dependencies:
   # 字符串替换
   sprintf: ^7.0.0
 
+  # uuid生成
+  uuid: ^4.2.2
+
   #android日志打印
   atmob_logging:
     version: ^0.0.5