From 52cc2e11fde1fadf62efac1ced707f562b272a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=8C=AF=E5=8D=87?= <359059686@qq.com> Date: Tue, 2 Sep 2025 16:31:14 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20=E4=B8=8D=E5=8F=AF=E5=8F=98=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/models/image_metadata_model.dart | 2 +- .../{enum_model.dart => image_status.dart} | 13 +- lib/data/models/operation.dart | 25 +++ lib/data/models/problem_model.dart | 68 +++++-- lib/data/models/sync_status.dart | 21 ++ lib/data/providers/sqlite_provider.dart | 183 +++++++++++------- lib/data/repositories/problem_repository.dart | 3 +- .../controllers/problem_controller.dart | 26 ++- .../controllers/problem_form_controller.dart | 3 +- .../problem/views/widgets/problem_card.dart | 6 +- 10 files changed, 245 insertions(+), 105 deletions(-) rename lib/data/models/{enum_model.dart => image_status.dart} (51%) create mode 100644 lib/data/models/operation.dart create mode 100644 lib/data/models/sync_status.dart diff --git a/lib/data/models/image_metadata_model.dart b/lib/data/models/image_metadata_model.dart index 522a01d..f8e8266 100644 --- a/lib/data/models/image_metadata_model.dart +++ b/lib/data/models/image_metadata_model.dart @@ -1,5 +1,5 @@ // image_metadata_model.dart -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/image_status.dart'; class ImageMetadata { final String localPath; diff --git a/lib/data/models/enum_model.dart b/lib/data/models/image_status.dart similarity index 51% rename from lib/data/models/enum_model.dart rename to lib/data/models/image_status.dart index 4292078..6a47d71 100644 --- a/lib/data/models/enum_model.dart +++ b/lib/data/models/image_status.dart @@ -1,15 +1,4 @@ -enum SyncStatus { - /// 未同步到服务器 - notSynced, - - /// 已同步,本地无修改 - synced, - - /// 已同步,但本地有修改 - modified, -} - -// 图片的同步状态 +/// 图片的同步状态 enum ImageStatus { /// 新增的本地图片,需要上传 local, diff --git a/lib/data/models/operation.dart b/lib/data/models/operation.dart new file mode 100644 index 0000000..9d5c61e --- /dev/null +++ b/lib/data/models/operation.dart @@ -0,0 +1,25 @@ +enum Operation { + /// 创建 + create, + + /// 修改 + update, + + /// 删除 + delete, +} + +/// Operation 枚举的扩展方法 +extension OperationExtension on Operation { + /// 获取操作的中文名 + String get name { + switch (this) { + case Operation.create: + return '创建'; + case Operation.update: + return '修改'; + case Operation.delete: + return '删除'; + } + } +} diff --git a/lib/data/models/problem_model.dart b/lib/data/models/problem_model.dart index c9a074e..55cad6c 100644 --- a/lib/data/models/problem_model.dart +++ b/lib/data/models/problem_model.dart @@ -1,32 +1,59 @@ // problem_model.dart + import 'dart:convert'; -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/operation.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/image_metadata_model.dart'; +/// 问题的数据模型。 +/// 用于表示系统中的一个具体问题,包含了问题的描述、位置、图片等信息。 class Problem { - String? id; - String description; - String location; - List imageUrls; - DateTime creationTime; - SyncStatus syncStatus; - String? censorTaskId; - String? bindData; - bool isChecked; + /// 问题的唯一标识符,可空。 + final String? id; + + /// 问题的详细描述。 + final String description; + + /// 问题发生的位置。 + final String location; + + /// 问题的图片元数据列表。 + final List imageUrls; + + /// 问题创建的时间。 + final DateTime creationTime; + + /// 问题的同步状态,默认为未同步。 + final SyncStatus syncStatus; + + /// 对问题的操作类型,默认为创建。 + final Operation operation; + + /// 相关的审查任务ID,可空。 + final String? censorTaskId; + + /// 绑定的附加数据,可空。 + final String? bindData; + + /// 问题是否已被检查,默认为false。 + final bool isChecked; - Problem({ + /// Problem 类的构造函数。 + const Problem({ this.id, required this.description, required this.location, required this.imageUrls, required this.creationTime, this.syncStatus = SyncStatus.notSynced, + this.operation = Operation.create, this.censorTaskId, this.bindData, this.isChecked = false, }); - // copyWith method to create a new instance with updated values + /// 创建一个新实例,并可以选择性地更新某些字段。 + /// 采用不可变设计模式,返回一个新对象而不是修改现有对象。 Problem copyWith({ String? id, String? description, @@ -34,8 +61,10 @@ class Problem { List? imageUrls, DateTime? creationTime, SyncStatus? syncStatus, + Operation? operation, String? censorTaskId, String? bindData, + bool? isChecked, }) { return Problem( id: id ?? this.id, @@ -44,31 +73,37 @@ class Problem { imageUrls: imageUrls ?? this.imageUrls, creationTime: creationTime ?? this.creationTime, syncStatus: syncStatus ?? this.syncStatus, + operation: operation ?? this.operation, censorTaskId: censorTaskId ?? this.censorTaskId, bindData: bindData ?? this.bindData, + isChecked: isChecked ?? this.isChecked, ); } - // toMap method for serializing to a database-friendly Map + /// 将 Problem 实例序列化为 Map,便于存储到数据库或发送到服务器。 Map toMap() { return { 'id': id, 'description': description, 'location': location, + // 使用 jsonEncode 将图片元数据列表转换为 JSON 字符串 'imageUrls': jsonEncode(imageUrls.map((meta) => meta.toMap()).toList()), 'creationTime': creationTime.millisecondsSinceEpoch, 'syncStatus': syncStatus.index, + 'operation': operation.index, // 增加对 operation 字段的序列化 'censorTaskId': censorTaskId, 'bindData': bindData, + 'isChecked': isChecked, // 增加对 isChecked 字段的序列化 }; } - // fromMap factory constructor for deserializing from a Map + /// 从 Map 中反序列化创建一个 Problem 实例。 factory Problem.fromMap(Map map) { return Problem( id: map['id'], description: map['description'], location: map['location'], + // jsonDecode 将 JSON 字符串转换为列表,然后映射为 ImageMetadata 对象 imageUrls: (jsonDecode(map['imageUrls']) as List) .map((item) => ImageMetadata.fromMap(item as Map)) .toList(), @@ -76,8 +111,13 @@ class Problem { map['creationTime'] as int, ), syncStatus: SyncStatus.values[map['syncStatus'] as int], + operation: + map.containsKey('operation') // 检查 operation 字段是否存在 + ? Operation.values[map['operation'] as int] + : Operation.create, // 兼容旧数据 censorTaskId: map['censorTaskId'], bindData: map['bindData'], + isChecked: map['isChecked'] as bool? ?? false, // 增加对 isChecked 字段的反序列化 ); } } diff --git a/lib/data/models/sync_status.dart b/lib/data/models/sync_status.dart new file mode 100644 index 0000000..9aa9451 --- /dev/null +++ b/lib/data/models/sync_status.dart @@ -0,0 +1,21 @@ +enum SyncStatus { + /// 已同步,本地无修改 + synced, + + /// 未同步到服务器 + notSynced, +} + +// 添加更多扩展方法 +extension SyncStatusExtension on SyncStatus { + String get displayName { + switch (this) { + case SyncStatus.synced: + return '已上传'; + case SyncStatus.notSynced: + return '未上传'; + } + } + + bool get isSynced => this == SyncStatus.synced; +} diff --git a/lib/data/providers/sqlite_provider.dart b/lib/data/providers/sqlite_provider.dart index ec81f0d..c79c3e7 100644 --- a/lib/data/providers/sqlite_provider.dart +++ b/lib/data/providers/sqlite_provider.dart @@ -1,6 +1,7 @@ // sqlite_provider.dart + import 'package:get/get.dart'; -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; @@ -24,13 +25,19 @@ class SQLiteProvider extends GetxService { /// 异步初始化数据库连接。如果数据库不存在,则会创建它。 Future _initDatabase() async { - final databasePath = await getDatabasesPath(); - final path = join(databasePath, _dbName); - - _database = await openDatabase(path, version: 1, onCreate: _onCreate); + try { + final databasePath = await getDatabasesPath(); + final path = join(databasePath, _dbName); + + _database = await openDatabase(path, version: 1, onCreate: _onCreate); + } catch (e) { + // 在这里添加日志记录,例如 Get.log('数据库初始化失败:$e'); + rethrow; + } } /// 数据库创建时的回调函数,用于定义表结构。 + /// **注意**:这里新增了 `operation` 和 `isChecked` 两个字段。 Future _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE $_tableName( @@ -40,102 +47,146 @@ class SQLiteProvider extends GetxService { imageUrls TEXT NOT NULL, creationTime INTEGER NOT NULL, syncStatus INTEGER NOT NULL, + operation INTEGER NOT NULL, -- 新增:问题操作类型 censorTaskId TEXT, - bindData TEXT + bindData TEXT, + isChecked INTEGER NOT NULL -- 新增:问题是否已检查(SQLite 用 INTEGER 表示布尔值) ) '''); } - /// --- + // --- /// **数据操作 (CRUD) 方法** /// 向数据库中插入一个新问题。 /// 如果 `problem` 没有 `id`,会自动生成一个唯一的 UUID。 + /// + /// 返回:`Future`,表示插入的行ID。如果失败,会返回 0。 Future insertProblem(Problem problem) async { - final problemToInsert = problem.copyWith( - id: problem.id ?? const Uuid().v4(), - ); - return await _database.insert( - _tableName, - problemToInsert.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); + try { + final problemToInsert = problem.copyWith( + id: problem.id ?? const Uuid().v4(), + ); + return await _database.insert( + _tableName, + problemToInsert.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } catch (e) { + // 可以添加日志记录 + return 0; // 返回 0 表示插入失败 + } } /// 根据 ID 从数据库中删除一个问题。 - /// 返回被删除的行数。 + /// + /// 参数:`id` - 要删除的问题ID。 + /// 返回:`Future`,表示被删除的行数。 Future deleteProblem(String id) async { - return await _database.delete(_tableName, where: 'id = ?', whereArgs: [id]); + try { + return await _database.delete( + _tableName, + where: 'id = ?', + whereArgs: [id], + ); + } catch (e) { + // 可以添加日志记录 + return 0; + } } /// 更新数据库中已存在的问题。 - /// 返回被更新的行数。 + /// + /// 参数:`problem` - 包含更新数据的 `Problem` 对象。 + /// 返回:`Future`,表示被更新的行数。 Future updateProblem(Problem problem) async { - return await _database.update( - _tableName, - problem.toMap(), - where: 'id = ?', - whereArgs: [problem.id], - ); + try { + return await _database.update( + _tableName, + problem.toMap(), + where: 'id = ?', + whereArgs: [problem.id], + ); + } catch (e) { + // 可以添加日志记录 + return 0; + } } /// 根据 ID 获取单个问题。 - /// 如果找到则返回 `Problem` 对象,否则返回 `null`。 + /// + /// 参数:`id` - 要获取的问题ID。 + /// 返回:`Future`,如果找到则返回 `Problem` 对象,否则返回 `null`。 Future getProblemById(String id) async { - final List> maps = await _database.query( - _tableName, - where: 'id = ?', - whereArgs: [id], - limit: 1, - ); - - if (maps.isNotEmpty) { - return Problem.fromMap(maps.first); + try { + final List> maps = await _database.query( + _tableName, + where: 'id = ?', + whereArgs: [id], + limit: 1, + ); + if (maps.isNotEmpty) { + return Problem.fromMap(maps.first); + } + return null; + } catch (e) { + // 可以添加日志记录 + return null; } - return null; } /// 获取所有问题,支持按筛选条件查询。 - /// 可选参数用于筛选创建时间范围和同步状态。 + /// + /// 可选参数: + /// - `startDate`: 创建时间范围的开始时间。 + /// - `endDate`: 创建时间范围的结束时间。 + /// - `syncStatus`: 同步状态。 + /// + /// 返回:`Future>`,返回符合条件的问题列表。如果查询失败,返回空列表。 Future> getProblems({ DateTime? startDate, DateTime? endDate, SyncStatus? syncStatus, }) async { - final List whereClauses = []; - final List whereArgs = []; - - if (startDate != null) { - whereClauses.add('creationTime >= ?'); - whereArgs.add(startDate.millisecondsSinceEpoch); + try { + final List whereClauses = []; + final List whereArgs = []; + + if (startDate != null) { + whereClauses.add('creationTime >= ?'); + whereArgs.add(startDate.millisecondsSinceEpoch); + } + + if (endDate != null) { + whereClauses.add('creationTime <= ?'); + whereArgs.add(endDate.millisecondsSinceEpoch); + } + + if (syncStatus != null) { + whereClauses.add('syncStatus = ?'); + whereArgs.add(syncStatus.index); + } + + final String? whereString = whereClauses.isNotEmpty + ? whereClauses.join(' AND ') + : null; + + final List> maps = await _database.query( + _tableName, + where: whereString, + whereArgs: whereArgs.isEmpty ? null : whereArgs, + orderBy: 'creationTime DESC', + ); + + return maps.map((json) => Problem.fromMap(json)).toList(); + } catch (e) { + // 可以添加日志记录 + return []; } - - if (endDate != null) { - whereClauses.add('creationTime <= ?'); - whereArgs.add(endDate.millisecondsSinceEpoch); - } - - if (syncStatus != null) { - whereClauses.add('syncStatus = ?'); - whereArgs.add(syncStatus.index); - } - - final String? whereString = whereClauses.isNotEmpty - ? whereClauses.join(' AND ') - : null; - - final List> maps = await _database.query( - _tableName, - where: whereString, - whereArgs: whereArgs.isEmpty ? null : whereArgs, - orderBy: 'creationTime DESC', - ); - - return maps.map((json) => Problem.fromMap(json)).toList(); } - /// --- + // --- /// `GetxService` 生命周期方法,在服务被销毁前调用, /// 用于关闭数据库连接,防止资源泄漏。 diff --git a/lib/data/repositories/problem_repository.dart b/lib/data/repositories/problem_repository.dart index be92bee..83e956c 100644 --- a/lib/data/repositories/problem_repository.dart +++ b/lib/data/repositories/problem_repository.dart @@ -2,7 +2,8 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:get/get.dart' hide MultipartFile, FormData; -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/image_status.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/image_metadata_model.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/modules/problem/controllers/problem_controller.dart index 781f52c..20cc9bb 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/modules/problem/controllers/problem_controller.dart @@ -80,17 +80,29 @@ class ProblemController extends GetxController } // #region 问题上传 - /// 当单个问题的选中状态改变时调用 - void onProblemCheckedChange() {} - /// 选择全部问题 + // 在你的 Controller 类中添加一个新方法 + void updateProblemCheckedStatus(Rx problem, bool isChecked) { + // 1. 使用 copyWith 创建一个包含新状态的新 Problem 对象 + final updatedProblem = problem.value.copyWith(isChecked: isChecked); + + // 2. 更新 Rx 的值,这会触发 UI 更新 + problem.value = updatedProblem; + } + void selectAll() { final bool newState = !allSelected.value; - for (var problem in unUploadedProblems) { - problem.isChecked = newState; - } + + // 使用 .map() 创建一个包含新副本的列表 + final updatedProblems = unUploadedProblems.map((problem) { + return problem.copyWith(isChecked: newState); + }).toList(); + + // 使用 assignAll 替换整个列表,并触发一次更新 + unUploadedProblems.assignAll(updatedProblems); + + // 更新全选状态 allSelected.value = newState; - // _updateSelectedList(); } // 启动上传流程 diff --git a/lib/modules/problem/controllers/problem_form_controller.dart b/lib/modules/problem/controllers/problem_form_controller.dart index 528374e..a6a61ed 100644 --- a/lib/modules/problem/controllers/problem_form_controller.dart +++ b/lib/modules/problem/controllers/problem_form_controller.dart @@ -4,7 +4,8 @@ import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/image_status.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/image_metadata_model.dart'; import 'dart:io'; import 'package:problem_check_system/data/models/problem_model.dart'; diff --git a/lib/modules/problem/views/widgets/problem_card.dart b/lib/modules/problem/views/widgets/problem_card.dart index 45ff053..3d800c7 100644 --- a/lib/modules/problem/views/widgets/problem_card.dart +++ b/lib/modules/problem/views/widgets/problem_card.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; -import 'package:problem_check_system/data/models/enum_model.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/modules/problem/views/widgets/custom_button.dart'; @@ -135,8 +135,8 @@ class ProblemCard extends GetView { value: problem.value.isChecked, // 当 Checkbox 状态改变时,调用 controller 中的方法来更新状态 onChanged: (bool? value) { - problem.value.isChecked = value ?? false; - controller.onProblemCheckedChange(); + // 调用 Controller 中的方法来处理状态更新 + controller.updateProblemCheckedStatus(problem, value ?? false); }, ), ),