51 changed files with 2508 additions and 1996 deletions
@ -0,0 +1,10 @@ |
|||||||
|
enum FormMode { |
||||||
|
/// 新增 |
||||||
|
add, |
||||||
|
|
||||||
|
/// 编辑 |
||||||
|
edit, |
||||||
|
|
||||||
|
/// 查看 |
||||||
|
view, |
||||||
|
} |
||||||
@ -1,31 +1,26 @@ |
|||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
|
||||||
import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; |
|
||||||
import 'package:problem_check_system/app/features/home/controllers/home_controller.dart'; |
import 'package:problem_check_system/app/features/home/controllers/home_controller.dart'; |
||||||
import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
|
|
||||||
class HomeBinding implements Bindings { |
class HomeBinding implements Bindings { |
||||||
@override |
@override |
||||||
void dependencies() { |
void dependencies() { |
||||||
/// 注册主页控制器 |
/// 注册主页控制器 |
||||||
Get.lazyPut<HomeController>(() => HomeController()); |
Get.lazyPut<HomeController>(() => HomeController()); |
||||||
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
// Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
||||||
// Get.lazyPut<EnterpriseListController>(() => EnterpriseListController()); |
// // Get.lazyPut<EnterpriseListController>(() => EnterpriseListController()); |
||||||
|
|
||||||
/// 注册问题控制器 |
// /// 注册问题控制器 |
||||||
Get.lazyPut<ProblemController>( |
// Get.lazyPut<ProblemController>( |
||||||
() => ProblemController( |
// () => ProblemController( |
||||||
problemRepository: Get.find<ProblemRepository>(), |
// problemRepository: Get.find<ProblemRepository>(), |
||||||
problemStateManager: Get.find<ProblemStateManager>(), |
// problemStateManager: Get.find<ProblemStateManager>(), |
||||||
), |
// ), |
||||||
fenix: true, |
// fenix: true, |
||||||
); |
// ); |
||||||
|
|
||||||
/// 注册我的控制器 |
// /// 注册我的控制器 |
||||||
Get.lazyPut<MyController>( |
// Get.lazyPut<MyController>( |
||||||
() => MyController(authRepository: Get.find<AuthRepository>()), |
// () => MyController(authRepository: Get.find<AuthRepository>()), |
||||||
); |
// ); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,145 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/sync_status.dart'; // 导入 SyncStatus |
||||||
|
import 'package:problem_check_system/app/core/services/database_service.dart'; |
||||||
|
import 'package:sqflite/sqflite.dart'; // 导入你的 DatabaseService |
||||||
|
|
||||||
|
/// IProblemLocalDataSource 定义了问题本地数据源的接口。 |
||||||
|
/// 它抽象了所有与本地数据库中 'problems' 表相关的 CRUD (创建, 读取, 更新, 删除) 操作。 |
||||||
|
/// 仓库层 (Repository) 将通过这个接口与数据源交互,而无需关心底层的数据库实现 (如 Sqflite)。 |
||||||
|
abstract class IProblemLocalDataSource { |
||||||
|
/// 根据 ID 从数据库获取一个问题。 |
||||||
|
/// |
||||||
|
/// [id] - 要查询的问题的唯一标识符。 |
||||||
|
/// 返回一个 Map<String, dynamic>,如果找不到则返回 null。 |
||||||
|
Future<Map<String, dynamic>?> getProblemById(String id); |
||||||
|
|
||||||
|
/// 从数据库获取所有问题,并可选择性地进行过滤。 |
||||||
|
/// |
||||||
|
/// [startDate] - 筛选创建时间晚于此日期的问题。 |
||||||
|
/// [endDate] - 筛选创建时间早于此日期的问题。 |
||||||
|
/// [syncStatus] - 筛选具有特定同步状态的问题。 |
||||||
|
/// [bindStatus] - (示例) 筛选具有特定绑定状态的问题。 |
||||||
|
Future<List<Map<String, dynamic>>> getAllProblems({ |
||||||
|
DateTime? startDate, |
||||||
|
DateTime? endDate, |
||||||
|
String? syncStatus, |
||||||
|
String? bindStatus, |
||||||
|
}); |
||||||
|
|
||||||
|
/// 向数据库中添加一个新问题。 |
||||||
|
/// |
||||||
|
/// [problemMap] - 包含问题数据的 Map,其键应与 'problems' 表的列名匹配。 |
||||||
|
Future<void> addProblem(Map<String, dynamic> problemMap); |
||||||
|
|
||||||
|
/// 更新数据库中的一个现有问题。 |
||||||
|
/// |
||||||
|
/// [problemMap] - 包含要更新的问题数据的 Map。它必须包含 'id' 键。 |
||||||
|
Future<void> updateProblem(Map<String, dynamic> problemMap); |
||||||
|
|
||||||
|
/// 根据 ID 从数据库中删除一个问题。 |
||||||
|
/// |
||||||
|
/// [id] - 要删除的问题的唯一标识符。 |
||||||
|
Future<void> deleteProblem(String id); |
||||||
|
} |
||||||
|
|
||||||
|
// 假设 IProblemLocalDataSource 接口定义在同一个文件中或已导入 |
||||||
|
|
||||||
|
class ProblemLocalDataSource implements IProblemLocalDataSource { |
||||||
|
final DatabaseService _databaseService; |
||||||
|
final String _tableName = 'problems'; |
||||||
|
|
||||||
|
ProblemLocalDataSource({required DatabaseService databaseService}) |
||||||
|
: _databaseService = databaseService; |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> addProblem(Map<String, dynamic> problemMap) async { |
||||||
|
final db = await _databaseService.database; |
||||||
|
await db.insert( |
||||||
|
_tableName, |
||||||
|
problemMap, |
||||||
|
// 如果插入的 ID 已存在,则替换它,这使得 add 同时具有 update 的功能,非常稳健。 |
||||||
|
conflictAlgorithm: ConflictAlgorithm.replace, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> deleteProblem(String id) async { |
||||||
|
final db = await _databaseService.database; |
||||||
|
await db.delete(_tableName, where: 'id = ?', whereArgs: [id]); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<List<Map<String, dynamic>>> getAllProblems({ |
||||||
|
DateTime? startDate, |
||||||
|
DateTime? endDate, |
||||||
|
String? syncStatus, |
||||||
|
String? bindStatus, // 注意:你的表结构中没有 bindStatus,这里我们忽略它 |
||||||
|
}) async { |
||||||
|
final db = await _databaseService.database; |
||||||
|
|
||||||
|
// 动态构建 WHERE 查询语句 |
||||||
|
List<String> whereClauses = []; |
||||||
|
List<dynamic> whereArgs = []; |
||||||
|
|
||||||
|
if (startDate != null) { |
||||||
|
// 数据库中存储的是 INTEGER (毫秒时间戳) |
||||||
|
whereClauses.add('creationTime >= ?'); |
||||||
|
whereArgs.add(startDate.millisecondsSinceEpoch); |
||||||
|
} |
||||||
|
if (endDate != null) { |
||||||
|
whereClauses.add('creationTime <= ?'); |
||||||
|
whereArgs.add(endDate.millisecondsSinceEpoch); |
||||||
|
} |
||||||
|
if (syncStatus != null) { |
||||||
|
// 数据库中存储的是 INTEGER (枚举的 index) |
||||||
|
// 我们需要将仓库层传来的字符串转回枚举的 index |
||||||
|
try { |
||||||
|
final status = SyncStatus.values.byName(syncStatus); |
||||||
|
whereClauses.add('syncStatus = ?'); |
||||||
|
whereArgs.add(status.index); |
||||||
|
} catch (e) { |
||||||
|
// 如果传来一个无效的 status 字符串,则忽略此过滤器 |
||||||
|
Get.log('无效的 syncStatus 过滤器: $syncStatus', isError: true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final String? whereString = whereClauses.isEmpty |
||||||
|
? null |
||||||
|
: whereClauses.join(' AND '); |
||||||
|
|
||||||
|
final List<Map<String, dynamic>> maps = await db.query( |
||||||
|
_tableName, |
||||||
|
where: whereString, |
||||||
|
whereArgs: whereArgs, |
||||||
|
orderBy: 'creationTime DESC', // 通常按创建时间降序排列 |
||||||
|
); |
||||||
|
return maps; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<Map<String, dynamic>?> getProblemById(String id) async { |
||||||
|
final db = await _databaseService.database; |
||||||
|
final List<Map<String, dynamic>> maps = await db.query( |
||||||
|
_tableName, |
||||||
|
where: 'id = ?', |
||||||
|
whereArgs: [id], |
||||||
|
limit: 1, // 限制只返回一条记录 |
||||||
|
); |
||||||
|
|
||||||
|
if (maps.isNotEmpty) { |
||||||
|
return maps.first; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> updateProblem(Map<String, dynamic> problemMap) async { |
||||||
|
final db = await _databaseService.database; |
||||||
|
await db.update( |
||||||
|
_tableName, |
||||||
|
problemMap, |
||||||
|
where: 'id = ?', |
||||||
|
whereArgs: [problemMap['id']], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,123 +0,0 @@ |
|||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
|
||||||
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
|
||||||
import 'package:problem_check_system/app/core/services/database_service.dart'; |
|
||||||
import 'package:sqflite/sqflite.dart'; |
|
||||||
|
|
||||||
const String _tableName = 'problems'; |
|
||||||
|
|
||||||
/// 数据源抽象接口 |
|
||||||
abstract class ProblemLocalDataSource { |
|
||||||
Future<int> insertProblem(Problem problem); |
|
||||||
Future<int> updateProblem(Problem problem); |
|
||||||
Future<int> deleteProblem(String problemId); |
|
||||||
Future<Problem?> getProblemById(String id); |
|
||||||
Future<List<Problem>> getProblems({ |
|
||||||
DateTime? startDate, |
|
||||||
DateTime? endDate, |
|
||||||
String? syncStatus, |
|
||||||
String? bindStatus, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/// 数据源的具体实现 |
|
||||||
class ProblemLocalDataSourceImpl implements ProblemLocalDataSource { |
|
||||||
final DatabaseService _databaseService; |
|
||||||
|
|
||||||
ProblemLocalDataSourceImpl({required DatabaseService databaseService}) |
|
||||||
: _databaseService = databaseService; |
|
||||||
|
|
||||||
@override |
|
||||||
Future<int> insertProblem(Problem problem) async { |
|
||||||
final db = await _databaseService.database; |
|
||||||
return await db.insert( |
|
||||||
_tableName, |
|
||||||
problem.toMap(), |
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<int> updateProblem(Problem problem) async { |
|
||||||
final db = await _databaseService.database; |
|
||||||
return await db.update( |
|
||||||
_tableName, |
|
||||||
problem.toMap(), |
|
||||||
where: 'id = ?', |
|
||||||
whereArgs: [problem.id], |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<int> deleteProblem(String problemId) async { |
|
||||||
final db = await _databaseService.database; |
|
||||||
return await db.delete(_tableName, where: 'id = ?', whereArgs: [problemId]); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<Problem?> getProblemById(String id) async { |
|
||||||
final db = await _databaseService.database; |
|
||||||
final results = await db.query( |
|
||||||
_tableName, |
|
||||||
where: 'id = ?', |
|
||||||
whereArgs: [id], |
|
||||||
limit: 1, |
|
||||||
); |
|
||||||
return results.isNotEmpty ? Problem.fromMap(results.first) : null; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<List<Problem>> getProblems({ |
|
||||||
DateTime? startDate, |
|
||||||
DateTime? endDate, |
|
||||||
String? syncStatus, |
|
||||||
String? bindStatus, |
|
||||||
}) async { |
|
||||||
final db = await _databaseService.database; |
|
||||||
final whereClauses = <String>[]; |
|
||||||
final whereArgs = <dynamic>[]; |
|
||||||
|
|
||||||
// 时间范围筛选 |
|
||||||
if (startDate != null) { |
|
||||||
whereClauses.add('creationTime >= ?'); |
|
||||||
whereArgs.add(startDate.millisecondsSinceEpoch); |
|
||||||
} |
|
||||||
|
|
||||||
if (endDate != null) { |
|
||||||
whereClauses.add('creationTime <= ?'); |
|
||||||
whereArgs.add(endDate.millisecondsSinceEpoch); |
|
||||||
} |
|
||||||
|
|
||||||
// 同步状态筛选 |
|
||||||
if (syncStatus != null && syncStatus != '全部') { |
|
||||||
if (syncStatus == '未上传') { |
|
||||||
whereClauses.add('syncStatus IN (?, ?, ?)'); |
|
||||||
whereArgs.addAll([ |
|
||||||
ProblemSyncStatus.pendingCreate.index, |
|
||||||
ProblemSyncStatus.pendingUpdate.index, |
|
||||||
ProblemSyncStatus.pendingDelete.index, |
|
||||||
]); |
|
||||||
} else { |
|
||||||
whereClauses.add('syncStatus = ?'); |
|
||||||
whereArgs.add(ProblemSyncStatus.synced.index); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 绑定状态筛选 |
|
||||||
if (bindStatus != null && bindStatus != '全部') { |
|
||||||
if (bindStatus == '已绑定') { |
|
||||||
whereClauses.add('bindData IS NOT NULL AND bindData != ""'); |
|
||||||
} else { |
|
||||||
whereClauses.add('(bindData IS NULL OR bindData = "")'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
final results = await db.query( |
|
||||||
_tableName, |
|
||||||
where: whereClauses.isNotEmpty ? whereClauses.join(' AND ') : null, |
|
||||||
whereArgs: whereArgs.isEmpty ? null : whereArgs, |
|
||||||
orderBy: 'creationTime DESC', |
|
||||||
); |
|
||||||
|
|
||||||
return results.map((json) => Problem.fromMap(json)).toList(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1 @@ |
|||||||
|
class ProblemRemoteDataSource {} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:problem_check_system/app/core/models/sync_status.dart'; |
||||||
|
import '../../domain/entities/problem_entity.dart'; |
||||||
|
|
||||||
|
/// ProblemLocalDto (重构版) |
||||||
|
/// |
||||||
|
/// 这个版本精确匹配了重构后的 `problems` 表结构。 |
||||||
|
/// 它负责处理领域实体 (Entity) 与数据库 Map 之间的数据格式转换, |
||||||
|
/// 并遵循了将时间和枚举存为可读、健壮的 TEXT 格式的最佳实践。 |
||||||
|
class ProblemLocalDto { |
||||||
|
final String id; |
||||||
|
final String enterpriseId; |
||||||
|
final String description; |
||||||
|
final String location; |
||||||
|
final String imageUrls; |
||||||
|
final String creatorId; |
||||||
|
final String creationTime; |
||||||
|
final String lastModifierId; |
||||||
|
final String lastModifiedTime; |
||||||
|
final SyncStatus syncStatus; |
||||||
|
final String? bindData; |
||||||
|
|
||||||
|
ProblemLocalDto({ |
||||||
|
required this.id, |
||||||
|
required this.enterpriseId, |
||||||
|
required this.description, |
||||||
|
required this.location, |
||||||
|
required this.imageUrls, |
||||||
|
required this.creatorId, |
||||||
|
required this.creationTime, |
||||||
|
required this.lastModifierId, |
||||||
|
required this.lastModifiedTime, |
||||||
|
required this.syncStatus, |
||||||
|
required this.bindData, |
||||||
|
}); |
||||||
|
|
||||||
|
Map<String, dynamic> toMap() { |
||||||
|
return { |
||||||
|
'id': id, |
||||||
|
'enterpriseId': enterpriseId, |
||||||
|
'description': description, |
||||||
|
'location': location, |
||||||
|
'imageUrls': imageUrls, |
||||||
|
'creatorId': creatorId, |
||||||
|
'creationTime': creationTime, |
||||||
|
'lastModifierId': lastModifierId, |
||||||
|
'lastModifiedTime': lastModifiedTime, |
||||||
|
'syncStatus': syncStatus.name, |
||||||
|
'bindData': bindData, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
factory ProblemLocalDto.fromMap(Map<String, dynamic> map) { |
||||||
|
return ProblemLocalDto( |
||||||
|
id: map['id'] as String, |
||||||
|
enterpriseId: map['enterpriseId'] as String, |
||||||
|
description: map['description'] as String, |
||||||
|
location: map['location'] as String, |
||||||
|
imageUrls: map['imageUrls'] as String, |
||||||
|
creatorId: map['creatorId'] as String, |
||||||
|
creationTime: map['creationTime'] as String, |
||||||
|
lastModifierId: map['lastModifierId'], |
||||||
|
lastModifiedTime: map['lastModifiedTime'] as String, |
||||||
|
syncStatus: SyncStatus.values.byName(map['syncStatus'] as String), |
||||||
|
bindData: map['bindData'] as String?, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 从领域实体 ProblemEntity 创建 DTO 实例。 |
||||||
|
factory ProblemLocalDto.fromEntity(ProblemEntity entity) { |
||||||
|
return ProblemLocalDto( |
||||||
|
id: entity.id, |
||||||
|
enterpriseId: entity.enterpriseId, |
||||||
|
description: entity.description, |
||||||
|
location: entity.location, |
||||||
|
imageUrls: jsonEncode(entity.imageUrls), |
||||||
|
creatorId: entity.creatorId, |
||||||
|
creationTime: entity.creationTime.toUtc().toIso8601String(), |
||||||
|
lastModifierId: entity.lastModifierId, |
||||||
|
lastModifiedTime: entity.lastModifiedTime.toUtc().toIso8601String(), |
||||||
|
syncStatus: entity.syncStatus, |
||||||
|
bindData: entity.bindData, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 将 DTO 实例转换回领域实体 ProblemEntity。 |
||||||
|
ProblemEntity toEntity() { |
||||||
|
return ProblemEntity( |
||||||
|
id: id, |
||||||
|
enterpriseId: enterpriseId, |
||||||
|
description: description, |
||||||
|
location: location, |
||||||
|
imageUrls: List<String>.from(jsonDecode(imageUrls)), |
||||||
|
creatorId: creatorId, |
||||||
|
creationTime: DateTime.parse(creationTime), |
||||||
|
lastModifierId: lastModifierId, |
||||||
|
lastModifiedTime: DateTime.parse(lastModifiedTime), |
||||||
|
syncStatus: syncStatus, |
||||||
|
bindData: bindData, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,144 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:problem_check_system/app/core/extensions/map_extensions.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/image_metadata_model.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
||||||
|
|
||||||
|
/// 问题的数据模型。 |
||||||
|
/// 用于表示系统中的一个具体问题,包含了问题的描述、位置、图片等信息。 |
||||||
|
class ProblemRemoteDto { |
||||||
|
/// 问题的唯一标识符,可空。 |
||||||
|
final String id; |
||||||
|
|
||||||
|
/// 企业id |
||||||
|
final String? enterpriseId; |
||||||
|
|
||||||
|
/// 问题的详细描述。 |
||||||
|
final String description; |
||||||
|
|
||||||
|
/// 问题发生的位置。 |
||||||
|
final String location; |
||||||
|
|
||||||
|
/// 问题的图片元数据列表。 |
||||||
|
final List<ImageMetadata> imageUrls; |
||||||
|
|
||||||
|
/// 问题创建的时间。 |
||||||
|
final DateTime creationTime; |
||||||
|
|
||||||
|
/// 问题创建id |
||||||
|
final String creatorId; |
||||||
|
|
||||||
|
/// 问题的同步状态,默认为未同步。 |
||||||
|
final ProblemSyncStatus syncStatus; |
||||||
|
|
||||||
|
/// 最后修改时间 |
||||||
|
final DateTime lastModifiedTime; |
||||||
|
|
||||||
|
/// 相关的审查任务ID,可空。 |
||||||
|
final String? censorTaskId; |
||||||
|
|
||||||
|
/// 绑定的附加数据,可空。 |
||||||
|
final String? bindData; |
||||||
|
|
||||||
|
/// 问题是否已被检查,默认为false。 |
||||||
|
final bool isChecked; |
||||||
|
|
||||||
|
ProblemRemoteDto({ |
||||||
|
required this.id, |
||||||
|
required this.description, |
||||||
|
required this.location, |
||||||
|
required this.imageUrls, |
||||||
|
required this.creationTime, |
||||||
|
required this.creatorId, |
||||||
|
required this.lastModifiedTime, |
||||||
|
this.syncStatus = ProblemSyncStatus.pendingCreate, |
||||||
|
this.censorTaskId, |
||||||
|
this.bindData, |
||||||
|
this.isChecked = false, |
||||||
|
this.enterpriseId, |
||||||
|
}); |
||||||
|
|
||||||
|
/// copyWith 方法,用于创建对象的副本并修改指定字段 |
||||||
|
ProblemRemoteDto copyWith({ |
||||||
|
String? id, |
||||||
|
String? enterpriseId, |
||||||
|
String? description, |
||||||
|
String? location, |
||||||
|
List<ImageMetadata>? imageUrls, |
||||||
|
DateTime? creationTime, |
||||||
|
String? creatorId, |
||||||
|
DateTime? lastModifiedTime, |
||||||
|
ProblemSyncStatus? syncStatus, |
||||||
|
bool? isDeleted, |
||||||
|
String? censorTaskId, |
||||||
|
String? bindData, |
||||||
|
bool? isChecked, |
||||||
|
}) { |
||||||
|
return ProblemRemoteDto( |
||||||
|
id: id ?? this.id, |
||||||
|
enterpriseId: enterpriseId ?? this.enterpriseId, |
||||||
|
description: description ?? this.description, |
||||||
|
location: location ?? this.location, |
||||||
|
imageUrls: imageUrls ?? this.imageUrls, |
||||||
|
creationTime: creationTime ?? this.creationTime, |
||||||
|
creatorId: creatorId ?? this.creatorId, |
||||||
|
lastModifiedTime: lastModifiedTime ?? this.lastModifiedTime, |
||||||
|
syncStatus: syncStatus ?? this.syncStatus, |
||||||
|
censorTaskId: censorTaskId ?? this.censorTaskId, |
||||||
|
bindData: bindData ?? this.bindData, |
||||||
|
isChecked: isChecked ?? this.isChecked, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 转换为JSON字符串 |
||||||
|
Map<String, dynamic> toJson() { |
||||||
|
return { |
||||||
|
'id': id, |
||||||
|
'enterpriseId': enterpriseId, |
||||||
|
'description': description, |
||||||
|
'location': location, |
||||||
|
'imageUrls': json.encode(imageUrls.map((e) => e.toMap()).toList()), |
||||||
|
'creationTime': creationTime.toIso8601String(), |
||||||
|
'creatorId': creatorId, |
||||||
|
'lastModifiedTime': lastModifiedTime.toIso8601String(), |
||||||
|
'censorTaskId': censorTaskId, |
||||||
|
'bindData': bindData, |
||||||
|
}.withoutNullOrEmptyValues; |
||||||
|
} |
||||||
|
|
||||||
|
/// 从Map创建对象,用于从SQLite读取 |
||||||
|
factory ProblemRemoteDto.fromJson(Map<String, dynamic> map) { |
||||||
|
// 处理imageUrls的转换 |
||||||
|
List<ImageMetadata> imageUrlsList = []; |
||||||
|
if (map['imageUrls'] != null) { |
||||||
|
try { |
||||||
|
final List<dynamic> imageList = json.decode(map['imageUrls']); |
||||||
|
imageUrlsList = imageList.map((e) => ImageMetadata.fromMap(e)).toList(); |
||||||
|
} catch (e) { |
||||||
|
// 如果解析失败,保持空列表 |
||||||
|
imageUrlsList = []; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ProblemRemoteDto( |
||||||
|
id: map['id'], |
||||||
|
enterpriseId: map['companyId'], |
||||||
|
description: map['description'], |
||||||
|
location: map['location'], |
||||||
|
imageUrls: imageUrlsList, |
||||||
|
creationTime: DateTime.fromMillisecondsSinceEpoch( |
||||||
|
map['creationTime'], |
||||||
|
isUtc: true, |
||||||
|
), |
||||||
|
creatorId: map['creatorId'], |
||||||
|
lastModifiedTime: DateTime.fromMillisecondsSinceEpoch( |
||||||
|
map['lastModifiedTime'], |
||||||
|
isUtc: true, |
||||||
|
), |
||||||
|
syncStatus: ProblemSyncStatus.values[map['syncStatus']], |
||||||
|
censorTaskId: map['censorTaskId'], |
||||||
|
bindData: map['bindData'], |
||||||
|
isChecked: map['isChecked'] == 1, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,139 +1,139 @@ |
|||||||
import 'package:dio/dio.dart'; |
// import 'package:dio/dio.dart'; |
||||||
import 'package:get/get.dart' hide MultipartFile, FormData, Response; |
// import 'package:get/get.dart' hide MultipartFile, FormData, Response; |
||||||
import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; |
// import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; |
||||||
import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; |
// import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
// import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
||||||
import 'package:problem_check_system/app/core/models/server_problem.dart'; |
// import 'package:problem_check_system/app/core/models/server_problem.dart'; |
||||||
import 'package:problem_check_system/app/core/services/network_status_service.dart'; |
// import 'package:problem_check_system/app/core/services/network_status_service.dart'; |
||||||
import 'package:problem_check_system/app/core/services/http_provider.dart'; |
// import 'package:problem_check_system/app/core/services/http_provider.dart'; |
||||||
import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
// import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_datasource.dart'; |
// import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
||||||
|
|
||||||
/// 问题仓库,负责处理问题数据的本地持久化。 |
// /// 问题仓库,负责处理问题数据的本地持久化。 |
||||||
/// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。 |
// /// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。 |
||||||
class ProblemRepository extends GetxService { |
// class ProblemRepository extends GetxService { |
||||||
final ProblemLocalDataSource problemLocalDataSource; |
// final ProblemLocalDataSource problemLocalDataSource; |
||||||
final HttpProvider httpProvider; |
// final HttpProvider httpProvider; |
||||||
final NetworkStatusService networkStatusService; |
// final NetworkStatusService networkStatusService; |
||||||
final AuthRepository authRepository; |
// final AuthRepository authRepository; |
||||||
|
|
||||||
RxBool get isOnline => networkStatusService.isOnline; |
// RxBool get isOnline => networkStatusService.isOnline; |
||||||
|
|
||||||
ProblemRepository({ |
// ProblemRepository({ |
||||||
required this.problemLocalDataSource, |
// required this.problemLocalDataSource, |
||||||
required this.httpProvider, |
// required this.httpProvider, |
||||||
required this.networkStatusService, |
// required this.networkStatusService, |
||||||
required this.authRepository, |
// required this.authRepository, |
||||||
}); |
// }); |
||||||
|
|
||||||
/// 更新本地数据库中的一个问题。 |
// /// 更新本地数据库中的一个问题。 |
||||||
Future<void> updateProblem(Problem problem) async { |
// Future<void> updateProblem(Problem problem) async { |
||||||
await problemLocalDataSource.updateProblem(problem); |
// await problemLocalDataSource.updateProblem(problem); |
||||||
} |
// } |
||||||
|
|
||||||
/// 通用查询方法,根据可选的筛选条件获取问题列表。 |
// /// 通用查询方法,根据可选的筛选条件获取问题列表。 |
||||||
/// - `startDate`/`endDate`:筛选创建时间范围。 |
// /// - `startDate`/`endDate`:筛选创建时间范围。 |
||||||
/// - `syncStatus`:筛选上传状态('已上传', '未上传', '全部')。 |
// /// - `syncStatus`:筛选上传状态('已上传', '未上传', '全部')。 |
||||||
/// - `bindStatus`:筛选绑定状态('已绑定', '未绑定', '全部')。 |
// /// - `bindStatus`:筛选绑定状态('已绑定', '未绑定', '全部')。 |
||||||
Future getProblems({ |
// Future getProblems({ |
||||||
DateTime? startDate, |
// DateTime? startDate, |
||||||
DateTime? endDate, |
// DateTime? endDate, |
||||||
String? syncStatus, |
// String? syncStatus, |
||||||
String? bindStatus, |
// String? bindStatus, |
||||||
}) async { |
// }) async { |
||||||
return await problemLocalDataSource.getProblems( |
// return await problemLocalDataSource.getProblems( |
||||||
startDate: startDate, |
// startDate: startDate, |
||||||
endDate: endDate, |
// endDate: endDate, |
||||||
syncStatus: syncStatus, |
// syncStatus: syncStatus, |
||||||
bindStatus: bindStatus, |
// bindStatus: bindStatus, |
||||||
); |
// ); |
||||||
} |
// } |
||||||
|
|
||||||
Future<void> insertProblem(Problem problem) async { |
// Future<void> insertProblem(Problem problem) async { |
||||||
await problemLocalDataSource.insertProblem(problem); |
// await problemLocalDataSource.insertProblem(problem); |
||||||
} |
// } |
||||||
|
|
||||||
Future<void> deleteProblem(String problemId) async { |
// Future<void> deleteProblem(String problemId) async { |
||||||
await problemLocalDataSource.deleteProblem(problemId); |
// await problemLocalDataSource.deleteProblem(problemId); |
||||||
} |
// } |
||||||
|
|
||||||
// 在ProblemRepository中添加 |
// // 在ProblemRepository中添加 |
||||||
Future<List<ServerProblem>> fetchProblemsFromServer({ |
// Future<List<ServerProblem>> fetchProblemsFromServer({ |
||||||
DateTime? startTime, |
// DateTime? startTime, |
||||||
DateTime? endTime, |
// DateTime? endTime, |
||||||
int? pageNumber, |
// int? pageNumber, |
||||||
int? pageSize, |
// int? pageSize, |
||||||
CancelToken? cancelToken, |
// CancelToken? cancelToken, |
||||||
}) async { |
// }) async { |
||||||
try { |
// try { |
||||||
final response = await httpProvider.get( |
// final response = await httpProvider.get( |
||||||
ApiEndpoints.getProblems, |
// ApiEndpoints.getProblems, |
||||||
queryParameters: { |
// queryParameters: { |
||||||
'creatorId': authRepository.getUserId(), |
// 'creatorId': authRepository.getUserId(), |
||||||
if (startTime != null) |
// if (startTime != null) |
||||||
'StartTime': startTime.toUtc().toIso8601String(), |
// 'StartTime': startTime.toUtc().toIso8601String(), |
||||||
if (endTime != null) 'EndTime': endTime.toUtc().toIso8601String(), |
// if (endTime != null) 'EndTime': endTime.toUtc().toIso8601String(), |
||||||
if (pageNumber != null) 'pageNumber': pageNumber, |
// if (pageNumber != null) 'pageNumber': pageNumber, |
||||||
if (pageSize != null) 'pageSize': pageSize, |
// if (pageSize != null) 'pageSize': pageSize, |
||||||
}, |
// }, |
||||||
cancelToken: cancelToken, |
// cancelToken: cancelToken, |
||||||
); |
// ); |
||||||
|
|
||||||
if (response.isSuccess) { |
// if (response.isSuccess) { |
||||||
// Dio 会自动解析 JSON,response.data 已经是 Map 或 List |
// // Dio 会自动解析 JSON,response.data 已经是 Map 或 List |
||||||
final Map<String, dynamic> data = response.data; |
// final Map<String, dynamic> data = response.data; |
||||||
final List<dynamic> items = data['items']; |
// final List<dynamic> items = data['items']; |
||||||
|
|
||||||
// 使用 Freezed 生成的 fromJson 方法 |
// // 使用 Freezed 生成的 fromJson 方法 |
||||||
return items.map((item) => ServerProblem.fromJson(item)).toList(); |
// return items.map((item) => ServerProblem.fromJson(item)).toList(); |
||||||
} else { |
// } else { |
||||||
throw Exception('拉取问题失败: ${response.statusCode}'); |
// throw Exception('拉取问题失败: ${response.statusCode}'); |
||||||
} |
// } |
||||||
} on DioException catch (e) { |
// } on DioException catch (e) { |
||||||
Get.log("Dio 异常$e"); |
// Get.log("Dio 异常$e"); |
||||||
rethrow; |
// rethrow; |
||||||
} catch (e) { |
// } catch (e) { |
||||||
Get.log("解析失败:$e"); |
// Get.log("解析失败:$e"); |
||||||
rethrow; |
// rethrow; |
||||||
} |
// } |
||||||
} |
// } |
||||||
|
|
||||||
/// post |
// /// post |
||||||
Future<Response> post( |
// Future<Response> post( |
||||||
Map<String, Object> apiPayload, |
// Map<String, Object> apiPayload, |
||||||
CancelToken cancelToken, |
// CancelToken cancelToken, |
||||||
) async { |
// ) async { |
||||||
// 3. 发送给服务器 |
// // 3. 发送给服务器 |
||||||
final response = await httpProvider.post( |
// final response = await httpProvider.post( |
||||||
ApiEndpoints.postProblem, |
// ApiEndpoints.postProblem, |
||||||
data: apiPayload, |
// data: apiPayload, |
||||||
cancelToken: cancelToken, |
// cancelToken: cancelToken, |
||||||
); |
// ); |
||||||
return response; |
// return response; |
||||||
} |
// } |
||||||
|
|
||||||
/// put |
// /// put |
||||||
Future<Response> put( |
// Future<Response> put( |
||||||
String id, |
// String id, |
||||||
Map<String, Object> apiPayload, |
// Map<String, Object> apiPayload, |
||||||
CancelToken cancelToken, |
// CancelToken cancelToken, |
||||||
) async { |
// ) async { |
||||||
// 3. 发送给服务器 |
// // 3. 发送给服务器 |
||||||
final response = await httpProvider.put( |
// final response = await httpProvider.put( |
||||||
ApiEndpoints.putProblemById(id), |
// ApiEndpoints.putProblemById(id), |
||||||
data: apiPayload, |
// data: apiPayload, |
||||||
cancelToken: cancelToken, |
// cancelToken: cancelToken, |
||||||
); |
// ); |
||||||
return response; |
// return response; |
||||||
} |
// } |
||||||
|
|
||||||
/// delete |
// /// delete |
||||||
Future<Response> delete(String id, CancelToken cancelToken) async { |
// Future<Response> delete(String id, CancelToken cancelToken) async { |
||||||
// 3. 发送给服务器 |
// // 3. 发送给服务器 |
||||||
final response = await httpProvider.delete( |
// final response = await httpProvider.delete( |
||||||
ApiEndpoints.deleteProblemById(id), |
// ApiEndpoints.deleteProblemById(id), |
||||||
cancelToken: cancelToken, |
// cancelToken: cancelToken, |
||||||
); |
// ); |
||||||
return response; |
// return response; |
||||||
} |
// } |
||||||
} |
// } |
||||||
|
|||||||
@ -0,0 +1,81 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/model/problem_local_dto.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
|
||||||
|
/// 问题仓库,负责处理问题数据的本地持久化。 |
||||||
|
/// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。 |
||||||
|
/// 它的核心工作是在领域实体 (ProblemEntity) 和数据传输对象 (ProblemLocalDto) 之间进行转换。 |
||||||
|
class ProblemRepository implements IProblemRepository { |
||||||
|
final IProblemLocalDataSource problemLocalDataSource; // 2. 依赖于数据源的抽象 |
||||||
|
|
||||||
|
ProblemRepository(this.problemLocalDataSource); |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemEntity> addProblem(ProblemEntity problem) async { |
||||||
|
// 1. 将领域实体 (Entity) 转换为本地数据传输对象 (DTO) |
||||||
|
final problemDto = ProblemLocalDto.fromEntity(problem); |
||||||
|
|
||||||
|
// 2. 调用数据源的方法,将 DTO 转换为 Map 进行存储 |
||||||
|
await problemLocalDataSource.addProblem(problemDto.toMap()); |
||||||
|
|
||||||
|
// 3. 操作成功后,返回传入的实体,确认操作完成 |
||||||
|
return problem; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> deleteProblem(String id) async { |
||||||
|
// 直接将 ID 传递给数据源进行删除操作 |
||||||
|
await problemLocalDataSource.deleteProblem(id); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<List<ProblemEntity>> getAllProblems({ |
||||||
|
DateTime? startDate, |
||||||
|
DateTime? endDate, |
||||||
|
String? syncStatus, |
||||||
|
String? bindStatus, |
||||||
|
}) async { |
||||||
|
// 1. 调用数据源获取所有问题的原始数据 (List of Maps) |
||||||
|
final problemMaps = await problemLocalDataSource.getAllProblems( |
||||||
|
// 将参数直接透传给数据源 |
||||||
|
startDate: startDate, |
||||||
|
endDate: endDate, |
||||||
|
syncStatus: syncStatus, |
||||||
|
bindStatus: bindStatus, |
||||||
|
); |
||||||
|
|
||||||
|
// 2. 将每个 Map 转换为 DTO,然后再转换为领域实体 (Entity) |
||||||
|
final problems = problemMaps |
||||||
|
.map((map) => ProblemLocalDto.fromMap(map).toEntity()) |
||||||
|
.toList(); |
||||||
|
|
||||||
|
return problems; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemEntity?> getProblemById(String id) async { |
||||||
|
// 1. 调用数据源通过 ID 获取问题的原始数据 (Map) |
||||||
|
final problemMap = await problemLocalDataSource.getProblemById(id); |
||||||
|
|
||||||
|
// 2. 如果数据不存在,则返回 null |
||||||
|
if (problemMap == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
// 3. 如果数据存在,则先转换为 DTO,再转换为领域实体并返回 |
||||||
|
return ProblemLocalDto.fromMap(problemMap).toEntity(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<ProblemEntity> updateProblem(ProblemEntity problem) async { |
||||||
|
// 1. 将领域实体 (Entity) 转换为本地数据传输对象 (DTO) |
||||||
|
final problemDto = ProblemLocalDto.fromEntity(problem); |
||||||
|
|
||||||
|
// 2. 调用数据源的方法,将 DTO 转换为 Map 进行更新 |
||||||
|
await problemLocalDataSource.updateProblem(problemDto.toMap()); |
||||||
|
|
||||||
|
// 3. 操作成功后,返回传入的实体,确认操作完成 |
||||||
|
return problem; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,19 +0,0 @@ |
|||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
|
||||||
|
|
||||||
/// Problem 仓库的抽象接口 |
|
||||||
/// 定义了业务逻辑层需要的数据操作。 |
|
||||||
abstract class ProblemRepository { |
|
||||||
Future<void> addProblem(Problem problem); |
|
||||||
Future<void> updateProblem(Problem problem); |
|
||||||
Future<void> deleteProblem(String problemId); |
|
||||||
Future<Problem?> getProblemById(String id); |
|
||||||
|
|
||||||
Future<void> markAsSynced(String id); |
|
||||||
|
|
||||||
Future<List<Problem>> getProblems({ |
|
||||||
DateTime? startDate, |
|
||||||
DateTime? endDate, |
|
||||||
String? syncStatus, // 业务逻辑层使用字符串,更直观 |
|
||||||
String? bindStatus, |
|
||||||
}); |
|
||||||
} |
|
||||||
@ -0,0 +1,16 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
|
||||||
|
/// Problem 仓库的抽象接口 |
||||||
|
/// 定义了业务逻辑层需要的数据操作。 |
||||||
|
abstract class IProblemRepository { |
||||||
|
Future<List<ProblemEntity>> getAllProblems({ |
||||||
|
DateTime? startDate, |
||||||
|
DateTime? endDate, |
||||||
|
String? syncStatus, |
||||||
|
String? bindStatus, |
||||||
|
}); |
||||||
|
Future<ProblemEntity?> getProblemById(String id); |
||||||
|
Future<ProblemEntity> addProblem(ProblemEntity problem); |
||||||
|
Future<ProblemEntity> updateProblem(ProblemEntity problem); |
||||||
|
Future<void> deleteProblem(String id); |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
import 'package:problem_check_system/app/core/models/sync_status.dart'; |
||||||
|
import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
import 'package:uuid/uuid.dart'; |
||||||
|
|
||||||
|
class AddProblemUsecase { |
||||||
|
final IProblemRepository problemRepository; |
||||||
|
final AuthRepository authRepository; |
||||||
|
final Uuid uuid; |
||||||
|
|
||||||
|
AddProblemUsecase({ |
||||||
|
required this.problemRepository, |
||||||
|
required this.authRepository, |
||||||
|
required this.uuid, |
||||||
|
}); |
||||||
|
|
||||||
|
Future<ProblemEntity> call({ |
||||||
|
required String enterpriseId, |
||||||
|
required String description, |
||||||
|
required String location, |
||||||
|
required List<String> imageUrls, |
||||||
|
}) async { |
||||||
|
final nowUtc = DateTime.now().toUtc(); |
||||||
|
final userId = authRepository.getUserId(); |
||||||
|
final newProblem = ProblemEntity( |
||||||
|
id: uuid.v4(), |
||||||
|
description: description, |
||||||
|
location: location, |
||||||
|
imageUrls: imageUrls, |
||||||
|
lastModifiedTime: nowUtc, |
||||||
|
lastModifierId: userId, |
||||||
|
syncStatus: SyncStatus.pendingCreate, |
||||||
|
enterpriseId: enterpriseId, |
||||||
|
creationTime: nowUtc, |
||||||
|
creatorId: userId, |
||||||
|
); |
||||||
|
return await problemRepository.addProblem(newProblem); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
|
||||||
|
class DeleteProblem { |
||||||
|
final IProblemRepository problemRepository; |
||||||
|
|
||||||
|
DeleteProblem({required this.problemRepository}); |
||||||
|
Future<void> call(String id) async { |
||||||
|
await problemRepository.deleteProblem(id); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
|
||||||
|
class GetAllProblemsUsecase { |
||||||
|
final IProblemRepository problemRepository; |
||||||
|
|
||||||
|
GetAllProblemsUsecase({required this.problemRepository}); |
||||||
|
|
||||||
|
Future<List<ProblemEntity>> call({ |
||||||
|
DateTime? startDate, |
||||||
|
DateTime? endDate, |
||||||
|
String? syncStatus, |
||||||
|
String? bindStatus, |
||||||
|
}) async { |
||||||
|
return await problemRepository.getAllProblems( |
||||||
|
startDate: startDate, |
||||||
|
endDate: endDate, |
||||||
|
syncStatus: syncStatus, |
||||||
|
bindStatus: bindStatus, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
|
||||||
|
class GetProblemByIdUsecase { |
||||||
|
final IProblemRepository problemRepository; |
||||||
|
|
||||||
|
GetProblemByIdUsecase({required this.problemRepository}); |
||||||
|
Future<ProblemEntity?> call(String id) async { |
||||||
|
return await problemRepository.getProblemById(id); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
import 'package:problem_check_system/app/core/models/sync_status.dart'; |
||||||
|
import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
|
|
||||||
|
class UpdateProblem { |
||||||
|
final IProblemRepository repository; |
||||||
|
final AuthRepository authRepository; |
||||||
|
|
||||||
|
UpdateProblem({required this.repository, required this.authRepository}); |
||||||
|
|
||||||
|
Future<ProblemEntity> call(ProblemEntity entity) async { |
||||||
|
final nowUtc = DateTime.now().toUtc(); |
||||||
|
final userId = authRepository.getUserId(); |
||||||
|
final newProblem = entity.copyWith( |
||||||
|
lastModifiedTime: nowUtc, |
||||||
|
lastModifierId: userId, |
||||||
|
syncStatus: SyncStatus.pendingUpdate, |
||||||
|
); |
||||||
|
return await repository.updateProblem(newProblem); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,37 +1,29 @@ |
|||||||
import 'package:get/get.dart'; |
// import 'package:get/get.dart'; |
||||||
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
// import 'package:problem_check_system/app/core/models/problem_sync_status.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/core/services/http_provider.dart'; |
// import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart'; |
||||||
import 'package:problem_check_system/app/core/services/network_status_service.dart'; |
// import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_datasource.dart'; |
// import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
|
|
||||||
class ProblemBinding extends Bindings { |
// class ProblemBinding extends Bindings { |
||||||
@override |
// @override |
||||||
void dependencies() { |
// void dependencies() { |
||||||
Get.lazyPut<ProblemLocalDataSource>( |
// Get.lazyPut<IProblemLocalDataSource>( |
||||||
() => ProblemLocalDataSourceImpl( |
// () => |
||||||
databaseService: Get.find<DatabaseService>(), |
// ProblemLocalDataSource(databaseService: Get.find<DatabaseService>()), |
||||||
), |
// ); |
||||||
); |
// Get.lazyPut<IProblemRepository>( |
||||||
Get.lazyPut<ProblemRepository>( |
// () => ProblemRepository(Get.find<ProblemLocalDataSource>()), |
||||||
() => ProblemRepository( |
// ); |
||||||
problemLocalDataSource: Get.find<ProblemLocalDataSource>(), |
// Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
||||||
httpProvider: Get.find<HttpProvider>(), |
|
||||||
networkStatusService: Get.find<NetworkStatusService>(), |
|
||||||
authRepository: Get.find(), |
|
||||||
), |
|
||||||
); |
|
||||||
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
|
||||||
|
|
||||||
/// 注册问题控制器 |
// /// 注册问题控制器 |
||||||
Get.lazyPut<ProblemController>( |
// Get.lazyPut<ProblemController>( |
||||||
() => ProblemController( |
// () => ProblemController( |
||||||
problemRepository: Get.find<ProblemRepository>(), |
// problemRepository: Get.find<ProblemRepository>(), |
||||||
problemStateManager: Get.find<ProblemStateManager>(), |
// problemStateManager: Get.find<ProblemStateManager>(), |
||||||
), |
// ), |
||||||
fenix: true, |
// fenix: true, |
||||||
); |
// ); |
||||||
} |
// } |
||||||
} |
// } |
||||||
|
|||||||
@ -1,27 +1,33 @@ |
|||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
import 'package:problem_check_system/app/core/models/form_mode.dart'; |
||||||
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart'; |
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart'; |
||||||
|
|
||||||
class ProblemFormBinding extends Bindings { |
class ProblemFormBinding extends Bindings { |
||||||
@override |
@override |
||||||
void dependencies() { |
void dependencies() { |
||||||
final dynamic arguments = Get.arguments; |
// final dynamic arguments = Get.arguments; |
||||||
final bool readOnly = Get.parameters['isReadOnly'] == 'true'; |
// final bool readOnly = Get.parameters['isReadOnly'] == 'true'; |
||||||
|
|
||||||
Problem? problem; |
ProblemEntity? problem; |
||||||
if (arguments != null && arguments is Problem) { |
FormMode formMode = FormMode.view; |
||||||
problem = arguments; |
|
||||||
|
if (Get.arguments is Map) { |
||||||
|
final arguments = Get.arguments as Map; |
||||||
|
|
||||||
|
// 设置模式 |
||||||
|
if (arguments.containsKey('mode')) { |
||||||
|
formMode = arguments['mode'] as FormMode; |
||||||
|
} |
||||||
|
|
||||||
|
// 如果是编辑或查看模式,需要填充数据 |
||||||
|
if (arguments.containsKey('data')) { |
||||||
|
problem = arguments['data'] as ProblemEntity?; |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
|
||||||
Get.lazyPut<ProblemFormController>( |
Get.lazyPut<ProblemFormController>( |
||||||
() => ProblemFormController( |
() => ProblemFormController(problem: problem, formMode: formMode), |
||||||
problemRepository: Get.find(), |
|
||||||
problemStateManager: Get.find(), |
|
||||||
problem: problem, |
|
||||||
isReadOnly: readOnly, |
|
||||||
), |
|
||||||
); |
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,35 @@ |
|||||||
|
import 'package:get/get.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/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/usecases/get_all_problems_usecase.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; |
||||||
|
|
||||||
|
class ProblemListBinding extends Bindings { |
||||||
|
@override |
||||||
|
void dependencies() { |
||||||
|
// 数据 |
||||||
|
Get.lazyPut<IProblemLocalDataSource>( |
||||||
|
() => |
||||||
|
ProblemLocalDataSource(databaseService: Get.find<DatabaseService>()), |
||||||
|
); |
||||||
|
// 仓库 |
||||||
|
Get.lazyPut<IProblemRepository>( |
||||||
|
() => ProblemRepository(Get.find<IProblemLocalDataSource>()), |
||||||
|
); |
||||||
|
// 用例 |
||||||
|
Get.lazyPut<GetAllProblemsUsecase>( |
||||||
|
() => GetAllProblemsUsecase( |
||||||
|
problemRepository: Get.find<IProblemRepository>(), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
/// 控制器 |
||||||
|
Get.lazyPut<ProblemListController>( |
||||||
|
() => ProblemListController( |
||||||
|
getAllProblemsUsecase: Get.find<GetAllProblemsUsecase>(), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
|
||||||
|
class ProblemUploadBinding extends Bindings { |
||||||
|
@override |
||||||
|
void dependencies() { |
||||||
|
// TODO: implement dependencies |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,208 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/company_enum.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/form_mode.dart'; |
||||||
|
import 'package:problem_check_system/app/core/routes/app_routes.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/usecases/get_all_problems_usecase.dart'; |
||||||
|
|
||||||
|
class ProblemListController extends GetxController { |
||||||
|
final GetAllProblemsUsecase getAllProblemsUsecase; |
||||||
|
// final SyncEnterprisesUsecase syncEnterprisesUsecase; // 新增 |
||||||
|
// final ResolveConflictUsecase resolveConflictUsecase; // 新增 |
||||||
|
|
||||||
|
ProblemListController({ |
||||||
|
required this.getAllProblemsUsecase, |
||||||
|
// required this.syncEnterprisesUsecase, |
||||||
|
// required this.resolveConflictUsecase, |
||||||
|
}); |
||||||
|
|
||||||
|
// --- 实现基类中定义的属性 --- |
||||||
|
final enterpriseList = <EnterpriseListItem>[].obs; |
||||||
|
final isLoading = false.obs; |
||||||
|
final isSyncing = false.obs; |
||||||
|
|
||||||
|
final nameController = TextEditingController(); |
||||||
|
final selectedType = Rx<CompanyType?>(null); |
||||||
|
final startDate = Rx<DateTime?>(null); |
||||||
|
final endDate = Rx<DateTime?>(null); |
||||||
|
final selectedEnterprises = <Enterprise>{}.obs; |
||||||
|
final ExpansibleController expansibleController = ExpansibleController(); |
||||||
|
|
||||||
|
@override |
||||||
|
void onInit() { |
||||||
|
// 页面初始化时,启动完整的“同步-再加载”流程 |
||||||
|
// loadAndSyncEnterprises(); |
||||||
|
search(); |
||||||
|
super.onInit(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void onClose() { |
||||||
|
nameController.dispose(); |
||||||
|
expansibleController.dispose(); |
||||||
|
super.onClose(); |
||||||
|
} |
||||||
|
|
||||||
|
void search() {} |
||||||
|
|
||||||
|
// --- 实现基类中定义的方法 --- |
||||||
|
// 核心流程方法 |
||||||
|
// Future<void> loadAndSyncEnterprises() async { |
||||||
|
// try { |
||||||
|
// isLoading(true); |
||||||
|
// isSyncing(true); |
||||||
|
|
||||||
|
// // 步骤 1: 执行同步 |
||||||
|
// final syncResult = await syncEnterprisesUsecase(); |
||||||
|
|
||||||
|
// // 步骤 2: 处理冲突 |
||||||
|
// if (syncResult.hasConflicts) { |
||||||
|
// // 如果有冲突,则逐个弹窗让用户选择 |
||||||
|
// for (final conflict in syncResult.conflicts) { |
||||||
|
// final chosenVersion = await _showConflictDialog(conflict); |
||||||
|
// if (chosenVersion != null) { |
||||||
|
// // 用户做出了选择,更新本地数据 |
||||||
|
// await resolveConflictUsecase(chosenVersion); |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } |
||||||
|
|
||||||
|
// isSyncing(false); |
||||||
|
|
||||||
|
// // 步骤 3: 所有同步和冲突解决完毕后,从本地加载最终数据 |
||||||
|
// await loadEnterprises(); |
||||||
|
// } catch (e) { |
||||||
|
// Get.snackbar('错误', '操作失败: $e'); |
||||||
|
// } finally { |
||||||
|
// isLoading(false); |
||||||
|
// isSyncing(false); |
||||||
|
// } |
||||||
|
// } |
||||||
|
|
||||||
|
// // [修改后] 弹出冲突选择对话框的辅助方法 |
||||||
|
// Future<Enterprise?> _showConflictDialog(EnterpriseConflict conflict) { |
||||||
|
// return Get.dialog<Enterprise>( |
||||||
|
// AlertDialog( |
||||||
|
// title: Text('数据冲突'), |
||||||
|
// content: Column( |
||||||
|
// mainAxisSize: MainAxisSize.min, |
||||||
|
// crossAxisAlignment: CrossAxisAlignment.stretch, |
||||||
|
// children: [ |
||||||
|
// Text('${conflict.localVersion.name} 服务器上的数据与本地数据不一致,请选择要保留的版本。'), |
||||||
|
// const SizedBox(height: 24), |
||||||
|
|
||||||
|
// // --- 本地版本选择区 --- |
||||||
|
// Row( |
||||||
|
// children: [ |
||||||
|
// // 使用 Expanded 让主按钮填充可用空间 |
||||||
|
// Expanded( |
||||||
|
// child: ElevatedButton( |
||||||
|
// child: Text( |
||||||
|
// '使用客户端版本\n(修改于: ${conflict.localVersion.lastModifiedTime.toLocal().toDateTimeString2()})', |
||||||
|
// textAlign: TextAlign.center, |
||||||
|
// ), |
||||||
|
// onPressed: () => Get.back(result: conflict.localVersion), |
||||||
|
// ), |
||||||
|
// ), |
||||||
|
// const SizedBox(width: 8), // 按钮间的间距 |
||||||
|
// // 查看详情按钮 |
||||||
|
// IconButton( |
||||||
|
// icon: const Icon(Icons.info_outline), |
||||||
|
// tooltip: '查看客户端版本详情', |
||||||
|
// onPressed: () => navigateToDetailsView(conflict.localVersion), |
||||||
|
// ), |
||||||
|
// ], |
||||||
|
// ), |
||||||
|
// const SizedBox(height: 8), |
||||||
|
|
||||||
|
// // --- 服务器版本选择区 --- |
||||||
|
// Row( |
||||||
|
// children: [ |
||||||
|
// Expanded( |
||||||
|
// child: ElevatedButton( |
||||||
|
// style: ElevatedButton.styleFrom( |
||||||
|
// backgroundColor: Get.theme.colorScheme.primary, |
||||||
|
// foregroundColor: Get.theme.colorScheme.onPrimary, |
||||||
|
// ), |
||||||
|
// child: Text( |
||||||
|
// '使用服务器版本\n(修改于: ${conflict.serverVersion.lastModifiedTime.toLocal().toDateTimeString2()})', |
||||||
|
// textAlign: TextAlign.center, |
||||||
|
// ), |
||||||
|
// onPressed: () => Get.back(result: conflict.serverVersion), |
||||||
|
// ), |
||||||
|
// ), |
||||||
|
// const SizedBox(width: 8), |
||||||
|
// IconButton( |
||||||
|
// icon: const Icon(Icons.info_outline), |
||||||
|
// tooltip: '查看服务器版本详情', |
||||||
|
// onPressed: () => |
||||||
|
// navigateToDetailsView(conflict.serverVersion), |
||||||
|
// ), |
||||||
|
// ], |
||||||
|
// ), |
||||||
|
// ], |
||||||
|
// ), |
||||||
|
// ), |
||||||
|
// // 设置为 false,防止用户点击对话框外部意外关闭它 |
||||||
|
// barrierDismissible: false, |
||||||
|
// ); |
||||||
|
// } |
||||||
|
|
||||||
|
// Future<void> loadEnterprises() async { |
||||||
|
// expansibleController.collapse(); |
||||||
|
// isLoading.value = true; |
||||||
|
// try { |
||||||
|
// final result = await getEnterpriseListUsecase.call( |
||||||
|
// name: nameController.text, |
||||||
|
// type: selectedType.value?.displayText, |
||||||
|
// startDate: startDate.value, |
||||||
|
// endDate: endDate.value, |
||||||
|
// ); |
||||||
|
// enterpriseList.assignAll(result); |
||||||
|
// } catch (e) { |
||||||
|
// Get.snackbar('错误', '加载企业列表失败: $e'); |
||||||
|
// } finally { |
||||||
|
// isLoading.value = false; |
||||||
|
// } |
||||||
|
// } |
||||||
|
|
||||||
|
// void clearFilters() { |
||||||
|
// nameController.clear(); |
||||||
|
// selectedType.value = null; |
||||||
|
// startDate.value = null; |
||||||
|
// endDate.value = null; |
||||||
|
// loadEnterprises(); |
||||||
|
// } |
||||||
|
|
||||||
|
/// 导航到问题表单页面 |
||||||
|
Future<void> navigateToProblemForm({ |
||||||
|
Enterprise? enterprise, |
||||||
|
FormMode? fromMode, |
||||||
|
}) async { |
||||||
|
final result = await Get.toNamed( |
||||||
|
AppRoutes.problemForm, |
||||||
|
arguments: {'data': enterprise, 'mode': fromMode}, |
||||||
|
); |
||||||
|
if (result == true) { |
||||||
|
search(); |
||||||
|
Get.snackbar( |
||||||
|
'操作成功', |
||||||
|
'问题信息已更新', |
||||||
|
backgroundColor: Colors.green[600], |
||||||
|
colorText: Colors.white, |
||||||
|
icon: const Icon(Icons.check_circle, color: Colors.white), |
||||||
|
duration: const Duration(seconds: 3), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// 导航到企业问题列表 |
||||||
|
// Future<void> navigateToEnterpriseInfoPage(Enterprise enterprise) async { |
||||||
|
// await Get.toNamed( |
||||||
|
// AppRoutes.enterpriseInfo, |
||||||
|
// arguments: {'data': enterprise, 'mode': FormMode.view}, |
||||||
|
// ); |
||||||
|
// } |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
import 'dart:ui'; |
||||||
|
|
||||||
|
class ProblemUploadController { |
||||||
|
int get selectedCount => 10; |
||||||
|
|
||||||
|
get allSelected => null; |
||||||
|
|
||||||
|
get unUploadedProblems => null; |
||||||
|
|
||||||
|
VoidCallback? get selectAll => null; |
||||||
|
|
||||||
|
get handleUpload => null; |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
class ProblemFormModel { |
||||||
|
final String enterpriseName; |
||||||
|
final String description; |
||||||
|
final String location; |
||||||
|
final List<String> imageUrls; |
||||||
|
|
||||||
|
ProblemFormModel({ |
||||||
|
required this.enterpriseName, |
||||||
|
required this.description, |
||||||
|
required this.location, |
||||||
|
required this.imageUrls, |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,182 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/form_mode.dart'; |
||||||
|
import 'package:problem_check_system/app/core/pages/widgets/custom_app_bar.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; |
||||||
|
|
||||||
|
class ProblemListPage extends GetView<ProblemListController> { |
||||||
|
const ProblemListPage({super.key}); |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Scaffold( |
||||||
|
appBar: CustomAppBar( |
||||||
|
titleName: '企业列表', |
||||||
|
actionsVisible: true, |
||||||
|
onAddPressed: () { |
||||||
|
controller.navigateToProblemForm(fromMode: FormMode.add); |
||||||
|
}, |
||||||
|
), |
||||||
|
body: Text("问题列表"), |
||||||
|
); |
||||||
|
// return Obx(() { |
||||||
|
// if (true) { |
||||||
|
// return const Center(child: CircularProgressIndicator()); |
||||||
|
// } |
||||||
|
|
||||||
|
// return EasyRefresh( |
||||||
|
// header: ClassicHeader( |
||||||
|
// dragText: '下拉刷新'.tr, |
||||||
|
// armedText: '释放开始'.tr, |
||||||
|
// readyText: '刷新中...'.tr, |
||||||
|
// processingText: '刷新中...'.tr, |
||||||
|
// processedText: '成功了'.tr, |
||||||
|
// noMoreText: 'No more'.tr, |
||||||
|
// failedText: '失败'.tr, |
||||||
|
// messageText: '最后更新于 %T'.tr, |
||||||
|
// ), |
||||||
|
// onRefresh: () async { |
||||||
|
// // 调用控制器的刷新方法 |
||||||
|
// await controller.pullDataFromServer(); |
||||||
|
// }, |
||||||
|
// child: ListView.builder( |
||||||
|
// padding: EdgeInsets.symmetric(horizontal: 17.w), |
||||||
|
// itemCount: problemsToShow.length, |
||||||
|
// itemBuilder: (context, index) { |
||||||
|
// // if (index == problemsToShow.length) { |
||||||
|
// // return SizedBox(height: 79.5.h); |
||||||
|
// // } |
||||||
|
// final problem = problemsToShow[index]; |
||||||
|
// return _buildSwipeableProblemCard(problem); |
||||||
|
// }, |
||||||
|
// ), |
||||||
|
// ); |
||||||
|
// }); |
||||||
|
} |
||||||
|
|
||||||
|
// Widget _buildSwipeableProblemCard(Problem problem) { |
||||||
|
// // 对于所有视图类型,如果是待删除状态,都禁用交互 |
||||||
|
// final bool isPendingDelete = |
||||||
|
// problem.syncStatus == ProblemSyncStatus.pendingDelete; |
||||||
|
|
||||||
|
// if (viewType == ProblemCardViewType.buttons) { |
||||||
|
// // buttons 视图类型:有条件启用滑动删除 |
||||||
|
// if (!isPendingDelete) { |
||||||
|
// // 非待删除状态:启用滑动删除 |
||||||
|
// return Dismissible( |
||||||
|
// key: ValueKey('${problem.id}-${problem.syncStatus}'), |
||||||
|
// direction: DismissDirection.endToStart, |
||||||
|
// background: Container( |
||||||
|
// color: Colors.red, |
||||||
|
// alignment: Alignment.centerRight, |
||||||
|
// padding: EdgeInsets.only(right: 20.w), |
||||||
|
// child: Icon(Icons.delete, color: Colors.white, size: 30.sp), |
||||||
|
// ), |
||||||
|
// confirmDismiss: (direction) async { |
||||||
|
// return await _showDeleteConfirmationDialog(problem); |
||||||
|
// }, |
||||||
|
// onDismissed: (direction) { |
||||||
|
// // controller.deleteProblem(problem); |
||||||
|
// Get.snackbar('成功', '问题已删除'); |
||||||
|
// }, |
||||||
|
// child: ProblemCard( |
||||||
|
// key: ValueKey(problem.id), |
||||||
|
// problem: problem, |
||||||
|
// viewType: viewType, |
||||||
|
// isSelected: false, |
||||||
|
// ), |
||||||
|
// ); |
||||||
|
// } else { |
||||||
|
// // 待删除状态:显示普通卡片(无滑动功能) |
||||||
|
// return ProblemCard( |
||||||
|
// key: ValueKey(problem.id), |
||||||
|
// problem: problem, |
||||||
|
// viewType: viewType, |
||||||
|
// isSelected: false, |
||||||
|
// ); |
||||||
|
// } |
||||||
|
// } else { |
||||||
|
// // 其他视图类型(list、grid等):使用 Obx 监听选中状态 |
||||||
|
// return Obx(() { |
||||||
|
// final isSelected = controller.selectedProblems.contains(problem); |
||||||
|
// return ProblemCard( |
||||||
|
// key: ValueKey(problem.id), |
||||||
|
// problem: problem, |
||||||
|
// viewType: viewType, |
||||||
|
// isSelected: isSelected, |
||||||
|
// onChanged: (problem, isChecked) { |
||||||
|
// controller.updateProblemSelection(problem, isChecked); |
||||||
|
// }, |
||||||
|
// ); |
||||||
|
// }); |
||||||
|
// } |
||||||
|
// } |
||||||
|
|
||||||
|
Future<bool> _showDeleteConfirmationDialog(Problem problem) async { |
||||||
|
// 确保在返回前关闭可能存在的snackbar |
||||||
|
if (Get.isSnackbarOpen) { |
||||||
|
Get.closeCurrentSnackbar(); |
||||||
|
} |
||||||
|
return await Get.bottomSheet<bool>( |
||||||
|
Container( |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0), |
||||||
|
decoration: const BoxDecoration( |
||||||
|
color: Colors.white, |
||||||
|
borderRadius: BorderRadius.only( |
||||||
|
topLeft: Radius.circular(16), |
||||||
|
topRight: Radius.circular(16), |
||||||
|
), |
||||||
|
), |
||||||
|
child: SafeArea( |
||||||
|
child: Column( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||||
|
children: [ |
||||||
|
const SizedBox(height: 16), |
||||||
|
const Text( |
||||||
|
'确认删除', |
||||||
|
textAlign: TextAlign.center, |
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), |
||||||
|
), |
||||||
|
const SizedBox(height: 8), |
||||||
|
Text( |
||||||
|
'确定要删除这个问题吗?此操作不可撤销。', |
||||||
|
textAlign: TextAlign.center, |
||||||
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]), |
||||||
|
), |
||||||
|
const SizedBox(height: 24), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: () => Get.back(result: true), |
||||||
|
style: ElevatedButton.styleFrom( |
||||||
|
backgroundColor: Colors.red, |
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16), |
||||||
|
shape: RoundedRectangleBorder( |
||||||
|
borderRadius: BorderRadius.circular(10), |
||||||
|
), |
||||||
|
), |
||||||
|
child: const Text( |
||||||
|
'删除', |
||||||
|
style: TextStyle(color: Colors.white, fontSize: 16), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 8), |
||||||
|
TextButton( |
||||||
|
onPressed: () => Get.back(result: false), |
||||||
|
style: TextButton.styleFrom( |
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16), |
||||||
|
), |
||||||
|
child: Text( |
||||||
|
'取消', |
||||||
|
style: TextStyle(color: Colors.grey[700], fontSize: 16), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 16), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
) ?? |
||||||
|
false; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,7 +1,7 @@ |
|||||||
// widgets/custom_filter_dropdown.dart |
// widgets/custom_filter_dropdown.dart |
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart'; |
import 'package:problem_check_system/app/features/problem/presentation/pages/widgets/models/dropdown_option.dart'; |
||||||
|
|
||||||
class CustomFilterDropdown extends StatelessWidget { |
class CustomFilterDropdown extends StatelessWidget { |
||||||
final String title; |
final String title; |
||||||
@ -1,6 +1,6 @@ |
|||||||
// models/date_range_enum.dart |
// models/date_range_enum.dart |
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart'; |
import 'package:problem_check_system/app/features/problem/presentation/pages/widgets/models/dropdown_option.dart'; |
||||||
|
|
||||||
enum DateRange { threeDays, oneWeek, oneMonth } |
enum DateRange { threeDays, oneWeek, oneMonth } |
||||||
|
|
||||||
@ -1,94 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:get/get.dart'; |
|
||||||
import 'package:problem_check_system/app/core/pages/widgets/custom_app_bar.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_list_page.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/current_filter_bar.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; |
|
||||||
|
|
||||||
class ProblemPage extends GetView<ProblemController> { |
|
||||||
const ProblemPage({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return Scaffold( |
|
||||||
appBar: CustomAppBar( |
|
||||||
titleName: '问题列表', |
|
||||||
actionsVisible: true, |
|
||||||
onAddPressed: () => controller.toProblemFormPageAndRefresh(), |
|
||||||
), |
|
||||||
body: Column( |
|
||||||
children: [ |
|
||||||
CurrentFilterBar(), |
|
||||||
Expanded( |
|
||||||
child: // 使用通用列表组件 |
|
||||||
ProblemListPage( |
|
||||||
problemsToShow: controller.problems, |
|
||||||
viewType: ProblemCardViewType.buttons, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
|
|
||||||
// floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, |
|
||||||
// 使用 Stack 统一管理所有浮动按钮 |
|
||||||
// floatingActionButton: Stack( |
|
||||||
// children: [ |
|
||||||
// // 固定位置的 "添加" 按钮 |
|
||||||
// // 使用 Align 和 Positioned |
|
||||||
// Align( |
|
||||||
// alignment: Alignment.bottomCenter, |
|
||||||
// child: Padding( |
|
||||||
// padding: EdgeInsets.only(bottom: 24.h), // 底部间距 |
|
||||||
// child: FloatingActionButton( |
|
||||||
// heroTag: "btn_add", |
|
||||||
// onPressed: () { |
|
||||||
// controller.toProblemFormPageAndRefresh(); |
|
||||||
// }, |
|
||||||
// shape: const CircleBorder(), |
|
||||||
// backgroundColor: Colors.blue[300], |
|
||||||
// foregroundColor: Colors.white, |
|
||||||
// child: const Icon(Icons.add), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
|
|
||||||
// // 可拖动的 "上传" 按钮 |
|
||||||
// Obx(() { |
|
||||||
// final isOnline = controller.isOnline.value; |
|
||||||
// return Positioned( |
|
||||||
// // 使用正确的坐标,left/right 对应 dx,top/bottom 对应 dy |
|
||||||
// left: controller.fabUploadPosition.value.dx, |
|
||||||
// top: controller.fabUploadPosition.value.dy, |
|
||||||
// child: GestureDetector( |
|
||||||
// onPanUpdate: (details) { |
|
||||||
// // 调用控制器中的方法来更新位置 |
|
||||||
// controller.updateFabUploadPosition(details.delta); |
|
||||||
// }, |
|
||||||
// onPanEnd: (details) { |
|
||||||
// // 拖动结束后调用吸附方法 |
|
||||||
// controller.snapToEdge(); |
|
||||||
// }, |
|
||||||
// child: FloatingActionButton( |
|
||||||
// heroTag: "btn_upload", |
|
||||||
// onPressed: isOnline |
|
||||||
// ? () => controller.showUploadPage() |
|
||||||
// : null, |
|
||||||
// foregroundColor: Colors.white, |
|
||||||
// backgroundColor: isOnline |
|
||||||
// ? Colors.red[300] |
|
||||||
// : Colors.grey[400], |
|
||||||
// child: Icon( |
|
||||||
// isOnline |
|
||||||
// ? Icons.file_upload_outlined |
|
||||||
// : Icons.cloud_off_outlined, |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// }), |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
// widgets/compact_filter_bar.dart |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
||||||
import 'package:get/get.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
|
|
||||||
import 'custom_filter_dropdown.dart'; |
|
||||||
|
|
||||||
class CurrentFilterBar extends GetView<ProblemController> { |
|
||||||
final EdgeInsetsGeometry? padding; |
|
||||||
|
|
||||||
const CurrentFilterBar({super.key, this.padding}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return Container( |
|
||||||
padding: padding ?? EdgeInsets.symmetric(horizontal: 16.w, vertical: 5.h), |
|
||||||
color: Colors.grey[50], |
|
||||||
child: Row( |
|
||||||
children: [ |
|
||||||
// 日期范围筛选 |
|
||||||
...[ |
|
||||||
Obx( |
|
||||||
() => CustomFilterDropdown( |
|
||||||
title: '时间范围', |
|
||||||
options: controller.dateRangeOptions, |
|
||||||
selectedValue: controller.currentDateRange.value.name, |
|
||||||
onChanged: controller.updateCurrentDateRange, |
|
||||||
width: 100.w, |
|
||||||
showBorder: false, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
|
|
||||||
// 上传状态筛选 |
|
||||||
...[ |
|
||||||
Obx( |
|
||||||
() => CustomFilterDropdown( |
|
||||||
title: '上传状态', |
|
||||||
options: controller.uploadOptions, |
|
||||||
selectedValue: controller.currentUploadFilter.value, |
|
||||||
onChanged: controller.updateCurrentUpload, |
|
||||||
width: 100.w, |
|
||||||
showBorder: false, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
|
|
||||||
// 绑定状态筛选 |
|
||||||
...[ |
|
||||||
Obx( |
|
||||||
() => CustomFilterDropdown( |
|
||||||
title: '绑定状态', |
|
||||||
options: controller.bindOptions, |
|
||||||
selectedValue: controller.currentBindFilter.value, |
|
||||||
onChanged: controller.updateCurrentBind, |
|
||||||
width: 100.w, |
|
||||||
showBorder: false, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,97 +0,0 @@ |
|||||||
// widgets/compact_filter_bar.dart |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
||||||
import 'package:get/get.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
|
|
||||||
import 'custom_filter_dropdown.dart'; |
|
||||||
|
|
||||||
class HistoryFilterBar extends GetView<ProblemController> { |
|
||||||
final EdgeInsetsGeometry? padding; |
|
||||||
|
|
||||||
const HistoryFilterBar({super.key, this.padding}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return Container( |
|
||||||
padding: padding ?? EdgeInsets.symmetric(horizontal: 16.w, vertical: 5.h), |
|
||||||
color: Colors.grey[50], |
|
||||||
child: Row( |
|
||||||
children: [ |
|
||||||
// 显示日期选择 |
|
||||||
...[ |
|
||||||
SizedBox( |
|
||||||
width: 110.w, |
|
||||||
// decoration: BoxDecoration( |
|
||||||
// border: Border.all(color: Colors.grey.shade300), |
|
||||||
// borderRadius: BorderRadius.circular(8.r), |
|
||||||
// ), |
|
||||||
child: TextButton( |
|
||||||
onPressed: () { |
|
||||||
controller.selectDateRange(context); |
|
||||||
}, |
|
||||||
style: TextButton.styleFrom( |
|
||||||
padding: EdgeInsets.symmetric( |
|
||||||
horizontal: 12.w, |
|
||||||
vertical: 4.h, |
|
||||||
), |
|
||||||
shape: RoundedRectangleBorder( |
|
||||||
borderRadius: BorderRadius.circular(8.r), |
|
||||||
), |
|
||||||
), |
|
||||||
child: Row( |
|
||||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||||
children: [ |
|
||||||
Icon( |
|
||||||
Icons.date_range, |
|
||||||
size: 16.sp, |
|
||||||
color: Colors.grey[700], |
|
||||||
), |
|
||||||
SizedBox(width: 4.w), |
|
||||||
|
|
||||||
Text( |
|
||||||
'选择日期', |
|
||||||
style: TextStyle( |
|
||||||
fontSize: 14.sp, |
|
||||||
color: Colors.black87, |
|
||||||
fontWeight: FontWeight.normal, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
|
|
||||||
// 上传状态筛选 |
|
||||||
...[ |
|
||||||
Obx( |
|
||||||
() => CustomFilterDropdown( |
|
||||||
title: '上传状态', |
|
||||||
options: controller.uploadOptions, |
|
||||||
selectedValue: controller.historyUploadFilter.value, |
|
||||||
onChanged: controller.updateHistoryUpload, |
|
||||||
width: 100.w, |
|
||||||
showBorder: false, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
|
|
||||||
// 绑定状态筛选 |
|
||||||
...[ |
|
||||||
Obx( |
|
||||||
() => CustomFilterDropdown( |
|
||||||
title: '绑定状态', |
|
||||||
options: controller.bindOptions, |
|
||||||
selectedValue: controller.historyBindFilter.value, |
|
||||||
onChanged: controller.updateHistoryBind, |
|
||||||
width: 100.w, |
|
||||||
showBorder: false, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,181 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
||||||
import 'package:get/get.dart'; |
|
||||||
import 'package:easy_refresh/easy_refresh.dart'; |
|
||||||
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; |
|
||||||
|
|
||||||
class ProblemListPage extends GetView<ProblemController> { |
|
||||||
final RxList<Problem> problemsToShow; |
|
||||||
final ProblemCardViewType viewType; |
|
||||||
|
|
||||||
const ProblemListPage({ |
|
||||||
super.key, |
|
||||||
required this.problemsToShow, |
|
||||||
this.viewType = ProblemCardViewType.buttons, |
|
||||||
}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return Obx(() { |
|
||||||
if (controller.isLoading.value) { |
|
||||||
return const Center(child: CircularProgressIndicator()); |
|
||||||
} |
|
||||||
|
|
||||||
return EasyRefresh( |
|
||||||
header: ClassicHeader( |
|
||||||
dragText: '下拉刷新'.tr, |
|
||||||
armedText: '释放开始'.tr, |
|
||||||
readyText: '刷新中...'.tr, |
|
||||||
processingText: '刷新中...'.tr, |
|
||||||
processedText: '成功了'.tr, |
|
||||||
noMoreText: 'No more'.tr, |
|
||||||
failedText: '失败'.tr, |
|
||||||
messageText: '最后更新于 %T'.tr, |
|
||||||
), |
|
||||||
onRefresh: () async { |
|
||||||
// 调用控制器的刷新方法 |
|
||||||
await controller.pullDataFromServer(); |
|
||||||
}, |
|
||||||
child: ListView.builder( |
|
||||||
padding: EdgeInsets.symmetric(horizontal: 17.w), |
|
||||||
itemCount: problemsToShow.length, |
|
||||||
itemBuilder: (context, index) { |
|
||||||
// if (index == problemsToShow.length) { |
|
||||||
// return SizedBox(height: 79.5.h); |
|
||||||
// } |
|
||||||
final problem = problemsToShow[index]; |
|
||||||
return _buildSwipeableProblemCard(problem); |
|
||||||
}, |
|
||||||
), |
|
||||||
); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
Widget _buildSwipeableProblemCard(Problem problem) { |
|
||||||
// 对于所有视图类型,如果是待删除状态,都禁用交互 |
|
||||||
final bool isPendingDelete = |
|
||||||
problem.syncStatus == ProblemSyncStatus.pendingDelete; |
|
||||||
|
|
||||||
if (viewType == ProblemCardViewType.buttons) { |
|
||||||
// buttons 视图类型:有条件启用滑动删除 |
|
||||||
if (!isPendingDelete) { |
|
||||||
// 非待删除状态:启用滑动删除 |
|
||||||
return Dismissible( |
|
||||||
key: ValueKey('${problem.id}-${problem.syncStatus}'), |
|
||||||
direction: DismissDirection.endToStart, |
|
||||||
background: Container( |
|
||||||
color: Colors.red, |
|
||||||
alignment: Alignment.centerRight, |
|
||||||
padding: EdgeInsets.only(right: 20.w), |
|
||||||
child: Icon(Icons.delete, color: Colors.white, size: 30.sp), |
|
||||||
), |
|
||||||
confirmDismiss: (direction) async { |
|
||||||
return await _showDeleteConfirmationDialog(problem); |
|
||||||
}, |
|
||||||
onDismissed: (direction) { |
|
||||||
controller.deleteProblem(problem); |
|
||||||
Get.snackbar('成功', '问题已删除'); |
|
||||||
}, |
|
||||||
child: ProblemCard( |
|
||||||
key: ValueKey(problem.id), |
|
||||||
problem: problem, |
|
||||||
viewType: viewType, |
|
||||||
isSelected: false, |
|
||||||
), |
|
||||||
); |
|
||||||
} else { |
|
||||||
// 待删除状态:显示普通卡片(无滑动功能) |
|
||||||
return ProblemCard( |
|
||||||
key: ValueKey(problem.id), |
|
||||||
problem: problem, |
|
||||||
viewType: viewType, |
|
||||||
isSelected: false, |
|
||||||
); |
|
||||||
} |
|
||||||
} else { |
|
||||||
// 其他视图类型(list、grid等):使用 Obx 监听选中状态 |
|
||||||
return Obx(() { |
|
||||||
final isSelected = controller.selectedProblems.contains(problem); |
|
||||||
return ProblemCard( |
|
||||||
key: ValueKey(problem.id), |
|
||||||
problem: problem, |
|
||||||
viewType: viewType, |
|
||||||
isSelected: isSelected, |
|
||||||
onChanged: (problem, isChecked) { |
|
||||||
controller.updateProblemSelection(problem, isChecked); |
|
||||||
}, |
|
||||||
); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<bool> _showDeleteConfirmationDialog(Problem problem) async { |
|
||||||
// 确保在返回前关闭可能存在的snackbar |
|
||||||
if (Get.isSnackbarOpen) { |
|
||||||
Get.closeCurrentSnackbar(); |
|
||||||
} |
|
||||||
return await Get.bottomSheet<bool>( |
|
||||||
Container( |
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0), |
|
||||||
decoration: const BoxDecoration( |
|
||||||
color: Colors.white, |
|
||||||
borderRadius: BorderRadius.only( |
|
||||||
topLeft: Radius.circular(16), |
|
||||||
topRight: Radius.circular(16), |
|
||||||
), |
|
||||||
), |
|
||||||
child: SafeArea( |
|
||||||
child: Column( |
|
||||||
mainAxisSize: MainAxisSize.min, |
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|
||||||
children: [ |
|
||||||
const SizedBox(height: 16), |
|
||||||
const Text( |
|
||||||
'确认删除', |
|
||||||
textAlign: TextAlign.center, |
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), |
|
||||||
), |
|
||||||
const SizedBox(height: 8), |
|
||||||
Text( |
|
||||||
'确定要删除这个问题吗?此操作不可撤销。', |
|
||||||
textAlign: TextAlign.center, |
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]), |
|
||||||
), |
|
||||||
const SizedBox(height: 24), |
|
||||||
ElevatedButton( |
|
||||||
onPressed: () => Get.back(result: true), |
|
||||||
style: ElevatedButton.styleFrom( |
|
||||||
backgroundColor: Colors.red, |
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16), |
|
||||||
shape: RoundedRectangleBorder( |
|
||||||
borderRadius: BorderRadius.circular(10), |
|
||||||
), |
|
||||||
), |
|
||||||
child: const Text( |
|
||||||
'删除', |
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16), |
|
||||||
), |
|
||||||
), |
|
||||||
const SizedBox(height: 8), |
|
||||||
TextButton( |
|
||||||
onPressed: () => Get.back(result: false), |
|
||||||
style: TextButton.styleFrom( |
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16), |
|
||||||
), |
|
||||||
child: Text( |
|
||||||
'取消', |
|
||||||
style: TextStyle(color: Colors.grey[700], fontSize: 16), |
|
||||||
), |
|
||||||
), |
|
||||||
const SizedBox(height: 16), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
) ?? |
|
||||||
false; |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue