You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

358 lines
10 KiB

// modules/problem/controllers/problem_controller.dart
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart' hide MultipartFile, FormData;
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
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';
class ProblemController extends GetxController
with GetSingleTickerProviderStateMixin {
/// 依赖问题数据
final ProblemRepository problemRepository;
/// 最近问题列表
final RxList<Problem> problems = <Problem>[].obs;
/// 历史问题列表
final RxList<Problem> historyProblems = <Problem>[].obs;
/// 未上传的问题列表
final RxList<Problem> unUploadedProblems = <Problem>[].obs;
final Rx<bool> allSelected = false.obs;
final RxDouble uploadProgress = 0.0.obs;
int get selectedCount {
return unUploadedProblems
.where((problem) => problem.isChecked.value)
.length;
}
List<Problem> get selectedUnUploadedProblems {
return unUploadedProblems
.where((problem) => problem.isChecked.value)
.toList();
}
/// 筛选条件
final Rx<DateRange> selectedDateRange = DateRange.oneWeek.obs;
final RxString selectedUploadStatus = '全部'.obs;
final RxString selectedBindingStatus = '全部'.obs;
/// 是否加载中
final RxBool isLoading = false.obs;
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 => problemRepository.isOnline;
ProblemController({required this.problemRepository});
@override
void onInit() {
super.onInit();
tabController = TabController(length: 2, vsync: this);
tabController.addListener(_onTabChanged);
loadProblems();
// 查询未上传问题
loadUnUploadedProblems();
}
@override
void onClose() {
tabController.dispose();
super.onClose();
}
// #region 问题上传
/// 当单个问题的选中状态改变时调用
void onProblemCheckedChange() {}
/// 选择全部问题
void selectAll() {
final bool newState = !allSelected.value;
for (var problem in unUploadedProblems) {
problem.isChecked.value = newState;
}
allSelected.value = newState;
// _updateSelectedList();
}
void uploadProblems() async {
// if (selectedUnUploadedProblems.isEmpty) return;
// // 实际的上传逻辑,例如调用 API
// // 上传完成后,清空列表或更新状态
// selectedUnUploadedProblems.clear();
for (var problem in selectedUnUploadedProblems) {
await uploadProblem(problem);
}
}
Future<bool> uploadProblem(Problem problem) async {
try {
final formData = FormData.fromMap({
'description': problem.description,
'location': problem.location,
'createdAt': problem.createdAt.toIso8601String(),
'boundInfo': problem.bindData ?? '',
});
for (var imageUrl in problem.imageUrls) {
final file = File(imageUrl);
if (await file.exists()) {
formData.files.add(
MapEntry(
'images',
await MultipartFile.fromFile(
imageUrl,
filename: path.basename(imageUrl),
),
),
);
}
}
// final response = await httpProvider.post(
// 'https://your-server.com/api/problems',
// data: formData,
// options: Options(
// sendTimeout: const Duration(seconds: 30),
// receiveTimeout: const Duration(seconds: 30),
// ),
// );
// if (response.statusCode == 200) {
// final updatedProblem = problem.copyWith(isUploaded: true);
// await updateProblem(updatedProblem);
return true;
// } else {
// throw Exception('服务器返回错误状态码: ${response.statusCode}');
// }
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout) {
Get.snackbar('网络超时', '请检查网络连接后重试');
} else {
Get.snackbar('网络错误', '上传问题失败: ${e.message}');
}
return false;
} catch (e) {
Get.snackbar('错误', '上传问题失败: $e');
return false;
}
}
// #endregion
// #region 悬浮按钮
/// floatingButton更新位置
void updateFabUploadPosition(Offset delta) {
final screenWidth = ScreenUtil().screenWidth;
final screenHeight = ScreenUtil().screenHeight;
Offset newPosition = fabUploadPosition.value + delta;
// 限制水平范围:按钮左边缘与屏幕左边缘的距离
double clampedDx = newPosition.dx.clamp(
_edgePaddingX,
screenWidth - _fabSize - _edgePaddingX,
);
// 限制垂直范围:按钮上边缘与屏幕上边缘的距离
double clampedDy = newPosition.dy.clamp(
_edgePaddingY,
screenHeight - _fabSize - _edgePaddingY,
);
fabUploadPosition.value = Offset(clampedDx, clampedDy);
}
/// floatingButton 贴靠
void snapToEdge() {
final screenWidth = ScreenUtil().screenWidth;
// 获取当前按钮的水平中心点
final buttonCenterDx = fabUploadPosition.value.dx + _fabSize / 2;
double newDx;
// 判断按钮中心点位于屏幕的左半部分还是右半部分
if (buttonCenterDx < screenWidth / 2) {
// 贴靠到左侧,按钮左边缘与屏幕左边缘距离为 _edgePaddingX
newDx = _edgePaddingX;
} else {
// 贴靠到右侧,按钮右边缘与屏幕右边缘距离为 _edgePaddingX
newDx = screenWidth - _fabSize - _edgePaddingX;
}
// 关键:只更新水平位置,垂直位置保持不变
fabUploadPosition.value = Offset(newDx, fabUploadPosition.value.dy);
}
// #endregion
// #region ta按钮
void _onTabChanged() {
if (!tabController.indexIsChanging) {
selectedDateRange.value = DateRange.oneWeek;
selectedUploadStatus.value = '全部';
selectedBindingStatus.value = '全部';
loadProblems();
}
}
// #endregion
/// 加载
Future<void> loadProblems() async {
isLoading.value = true;
try {
// 根据 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 problemRepository.getProblems(
startDate: startDate,
endDate: endDate,
uploadStatus: uploadStatus,
bindStatus: bindStatus,
);
// 根据 Tab 索引将数据分配给正确的列表
if (isProblemListTab) {
problems.assignAll(loadedProblems);
} else {
historyProblems.assignAll(loadedProblems);
}
} catch (e) {
Get.snackbar('错误', '加载问题失败: $e');
} finally {
isLoading.value = false;
}
}
/// 筛选问题
void updateFiltersAndLoadProblems({
DateRange? newDateRange,
String? newUploadStatus,
String? newBindingStatus,
}) {
if (newDateRange != null) {
selectedDateRange.value = newDateRange;
}
if (newUploadStatus != null) {
selectedUploadStatus.value = newUploadStatus;
}
if (newBindingStatus != null) {
selectedBindingStatus.value = newBindingStatus;
}
// 只要调用此方法,就重新加载数据
loadProblems();
}
// 新增方法:查询所有未上传的问题
Future<void> loadUnUploadedProblems() async {
isLoading.value = true;
try {
// 调用 _localDatabase.getProblems 并只筛选 '未上传' 的问题
unUploadedProblems.value = await problemRepository.getProblems(
uploadStatus: '未上传',
);
} catch (e) {
Get.snackbar('错误', '加载未上传问题失败: $e');
} finally {
isLoading.value = false;
}
}
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> deleteProblem(Problem problem) async {
try {
if (problem.id != null) {
await problemRepository.deleteProblem(problem.id!);
await _deleteProblemImages(problem);
loadProblems();
}
} catch (e) {
Get.snackbar('错误', '删除问题失败: $e');
rethrow;
}
}
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);
}
}
}
/// 显示日期选择器
///
Future<void> selectDateRange(BuildContext context) async {
final initialDateRange = DateTimeRange(
start: DateTime.now().subtract(const Duration(days: 7)),
end: DateTime.now(),
);
final DateTimeRange? picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2025, 8, 1), // 可选的最早日期
lastDate: DateTime(2101), // 可选的最晚日期
initialDateRange: initialDateRange,
);
if (picked != null) {
// 处理用户选择的日期范围
log('选择的日期范围是: ${picked.start}${picked.end}');
}
}
}