diff --git a/lib/data/providers/local_database.dart b/lib/data/providers/local_database.dart index cfa407f..f06700a 100644 --- a/lib/data/providers/local_database.dart +++ b/lib/data/providers/local_database.dart @@ -58,36 +58,51 @@ class LocalDatabase { return await db.delete(_tableName, where: 'id = ?', whereArgs: [id]); } - /// 通用查询方法,根据时间范围、上传状态和绑定状态(censorTaskId)筛选问题。 + /// 通用查询方法 + /// 根据可选的参数筛选问题。 + /// 时间范围、 + /// 上传状态 + /// 绑定状态 Future> getProblems({ - required DateTime startDate, - required DateTime endDate, - required String uploadStatus, // '已上传', '未上传', '全部' - required String bindStatus, // '已绑定', '未绑定', '全部' + DateTime? startDate, + DateTime? endDate, + String? uploadStatus, // '已上传', '未上传', '全部' + String? bindStatus, // '已绑定', '未绑定', '全部' }) async { final db = await database; - final int startTimestamp = startDate.millisecondsSinceEpoch; - final int endTimestamp = endDate.millisecondsSinceEpoch; - final List whereClauses = ['createdAt >= ?', 'createdAt <= ?']; - final List whereArgs = [startTimestamp, endTimestamp]; + // 使用可变列表来构建筛选条件 + final List whereClauses = []; + final List whereArgs = []; - // 根据上传状态添加筛选条件 - if (uploadStatus == '已上传') { - whereClauses.add('isUploaded = ?'); - whereArgs.add(1); - } else if (uploadStatus == '未上传') { + // 根据 startDate 添加筛选条件 + if (startDate != null) { + whereClauses.add('createdAt >= ?'); + whereArgs.add(startDate.millisecondsSinceEpoch); + } + + // 根据 endDate 添加筛选条件 + if (endDate != null) { + whereClauses.add('createdAt <= ?'); + whereArgs.add(endDate.millisecondsSinceEpoch); + } + + // 根据 uploadStatus 添加筛选条件 + if (uploadStatus != null && uploadStatus != '全部') { whereClauses.add('isUploaded = ?'); - whereArgs.add(0); + whereArgs.add(uploadStatus == '已上传' ? 1 : 0); } - // 根据 censorTaskId 的值判断绑定状态 - if (bindStatus == '已绑定') { - whereClauses.add('censorTaskId IS NOT NULL'); - } else if (bindStatus == '未绑定') { - whereClauses.add('censorTaskId IS NULL'); + // 根据 bindStatus 添加筛选条件 + if (bindStatus != null && bindStatus != '全部') { + if (bindStatus == '已绑定') { + whereClauses.add('censorTaskId IS NOT NULL'); + } else { + whereClauses.add('censorTaskId IS NULL'); + } } + // 将所有条件用 ' AND ' 连接起来 final String whereString = whereClauses.join(' AND '); final List> maps = await db.query( diff --git a/lib/modules/problem/bindings/problem_binding.dart b/lib/modules/problem/bindings/problem_binding.dart index afbf807..4fc8e2e 100644 --- a/lib/modules/problem/bindings/problem_binding.dart +++ b/lib/modules/problem/bindings/problem_binding.dart @@ -3,7 +3,6 @@ import 'package:dio/dio.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_upload_controller.dart'; class ProblemBinding implements Bindings { @override @@ -18,6 +17,5 @@ class ProblemBinding implements Bindings { connectivityProvider: Get.find(), ), ); - Get.lazyPut(() => ProblemUploadController()); } } diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/modules/problem/controllers/problem_controller.dart index e2cce29..b9d2499 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/modules/problem/controllers/problem_controller.dart @@ -12,20 +12,42 @@ import 'package:problem_check_system/data/providers/connectivity_provider.dart'; class ProblemController extends GetxController with GetSingleTickerProviderStateMixin { + /// 本地数据库 final LocalDatabase _localDatabase; + + /// 最近问题列表 final RxList problems = [].obs; + + /// 历史问题列表 final RxList historyProblems = [].obs; + /// 未上传的问题列表 + final RxList unUploadedProblems = [].obs; + final RxList selectedUnUploadedProblems = [].obs; + final Rx allSelected = false.obs; + final RxBool isUploadEnabled = false.obs; + + /// 筛选条件 final Rx selectedDateRange = DateRange.oneWeek.obs; final RxString selectedUploadStatus = '全部'.obs; final RxString selectedBindingStatus = '全部'.obs; + /// 是否加载中 final RxBool isLoading = false.obs; final Dio _dio; final ConnectivityProvider _connectivityProvider; late TabController tabController; + /// floatingButton 拖动 + final double _fabSize = 56.0; + final double _edgePaddingX = 27.0.w; + final double _edgePaddingY = 111.0.h; + final fabUploadPosition = Offset(337.0, 703.7).obs; + + /// get 选中的 + RxBool get isOnline => _connectivityProvider.isOnline; + ProblemController({ required LocalDatabase localDatabase, required Dio dio, @@ -34,23 +56,66 @@ class ProblemController extends GetxController _dio = dio, _connectivityProvider = connectivityProvider; - RxBool get isOnline => _connectivityProvider.isOnline; + @override + void onInit() { + super.onInit(); + tabController = TabController(length: 2, vsync: this); + tabController.addListener(_onTabChanged); + // 监听 unUploadedProblems 列表的变化,并在变化时更新 selectedProblems + ever(unUploadedProblems, (_) => _updateSelectedList()); + // 监听 selectedProblems 列表,更新上传按钮状态 + ever(selectedUnUploadedProblems, (_) => _updateUploadButtonState()); + loadProblems(); + // 查询未上传问题 + loadUnUploadedProblems(); + } + + @override + void onClose() { + tabController.dispose(); + super.onClose(); + } - List get selectedProblems { - return historyProblems.where((p) => p.isChecked.value).toList(); + /// 当单个问题的选中状态改变时调用 + void onProblemCheckedChange() { + // 重新计算 selectedUnUploadedProblems 列表 + _updateSelectedList(); + // 因为 _updateSelectedList 已经会触发 selectedUnUploadedProblems 的变化, + // 所以 _updateUploadButtonState() 会被 ever 监听器自动调用。 } - List get unuploadedProblems { - return problems.where((p) => !p.isUploaded).toList(); + /// 上传问题逻辑 + void _updateSelectedList() { + selectedUnUploadedProblems.clear(); + for (var problem in unUploadedProblems) { + if (problem.isChecked.value) { + selectedUnUploadedProblems.add(problem); + } + } } - // 常量:FAB的尺寸和贴靠间距 - final double _fabSize = 56.0; // FloatingActionButton的默认尺寸 - final double _edgePaddingX = 27.0.w; // 边缘间距 - final double _edgePaddingY = 111.0.h; // 边缘间距 - // 可拖动按钮的位置,使用 Rx - final fabUploadPosition = Offset(337.0, 703.7).obs; + void _updateUploadButtonState() { + isUploadEnabled.value = selectedUnUploadedProblems.isNotEmpty; + } + + void selectAll() { + final bool newState = !allSelected.value; + for (var problem in unUploadedProblems) { + problem.isChecked.value = newState; + } + allSelected.value = newState; + _updateSelectedList(); + } + void uploadProblems() { + if (selectedUnUploadedProblems.isEmpty) return; + // 实际的上传逻辑,例如调用 API + print('开始上传 ${selectedUnUploadedProblems.length} 个问题...'); + // 上传完成后,清空列表或更新状态 + selectedUnUploadedProblems.clear(); + } + + /// floatingButton更新位置 void updateFabUploadPosition(Offset delta) { final screenWidth = ScreenUtil().screenWidth; final screenHeight = ScreenUtil().screenHeight; @@ -72,6 +137,7 @@ class ProblemController extends GetxController fabUploadPosition.value = Offset(clampedDx, clampedDy); } + /// floatingButton 贴靠 void snapToEdge() { final screenWidth = ScreenUtil().screenWidth; @@ -91,23 +157,6 @@ class ProblemController extends GetxController // 关键:只更新水平位置,垂直位置保持不变 fabUploadPosition.value = Offset(newDx, fabUploadPosition.value.dy); - - print(fabUploadPosition.value); - } - - @override - void onInit() { - super.onInit(); - tabController = TabController(length: 2, vsync: this); - tabController.addListener(_onTabChanged); - - loadProblems(); - } - - @override - void onClose() { - tabController.dispose(); - super.onClose(); } void _onTabChanged() { @@ -119,30 +168,39 @@ class ProblemController extends GetxController } } - void loadProblems() async { + Future loadProblems() async { isLoading.value = true; try { - if (tabController.index == 0) { - // "问题列表" Tab: 使用日期范围和筛选条件 - final startDate = selectedDateRange.value.startDate; - final endDate = DateTime.now(); - - final problems = await _localDatabase.getProblems( - startDate: startDate, - endDate: endDate, - uploadStatus: selectedUploadStatus.value, - bindStatus: selectedBindingStatus.value, - ); - this.problems.assignAll(problems); + // 根据 Tab 索引设置查询参数的默认值 + final bool isProblemListTab = tabController.index == 0; + + final DateTime startDate = isProblemListTab + ? selectedDateRange.value.startDate + : DateTime(2000); // 历史列表从很早的日期开始 + + final DateTime endDate = DateTime.now(); + + final String uploadStatus = isProblemListTab + ? selectedUploadStatus.value + : '全部'; + + final String bindStatus = isProblemListTab + ? selectedBindingStatus.value + : '全部'; + + // 只执行一次数据库查询 + final loadedProblems = await _localDatabase.getProblems( + startDate: startDate, + endDate: endDate, + uploadStatus: uploadStatus, + bindStatus: bindStatus, + ); + + // 根据 Tab 索引将数据分配给正确的列表 + if (isProblemListTab) { + problems.assignAll(loadedProblems); } else { - // "历史问题列表" Tab: 查询所有问题 - final allProblems = await _localDatabase.getProblems( - startDate: DateTime(2000), - endDate: DateTime.now(), - uploadStatus: '全部', - bindStatus: '全部', - ); - this.historyProblems.assignAll(allProblems); + historyProblems.assignAll(loadedProblems); } } catch (e) { Get.snackbar('错误', '加载问题失败: $e'); @@ -151,7 +209,7 @@ class ProblemController extends GetxController } } - /// 通用方法,用于更新筛选条件并重新加载问题列表。 + /// 筛选问题 void updateFiltersAndLoadProblems({ DateRange? newDateRange, String? newUploadStatus, @@ -167,11 +225,22 @@ class ProblemController extends GetxController selectedBindingStatus.value = newBindingStatus; } - // 只有在参数被传递时才执行 loadProblems - if (newDateRange != null || - newUploadStatus != null || - newBindingStatus != null) { - loadProblems(); + // 只要调用此方法,就重新加载数据 + loadProblems(); + } + + // 新增方法:查询所有未上传的问题 + Future loadUnUploadedProblems() async { + isLoading.value = true; + try { + // 调用 _localDatabase.getProblems 并只筛选 '未上传' 的问题 + unUploadedProblems.value = await _localDatabase.getProblems( + uploadStatus: '未上传', + ); + } catch (e) { + Get.snackbar('错误', '加载未上传问题失败: $e'); + } finally { + isLoading.value = false; } } @@ -209,7 +278,7 @@ class ProblemController extends GetxController } Future deleteSelectedProblems() async { - final problemsToDelete = selectedProblems; + final problemsToDelete = selectedUnUploadedProblems; if (problemsToDelete.isEmpty) { Get.snackbar('提示', '请至少选择一个问题进行删除'); return; @@ -301,7 +370,7 @@ class ProblemController extends GetxController return; } - final unuploaded = unuploadedProblems; + final unuploaded = unUploadedProblems; if (unuploaded.isEmpty) { Get.snackbar('提示', '没有需要上传的问题'); return; diff --git a/lib/modules/problem/controllers/problem_upload_controller.dart b/lib/modules/problem/controllers/problem_upload_controller.dart deleted file mode 100644 index a673c8b..0000000 --- a/lib/modules/problem/controllers/problem_upload_controller.dart +++ /dev/null @@ -1,66 +0,0 @@ -// problem_upload_controller.dart -import 'package:get/get.dart'; -import 'package:problem_check_system/data/models/problem_model.dart'; -// import 'package:problem_check_system/services/problem_service.dart'; - -class ProblemUploadController extends GetxController { - ProblemUploadController(); - - // 假设从数据库或其他来源获取问题列表 - final RxList problems = [].obs; - - // 用于存储用户选中的问题 - final RxList selectedProblems = [].obs; - - @override - void onInit() { - super.onInit(); - // 监听 problems 列表的变化,并在变化时更新 selectedProblems - ever(problems, (_) => _updateSelectedList()); - // 监听 selectedProblems 列表,更新上传按钮状态 - ever(selectedProblems, (_) => _updateUploadButtonState()); - - fetchProblems(); // 假设从数据源获取问题列表 - } - - void fetchProblems() async { - // 模拟从服务中获取问题列表 - // final fetched = await ProblemService.getProblemsForUpload(); - // problems.assignAll(fetched); - } - - // 监听所有问题的 isChecked 状态,更新 selectedProblems 列表 - void _updateSelectedList() { - selectedProblems.clear(); - for (var problem in problems) { - if (problem.isChecked.value) { - selectedProblems.add(problem); - } - } - } - - // 控制上传按钮是否可用 - RxBool isUploadEnabled = false.obs; - void _updateUploadButtonState() { - isUploadEnabled.value = selectedProblems.isNotEmpty; - } - - // 控制全选/取消全选按钮的文案 - RxBool allSelected = false.obs; - - void selectAll() { - final bool newState = !allSelected.value; - for (var problem in problems) { - problem.isChecked.value = newState; - } - allSelected.value = newState; - } - - void uploadProblems() { - if (selectedProblems.isEmpty) return; - // 实际的上传逻辑,例如调用 API - print('开始上传 ${selectedProblems.length} 个问题...'); - // 上传完成后,清空列表或更新状态 - selectedProblems.clear(); - } -} diff --git a/lib/modules/problem/views/problem_upload_page.dart b/lib/modules/problem/views/problem_upload_page.dart index f873f8e..5e72ea5 100644 --- a/lib/modules/problem/views/problem_upload_page.dart +++ b/lib/modules/problem/views/problem_upload_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_upload_controller.dart'; +import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; // todo 使用problem_list_page,填充内容,problem_controller 控制内容 -class ProblemUploadPage extends GetView { +class ProblemUploadPage extends GetView { const ProblemUploadPage({super.key}); @override @@ -21,8 +21,8 @@ class ProblemUploadPage extends GetView { PreferredSizeWidget _buildAppBar() { return AppBar( title: Obx(() { - final selectedCount = controller.selectedProblems.length; - return Text(selectedCount > 0 ? '已选择$selectedCount项' : '问题上传'); + final selectedCount = controller.selectedUnUploadedProblems.length; + return Text('已选择$selectedCount项'); }), centerTitle: true, leading: IconButton(icon: Icon(Icons.close), onPressed: () => Get.back()), @@ -44,9 +44,9 @@ class ProblemUploadPage extends GetView { Widget _buildBody() { return Obx(() { return ListView.builder( - itemCount: controller.problems.length, + itemCount: controller.unUploadedProblems.length, itemBuilder: (context, index) { - final problem = controller.problems[index]; + final problem = controller.unUploadedProblems[index]; return ProblemCard( problem, // 传递视图类型,显示 Checkbox @@ -65,17 +65,19 @@ class ProblemUploadPage extends GetView { color: Colors.white, border: Border(top: BorderSide(color: Colors.grey.shade300)), ), - child: ElevatedButton( - onPressed: controller.isUploadEnabled.value - ? controller.uploadProblems - : null, - child: Text('点击上传'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + child: Obx( + () => ElevatedButton( + onPressed: controller.isUploadEnabled.value + ? controller.uploadProblems + : null, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + ), ), + child: Text('点击上传'), ), ), ); diff --git a/lib/modules/problem/views/widgets/problem_card.dart b/lib/modules/problem/views/widgets/problem_card.dart index e259925..c5368c0 100644 --- a/lib/modules/problem/views/widgets/problem_card.dart +++ b/lib/modules/problem/views/widgets/problem_card.dart @@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:intl/intl.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'; import 'package:problem_check_system/modules/problem/views/problem_form_page.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; @@ -10,7 +11,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; // 定义枚举类型 enum ProblemCardViewType { buttons, checkbox } -class ProblemCard extends StatelessWidget { +class ProblemCard extends GetView { final Problem problem; final ProblemCardViewType viewType; @@ -126,6 +127,7 @@ class ProblemCard extends StatelessWidget { // 当 Checkbox 状态改变时,调用 controller 中的方法来更新状态 onChanged: (bool? value) { problem.isChecked.value = value ?? false; + controller.onProblemCheckedChange(); }, ), ),