26 changed files with 620 additions and 251 deletions
@ -0,0 +1,34 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart'; |
||||||
|
|
||||||
|
/// 上传控制器接口 |
||||||
|
/// |
||||||
|
/// 定义了所有上传流程控制器必须提供的通用属性和方法。 |
||||||
|
/// 这使得 UI 组件 (如 UploadProgressDialog) 可以与任何实现了此接口的控制器协作, |
||||||
|
/// 而无需知道其具体类型(是企业上传还是问题上传)。 |
||||||
|
abstract class IUploadController { |
||||||
|
// --- 状态属性 (State Properties) --- |
||||||
|
|
||||||
|
/// 是否正在上传 |
||||||
|
RxBool get isUploading; |
||||||
|
|
||||||
|
/// 上传进度 (0.0 to 1.0) |
||||||
|
RxDouble get uploadProgress; |
||||||
|
|
||||||
|
/// 已上传的数量 |
||||||
|
RxInt get uploadedCount; |
||||||
|
|
||||||
|
/// 总共需要上传的数量 |
||||||
|
RxInt get totalToUpload; |
||||||
|
|
||||||
|
/// 上传结果 |
||||||
|
Rx<UploadResult?> get uploadResult; |
||||||
|
|
||||||
|
// --- 操作方法 (Action Methods) --- |
||||||
|
|
||||||
|
/// 取消上传 |
||||||
|
void cancelUpload(); |
||||||
|
|
||||||
|
/// 关闭上传对话框 (通常在上传完成后调用) |
||||||
|
void closeUploadDialog(); |
||||||
|
} |
||||||
@ -1,11 +1,11 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/controllers/i_upload_controller.dart'; |
||||||
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart'; |
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart'; |
||||||
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart'; |
|
||||||
import 'package:problem_check_system/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart'; |
|
||||||
|
|
||||||
class UploadProgressDialog extends GetView<EnterpriseUploadController> { |
class UploadProgressDialog extends StatelessWidget { |
||||||
const UploadProgressDialog({super.key}); |
final IUploadController controller; |
||||||
|
const UploadProgressDialog({super.key, required this.controller}); |
||||||
|
|
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
Widget build(BuildContext context) { |
||||||
@ -1 +1,117 @@ |
|||||||
class ProblemRemoteDataSource {} |
import 'package:dio/dio.dart'; |
||||||
|
import 'package:problem_check_system/app/core/services/http_provider.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/model/problem_dto.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/model/problem_filter_dto.dart'; |
||||||
|
|
||||||
|
/// 问题远程数据源接口 |
||||||
|
/// |
||||||
|
/// 定义了与问题相关的远程 API 的所有操作。 |
||||||
|
/// 这个接口处理的是 DTO (Data Transfer Objects),与服务器直接交互。 |
||||||
|
abstract class ProblemRemoteDataSource { |
||||||
|
// --- 查 (Read/Query) --- |
||||||
|
|
||||||
|
/// 根据筛选条件获取问题列表 |
||||||
|
/// |
||||||
|
/// [filter]: 包含所有筛选条件的 DTO |
||||||
|
/// 返回一个 ProblemDto 列表的 Future |
||||||
|
Future<List<ProblemDto>> getProblems(ProblemFilterDto filter); |
||||||
|
|
||||||
|
/// 根据 ID 获取单个问题的详细信息 |
||||||
|
/// |
||||||
|
/// [problemId]: 问题的唯一标识符 |
||||||
|
/// 如果找到,返回一个 ProblemDto 的 Future;否则可能抛出异常 |
||||||
|
Future<ProblemDto> getProblemById(String problemId); |
||||||
|
|
||||||
|
// --- 增 (Create) --- |
||||||
|
|
||||||
|
/// 创建一个新问题 |
||||||
|
/// |
||||||
|
/// [problem]: 包含新问题信息的 DTO |
||||||
|
/// 服务器通常会返回创建成功后的完整问题对象(包含服务器生成的ID和时间戳) |
||||||
|
Future<ProblemDto> createProblem(ProblemDto problem); |
||||||
|
|
||||||
|
// --- 改 (Update) --- |
||||||
|
|
||||||
|
/// 更新一个已存在的问题 |
||||||
|
/// |
||||||
|
/// [problem]: 包含要更新的问题信息的 DTO |
||||||
|
/// 服务器通常会返回更新成功后的完整问题对象 |
||||||
|
Future<ProblemDto> updateProblem(ProblemDto problem); |
||||||
|
|
||||||
|
// --- 删 (Delete) --- |
||||||
|
|
||||||
|
/// 根据 ID 删除一个问题 |
||||||
|
/// |
||||||
|
/// [problemId]: 要删除的问题的唯一标识符 |
||||||
|
/// 删除操作通常成功后没有返回内容,所以使用 Future<void> |
||||||
|
Future<void> deleteProblem(String problemId); |
||||||
|
} |
||||||
|
|
||||||
|
class ProblemRemoteDataSourceImpl implements ProblemRemoteDataSource { |
||||||
|
final HttpProvider http; |
||||||
|
static const String problemsEndpoint = '/api/Memorandum'; |
||||||
|
// 通过依赖注入传入 Dio 实例 |
||||||
|
ProblemRemoteDataSourceImpl({required this.http}); |
||||||
|
|
||||||
|
@override |
||||||
|
Future<List<ProblemDto>> getProblems(ProblemFilterDto filter) async { |
||||||
|
try { |
||||||
|
final response = await http.get( |
||||||
|
problemsEndpoint, // 假设这是获取列表的端点 |
||||||
|
queryParameters: filter.toJson(), |
||||||
|
); |
||||||
|
// 假设服务器返回的数据在 'data' 字段中,且是一个列表 |
||||||
|
final List<dynamic> problemListJson = response.data['data']; |
||||||
|
return problemListJson.map((json) => ProblemDto.fromJson(json)).toList(); |
||||||
|
} on DioException catch (e) { |
||||||
|
// 在这里处理网络错误,可以抛出一个自定义的异常 |
||||||
|
throw Exception('获取问题列表失败: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemDto> getProblemById(String problemId) async { |
||||||
|
try { |
||||||
|
final response = await http.get('$problemsEndpoint/$problemId'); |
||||||
|
return ProblemDto.fromJson(response.data); |
||||||
|
} on DioException catch (e) { |
||||||
|
throw Exception('获取问题失败: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemDto> createProblem(ProblemDto problem) async { |
||||||
|
try { |
||||||
|
final response = await http.post( |
||||||
|
problemsEndpoint, |
||||||
|
data: problem.toJson(), // 将 DTO 转换为 Map 发送 |
||||||
|
); |
||||||
|
return ProblemDto.fromJson(response.data); |
||||||
|
} on DioException catch (e) { |
||||||
|
throw Exception('创建问题失败: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemDto> updateProblem(ProblemDto problem) async { |
||||||
|
try { |
||||||
|
final response = await http.put( |
||||||
|
'$problemsEndpoint/${problem.id}', // 通常 PUT 请求需要 ID 在 URL 中 |
||||||
|
data: problem.toJson(), |
||||||
|
); |
||||||
|
return ProblemDto.fromJson(response.data); |
||||||
|
} on DioException catch (e) { |
||||||
|
throw Exception('更新问题失败: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> deleteProblem(String problemId) async { |
||||||
|
try { |
||||||
|
// 删除成功通常返回 204 No Content,Dio 会默认处理 |
||||||
|
await http.delete('$problemsEndpoint/$problemId'); |
||||||
|
} on DioException catch (e) { |
||||||
|
throw Exception('删除问题失败: $e'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|||||||
@ -1,115 +1,108 @@ |
|||||||
import 'dart:convert'; |
import 'dart:convert'; |
||||||
|
|
||||||
import 'package:problem_check_system/app/core/extensions/map_extensions.dart'; |
|
||||||
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; |
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; |
||||||
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; |
import 'package:problem_check_system/app/core/extensions/map_extensions.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
|
||||||
/// ProblemDto (Data Transfer Object) |
|
||||||
/// |
|
||||||
/// 这个模型专门用于与远程 API 进行数据交互。 |
|
||||||
/// 它的字段和结构【严格匹配】服务器 API 定义的 JSON 格式。 |
|
||||||
class ProblemDto { |
class ProblemDto { |
||||||
final String? id; |
final String id; |
||||||
final String? title; |
final String? title; |
||||||
final String? location; |
final String? location; |
||||||
final String? censorTaskId; |
final String? censorTaskId; |
||||||
final String? rowId; |
final String? rowId; |
||||||
final String? bindData; |
final String? bindData; |
||||||
final List<String?>? imageUrls; |
final List<String>? imageUrls; |
||||||
final String? creationTime; |
final String creatorId; |
||||||
|
final String creationTime; |
||||||
|
final String? lastModifierId; |
||||||
|
final String? lastModificationTime; |
||||||
final String? companyId; |
final String? companyId; |
||||||
|
|
||||||
ProblemDto({ |
ProblemDto({ |
||||||
this.id, |
required this.id, |
||||||
this.title, |
this.title, |
||||||
this.location, |
this.location, |
||||||
this.censorTaskId, |
this.censorTaskId, |
||||||
this.rowId, |
this.rowId, |
||||||
this.bindData, |
this.bindData, |
||||||
this.imageUrls, |
this.imageUrls, |
||||||
this.creationTime, |
required this.creatorId, |
||||||
|
required this.creationTime, |
||||||
|
this.lastModifierId, |
||||||
|
this.lastModificationTime, |
||||||
this.companyId, |
this.companyId, |
||||||
}); |
}); |
||||||
|
|
||||||
|
factory ProblemDto.fromEntity(ProblemEntity entity) { |
||||||
factory ProblemDto.fromModel(ProblemModel model) { |
|
||||||
return ProblemDto( |
return ProblemDto( |
||||||
id: model.id, |
id: entity.id, |
||||||
title: model.description, |
title: entity.description, |
||||||
location: model.location, |
location: entity.location, |
||||||
bindData: model.bindData, |
bindData: entity.bindData, |
||||||
imageUrls: List<String>.from(jsonDecode(model.imageUrls)), |
imageUrls: entity.imageUrls, |
||||||
creationTime: model.creationTime, |
creationTime: entity.creationTime.toIso8601String(), |
||||||
companyId: model.enterpriseId, |
creatorId: entity.creatorId, |
||||||
|
companyId: entity.enterpriseId, |
||||||
); |
); |
||||||
} |
} |
||||||
ProblemModel toModel() { |
|
||||||
return ProblemModel( |
|
||||||
id: |
|
||||||
|
|
||||||
|
ProblemEntity toEntity() { |
||||||
|
return ProblemEntity( |
||||||
|
id: id, |
||||||
|
description: title ?? '', |
||||||
|
location: location ?? '', |
||||||
|
imageUrls: imageUrls ?? [], |
||||||
|
enterpriseId: companyId ?? '', |
||||||
|
creatorId: creatorId, |
||||||
|
creationTime: DateTime.tryParse(creationTime) ?? DateTime.now(), |
||||||
|
lastModifierId: lastModifierId ?? creatorId, |
||||||
|
lastModifiedTime: |
||||||
|
DateTime.tryParse(lastModificationTime ?? '') ?? DateTime.now(), |
||||||
|
syncStatus: SyncStatus.synced, |
||||||
|
bindData: bindData, |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
// ======================================================================= |
// ✅ FIX: fromJson - 对所有字段进行安全转换 |
||||||
// 新增方法 |
|
||||||
// ======================================================================= |
|
||||||
|
|
||||||
/// [新增] fromJson 工厂构造函数:从 JSON Map 创建 EnterpriseDto 实例。 |
|
||||||
/// |
|
||||||
/// 当从服务器接收到 JSON 数据时,调用此方法将其转换为 Dart 对象。 |
|
||||||
factory ProblemDto.fromJson(Map<String, dynamic> json) { |
factory ProblemDto.fromJson(Map<String, dynamic> json) { |
||||||
final creationTime = DateTime.parse(json['creationTime'] as String); |
final creatorIdData = json['creatorId'] as String? ?? ''; |
||||||
final creatorId = json['creatorId'] as String; |
final creationTimeData = json['creationTime'] as String? ?? ''; |
||||||
final lastModTimeStr = json['lastModificationTime'] as String?; |
|
||||||
return ProblemDto( |
|
||||||
// 必须存在的字段 |
|
||||||
id: json['id'] as String, |
|
||||||
creationTime: creationTime, |
|
||||||
creatorId: creatorId, |
|
||||||
lastModificationTime: lastModTimeStr != null |
|
||||||
? DateTime.parse(lastModTimeStr) |
|
||||||
: creationTime, |
|
||||||
lastModifierId: json['lastModifierId'] as String? ?? creatorId, |
|
||||||
companyName: json['companyName'] as String, |
|
||||||
companyType: json['companyType'] as String? ?? "生产", |
|
||||||
|
|
||||||
// 可选字段 |
return ProblemDto( |
||||||
companyScope: json['companyScope'] as String?, |
id: json['id'] as String? ?? '', |
||||||
mainPrincipalName: json['mainPrincipalName'] as String?, |
creationTime: creationTimeData, |
||||||
mainPrincipalPhone: json['mainPrincipalPhone'] as String?, |
creatorId: creatorIdData, |
||||||
securityPrincipalName: json['securityPrincipalName'] as String?, |
lastModificationTime: |
||||||
securityPrincipalPhone: json['securityPrincipalPhone'] as String?, |
json['lastModificationTime'] as String? ?? creationTimeData, |
||||||
companyAddress: json['companyAddress'] as String?, |
lastModifierId: json['lastModifierId'] as String? ?? creatorIdData, |
||||||
majorHazard: json['majorHazard'] as String?, |
title: json['title'] as String?, |
||||||
|
location: json['location'] as String?, |
||||||
|
bindData: json['bindData'] as String?, |
||||||
|
imageUrls: (json['imageUrls'] as List? ?? []) |
||||||
|
.whereType<String>() |
||||||
|
.toList(), |
||||||
|
companyId: json['companyId'] as String?, |
||||||
|
censorTaskId: json['censorTaskId'] as String?, |
||||||
|
rowId: json['rowId'] as String?, |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
/// [新增] toJson 方法:将 EnterpriseDto 实例转换为 JSON Map。 |
// 🟡 提醒: 请确认是否需要包含所有字段 |
||||||
/// |
|
||||||
/// 当需要将数据发送到服务器时,调用此方法将其转换为 JSON 格式。 |
|
||||||
Map<String, dynamic> toJson() { |
Map<String, dynamic> toJson() { |
||||||
final jsonMap = { |
final jsonMap = { |
||||||
'id': id, |
// 建议包含所有需要发送给服务器的字段 |
||||||
// 使用 toIso8601String() 是将 DateTime 转换为标准化字符串的最佳实践 |
"id": id, |
||||||
'creationTime': creationTime.toIso8601String(), |
"title": title, |
||||||
'creatorId': creatorId, |
"location": location, |
||||||
'lastModificationTime': lastModificationTime?.toIso8601String(), |
"bindData": bindData, |
||||||
'lastModifierId': lastModifierId, |
"imageUrls": imageUrls, |
||||||
'companyName': companyName, |
"creatorId": creatorId, |
||||||
'companyType': companyType, |
"creationTime": creationTime, |
||||||
'companyScope': companyScope, |
"companyId": companyId, |
||||||
'mainPrincipalName': mainPrincipalName, |
"censorTaskId": censorTaskId, |
||||||
'mainPrincipalPhone': mainPrincipalPhone, |
"rowId": rowId, |
||||||
'securityPrincipalName': securityPrincipalName, |
"lastModifierId": lastModifierId, |
||||||
'securityPrincipalPhone': securityPrincipalPhone, |
"lastModificationTime": lastModificationTime, |
||||||
'companyAddress': companyAddress, |
|
||||||
'majorHazard': majorHazard, |
|
||||||
}; |
}; |
||||||
return jsonMap.withoutNullOrEmptyValues; |
return jsonMap.withoutNullOrEmptyValues; |
||||||
} |
} |
||||||
|
|
||||||
/// [核心] 将 DTO (网络数据) 转换为 Model (本地/业务模型)。 |
|
||||||
/// |
|
||||||
/// 这个转换是数据流入应用内部的关键步骤。 |
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,3 @@ |
|||||||
|
class ProblemFilterDto { |
||||||
|
toJson() {} |
||||||
|
} |
||||||
@ -1,35 +1,52 @@ |
|||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/bindings/base_bindings.dart'; |
||||||
import 'package:problem_check_system/app/core/services/database_service.dart'; |
import 'package:problem_check_system/app/core/services/database_service.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/datasources/problem_remote_data_source.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart'; |
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/domain/usecases/get_all_problems_usecase.dart'; |
import 'package:problem_check_system/app/features/problem/domain/usecases/get_all_problems_usecase.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; |
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; |
||||||
|
|
||||||
class ProblemListBinding extends Bindings { |
class ProblemListBinding extends BaseBindings { |
||||||
@override |
@override |
||||||
void dependencies() { |
void register1Services() { |
||||||
// 数据 |
// TODO: implement register1Services |
||||||
Get.lazyPut<IProblemLocalDataSource>( |
} |
||||||
() => |
|
||||||
ProblemLocalDataSource(databaseService: Get.find<DatabaseService>()), |
@override |
||||||
|
void register2DataSource() { |
||||||
|
Get.lazyPut<ProblemLocalDataSource>( |
||||||
|
() => ProblemLocalDataSourceImpl( |
||||||
|
databaseService: Get.find<DatabaseService>(), |
||||||
|
), |
||||||
); |
); |
||||||
// 仓库 |
Get.lazyPut<ProblemRemoteDataSource>( |
||||||
Get.lazyPut<IProblemRepository>( |
() => ProblemRemoteDataSourceImpl(http: Get.find()), |
||||||
() => ProblemRepository(Get.find<IProblemLocalDataSource>()), |
|
||||||
); |
); |
||||||
// 用例 |
} |
||||||
Get.lazyPut<GetAllProblemsUsecase>( |
|
||||||
() => GetAllProblemsUsecase( |
@override |
||||||
problemRepository: Get.find<IProblemRepository>(), |
void register3Repositories() { |
||||||
|
Get.lazyPut<ProblemRepository>( |
||||||
|
() => ProblemRepositoryImpl( |
||||||
|
localDataSource: Get.find(), |
||||||
|
remoteDataSource: Get.find(), |
||||||
), |
), |
||||||
); |
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void register4Usecases() { |
||||||
|
Get.lazyPut<GetAllProblemsUsecase>( |
||||||
|
() => GetAllProblemsUsecase(problemRepository: Get.find()), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
/// 控制器 |
@override |
||||||
|
void register5Controllers() { |
||||||
Get.lazyPut<ProblemListController>( |
Get.lazyPut<ProblemListController>( |
||||||
() => ProblemListController( |
() => ProblemListController(getAllProblemsUsecase: Get.find()), |
||||||
getAllProblemsUsecase: Get.find<GetAllProblemsUsecase>(), |
|
||||||
), |
|
||||||
); |
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue