|
|
|
|
@ -1,9 +1,10 @@
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
|
|
|
import 'package:get/get.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/models/form_mode.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart'; |
|
|
|
|
import 'package:problem_check_system/app/core/pages/widgets/custom_app_bar.dart'; |
|
|
|
|
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
|
|
|
|
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; |
|
|
|
|
import 'package:problem_check_system/app/features/problem/presentation/pages/widgets/problem_card.dart'; |
|
|
|
|
|
|
|
|
|
class ProblemListPage extends GetView<ProblemListController> { |
|
|
|
|
const ProblemListPage({super.key}); |
|
|
|
|
@ -12,171 +13,289 @@ class ProblemListPage extends GetView<ProblemListController> {
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
return Scaffold( |
|
|
|
|
appBar: CustomAppBar( |
|
|
|
|
titleName: '企业列表', |
|
|
|
|
titleName: '问题列表', |
|
|
|
|
actionsVisible: true, |
|
|
|
|
onAddPressed: () { |
|
|
|
|
controller.navigateToProblemForm(fromMode: FormMode.add); |
|
|
|
|
}, |
|
|
|
|
onAddPressed: () => controller.navigateToAddForm(), |
|
|
|
|
), |
|
|
|
|
body: Text("问题列表"), |
|
|
|
|
); |
|
|
|
|
// return Obx(() { |
|
|
|
|
// if (true) { |
|
|
|
|
// return const Center(child: CircularProgressIndicator()); |
|
|
|
|
// } |
|
|
|
|
body: Column( |
|
|
|
|
children: [ |
|
|
|
|
// 根据参数决定是否构建筛选区域 |
|
|
|
|
_buildFilterSection(), |
|
|
|
|
const Divider(height: 1, thickness: 1), |
|
|
|
|
|
|
|
|
|
// return EasyRefresh( |
|
|
|
|
// header: ClassicHeader( |
|
|
|
|
// dragText: '下拉刷新'.tr, |
|
|
|
|
// armedText: '释放开始'.tr, |
|
|
|
|
// readyText: '刷新中...'.tr, |
|
|
|
|
// processingText: '刷新中...'.tr, |
|
|
|
|
// processedText: '成功了'.tr, |
|
|
|
|
// noMoreText: 'No more'.tr, |
|
|
|
|
// failedText: '失败'.tr, |
|
|
|
|
// messageText: '最后更新于 %T'.tr, |
|
|
|
|
// ), |
|
|
|
|
// onRefresh: () async { |
|
|
|
|
// // 调用控制器的刷新方法 |
|
|
|
|
// await controller.pullDataFromServer(); |
|
|
|
|
// }, |
|
|
|
|
// child: ListView.builder( |
|
|
|
|
// padding: EdgeInsets.symmetric(horizontal: 17.w), |
|
|
|
|
// itemCount: problemsToShow.length, |
|
|
|
|
// itemBuilder: (context, index) { |
|
|
|
|
// // if (index == problemsToShow.length) { |
|
|
|
|
// // return SizedBox(height: 79.5.h); |
|
|
|
|
// // } |
|
|
|
|
// final problem = problemsToShow[index]; |
|
|
|
|
// return _buildSwipeableProblemCard(problem); |
|
|
|
|
// }, |
|
|
|
|
// ), |
|
|
|
|
// ); |
|
|
|
|
// }); |
|
|
|
|
// 列表区域总是显示 |
|
|
|
|
Expanded(child: _buildEnterpriseList()), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Widget _buildSwipeableProblemCard(Problem problem) { |
|
|
|
|
// // 对于所有视图类型,如果是待删除状态,都禁用交互 |
|
|
|
|
// final bool isPendingDelete = |
|
|
|
|
// problem.syncStatus == ProblemSyncStatus.pendingDelete; |
|
|
|
|
/// 构建可展开的筛选查询区域。 |
|
|
|
|
/// 此方法的所有交互都绑定到 `BaseEnterpriseListController` 的接口, |
|
|
|
|
/// 因此它无需关心具体的控制器是哪个。 |
|
|
|
|
Widget _buildFilterSection() { |
|
|
|
|
return ExpansionTile( |
|
|
|
|
controller: controller.expansibleController, |
|
|
|
|
title: const Text('筛选查询'), |
|
|
|
|
leading: const Icon(Icons.filter_alt_outlined), |
|
|
|
|
tilePadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0.0.h), |
|
|
|
|
childrenPadding: EdgeInsets.symmetric( |
|
|
|
|
horizontal: 16.0, |
|
|
|
|
vertical: 8.0.h, |
|
|
|
|
).copyWith(top: 0), |
|
|
|
|
dense: true, |
|
|
|
|
children: [ |
|
|
|
|
Padding( |
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h), |
|
|
|
|
child: Column( |
|
|
|
|
children: [ |
|
|
|
|
// 1. 企业名称输入框 |
|
|
|
|
TextFormField( |
|
|
|
|
controller: controller.nameController, |
|
|
|
|
decoration: InputDecoration( |
|
|
|
|
labelText: '企业名称', |
|
|
|
|
hintText: '请输入关键词', |
|
|
|
|
prefixIcon: const Icon(Icons.business_center), |
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
borderRadius: BorderRadius.circular(8.r), |
|
|
|
|
), |
|
|
|
|
isDense: true, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
SizedBox(height: 12.h), |
|
|
|
|
|
|
|
|
|
// if (viewType == ProblemCardViewType.buttons) { |
|
|
|
|
// // buttons 视图类型:有条件启用滑动删除 |
|
|
|
|
// if (!isPendingDelete) { |
|
|
|
|
// // 非待删除状态:启用滑动删除 |
|
|
|
|
// return Dismissible( |
|
|
|
|
// key: ValueKey('${problem.id}-${problem.syncStatus}'), |
|
|
|
|
// 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), |
|
|
|
|
// // 2. 企业类型下拉框 |
|
|
|
|
// Obx( |
|
|
|
|
// () => DropdownButtonFormField<CompanyType>( |
|
|
|
|
// initialValue: controller.selectedType.value, |
|
|
|
|
// decoration: InputDecoration( |
|
|
|
|
// labelText: '企业类型', |
|
|
|
|
// prefixIcon: const Icon(Icons.category), |
|
|
|
|
// border: OutlineInputBorder( |
|
|
|
|
// borderRadius: BorderRadius.circular(8.r), |
|
|
|
|
// ), |
|
|
|
|
// confirmDismiss: (direction) async { |
|
|
|
|
// return await _showDeleteConfirmationDialog(problem); |
|
|
|
|
// }, |
|
|
|
|
// onDismissed: (direction) { |
|
|
|
|
// // controller.deleteProblem(problem); |
|
|
|
|
// Get.snackbar('成功', '问题已删除'); |
|
|
|
|
// }, |
|
|
|
|
// child: ProblemCard( |
|
|
|
|
// key: ValueKey(problem.id), |
|
|
|
|
// problem: problem, |
|
|
|
|
// viewType: viewType, |
|
|
|
|
// isSelected: false, |
|
|
|
|
// isDense: true, |
|
|
|
|
// ), |
|
|
|
|
// hint: const Text('请选择企业类型'), |
|
|
|
|
// isExpanded: true, |
|
|
|
|
// items: CompanyType.values.map((type) { |
|
|
|
|
// return DropdownMenuItem( |
|
|
|
|
// value: type, |
|
|
|
|
// child: Text(type.displayText), |
|
|
|
|
// ); |
|
|
|
|
// } else { |
|
|
|
|
// // 待删除状态:显示普通卡片(无滑动功能) |
|
|
|
|
// return ProblemCard( |
|
|
|
|
// key: ValueKey(problem.id), |
|
|
|
|
// problem: problem, |
|
|
|
|
// viewType: viewType, |
|
|
|
|
// isSelected: false, |
|
|
|
|
// ); |
|
|
|
|
// } |
|
|
|
|
// } else { |
|
|
|
|
// // 其他视图类型(list、grid等):使用 Obx 监听选中状态 |
|
|
|
|
// return Obx(() { |
|
|
|
|
// final isSelected = controller.selectedProblems.contains(problem); |
|
|
|
|
// return ProblemCard( |
|
|
|
|
// key: ValueKey(problem.id), |
|
|
|
|
// problem: problem, |
|
|
|
|
// viewType: viewType, |
|
|
|
|
// isSelected: isSelected, |
|
|
|
|
// onChanged: (problem, isChecked) { |
|
|
|
|
// controller.updateProblemSelection(problem, isChecked); |
|
|
|
|
// }).toList(), |
|
|
|
|
// onChanged: (value) { |
|
|
|
|
// controller.selectedType.value = value; |
|
|
|
|
// }, |
|
|
|
|
// ); |
|
|
|
|
// }); |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
// ), |
|
|
|
|
// ), |
|
|
|
|
// SizedBox(height: 12.h), |
|
|
|
|
|
|
|
|
|
// 3. 日期范围选择器 |
|
|
|
|
Row( |
|
|
|
|
children: [ |
|
|
|
|
Expanded( |
|
|
|
|
child: _buildDatePickerField('开始日期', controller.startDate), |
|
|
|
|
), |
|
|
|
|
SizedBox(width: 12.w), |
|
|
|
|
Expanded( |
|
|
|
|
child: _buildDatePickerField('结束日期', controller.endDate), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
SizedBox(height: 16.h), |
|
|
|
|
|
|
|
|
|
Future<bool> _showDeleteConfirmationDialog(Problem problem) async { |
|
|
|
|
// 确保在返回前关闭可能存在的snackbar |
|
|
|
|
if (Get.isSnackbarOpen) { |
|
|
|
|
Get.closeCurrentSnackbar(); |
|
|
|
|
// 4. 操作按钮 |
|
|
|
|
Row( |
|
|
|
|
children: [ |
|
|
|
|
Expanded( |
|
|
|
|
child: OutlinedButton.icon( |
|
|
|
|
onPressed: controller.clearFilters, |
|
|
|
|
icon: const Icon(Icons.refresh), |
|
|
|
|
label: const Text('重置'), |
|
|
|
|
style: OutlinedButton.styleFrom( |
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 12.h), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
SizedBox(width: 16.w), |
|
|
|
|
Expanded( |
|
|
|
|
child: ElevatedButton.icon( |
|
|
|
|
onPressed: controller.search, |
|
|
|
|
icon: const Icon(Icons.search), |
|
|
|
|
label: const Text('查询'), |
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 12.h), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
SizedBox(height: 8.h), // 增加底部间距 |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
return await Get.bottomSheet<bool>( |
|
|
|
|
Container( |
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0), |
|
|
|
|
decoration: const BoxDecoration( |
|
|
|
|
color: Colors.white, |
|
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
|
topLeft: Radius.circular(16), |
|
|
|
|
topRight: Radius.circular(16), |
|
|
|
|
|
|
|
|
|
/// 构建日期选择器的辅助方法 |
|
|
|
|
Widget _buildDatePickerField(String label, Rx<DateTime?> date) { |
|
|
|
|
return InkWell( |
|
|
|
|
onTap: () async { |
|
|
|
|
final pickedDate = await showDatePicker( |
|
|
|
|
context: Get.context!, |
|
|
|
|
initialDate: date.value ?? DateTime.now(), |
|
|
|
|
firstDate: DateTime(2020), // 调整一个合理的开始年份 |
|
|
|
|
lastDate: DateTime(2125), |
|
|
|
|
); |
|
|
|
|
if (pickedDate != null) { |
|
|
|
|
date.value = pickedDate; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
child: InputDecorator( |
|
|
|
|
decoration: InputDecoration( |
|
|
|
|
labelText: label, |
|
|
|
|
prefixIcon: const Icon(Icons.calendar_today), |
|
|
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)), |
|
|
|
|
isDense: true, |
|
|
|
|
contentPadding: EdgeInsets.symmetric( |
|
|
|
|
horizontal: 12.w, |
|
|
|
|
vertical: 14.h, |
|
|
|
|
), // 调整内边距 |
|
|
|
|
), |
|
|
|
|
child: Obx( |
|
|
|
|
() => Text( |
|
|
|
|
date.value == null ? '请选择日期' : date.value!.toDateString(), |
|
|
|
|
style: TextStyle( |
|
|
|
|
fontSize: 14.sp, |
|
|
|
|
color: date.value == null ? Colors.grey[700] : Colors.black87, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
child: SafeArea( |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 构建问题列表 |
|
|
|
|
Widget _buildEnterpriseList() { |
|
|
|
|
// 使用 Obx 包裹以监听 controller 中所有 Rx 变量的变化 |
|
|
|
|
return Obx(() { |
|
|
|
|
// 在列表为空且仍在加载时显示加载指示器 |
|
|
|
|
if (controller.isLoading.value) { |
|
|
|
|
return const Center(child: CircularProgressIndicator()); |
|
|
|
|
} |
|
|
|
|
// 在加载完成但列表为空时显示提示信息 |
|
|
|
|
if (controller.problemList.isEmpty) { |
|
|
|
|
return Center( |
|
|
|
|
child: Column( |
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch, |
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
|
children: [ |
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
const Text( |
|
|
|
|
'确认删除', |
|
|
|
|
textAlign: TextAlign.center, |
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), |
|
|
|
|
Icon( |
|
|
|
|
Icons.folder_off_outlined, |
|
|
|
|
size: 60.sp, |
|
|
|
|
color: Colors.grey[400], |
|
|
|
|
), |
|
|
|
|
const SizedBox(height: 8), |
|
|
|
|
SizedBox(height: 16.h), |
|
|
|
|
Text( |
|
|
|
|
'确定要删除这个问题吗?此操作不可撤销。', |
|
|
|
|
textAlign: TextAlign.center, |
|
|
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]), |
|
|
|
|
'没有找到相关问题', |
|
|
|
|
style: TextStyle(fontSize: 16.sp, color: Colors.grey[600]), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
const SizedBox(height: 24), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 使用下拉刷新包裹列表 |
|
|
|
|
return RefreshIndicator( |
|
|
|
|
onRefresh: () async => controller.loadAndSyncEnterprises(), |
|
|
|
|
child: ListView.builder( |
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), |
|
|
|
|
itemCount: controller.problemList.length, |
|
|
|
|
itemBuilder: (context, index) { |
|
|
|
|
final item = controller.problemList[index]; |
|
|
|
|
final enterprise = item.problemEntity; |
|
|
|
|
// 核心: 从 base controller 获取选中状态 |
|
|
|
|
final isSelected = controller.selectedProblems.contains(enterprise); |
|
|
|
|
|
|
|
|
|
return Padding( |
|
|
|
|
padding: EdgeInsets.only(bottom: 12.h), |
|
|
|
|
child: ProblemCard( |
|
|
|
|
problemListItem: item, |
|
|
|
|
isSelected: isSelected, |
|
|
|
|
onTap: () => |
|
|
|
|
controller.navigateToDetailsView(item.problemEntity), |
|
|
|
|
actions: Row( |
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end, |
|
|
|
|
children: [ |
|
|
|
|
// “修改信息” 按钮 |
|
|
|
|
ElevatedButton( |
|
|
|
|
onPressed: () => Get.back(result: true), |
|
|
|
|
onPressed: () => |
|
|
|
|
controller.navigateToEditForm(item.problemEntity), |
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 16), |
|
|
|
|
backgroundColor: const Color(0xFF42A5F5), |
|
|
|
|
foregroundColor: Colors.white, |
|
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
|
horizontal: 20.w, |
|
|
|
|
vertical: 8.h, |
|
|
|
|
), |
|
|
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, |
|
|
|
|
// [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 |
|
|
|
|
elevation: 0, |
|
|
|
|
// [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 |
|
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
|
borderRadius: BorderRadius.circular(10), |
|
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
|
topLeft: Radius.circular(12.r), |
|
|
|
|
bottomRight: Radius.circular(12.r), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
child: const Text( |
|
|
|
|
'删除', |
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 16), |
|
|
|
|
), |
|
|
|
|
child: Text( |
|
|
|
|
"修改", |
|
|
|
|
style: TextStyle( |
|
|
|
|
fontSize: 13.sp, |
|
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
SizedBox(width: 8.w), |
|
|
|
|
// “查看问题” 按钮 (关键样式在这里) |
|
|
|
|
ElevatedButton( |
|
|
|
|
onPressed: () => |
|
|
|
|
controller.navigateToDetailsView(item.problemEntity), |
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
backgroundColor: const Color(0xFF42A5F5), |
|
|
|
|
foregroundColor: Colors.white, |
|
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
|
horizontal: 20.w, |
|
|
|
|
vertical: 8.h, |
|
|
|
|
), |
|
|
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, |
|
|
|
|
// [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 |
|
|
|
|
elevation: 0, |
|
|
|
|
// [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 |
|
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
|
borderRadius: BorderRadius.only( |
|
|
|
|
topLeft: Radius.circular(12.r), |
|
|
|
|
bottomRight: Radius.circular(12.r), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
const SizedBox(height: 8), |
|
|
|
|
TextButton( |
|
|
|
|
onPressed: () => Get.back(result: false), |
|
|
|
|
style: TextButton.styleFrom( |
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 16), |
|
|
|
|
), |
|
|
|
|
child: Text( |
|
|
|
|
'取消', |
|
|
|
|
style: TextStyle(color: Colors.grey[700], fontSize: 16), |
|
|
|
|
"查看", |
|
|
|
|
style: TextStyle( |
|
|
|
|
fontSize: 13.sp, |
|
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
) ?? |
|
|
|
|
false; |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|