21 changed files with 701 additions and 607 deletions
@ -1,13 +1,9 @@ |
|||||||
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'; |
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() { |
||||||
Get.lazyPut<EnterpriseUploadController>(() => EnterpriseUploadController()); |
Get.lazyPut<EnterpriseUploadController>(() => EnterpriseUploadController()); |
||||||
Get.lazyPut<BaseEnterpriseListController>( |
|
||||||
() => Get.find<EnterpriseUploadController>(), |
|
||||||
); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,66 +0,0 @@ |
|||||||
// 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); |
|
||||||
} |
|
||||||
@ -1,280 +0,0 @@ |
|||||||
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,230 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||||
|
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart'; |
||||||
|
import 'package:problem_check_system/app/core/models/sync_status.dart'; |
||||||
|
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; |
||||||
|
|
||||||
|
// [重构 1] 新的统一卡片组件 |
||||||
|
class UnifiedEnterpriseCard extends StatelessWidget { |
||||||
|
final EnterpriseListItem enterpriseListItem; |
||||||
|
final bool isSelected; |
||||||
|
final VoidCallback? onTap; |
||||||
|
|
||||||
|
final Widget? actions; |
||||||
|
|
||||||
|
const UnifiedEnterpriseCard({ |
||||||
|
super.key, |
||||||
|
required this.enterpriseListItem, |
||||||
|
this.isSelected = false, // 默认未选中 |
||||||
|
this.onTap, |
||||||
|
this.actions, |
||||||
|
}); |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Card( |
||||||
|
elevation: isSelected ? 4.0 : .2, |
||||||
|
shape: RoundedRectangleBorder( |
||||||
|
borderRadius: BorderRadius.circular(12.r), |
||||||
|
side: isSelected |
||||||
|
? BorderSide(color: Theme.of(context).primaryColor, width: 1.5.w) |
||||||
|
: BorderSide(color: Colors.grey.shade300, width: 1.w), |
||||||
|
), |
||||||
|
// [关键 1] 必须剪裁,才能让按钮的形状与卡片边缘完美融合 |
||||||
|
clipBehavior: Clip.antiAlias, |
||||||
|
margin: EdgeInsets.zero, |
||||||
|
child: InkWell( |
||||||
|
onTap: onTap, |
||||||
|
// [关键 2] 使用 Column 作为主布局 |
||||||
|
child: Column( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
// --- 区域 1: 顶部和中部的内容 --- |
||||||
|
// 这部分内容有完整的左右内边距 |
||||||
|
Padding( |
||||||
|
padding: EdgeInsets.all(16.0.w), |
||||||
|
child: Column( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
_buildTopSection(), |
||||||
|
SizedBox(height: 12.h), |
||||||
|
_buildMiddleSection(), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
|
||||||
|
// 可选的分割线,让布局更清晰 |
||||||
|
// if (actions != null) const Divider(height: 1), |
||||||
|
|
||||||
|
// --- 区域 2: 底部的操作栏 --- |
||||||
|
// [关键 3] 这一行是整个布局的核心 |
||||||
|
if (actions != null) _buildBottomActionRow(), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建顶部区域:企业名称、类型和状态 |
||||||
|
Widget _buildTopSection() { |
||||||
|
return Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: [ |
||||||
|
Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Text( |
||||||
|
'企业名称', |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 9.sp, |
||||||
|
color: Colors.grey.shade500, |
||||||
|
), // .sp 适配字体 |
||||||
|
), |
||||||
|
SizedBox(height: 4.h), // .h 适配垂直间距 |
||||||
|
Text( |
||||||
|
enterpriseListItem.enterprise.name, |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 12.5.sp, |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
color: Colors.black87, |
||||||
|
), |
||||||
|
overflow: TextOverflow.ellipsis, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
|
||||||
|
Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Text( |
||||||
|
'企业类型', |
||||||
|
style: TextStyle(fontSize: 9.sp, color: Colors.grey.shade500), |
||||||
|
), |
||||||
|
SizedBox(height: 4.h), |
||||||
|
Text( |
||||||
|
enterpriseListItem.enterprise.type, |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 12.5.sp, |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
color: Colors.black87, |
||||||
|
), |
||||||
|
overflow: TextOverflow.ellipsis, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
// SizedBox(width: 8.w), |
||||||
|
Container( |
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), |
||||||
|
decoration: BoxDecoration( |
||||||
|
borderRadius: BorderRadius.circular(8.r), |
||||||
|
border: Border.all(color: Colors.red.shade400, width: 1.w), |
||||||
|
), |
||||||
|
child: Text( |
||||||
|
enterpriseListItem.enterprise.syncStatus == SyncStatus.synced |
||||||
|
? '信息已上传' |
||||||
|
: '信息未上传', |
||||||
|
style: TextStyle(fontSize: 7.sp, color: Colors.red.shade400), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建中间区域:问题总数和创建时间 |
||||||
|
Widget _buildMiddleSection() { |
||||||
|
return Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: [ |
||||||
|
Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Icon(Icons.description_outlined, color: Colors.grey, size: 16.sp), |
||||||
|
SizedBox(width: 4.w), |
||||||
|
Text( |
||||||
|
'问题总数: ', |
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.grey), |
||||||
|
), |
||||||
|
Text( |
||||||
|
enterpriseListItem.totalProblems.toString(), |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 12.5.sp, |
||||||
|
color: Colors.black87, |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
Icon(Icons.access_time, color: Colors.grey, size: 16.sp), |
||||||
|
Text( |
||||||
|
'创建时间: ${enterpriseListItem.enterprise.creationTime.toDateTimeString()}', |
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.grey), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 构建底部的“标签+操作”行 |
||||||
|
Widget _buildBottomActionRow() { |
||||||
|
return Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
// [关键 4] 只给左侧的标签组添加左边距 |
||||||
|
Padding( |
||||||
|
padding: EdgeInsets.only(left: 16.w), |
||||||
|
child: Row( |
||||||
|
children: [ |
||||||
|
_buildTag( |
||||||
|
text: '已上传 ${enterpriseListItem.uploadedProblems}', |
||||||
|
textColor: Colors.blue.shade700, |
||||||
|
backgroundColor: Colors.blue.shade50, |
||||||
|
), |
||||||
|
SizedBox(width: 8.w), |
||||||
|
_buildTag( |
||||||
|
text: '未上传 ${enterpriseListItem.pendingProblems}', |
||||||
|
textColor: Colors.red.shade600, |
||||||
|
backgroundColor: Colors.red.shade50, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
|
||||||
|
const Spacer(), |
||||||
|
|
||||||
|
// [关键 5] actions 插槽在这里,它自身没有任何外层 Padding,所以可以贴边 |
||||||
|
actions!, |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// 用于创建“已上传”和“未上传”标签的辅助方法 |
||||||
|
Widget _buildTag({ |
||||||
|
required String text, |
||||||
|
required Color textColor, |
||||||
|
required Color backgroundColor, |
||||||
|
}) { |
||||||
|
return Container( |
||||||
|
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), |
||||||
|
decoration: BoxDecoration( |
||||||
|
color: backgroundColor, |
||||||
|
// borderRadius: BorderRadius.circular(4.r), |
||||||
|
// border: Border.all(color: textColor.withAlpha(128), width: 1.w), |
||||||
|
), |
||||||
|
child: Text( |
||||||
|
text, |
||||||
|
style: TextStyle( |
||||||
|
color: textColor, |
||||||
|
fontSize: 10.sp, |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue