21 changed files with 911 additions and 440 deletions
@ -1,44 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|
||||||
|
|
||||||
class AppBarAdd extends StatelessWidget implements PreferredSizeWidget { |
|
||||||
const AppBarAdd({super.key, required this.titleName, this.onAddPressed}); |
|
||||||
final String titleName; |
|
||||||
final VoidCallback? onAddPressed; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return AppBar( |
|
||||||
title: Text( |
|
||||||
titleName, |
|
||||||
style: TextStyle( |
|
||||||
fontWeight: FontWeight.bold, |
|
||||||
fontFamily: 'MyFont', |
|
||||||
fontSize: 18.sp, |
|
||||||
color: Colors.white, |
|
||||||
), |
|
||||||
), |
|
||||||
backgroundColor: Colors.transparent, |
|
||||||
flexibleSpace: Container( |
|
||||||
decoration: const BoxDecoration( |
|
||||||
gradient: LinearGradient( |
|
||||||
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)], |
|
||||||
begin: Alignment.centerLeft, |
|
||||||
end: Alignment.centerRight, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
elevation: 0, |
|
||||||
centerTitle: true, |
|
||||||
actions: [ |
|
||||||
IconButton( |
|
||||||
icon: Icon(Icons.add, color: Colors.white), // 使用 .sp |
|
||||||
onPressed: onAddPressed, |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight); |
|
||||||
} |
|
||||||
@ -0,0 +1,66 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
|
||||||
|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { |
||||||
|
const CustomAppBar({ |
||||||
|
super.key, |
||||||
|
required this.titleName, |
||||||
|
this.leadingVisible = false, |
||||||
|
this.actionsVisible = false, |
||||||
|
this.onAddPressed, |
||||||
|
}); |
||||||
|
final String titleName; |
||||||
|
final bool leadingVisible; |
||||||
|
final bool actionsVisible; |
||||||
|
final VoidCallback? onAddPressed; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return AppBar( |
||||||
|
title: Text( |
||||||
|
titleName, |
||||||
|
style: TextStyle( |
||||||
|
fontWeight: FontWeight.bold, |
||||||
|
fontFamily: 'MyFont', |
||||||
|
fontSize: 17.5.sp, |
||||||
|
color: Colors.white, |
||||||
|
), |
||||||
|
), |
||||||
|
automaticallyImplyLeading: leadingVisible, |
||||||
|
leading: leadingVisible |
||||||
|
? IconButton( |
||||||
|
icon: Icon( |
||||||
|
Icons.arrow_back_ios_new_rounded, |
||||||
|
size: 17.5.sp, |
||||||
|
color: Colors.white, |
||||||
|
), |
||||||
|
onPressed: () => Get.back(), |
||||||
|
) |
||||||
|
: null, |
||||||
|
backgroundColor: Colors.transparent, |
||||||
|
flexibleSpace: Container( |
||||||
|
decoration: const BoxDecoration( |
||||||
|
gradient: LinearGradient( |
||||||
|
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)], |
||||||
|
begin: Alignment.centerLeft, |
||||||
|
end: Alignment.centerRight, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
elevation: 0, |
||||||
|
centerTitle: true, |
||||||
|
actions: actionsVisible |
||||||
|
? [ |
||||||
|
IconButton( |
||||||
|
icon: Icon(Icons.add, color: Colors.white), // 使用 .sp |
||||||
|
onPressed: onAddPressed, |
||||||
|
), |
||||||
|
] |
||||||
|
: null, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight); |
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/services/database_service.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/base_enterprise_list_controller.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; |
||||||
|
|
||||||
|
class EnterpriseListBinding extends Bindings { |
||||||
|
@override |
||||||
|
void dependencies() { |
||||||
|
Get.put<EnterpriseLocalDataSource>( |
||||||
|
EnterpriseLocalDataSourceImpl( |
||||||
|
databaseService: Get.find<DatabaseService>(), |
||||||
|
), |
||||||
|
); |
||||||
|
Get.put<EnterpriseRemoteDataSource>(EnterpriseRemoteDataSourceImpl()); |
||||||
|
Get.put<EnterpriseRepository>( |
||||||
|
EnterpriseRepositoryImpl( |
||||||
|
localDataSource: Get.find<EnterpriseLocalDataSource>(), |
||||||
|
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(), |
||||||
|
), |
||||||
|
); |
||||||
|
Get.put<GetEnterpriseListUsecase>( |
||||||
|
GetEnterpriseListUsecase(repository: Get.find<EnterpriseRepository>()), |
||||||
|
); |
||||||
|
Get.lazyPut<EnterpriseListController>( |
||||||
|
() => EnterpriseListController( |
||||||
|
getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(), |
||||||
|
), |
||||||
|
); |
||||||
|
Get.lazyPut<BaseEnterpriseListController>( |
||||||
|
() => Get.find<EnterpriseListController>(), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,8 +1,13 @@ |
|||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/base_enterprise_list_controller.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart'; |
||||||
|
|
||||||
class EnterpriseUploadBinding extends Bindings { |
class EnterpriseUploadBinding extends Bindings { |
||||||
@override |
@override |
||||||
void dependencies() { |
void dependencies() { |
||||||
// TODO: implement dependencies |
Get.lazyPut<EnterpriseUploadController>(() => EnterpriseUploadController()); |
||||||
|
Get.lazyPut<BaseEnterpriseListController>( |
||||||
|
() => Get.find<EnterpriseUploadController>(), |
||||||
|
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,66 @@ |
|||||||
|
// lib/app/features/enterprise/presentation/controllers/base_enterprise_list_controller.dart |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/company_enum.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; |
||||||
|
|
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// [抽象基类] 企业列表控制器接口 |
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// |
||||||
|
/// 定义了所有希望驱动 `EnterpriseListView` 的控制器必须实现的属性和方法。 |
||||||
|
/// |
||||||
|
/// **设计思想:** |
||||||
|
/// - **依赖倒置原则**: `EnterpriseListView` (高层模块) 不直接依赖于 |
||||||
|
/// `EnterpriseListController` 或 `EnterpriseUploadController` (低层模块), |
||||||
|
/// 而是两者都依赖于这个抽象 (`BaseEnterpriseListController`)。 |
||||||
|
/// - **接口隔离原则**: 这个基类只定义了 `EnterpriseListView` 真正需要交互的成员, |
||||||
|
/// 不多也不少,确保了 View 和 Controller 之间的最小化耦合。 |
||||||
|
/// |
||||||
|
abstract class BaseEnterpriseListController extends GetxController { |
||||||
|
// --- 状态属性 (View 需要监听的数据) --- |
||||||
|
|
||||||
|
/// 可观察的企业列表数据源。 |
||||||
|
/// View 将监听此列表的变化来刷新UI。 |
||||||
|
RxList<EnterpriseListItem> get enterpriseList; |
||||||
|
|
||||||
|
/// 是否正在加载数据。 |
||||||
|
/// View 用它来显示或隐藏加载指示器 (如 `CircularProgressIndicator`)。 |
||||||
|
RxBool get isLoading; |
||||||
|
|
||||||
|
/// [筛选功能] 企业名称的文本编辑器控制器。 |
||||||
|
TextEditingController get nameController; |
||||||
|
|
||||||
|
/// [筛选功能] 已选择的企业类型。 |
||||||
|
Rx<CompanyType?> get selectedType; |
||||||
|
|
||||||
|
/// [筛选功能] 已选择的开始日期。 |
||||||
|
Rx<DateTime?> get startDate; |
||||||
|
|
||||||
|
/// [筛选功能] 已选择的结束日期。 |
||||||
|
Rx<DateTime?> get endDate; |
||||||
|
|
||||||
|
/// [选择功能] 被选中的企业集合。 |
||||||
|
/// 使用 `Set` 可以高效地进行增、删、查操作,并保证元素的唯一性。 |
||||||
|
RxSet<Enterprise> get selectedEnterprises; |
||||||
|
|
||||||
|
// --- 操作方法 (View 需要调用的行为) --- |
||||||
|
|
||||||
|
/// 执行搜索/筛选操作。 |
||||||
|
/// 由 View 中的“查询”按钮调用。 |
||||||
|
void search(); |
||||||
|
|
||||||
|
/// 清空所有筛选条件并重新加载数据。 |
||||||
|
/// 由 View 中的“重置”按钮调用。 |
||||||
|
void clearFilters(); |
||||||
|
|
||||||
|
/// 处理整个卡片的点击事件。 |
||||||
|
/// 在不同模式下(管理/选择),此方法的具体行为由子类实现。 |
||||||
|
void onItemTap(EnterpriseListItem item); |
||||||
|
|
||||||
|
/// 处理卡片上复选框状态的变化。 |
||||||
|
/// 当用户在选择模式下勾选或取消勾选时被调用。 |
||||||
|
void onSelectionChanged(Enterprise enterprise); |
||||||
|
} |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
// lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/company_enum.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; |
||||||
|
import 'base_enterprise_list_controller.dart'; |
||||||
|
// 假设你有一个专门获取待上传列表的 Usecase |
||||||
|
// import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_pending_uploads_usecase.dart'; |
||||||
|
|
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// [具体实现] 企业上传选择控制器 |
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// |
||||||
|
/// 继承自 `BaseEnterpriseListController`,专门为“选择上传企业”页面服务。 |
||||||
|
/// |
||||||
|
/// **职责:** |
||||||
|
/// - 从数据源加载**待上传**的企业列表。 |
||||||
|
/// - 管理用户的多选状态 (`selectedEnterprises`)。 |
||||||
|
/// - 处理卡片点击和复选框事件,更新选择集。 |
||||||
|
/// - 提供确认上传的方法,并将结果返回给上一个页面。 |
||||||
|
/// |
||||||
|
class EnterpriseUploadController extends BaseEnterpriseListController { |
||||||
|
// final GetPendingUploadsUsecase getPendingUploadsUsecase; |
||||||
|
// EnterpriseUploadController({required this.getPendingUploadsUsecase}); |
||||||
|
|
||||||
|
// --- 实现基类中定义的属性 --- |
||||||
|
@override |
||||||
|
final enterpriseList = <EnterpriseListItem>[].obs; |
||||||
|
@override |
||||||
|
final isLoading = false.obs; |
||||||
|
@override |
||||||
|
final selectedEnterprises = <Enterprise>{}.obs; // 此控制器会重度使用此 Set |
||||||
|
|
||||||
|
// 在上传页,复杂的筛选功能通常是不需要的。 |
||||||
|
// 我们仍然需要提供这些属性的实例以满足接口要求,但它们可能不会在UI中被使用。 |
||||||
|
@override |
||||||
|
final nameController = TextEditingController(); |
||||||
|
@override |
||||||
|
final selectedType = Rx<CompanyType?>(null); |
||||||
|
@override |
||||||
|
final startDate = Rx<DateTime?>(null); |
||||||
|
@override |
||||||
|
final endDate = Rx<DateTime?>(null); |
||||||
|
|
||||||
|
@override |
||||||
|
void onInit() { |
||||||
|
super.onInit(); |
||||||
|
fetchPendingUploads(); |
||||||
|
} |
||||||
|
|
||||||
|
// --- 实现基类中定义的方法 --- |
||||||
|
@override |
||||||
|
void search() { |
||||||
|
// 上传页的“搜索”可能只是重新加载列表 |
||||||
|
fetchPendingUploads(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void clearFilters() { |
||||||
|
// 上传页的“重置”可能只是清空一个简单的搜索框 |
||||||
|
nameController.clear(); |
||||||
|
fetchPendingUploads(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void onItemTap(EnterpriseListItem item) { |
||||||
|
// 在选择模式下,点击整个卡片的效果等同于操作复选框 |
||||||
|
onSelectionChanged(item.enterprise); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void onSelectionChanged(Enterprise enterprise) { |
||||||
|
// 核心逻辑:切换单个企业的选中状态 |
||||||
|
if (selectedEnterprises.contains(enterprise)) { |
||||||
|
selectedEnterprises.remove(enterprise); |
||||||
|
} else { |
||||||
|
selectedEnterprises.add(enterprise); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// --- EnterpriseUploadController 特有的方法 --- |
||||||
|
|
||||||
|
/// 从数据源获取待上传的企业列表 |
||||||
|
Future<void> fetchPendingUploads() async { |
||||||
|
isLoading.value = true; |
||||||
|
try { |
||||||
|
// 在这里调用您的 Usecase 来获取待上传列表 |
||||||
|
// final result = await getPendingUploadsUsecase.call(); |
||||||
|
// enterpriseList.assignAll(result); |
||||||
|
|
||||||
|
// --- 使用模拟数据进行演示 --- |
||||||
|
await Future.delayed(const Duration(seconds: 1)); |
||||||
|
// final mockData = createMockEnterpriseListItems(); |
||||||
|
// enterpriseList.assignAll(mockData); |
||||||
|
} catch (e) { |
||||||
|
Get.snackbar('错误', '加载待上传列表失败: $e'); |
||||||
|
} finally { |
||||||
|
isLoading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// 确认上传,并返回结果 |
||||||
|
void confirmUpload() { |
||||||
|
if (selectedEnterprises.isEmpty) { |
||||||
|
Get.snackbar('提示', '请至少选择一个企业进行上传'); |
||||||
|
return; |
||||||
|
} |
||||||
|
// 在这里执行实际的上传业务逻辑... |
||||||
|
print('正在上传 ${selectedEnterprises.length} 个企业...'); |
||||||
|
|
||||||
|
// 操作成功后,返回 true 通知上一个页面刷新 |
||||||
|
Get.back(result: true); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,280 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/company_enum.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/base_enterprise_list_controller.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; |
||||||
|
import 'enterprise_card.dart'; |
||||||
|
|
||||||
|
/// 定义列表项的显示模式 |
||||||
|
enum EnterpriseListPageMode { |
||||||
|
/// 普通模式,显示操作按钮 |
||||||
|
normal, |
||||||
|
|
||||||
|
/// 选择模式,显示复选框 |
||||||
|
select, |
||||||
|
} |
||||||
|
|
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// [共享UI组件] 企业列表视图 |
||||||
|
/// ----------------------------------------------------------------------------- |
||||||
|
/// |
||||||
|
/// 一个可复用的、“哑”的(Dumb)UI组件,负责渲染企业列表和筛选器。 |
||||||
|
/// |
||||||
|
/// **设计思想:** |
||||||
|
/// - **高内聚,低耦合**: 此 View 只负责 UI 的展示和用户事件的转发。它不包含任何业务逻辑。 |
||||||
|
/// - **依赖于抽象**: 通过 `GetView<BaseEnterpriseListController>`,它依赖于我们定义的 |
||||||
|
/// 抽象基类 (`BaseEnterpriseListController`),而不是任何具体的控制器实现。 |
||||||
|
/// 这使得它可以被任何实现了该接口的控制器驱动。 |
||||||
|
/// |
||||||
|
class EnterpriseListView extends StatelessWidget { |
||||||
|
const EnterpriseListView({ |
||||||
|
super.key, |
||||||
|
required this.controller, |
||||||
|
this.filterSectionVisible = true, |
||||||
|
this.itemMode = EnterpriseListPageMode.normal, |
||||||
|
}); |
||||||
|
final BaseEnterpriseListController controller; |
||||||
|
|
||||||
|
/// 是否显示顶部的筛选区域 |
||||||
|
final bool filterSectionVisible; |
||||||
|
|
||||||
|
/// 列表项的模式(按钮模式或选择模式) |
||||||
|
final EnterpriseListPageMode itemMode; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Column( |
||||||
|
children: [ |
||||||
|
// 根据参数决定是否构建筛选区域 |
||||||
|
if (filterSectionVisible) ...[ |
||||||
|
_buildFilterSection(), |
||||||
|
const Divider(height: 1, thickness: .1), |
||||||
|
], |
||||||
|
// 列表区域总是显示 |
||||||
|
Expanded(child: _buildEnterpriseList()), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建可展开的筛选查询区域。 |
||||||
|
/// 此方法的所有交互都绑定到 `BaseEnterpriseListController` 的接口, |
||||||
|
/// 因此它无需关心具体的控制器是哪个。 |
||||||
|
Widget _buildFilterSection() { |
||||||
|
return ExpansionTile( |
||||||
|
title: const Text('筛选查询'), |
||||||
|
leading: const Icon(Icons.filter_alt_outlined), |
||||||
|
initiallyExpanded: false, // 默认收起 |
||||||
|
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), |
||||||
|
|
||||||
|
// 2. 企业类型下拉框 |
||||||
|
Obx( |
||||||
|
() => DropdownButtonFormField<CompanyType>( |
||||||
|
initialValue: controller.selectedType.value, |
||||||
|
decoration: InputDecoration( |
||||||
|
labelText: '企业类型', |
||||||
|
prefixIcon: const Icon(Icons.category), |
||||||
|
border: OutlineInputBorder( |
||||||
|
borderRadius: BorderRadius.circular(8.r), |
||||||
|
), |
||||||
|
isDense: true, |
||||||
|
), |
||||||
|
hint: const Text('请选择企业类型'), |
||||||
|
isExpanded: true, |
||||||
|
items: CompanyType.values.map((type) { |
||||||
|
return DropdownMenuItem( |
||||||
|
value: type, |
||||||
|
child: Text(type.displayText), |
||||||
|
); |
||||||
|
}).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), |
||||||
|
|
||||||
|
// 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), // 增加底部间距 |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建日期选择器的辅助方法 |
||||||
|
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, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建企业列表 |
||||||
|
Widget _buildEnterpriseList() { |
||||||
|
// 使用 Obx 包裹以监听 controller 中所有 Rx 变量的变化 |
||||||
|
return Obx(() { |
||||||
|
// 在列表为空且仍在加载时显示加载指示器 |
||||||
|
if (controller.isLoading.value && controller.enterpriseList.isEmpty) { |
||||||
|
return const Center(child: CircularProgressIndicator()); |
||||||
|
} |
||||||
|
// 在加载完成但列表为空时显示提示信息 |
||||||
|
if (controller.enterpriseList.isEmpty) { |
||||||
|
return Center( |
||||||
|
child: Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
Icon( |
||||||
|
Icons.folder_off_outlined, |
||||||
|
size: 60.sp, |
||||||
|
color: Colors.grey[400], |
||||||
|
), |
||||||
|
SizedBox(height: 16.h), |
||||||
|
Text( |
||||||
|
'没有找到相关企业', |
||||||
|
style: TextStyle(fontSize: 16.sp, color: Colors.grey[600]), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// 使用下拉刷新包裹列表 |
||||||
|
return RefreshIndicator( |
||||||
|
onRefresh: () async => controller.search(), |
||||||
|
child: ListView.builder( |
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), |
||||||
|
itemCount: controller.enterpriseList.length, |
||||||
|
itemBuilder: (context, index) { |
||||||
|
final item = controller.enterpriseList[index]; |
||||||
|
final enterprise = item.enterprise; |
||||||
|
// 核心: 从 base controller 获取选中状态 |
||||||
|
final isSelected = controller.selectedEnterprises.contains( |
||||||
|
enterprise, |
||||||
|
); |
||||||
|
|
||||||
|
return Padding( |
||||||
|
padding: EdgeInsets.only(bottom: 12.h), |
||||||
|
child: EnterpriseCard( |
||||||
|
enterpriseListItem: item, |
||||||
|
isSelected: isSelected, |
||||||
|
// 根据外部传入的 itemMode 决定卡片内部的 mode |
||||||
|
mode: itemMode, |
||||||
|
// --- [核心] 将卡片的所有交互事件转发给 controller 的抽象方法 --- |
||||||
|
onTap: () => controller.onItemTap(item), |
||||||
|
onSelectionChanged: (value) => |
||||||
|
controller.onSelectionChanged(enterprise), |
||||||
|
|
||||||
|
// 对于只存在于特定 Controller 的方法,需要进行类型检查。 |
||||||
|
// 这样可以确保即使在上传页面(其控制器没有这些方法),代码也不会崩溃。 |
||||||
|
onEdit: () { |
||||||
|
if (controller is EnterpriseListController) { |
||||||
|
// 只有当控制器是 EnterpriseListController 类型时,才调用其特有方法 |
||||||
|
(controller as EnterpriseListController).navigateToEditForm( |
||||||
|
enterprise, |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
onViewProblems: () { |
||||||
|
if (controller is EnterpriseListController) { |
||||||
|
(controller as EnterpriseListController) |
||||||
|
.navigateToEnterpriseInfoPage(enterprise); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; |
||||||
|
import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; |
||||||
|
|
||||||
|
class ProfileBinding implements Bindings { |
||||||
|
@override |
||||||
|
void dependencies() { |
||||||
|
/// 注册我的控制器 |
||||||
|
Get.lazyPut<MyController>( |
||||||
|
() => MyController(authRepository: Get.find<AuthRepository>()), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; |
||||||
|
|
||||||
|
class ProblemBinding extends Bindings { |
||||||
|
@override |
||||||
|
void dependencies() { |
||||||
|
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); |
||||||
|
|
||||||
|
/// 注册问题控制器 |
||||||
|
Get.lazyPut<ProblemController>( |
||||||
|
() => ProblemController( |
||||||
|
problemRepository: Get.find<ProblemRepository>(), |
||||||
|
problemStateManager: Get.find<ProblemStateManager>(), |
||||||
|
), |
||||||
|
fenix: true, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue