|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
// modules/problem/controllers/problem_controller.dart |
|
|
|
|
import 'dart:developer'; |
|
|
|
|
import 'dart:io'; |
|
|
|
|
|
|
|
|
|
import 'package:dio/dio.dart'; |
|
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
|
|
@ -7,8 +8,9 @@ import 'package:get/get.dart' hide MultipartFile, FormData;
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
|
import 'package:problem_check_system/app/routes/app_routes.dart'; |
|
|
|
|
import 'package:problem_check_system/data/repositories/problem_repository.dart'; |
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/custom_data_range_dropdown.dart'; |
|
|
|
|
import 'package:problem_check_system/data/models/problem_model.dart'; |
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/models/date_range_enum.dart'; |
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; |
|
|
|
|
|
|
|
|
|
class ProblemController extends GetxController |
|
|
|
|
with GetSingleTickerProviderStateMixin { |
|
|
|
@ -28,15 +30,30 @@ class ProblemController extends GetxController
|
|
|
|
|
// Dio 的取消令牌,用于取消正在进行的请求 |
|
|
|
|
late CancelToken _cancelToken; |
|
|
|
|
|
|
|
|
|
int get selectedCount { |
|
|
|
|
return unUploadedProblems.where((problem) => problem.isChecked).length; |
|
|
|
|
} |
|
|
|
|
final RxSet<Problem> _selectedProblems = <Problem>{}.obs; |
|
|
|
|
|
|
|
|
|
Set<Problem> get selectedProblems => _selectedProblems; |
|
|
|
|
|
|
|
|
|
int get selectedCount => _selectedProblems.length; |
|
|
|
|
|
|
|
|
|
List<Problem> get selectedProblems { |
|
|
|
|
return unUploadedProblems.where((problem) => problem.isChecked).toList(); |
|
|
|
|
// 在 ProblemController 中添加 |
|
|
|
|
// 添加日期范围选项列表 |
|
|
|
|
List<DropdownOption> get dateRangeOptions { |
|
|
|
|
return DateRange.values.map((range) => range.toDropdownOption()).toList(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 问题列表筛选条件 |
|
|
|
|
final List<DropdownOption> uploadOptions = const [ |
|
|
|
|
DropdownOption(label: '全部', value: '全部', icon: Icons.all_inclusive), |
|
|
|
|
DropdownOption(label: '已上传', value: '已上传', icon: Icons.cloud_done), |
|
|
|
|
DropdownOption(label: '未上传', value: '未上传', icon: Icons.cloud_off), |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
final List<DropdownOption> bindOptions = const [ |
|
|
|
|
DropdownOption(label: '全部', value: '全部', icon: Icons.all_inclusive), |
|
|
|
|
DropdownOption(label: '已绑定', value: '已绑定', icon: Icons.link), |
|
|
|
|
DropdownOption(label: '未绑定', value: '未绑定', icon: Icons.link_off), |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
final Rx<DateRange> currentDateRange = DateRange.oneWeek.obs; |
|
|
|
|
final RxString currentUploadFilter = '全部'.obs; |
|
|
|
|
final RxString currentBindFilter = '全部'.obs; |
|
|
|
@ -66,6 +83,7 @@ class ProblemController extends GetxController
|
|
|
|
|
@override |
|
|
|
|
void onInit() { |
|
|
|
|
super.onInit(); |
|
|
|
|
|
|
|
|
|
tabController = TabController(length: 2, vsync: this); |
|
|
|
|
tabController.addListener(_onTabChanged); |
|
|
|
|
loadProblems(); |
|
|
|
@ -81,33 +99,36 @@ class ProblemController extends GetxController
|
|
|
|
|
|
|
|
|
|
// #region 问题上传 |
|
|
|
|
|
|
|
|
|
// 在你的 Controller 类中添加一个新方法 |
|
|
|
|
void updateProblemCheckedStatus(Rx<Problem> problem, bool isChecked) { |
|
|
|
|
// 1. 使用 copyWith 创建一个包含新状态的新 Problem 对象 |
|
|
|
|
final updatedProblem = problem.value.copyWith(isChecked: isChecked); |
|
|
|
|
|
|
|
|
|
// 2. 更新 Rx<Problem> 的值,这会触发 UI 更新 |
|
|
|
|
problem.value = updatedProblem; |
|
|
|
|
void updateProblemSelection(Problem problem, bool isChecked) { |
|
|
|
|
if (isChecked) { |
|
|
|
|
_selectedProblems.add(problem); |
|
|
|
|
} else { |
|
|
|
|
_selectedProblems.remove(problem); |
|
|
|
|
} |
|
|
|
|
// 更新全选状态 |
|
|
|
|
allSelected.value = _selectedProblems.length == unUploadedProblems.length; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void selectAll() { |
|
|
|
|
final bool newState = !allSelected.value; |
|
|
|
|
|
|
|
|
|
// 使用 .map() 创建一个包含新副本的列表 |
|
|
|
|
final updatedProblems = unUploadedProblems.map((problem) { |
|
|
|
|
return problem.copyWith(isChecked: newState); |
|
|
|
|
}).toList(); |
|
|
|
|
|
|
|
|
|
// 使用 assignAll 替换整个列表,并触发一次更新 |
|
|
|
|
unUploadedProblems.assignAll(updatedProblems); |
|
|
|
|
if (allSelected.value) { |
|
|
|
|
// 如果已经是全选,则取消全选 |
|
|
|
|
_selectedProblems.clear(); |
|
|
|
|
} else { |
|
|
|
|
// 如果是取消全选,则选择所有 |
|
|
|
|
_selectedProblems.addAll(unUploadedProblems); |
|
|
|
|
} |
|
|
|
|
allSelected.value = !allSelected.value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 更新全选状态 |
|
|
|
|
allSelected.value = newState; |
|
|
|
|
// 上传完成后清空选中状态 |
|
|
|
|
void clearSelection() { |
|
|
|
|
_selectedProblems.clear(); |
|
|
|
|
allSelected.value = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 启动上传流程 |
|
|
|
|
// 在 handleUpload 方法中,上传完成后调用 clearSelection |
|
|
|
|
Future<void> handleUpload() async { |
|
|
|
|
if (selectedProblems.isEmpty) { |
|
|
|
|
if (_selectedProblems.isEmpty) { |
|
|
|
|
Get.snackbar('提示', '请选择要上传的问题'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
@ -115,13 +136,11 @@ class ProblemController extends GetxController
|
|
|
|
|
uploadProgress.value = 0.0; |
|
|
|
|
_cancelToken = CancelToken(); |
|
|
|
|
|
|
|
|
|
// 显示上传对话框 |
|
|
|
|
showUploadProgressDialog(); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
// 直接将问题列表传递给 Repository |
|
|
|
|
await problemRepository.uploadProblems( |
|
|
|
|
selectedProblems, |
|
|
|
|
_selectedProblems.toList(), // 转换为列表 |
|
|
|
|
cancelToken: _cancelToken, |
|
|
|
|
onProgress: (progress) { |
|
|
|
|
uploadProgress.value = progress; |
|
|
|
@ -130,6 +149,11 @@ class ProblemController extends GetxController
|
|
|
|
|
|
|
|
|
|
Get.back(); |
|
|
|
|
Get.snackbar('成功', '所有问题已成功上传!', snackPosition: SnackPosition.BOTTOM); |
|
|
|
|
|
|
|
|
|
// 上传成功后清空选中状态 |
|
|
|
|
clearSelection(); |
|
|
|
|
// 重新加载未上传的问题列表 |
|
|
|
|
loadUnUploadedProblems(); |
|
|
|
|
} on DioException catch (e) { |
|
|
|
|
Get.back(); |
|
|
|
|
if (CancelToken.isCancel(e)) { |
|
|
|
@ -259,7 +283,27 @@ class ProblemController extends GetxController
|
|
|
|
|
loadProblems(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// #endregion |
|
|
|
|
// 更新日期范围的方法 |
|
|
|
|
void updateDateRange(String rangeValue) { |
|
|
|
|
final newRange = rangeValue.toDateRange(); |
|
|
|
|
if (newRange != null) { |
|
|
|
|
currentDateRange.value = newRange; |
|
|
|
|
loadProblems(); // 重新加载数据 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 添加筛选方法 |
|
|
|
|
void updateUploadFilter(String value) { |
|
|
|
|
currentUploadFilter.value = value; |
|
|
|
|
loadProblems(); // 重新加载数据 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void updateBindFilter(String value) { |
|
|
|
|
currentBindFilter.value = value; |
|
|
|
|
loadProblems(); // 重新加载数据 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 加载 |
|
|
|
|
Future<void> loadProblems() async { |
|
|
|
@ -305,58 +349,12 @@ class ProblemController extends GetxController
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// |
|
|
|
|
void updateCurrentFilters({ |
|
|
|
|
DateRange? newDateRange, |
|
|
|
|
String? newUploadStatus, |
|
|
|
|
String? newBindingStatus, |
|
|
|
|
}) { |
|
|
|
|
if (newDateRange != null && currentDateRange.value != newDateRange) { |
|
|
|
|
currentDateRange.value = newDateRange; |
|
|
|
|
} |
|
|
|
|
if (newUploadStatus != null && |
|
|
|
|
currentUploadFilter.value != newUploadStatus) { |
|
|
|
|
currentUploadFilter.value = newUploadStatus; |
|
|
|
|
} |
|
|
|
|
if (newBindingStatus != null && |
|
|
|
|
currentBindFilter.value != newBindingStatus) { |
|
|
|
|
currentBindFilter.value = newBindingStatus; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 只要调用此方法,就重新加载数据 |
|
|
|
|
loadProblems(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void updateHistoryFilters({ |
|
|
|
|
DateTime? newStartTime, |
|
|
|
|
DateTime? newEndTime, |
|
|
|
|
String? newUploadStatus, |
|
|
|
|
String? newBindingStatus, |
|
|
|
|
}) { |
|
|
|
|
if (newStartTime != null && historyStartTime.value != newStartTime) { |
|
|
|
|
historyStartTime.value = newStartTime; |
|
|
|
|
} |
|
|
|
|
if (newUploadStatus != null && |
|
|
|
|
historyUploadFilter.value != newUploadStatus) { |
|
|
|
|
historyUploadFilter.value = newUploadStatus; |
|
|
|
|
} |
|
|
|
|
if (newBindingStatus != null && |
|
|
|
|
historyBindFilter.value != newBindingStatus) { |
|
|
|
|
historyBindFilter.value = newBindingStatus; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 只要调用此方法,就重新加载数据 |
|
|
|
|
loadProblems(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 新增方法:查询所有未上传的问题 |
|
|
|
|
Future<void> loadUnUploadedProblems() async { |
|
|
|
|
isLoading.value = true; |
|
|
|
|
try { |
|
|
|
|
// 调用 _localDatabase.getProblems 并只筛选 '未上传' 的问题 |
|
|
|
|
unUploadedProblems.value = await problemRepository.getProblems( |
|
|
|
|
uploadStatus: '未上传', |
|
|
|
|
); |
|
|
|
|
unUploadedProblems.value = await problemRepository.getProblemsForSync(); |
|
|
|
|
} catch (e) { |
|
|
|
|
Get.snackbar('错误', '加载未上传问题失败: $e'); |
|
|
|
|
} finally { |
|
|
|
@ -364,25 +362,25 @@ class ProblemController extends GetxController
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Future<void> addProblem(Problem problem) async { |
|
|
|
|
try { |
|
|
|
|
await problemRepository.insertProblem(problem); |
|
|
|
|
loadProblems(); |
|
|
|
|
} catch (e) { |
|
|
|
|
Get.snackbar('错误', '保存问题失败: $e'); |
|
|
|
|
rethrow; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Future<void> addProblem(Problem problem) async { |
|
|
|
|
// try { |
|
|
|
|
// await problemRepository.insertProblem(problem); |
|
|
|
|
// loadProblems(); |
|
|
|
|
// } catch (e) { |
|
|
|
|
// Get.snackbar('错误', '保存问题失败: $e'); |
|
|
|
|
// rethrow; |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
Future<void> updateProblem(Problem problem) async { |
|
|
|
|
try { |
|
|
|
|
await problemRepository.updateProblem(problem); |
|
|
|
|
loadProblems(); |
|
|
|
|
} catch (e) { |
|
|
|
|
Get.snackbar('错误', '更新问题失败: $e'); |
|
|
|
|
rethrow; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Future<void> updateProblem(Problem problem) async { |
|
|
|
|
// try { |
|
|
|
|
// await problemRepository.updateProblem(problem); |
|
|
|
|
// loadProblems(); |
|
|
|
|
// } catch (e) { |
|
|
|
|
// Get.snackbar('错误', '更新问题失败: $e'); |
|
|
|
|
// rethrow; |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
Future<void> deleteProblem(Problem problem) async { |
|
|
|
|
try { |
|
|
|
@ -397,17 +395,18 @@ class ProblemController extends GetxController
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 删除本地文件 |
|
|
|
|
Future<void> _deleteProblemImages(Problem problem) async { |
|
|
|
|
// for (var imagePath in problem.imageUrls) { |
|
|
|
|
// try { |
|
|
|
|
// final file = File(imagePath); |
|
|
|
|
// if (await file.exists()) { |
|
|
|
|
// await file.delete(); |
|
|
|
|
// } |
|
|
|
|
// } catch (e) { |
|
|
|
|
// throw Exception(e); |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
for (var imagePath in problem.imageUrls) { |
|
|
|
|
try { |
|
|
|
|
final file = File(imagePath.localPath); |
|
|
|
|
if (await file.exists()) { |
|
|
|
|
await file.delete(); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
throw Exception(e); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 显示日期选择器 |
|
|
|
|