|
|
@ -2,10 +2,11 @@ import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; |
|
|
|
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; |
|
|
|
import 'package:problem_check_system/data/models/problem_model.dart'; |
|
|
|
import 'package:problem_check_system/modules/problem/views/problem_list_page.dart'; // 导入修正后的 ProblemListPage |
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/date_picker_button.dart'; |
|
|
|
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; |
|
|
|
|
|
|
|
import 'package:problem_check_system/modules/problem/views/problem_form_page.dart'; |
|
|
|
import 'package:problem_check_system/modules/problem/views/problem_form_page.dart'; |
|
|
|
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/custom_data_range_dropdown.dart'; |
|
|
|
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/custom_string_dropdown.dart'; |
|
|
|
|
|
|
|
import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; // 导入自定义下拉菜单 |
|
|
|
|
|
|
|
|
|
|
|
class ProblemPage extends GetView<ProblemController> { |
|
|
|
class ProblemPage extends GetView<ProblemController> { |
|
|
|
const ProblemPage({super.key}); |
|
|
|
const ProblemPage({super.key}); |
|
|
@ -16,9 +17,7 @@ class ProblemPage extends GetView<ProblemController> { |
|
|
|
initialIndex: 0, |
|
|
|
initialIndex: 0, |
|
|
|
length: 2, |
|
|
|
length: 2, |
|
|
|
child: Scaffold( |
|
|
|
child: Scaffold( |
|
|
|
body: ConstrainedBox( |
|
|
|
body: Column( |
|
|
|
constraints: BoxConstraints(maxHeight: 812.h), |
|
|
|
|
|
|
|
child: Column( |
|
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
Container( |
|
|
|
Container( |
|
|
@ -33,9 +32,10 @@ class ProblemPage extends GetView<ProblemController> { |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
child: TabBar( |
|
|
|
child: TabBar( |
|
|
|
|
|
|
|
controller: controller.tabController, |
|
|
|
indicatorSize: TabBarIndicatorSize.tab, |
|
|
|
indicatorSize: TabBarIndicatorSize.tab, |
|
|
|
indicator: BoxDecoration( |
|
|
|
indicator: const BoxDecoration( |
|
|
|
color: const Color(0xfffff7f7), |
|
|
|
color: Color(0xfffff7f7), |
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
topLeft: Radius.circular(8), |
|
|
|
topLeft: Radius.circular(8), |
|
|
|
topRight: Radius.circular(60), |
|
|
|
topRight: Radius.circular(60), |
|
|
@ -61,88 +61,195 @@ class ProblemPage extends GetView<ProblemController> { |
|
|
|
), |
|
|
|
), |
|
|
|
Expanded( |
|
|
|
Expanded( |
|
|
|
child: TabBarView( |
|
|
|
child: TabBarView( |
|
|
|
|
|
|
|
controller: controller.tabController, |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
|
|
|
|
// 问题列表 Tab |
|
|
|
Column( |
|
|
|
Column( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
Container( |
|
|
|
Container( |
|
|
|
margin: EdgeInsets.all(16), |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 17.w), |
|
|
|
child: Row( |
|
|
|
child: Row( |
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|
|
|
children: [ |
|
|
|
children: [DatePickerButton(), DatePickerButton()], |
|
|
|
CustomDateRangeDropdown( |
|
|
|
), |
|
|
|
selectedRange: controller.selectedDateRange, |
|
|
|
|
|
|
|
onChanged: (rangeValue) { |
|
|
|
|
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newDateRange: rangeValue, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
), |
|
|
|
), |
|
|
|
Expanded( |
|
|
|
|
|
|
|
child: Obx(() { |
|
|
|
|
|
|
|
if (controller.isLoading.value) { |
|
|
|
|
|
|
|
return Center(child: CircularProgressIndicator()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ListView.builder( |
|
|
|
|
|
|
|
// +1 是为了在列表末尾添加一个额外的“空”项 |
|
|
|
|
|
|
|
itemCount: controller.problems.length + 1, |
|
|
|
|
|
|
|
itemBuilder: (context, index) { |
|
|
|
|
|
|
|
// 如果是最后一个“空”项,返回一个 SizedBox |
|
|
|
|
|
|
|
if (index == controller.problems.length) { |
|
|
|
|
|
|
|
return SizedBox(height: 79.5.h); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 否则,返回正常的列表项 |
|
|
|
CustomStringDropdown( |
|
|
|
final problem = controller.problems[index]; |
|
|
|
selectedValue: controller.selectedUploadStatus, |
|
|
|
return _buildSwipeableProblemCard( |
|
|
|
items: const ['全部', '未上传', '已上传'], |
|
|
|
problem, |
|
|
|
onChanged: (uploadValue) { |
|
|
|
controller, |
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newUploadStatus: uploadValue, |
|
|
|
); |
|
|
|
); |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CustomStringDropdown( |
|
|
|
|
|
|
|
selectedValue: controller.selectedBindingStatus, |
|
|
|
|
|
|
|
items: const ['全部', '未绑定', '已绑定'], |
|
|
|
|
|
|
|
onChanged: (bindingValue) { |
|
|
|
|
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newBindingStatus: bindingValue, |
|
|
|
); |
|
|
|
); |
|
|
|
}), |
|
|
|
}, |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
Obx(() { |
|
|
|
), |
|
|
|
if (controller.isLoading.value) { |
|
|
|
|
|
|
|
return Center(child: CircularProgressIndicator()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ListView.builder( |
|
|
|
Expanded( |
|
|
|
itemCount: controller.problems.length, |
|
|
|
child: // 使用通用列表组件 |
|
|
|
itemBuilder: (context, index) { |
|
|
|
ProblemListPage( |
|
|
|
final problem = controller.problems[index]; |
|
|
|
problemsToShow: controller.problems, |
|
|
|
return _buildSwipeableProblemCard( |
|
|
|
viewType: ProblemCardViewType.buttons, |
|
|
|
problem, |
|
|
|
), |
|
|
|
controller, |
|
|
|
), |
|
|
|
viewType: ProblemCardViewType.checkbox, |
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
// 历史问题列表 Tab |
|
|
|
|
|
|
|
Column( |
|
|
|
|
|
|
|
children: [ |
|
|
|
|
|
|
|
Container( |
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 17.w), |
|
|
|
|
|
|
|
child: Row( |
|
|
|
|
|
|
|
children: [ |
|
|
|
|
|
|
|
CustomDateRangeDropdown( |
|
|
|
|
|
|
|
selectedRange: controller.selectedDateRange, |
|
|
|
|
|
|
|
onChanged: (rangeValue) { |
|
|
|
|
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newDateRange: rangeValue, |
|
|
|
); |
|
|
|
); |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CustomStringDropdown( |
|
|
|
|
|
|
|
selectedValue: controller.selectedUploadStatus, |
|
|
|
|
|
|
|
items: const ['全部', '未上传', '已上传'], |
|
|
|
|
|
|
|
onChanged: (uploadValue) { |
|
|
|
|
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newUploadStatus: uploadValue, |
|
|
|
); |
|
|
|
); |
|
|
|
}), |
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CustomStringDropdown( |
|
|
|
|
|
|
|
selectedValue: controller.selectedBindingStatus, |
|
|
|
|
|
|
|
items: const ['全部', '未绑定', '已绑定'], |
|
|
|
|
|
|
|
onChanged: (bindingValue) { |
|
|
|
|
|
|
|
controller.updateFiltersAndLoadProblems( |
|
|
|
|
|
|
|
newBindingStatus: bindingValue, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Expanded( |
|
|
|
|
|
|
|
child: // 使用通用列表组件 |
|
|
|
|
|
|
|
ProblemListPage( |
|
|
|
|
|
|
|
problemsToShow: controller.problems, |
|
|
|
|
|
|
|
viewType: ProblemCardViewType.buttons, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, |
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, |
|
|
|
|
|
|
|
// floatingActionButton: Stack( |
|
|
|
|
|
|
|
// children: [ |
|
|
|
|
|
|
|
// Align( |
|
|
|
|
|
|
|
// alignment: Alignment.bottomCenter, |
|
|
|
|
|
|
|
// child: FloatingActionButton( |
|
|
|
|
|
|
|
// heroTag: "btn_add", |
|
|
|
|
|
|
|
// onPressed: () { |
|
|
|
|
|
|
|
// Get.to(() => ProblemFormPage()); |
|
|
|
|
|
|
|
// }, |
|
|
|
|
|
|
|
// shape: const CircleBorder(), |
|
|
|
|
|
|
|
// backgroundColor: Colors.blue[300], |
|
|
|
|
|
|
|
// foregroundColor: Colors.white, |
|
|
|
|
|
|
|
// child: const Icon(Icons.add), |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
// Positioned( |
|
|
|
|
|
|
|
// right: controller.fabPosition.value.dx, //27.w, |
|
|
|
|
|
|
|
// bottom: controller.fabPosition.value.dy, //56.h, |
|
|
|
|
|
|
|
// child: Obx(() { |
|
|
|
|
|
|
|
// final bool isOnline = controller.isOnline.value; |
|
|
|
|
|
|
|
// return GestureDetector( |
|
|
|
|
|
|
|
// onPanUpdate: (details) { |
|
|
|
|
|
|
|
// // 调用控制器中的方法来更新位置 |
|
|
|
|
|
|
|
// controller.updatePosition(details.delta); |
|
|
|
|
|
|
|
// }, |
|
|
|
|
|
|
|
// child: FloatingActionButton( |
|
|
|
|
|
|
|
// heroTag: "btn_upload", |
|
|
|
|
|
|
|
// onPressed: isOnline |
|
|
|
|
|
|
|
// ? () => controller.uploadAllUnuploaded() |
|
|
|
|
|
|
|
// : null, |
|
|
|
|
|
|
|
// foregroundColor: Colors.white, |
|
|
|
|
|
|
|
// backgroundColor: isOnline |
|
|
|
|
|
|
|
// ? Colors.red[300] |
|
|
|
|
|
|
|
// : Colors.grey[400], |
|
|
|
|
|
|
|
// child: Icon( |
|
|
|
|
|
|
|
// isOnline |
|
|
|
|
|
|
|
// ? Icons.file_upload_outlined |
|
|
|
|
|
|
|
// : Icons.cloud_off_outlined, |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
// ); |
|
|
|
|
|
|
|
// }), |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
// ], |
|
|
|
|
|
|
|
// ), |
|
|
|
|
|
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, |
|
|
|
|
|
|
|
// 使用 Stack 统一管理所有浮动按钮 |
|
|
|
floatingActionButton: Stack( |
|
|
|
floatingActionButton: Stack( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
|
|
|
|
// 固定位置的 "添加" 按钮 |
|
|
|
|
|
|
|
// 使用 Align 和 Positioned |
|
|
|
Align( |
|
|
|
Align( |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
|
|
|
|
child: Padding( |
|
|
|
|
|
|
|
padding: EdgeInsets.only(bottom: 24.h), // 底部间距 |
|
|
|
child: FloatingActionButton( |
|
|
|
child: FloatingActionButton( |
|
|
|
heroTag: "btn_add", |
|
|
|
heroTag: "btn_add", |
|
|
|
onPressed: () { |
|
|
|
onPressed: () { |
|
|
|
Get.to(() => ProblemFormPage()); |
|
|
|
Get.to(() => ProblemFormPage()); |
|
|
|
}, |
|
|
|
}, |
|
|
|
shape: CircleBorder(), |
|
|
|
shape: const CircleBorder(), |
|
|
|
backgroundColor: Colors.blue[300], |
|
|
|
backgroundColor: Colors.blue[300], |
|
|
|
foregroundColor: Colors.white, |
|
|
|
foregroundColor: Colors.white, |
|
|
|
child: const Icon(Icons.add), |
|
|
|
child: const Icon(Icons.add), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
Positioned( |
|
|
|
), |
|
|
|
bottom: 56.h, // 设置距离底部100像素 |
|
|
|
|
|
|
|
right: 27.w, // 设置距离右侧16像素(通常的浮动按钮默认位置) |
|
|
|
// 可拖动的 "上传" 按钮 |
|
|
|
child: Obx(() { |
|
|
|
Obx(() { |
|
|
|
final bool isOnline = controller.isOnline.value; |
|
|
|
final isOnline = controller.isOnline.value; |
|
|
|
return FloatingActionButton( |
|
|
|
return Positioned( |
|
|
|
|
|
|
|
// 使用正确的坐标,left/right 对应 dx,top/bottom 对应 dy |
|
|
|
|
|
|
|
left: controller.fabUploadPosition.value.dx, |
|
|
|
|
|
|
|
top: controller.fabUploadPosition.value.dy, |
|
|
|
|
|
|
|
child: GestureDetector( |
|
|
|
|
|
|
|
onPanUpdate: (details) { |
|
|
|
|
|
|
|
// 调用控制器中的方法来更新位置 |
|
|
|
|
|
|
|
controller.updateFabUploadPosition(details.delta); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onPanEnd: (details) { |
|
|
|
|
|
|
|
// 拖动结束后调用吸附方法 |
|
|
|
|
|
|
|
controller.snapToEdge(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
child: FloatingActionButton( |
|
|
|
heroTag: "btn_upload", |
|
|
|
heroTag: "btn_upload", |
|
|
|
onPressed: isOnline |
|
|
|
onPressed: isOnline |
|
|
|
? () => controller.uploadAllUnuploaded() |
|
|
|
? () => controller.uploadAllUnuploaded() |
|
|
@ -156,101 +263,13 @@ class ProblemPage extends GetView<ProblemController> { |
|
|
|
? Icons.file_upload_outlined |
|
|
|
? Icons.file_upload_outlined |
|
|
|
: Icons.cloud_off_outlined, |
|
|
|
: Icons.cloud_off_outlined, |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
}), |
|
|
|
|
|
|
|
|
|
|
|
Widget _buildSwipeableProblemCard( |
|
|
|
|
|
|
|
Problem problem, |
|
|
|
|
|
|
|
ProblemController controller, { |
|
|
|
|
|
|
|
ProblemCardViewType viewType = ProblemCardViewType.buttons, |
|
|
|
|
|
|
|
}) { |
|
|
|
|
|
|
|
return Dismissible( |
|
|
|
|
|
|
|
key: Key(problem.id ?? UniqueKey().toString()), |
|
|
|
|
|
|
|
direction: DismissDirection.endToStart, |
|
|
|
|
|
|
|
background: Container( |
|
|
|
|
|
|
|
color: Colors.red, |
|
|
|
|
|
|
|
alignment: Alignment.centerRight, |
|
|
|
|
|
|
|
padding: EdgeInsets.only(right: 20.w), |
|
|
|
|
|
|
|
child: Icon(Icons.delete, color: Colors.white, size: 30.sp), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
confirmDismiss: (direction) async { |
|
|
|
|
|
|
|
return await _showDeleteConfirmationDialog(problem); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onDismissed: (direction) { |
|
|
|
|
|
|
|
controller.deleteProblem(problem); |
|
|
|
|
|
|
|
Get.snackbar('成功', '问题已删除'); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
child: ProblemCard(problem, viewType: viewType), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Future<bool> _showDeleteConfirmationDialog(Problem problem) async { |
|
|
|
|
|
|
|
return await Get.bottomSheet<bool>( |
|
|
|
|
|
|
|
Container( |
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.0), |
|
|
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
|
|
color: Colors.white, |
|
|
|
|
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
|
|
|
|
topLeft: Radius.circular(16), |
|
|
|
|
|
|
|
topRight: Radius.circular(16), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
child: SafeArea( |
|
|
|
|
|
|
|
child: Column( |
|
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch, |
|
|
|
|
|
|
|
children: [ |
|
|
|
|
|
|
|
SizedBox(height: 16), |
|
|
|
|
|
|
|
Text( |
|
|
|
|
|
|
|
'确认删除', |
|
|
|
|
|
|
|
textAlign: TextAlign.center, |
|
|
|
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
SizedBox(height: 8), |
|
|
|
|
|
|
|
Text( |
|
|
|
|
|
|
|
'确定要删除这个问题吗?此操作不可撤销。', |
|
|
|
|
|
|
|
textAlign: TextAlign.center, |
|
|
|
|
|
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
SizedBox(height: 24), |
|
|
|
|
|
|
|
ElevatedButton( |
|
|
|
|
|
|
|
onPressed: () => Get.back(result: true), |
|
|
|
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 16), |
|
|
|
|
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
|
|
|
|
borderRadius: BorderRadius.circular(10), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
child: Text( |
|
|
|
|
|
|
|
'删除', |
|
|
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 16), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
SizedBox(height: 8), |
|
|
|
|
|
|
|
TextButton( |
|
|
|
|
|
|
|
onPressed: () => Get.back(result: false), |
|
|
|
|
|
|
|
style: TextButton.styleFrom( |
|
|
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 16), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
child: Text( |
|
|
|
|
|
|
|
'取消', |
|
|
|
|
|
|
|
style: TextStyle(color: Colors.grey[700], fontSize: 16), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
SizedBox(height: 16), |
|
|
|
|
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
isDismissible: false, |
|
|
|
|
|
|
|
) ?? |
|
|
|
|
|
|
|
false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|