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.
236 lines
6.6 KiB
236 lines
6.6 KiB
2 weeks ago
|
import 'package:dio/dio.dart';
|
||
|
import 'package:get/get.dart' hide MultipartFile, FormData;
|
||
|
import 'dart:io';
|
||
|
import 'package:path/path.dart' as path;
|
||
|
import '../../../data/models/problem_model.dart';
|
||
|
import '../../../data/providers/local_database.dart';
|
||
|
|
||
|
class ProblemController extends GetxController {
|
||
|
final LocalDatabase _localDatabase;
|
||
|
final RxList<Problem> problems = <Problem>[].obs;
|
||
|
final RxBool isLoading = false.obs;
|
||
|
final Dio _dio;
|
||
|
|
||
|
// 依赖注入,由 bindings 传入所有依赖
|
||
|
ProblemController({required LocalDatabase localDatabase, required Dio dio})
|
||
|
: _localDatabase = localDatabase,
|
||
|
_dio = dio;
|
||
|
|
||
|
// 获取所有已选中的问题对象
|
||
|
List<Problem> get selectedProblems {
|
||
|
return problems.where((p) => p.isChecked.value).toList();
|
||
|
}
|
||
|
|
||
|
// 获取未上传的问题
|
||
|
List<Problem> get unuploadedProblems {
|
||
|
return problems.where((p) => !p.isUploaded).toList();
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
void onInit() {
|
||
|
super.onInit();
|
||
|
loadProblems();
|
||
|
}
|
||
|
|
||
|
Future<void> loadProblems() async {
|
||
|
isLoading.value = true;
|
||
|
try {
|
||
|
problems.value = await _localDatabase.getProblems();
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '加载问题失败: $e');
|
||
|
rethrow;
|
||
|
} finally {
|
||
|
isLoading.value = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 在本地数据库和GetX列表中添加一个新问题
|
||
|
Future<void> addProblem(Problem problem) async {
|
||
|
try {
|
||
|
// 确保问题有ID
|
||
|
if (problem.id == null) {
|
||
|
problem = problem.copyWith(
|
||
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
await _localDatabase.insertProblem(problem);
|
||
|
problems.add(problem);
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '保存问题失败: $e');
|
||
|
rethrow;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 在本地数据库和GetX列表中更新一个现有问题
|
||
|
Future<void> updateProblem(Problem problem) async {
|
||
|
try {
|
||
|
await _localDatabase.updateProblem(problem);
|
||
|
final index = problems.indexWhere((p) => p.id == problem.id);
|
||
|
if (index != -1) {
|
||
|
problems[index] = problem;
|
||
|
}
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '更新问题失败: $e');
|
||
|
rethrow;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 删除单个问题
|
||
|
Future<void> deleteProblem(Problem problem) async {
|
||
|
try {
|
||
|
if (problem.id != null) {
|
||
|
await _localDatabase.deleteProblem(problem.id!);
|
||
|
|
||
|
// 从本地列表中移除
|
||
|
problems.remove(problem);
|
||
|
|
||
|
// 删除关联的图片文件
|
||
|
await _deleteProblemImages(problem);
|
||
|
}
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '删除问题失败: $e');
|
||
|
rethrow;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 批量删除选中的问题
|
||
|
Future<void> deleteSelectedProblems() async {
|
||
|
final problemsToDelete = selectedProblems;
|
||
|
if (problemsToDelete.isEmpty) {
|
||
|
Get.snackbar('提示', '请至少选择一个问题进行删除');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
for (var problem in problemsToDelete) {
|
||
|
await deleteProblem(problem);
|
||
|
}
|
||
|
Get.snackbar('成功', '已删除${problemsToDelete.length}个问题');
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '删除问题失败: $e');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 删除问题关联的图片文件
|
||
|
Future<void> _deleteProblemImages(Problem problem) async {
|
||
|
for (var imagePath in problem.imagePaths) {
|
||
|
try {
|
||
|
final file = File(imagePath);
|
||
|
if (await file.exists()) {
|
||
|
await file.delete();
|
||
|
}
|
||
|
} catch (e) {
|
||
|
print('删除图片失败: $imagePath, 错误: $e');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 上传单个问题到服务器
|
||
|
Future<bool> uploadProblem(Problem problem) async {
|
||
|
try {
|
||
|
final formData = FormData.fromMap({
|
||
|
'description': problem.description,
|
||
|
'location': problem.location,
|
||
|
'createdAt': problem.createdAt.toIso8601String(),
|
||
|
'boundInfo': problem.boundInfo ?? '',
|
||
|
});
|
||
|
|
||
|
// 添加图片文件
|
||
|
for (var imagePath in problem.imagePaths) {
|
||
|
final file = File(imagePath);
|
||
|
if (await file.exists()) {
|
||
|
formData.files.add(
|
||
|
MapEntry(
|
||
|
'images',
|
||
|
await MultipartFile.fromFile(
|
||
|
imagePath,
|
||
|
filename: path.basename(imagePath),
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
final response = await _dio.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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 批量上传所有未上传的问题
|
||
|
Future<void> uploadAllUnuploaded() async {
|
||
|
final unuploaded = unuploadedProblems;
|
||
|
if (unuploaded.isEmpty) {
|
||
|
Get.snackbar('提示', '没有需要上传的问题');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
isLoading.value = true;
|
||
|
int successCount = 0;
|
||
|
|
||
|
for (var problem in unuploaded) {
|
||
|
final success = await uploadProblem(problem);
|
||
|
if (success) {
|
||
|
successCount++;
|
||
|
}
|
||
|
// 添加短暂延迟,避免服务器压力过大
|
||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||
|
}
|
||
|
|
||
|
isLoading.value = false;
|
||
|
|
||
|
if (successCount > 0) {
|
||
|
Get.snackbar('成功', '已成功上传$successCount个问题');
|
||
|
}
|
||
|
|
||
|
if (successCount < unuploaded.length) {
|
||
|
Get.snackbar('部分成功', '有${unuploaded.length - successCount}个问题上传失败');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 绑定信息到问题
|
||
|
Future<void> bindInfoToProblem(String id, String info) async {
|
||
|
try {
|
||
|
final problem = problems.firstWhere((p) => p.id == id);
|
||
|
final updatedProblem = problem.copyWith(boundInfo: info);
|
||
|
await updateProblem(updatedProblem);
|
||
|
Get.snackbar('成功', '信息已绑定');
|
||
|
} catch (e) {
|
||
|
Get.snackbar('错误', '未找到问题或绑定失败: $e');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 清除所有选中的状态
|
||
|
void clearSelections() {
|
||
|
for (var problem in problems) {
|
||
|
problem.isChecked.value = false;
|
||
|
}
|
||
|
}
|
||
|
}
|