11 changed files with 659 additions and 593 deletions
@ -0,0 +1,11 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
enum ProblemBindStatus { |
||||||
|
bound('绑定', Colors.green), |
||||||
|
unbound('未绑定', Colors.red); |
||||||
|
|
||||||
|
final String displayName; |
||||||
|
final Color displayColor; |
||||||
|
|
||||||
|
const ProblemBindStatus(this.displayName, this.displayColor); |
||||||
|
} |
||||||
@ -1,15 +1,20 @@ |
|||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_bind_status.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
||||||
|
|
||||||
class ProblemListItemEntity { |
class ProblemListItemEntity { |
||||||
final String id; |
final ProblemEntity problemEntity; |
||||||
final String description; |
|
||||||
final String location; |
|
||||||
final DateTime creationTime; |
|
||||||
final String enterpriseName; |
final String enterpriseName; |
||||||
|
|
||||||
ProblemListItemEntity({ |
ProblemListItemEntity({ |
||||||
required this.id, |
required this.problemEntity, |
||||||
required this.description, |
|
||||||
required this.location, |
|
||||||
required this.creationTime, |
|
||||||
required this.enterpriseName, |
required this.enterpriseName, |
||||||
}); |
}); |
||||||
|
|
||||||
|
ProblemBindStatus get boundStatus { |
||||||
|
if (problemEntity.bindData == null || problemEntity.bindData!.isEmpty) { |
||||||
|
return ProblemBindStatus.unbound; |
||||||
|
} else { |
||||||
|
return ProblemBindStatus.bound; |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,8 +1,30 @@ |
|||||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||||
|
import 'package:problem_check_system/app/core/bindings/base_bindings.dart'; |
||||||
|
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_upload_controller.dart'; |
||||||
|
|
||||||
class ProblemUploadBinding extends Bindings { |
class ProblemUploadBinding extends BaseBindings { |
||||||
@override |
@override |
||||||
void dependencies() { |
void register1Services() { |
||||||
// TODO: implement dependencies |
// TODO: implement register1Services |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void register2DataSource() { |
||||||
|
// TODO: implement register2DataSource |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void register3Repositories() { |
||||||
|
// TODO: implement register3Repositories |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void register4Usecases() { |
||||||
|
// TODO: implement register4Usecases |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void register5Controllers() { |
||||||
|
Get.lazyPut(() => ProblemUploadController()); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,97 +0,0 @@ |
|||||||
// presentation/states/problem_card_state.dart |
|
||||||
import 'package:equatable/equatable.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; |
|
||||||
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart'; |
|
||||||
|
|
||||||
class ProblemCardState extends Equatable { |
|
||||||
final String problemId; // 用于点击事件导航 |
|
||||||
final String? previewImageUrl; // [图片] 可空,因为问题可能没有图片 |
|
||||||
final String description; // [描述] |
|
||||||
final String enterpriseName; // [企业名称] |
|
||||||
final String location; // [地点] |
|
||||||
final String creationTime; // [创建时间] 已格式化好的字符串 |
|
||||||
final StatusTagState uploadStatus; // [上传状态] 包含文本和颜色 |
|
||||||
final StatusTagState bindingStatus; // [绑定状态] 包含文本和颜色 |
|
||||||
|
|
||||||
const ProblemCardState({ |
|
||||||
required this.problemId, |
|
||||||
this.previewImageUrl, |
|
||||||
required this.description, |
|
||||||
required this.enterpriseName, |
|
||||||
required this.location, |
|
||||||
required this.creationTime, |
|
||||||
required this.uploadStatus, |
|
||||||
required this.bindingStatus, |
|
||||||
}); |
|
||||||
|
|
||||||
// [核心逻辑]:从领域实体转换的工厂构造函数 |
|
||||||
factory ProblemCardState.fromEntities({ |
|
||||||
required ProblemEntity problem, |
|
||||||
required Enterprise enterprise, |
|
||||||
}) { |
|
||||||
// 1. 处理图片:取列表的第一张图作为预览,如果列表为空则为 null |
|
||||||
final String? previewImageUrl = problem.imageUrls.isNotEmpty |
|
||||||
? problem.imageUrls.first |
|
||||||
: null; |
|
||||||
|
|
||||||
// 2. 格式化时间:将 DateTime 转换为用户友好的字符串 |
|
||||||
final String formattedTime = |
|
||||||
'${problem.creationTime.year}-${problem.creationTime.month.toString().padLeft(2, '0')}-${problem.creationTime.day.toString().padLeft(2, '0')}'; |
|
||||||
|
|
||||||
// 3. 转换上传状态:将领域的 SyncStatus 映射为UI的 StatusTagState |
|
||||||
StatusTagState uploadStatusState; |
|
||||||
switch (problem.syncStatus) { |
|
||||||
case SyncStatus.synced: |
|
||||||
uploadStatusState = StatusTagState(text: '已上传', color: Colors.green); |
|
||||||
break; |
|
||||||
case SyncStatus.pendingCreate: |
|
||||||
case SyncStatus.pendingUpdate: |
|
||||||
case SyncStatus.pendingDelete: |
|
||||||
uploadStatusState = StatusTagState(text: '待上传', color: Colors.orange); |
|
||||||
break; |
|
||||||
case SyncStatus.untracked: |
|
||||||
default: |
|
||||||
uploadStatusState = StatusTagState(text: '未上传', color: Colors.grey); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// 4. 转换绑定状态:根据 bindData 是否有值来判断 |
|
||||||
final StatusTagState bindingStatusState = |
|
||||||
(problem.bindData != null && problem.bindData!.isNotEmpty) |
|
||||||
? StatusTagState(text: '已绑定', color: Colors.blue) |
|
||||||
: StatusTagState(text: '未绑定', color: Colors.red); |
|
||||||
|
|
||||||
return ProblemCardState( |
|
||||||
problemId: problem.id, |
|
||||||
previewImageUrl: previewImageUrl, |
|
||||||
description: problem.description, |
|
||||||
enterpriseName: enterprise.name, |
|
||||||
location: problem.location, |
|
||||||
creationTime: formattedTime, |
|
||||||
uploadStatus: uploadStatusState, |
|
||||||
bindingStatus: bindingStatusState, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
List<Object?> get props => [ |
|
||||||
problemId, |
|
||||||
previewImageUrl, |
|
||||||
description, |
|
||||||
enterpriseName, |
|
||||||
location, |
|
||||||
creationTime, |
|
||||||
uploadStatus, |
|
||||||
bindingStatus, |
|
||||||
]; |
|
||||||
} |
|
||||||
|
|
||||||
// 一个通用的状态标签模型,包含文本和颜色 |
|
||||||
class StatusTagState { |
|
||||||
final String text; |
|
||||||
final Color color; |
|
||||||
|
|
||||||
StatusTagState({required this.text, required this.color}); |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
class ProblemFormModel { |
|
||||||
final String enterpriseName; |
|
||||||
final String description; |
|
||||||
final String location; |
|
||||||
final List<String> imageUrls; |
|
||||||
|
|
||||||
ProblemFormModel({ |
|
||||||
required this.enterpriseName, |
|
||||||
required this.description, |
|
||||||
required this.location, |
|
||||||
required this.imageUrls, |
|
||||||
}); |
|
||||||
} |
|
||||||
@ -1,248 +1,240 @@ |
|||||||
|
import 'dart:io'; |
||||||
|
|
||||||
import 'package:flutter/material.dart'; |
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:problem_check_system/app/core/domain/entities/sync_status.dart'; |
||||||
import 'package:intl/intl.dart'; |
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart'; |
||||||
import 'package:problem_check_system/app/core/routes/app_routes.dart'; |
import 'package:problem_check_system/app/features/problem/domain/entities/problem_list_item_entity.dart'; |
||||||
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart'; |
|
||||||
import 'package:problem_check_system/app/features/problem/presentation/pages/widgets/custom_button.dart'; |
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
||||||
import 'dart:io'; // 添加文件操作支持 |
|
||||||
|
|
||||||
// 定义枚举类型 |
|
||||||
enum ProblemCardViewType { buttons, checkbox } |
|
||||||
|
|
||||||
class ProblemCard extends StatelessWidget { |
class ProblemCard extends StatelessWidget { |
||||||
final Problem problem; |
final ProblemListItemEntity problemListItem; |
||||||
final ProblemCardViewType viewType; |
final bool isSelected; |
||||||
final Function(Problem, bool)? onChanged; |
final VoidCallback? onTap; |
||||||
final Function()? onTap; |
final Widget? actions; |
||||||
final bool isSelected; // 改为必需参数 |
|
||||||
|
|
||||||
const ProblemCard({ |
const ProblemCard({ |
||||||
super.key, |
super.key, |
||||||
required this.problem, |
required this.problemListItem, |
||||||
this.viewType = ProblemCardViewType.buttons, |
this.isSelected = false, |
||||||
this.onChanged, |
|
||||||
this.onTap, |
this.onTap, |
||||||
required this.isSelected, // 改为必需参数 |
this.actions, |
||||||
}); |
}); |
||||||
|
|
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
Widget build(BuildContext context) { |
||||||
// 根据是否已删除决定卡片的颜色 |
|
||||||
final bool isDeleted = |
final bool isDeleted = |
||||||
problem.syncStatus == ProblemSyncStatus.pendingDelete; |
problemListItem.problemEntity.syncStatus == SyncStatus.pendingDelete; |
||||||
final Color cardColor = isDeleted |
final Color cardColor = isDeleted |
||||||
? Colors.grey[300]! |
? Colors.grey[200]! // 使用更浅的灰色以保证内容可读 |
||||||
: Theme.of(context).cardColor; |
: Theme.of(context).cardColor; |
||||||
final Color contentColor = isDeleted |
final Color contentColor = isDeleted |
||||||
? Colors.grey[600]! |
? Colors.grey[600]! |
||||||
: Theme.of(context).textTheme.bodyMedium!.color!; |
: Theme.of(context).textTheme.bodyMedium!.color!; |
||||||
|
|
||||||
return Card( |
return Card( |
||||||
|
elevation: isSelected ? 4.0 : 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), |
||||||
|
), |
||||||
|
clipBehavior: Clip.antiAlias, |
||||||
|
margin: EdgeInsets.zero, |
||||||
color: cardColor, |
color: cardColor, |
||||||
child: InkWell( |
child: InkWell( |
||||||
onTap: viewType == ProblemCardViewType.checkbox |
onTap: onTap, |
||||||
? () { |
// FIX 5: 添加了整体的内边距 |
||||||
onChanged?.call(problem, !isSelected); |
|
||||||
} |
|
||||||
: null, |
|
||||||
child: Column( |
child: Column( |
||||||
mainAxisSize: MainAxisSize.min, |
children: [ |
||||||
children: <Widget>[ |
Padding( |
||||||
ListTile( |
padding: EdgeInsets.all(12.r), |
||||||
leading: _buildImageWidget(isDeleted), // 使用新的图片构建方法 |
child: Column( |
||||||
title: Text( |
mainAxisSize: MainAxisSize.min, |
||||||
'问题描述', |
children: <Widget>[ |
||||||
style: TextStyle(fontSize: 16.sp, color: contentColor), |
Row( |
||||||
), |
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
subtitle: LayoutBuilder( |
children: [ |
||||||
builder: (context, constraints) { |
_buildImageWidget(isDeleted), |
||||||
return Text( |
SizedBox(width: 12.w), // FIX 7: 统一单位 |
||||||
problem.description, |
// 使用 Expanded 确保文字部分能自适应宽度 |
||||||
maxLines: 2, |
Expanded( |
||||||
overflow: TextOverflow.ellipsis, |
child: _buildDescriptionAndCompany(contentColor), |
||||||
style: TextStyle(fontSize: 14.sp, color: contentColor), |
), |
||||||
); |
], |
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
SizedBox(height: 8.h), |
|
||||||
Row( |
|
||||||
children: [ |
|
||||||
SizedBox(width: 16.w), |
|
||||||
Icon(Icons.location_on, color: contentColor, size: 16.h), |
|
||||||
SizedBox(width: 8.w), |
|
||||||
SizedBox( |
|
||||||
width: 100.w, |
|
||||||
child: Text( |
|
||||||
problem.location, |
|
||||||
style: TextStyle(fontSize: 12.sp, color: contentColor), |
|
||||||
overflow: TextOverflow.ellipsis, |
|
||||||
maxLines: 1, |
|
||||||
), |
|
||||||
), |
|
||||||
SizedBox(width: 16.w), |
|
||||||
Icon(Icons.access_time, color: contentColor, size: 16.h), |
|
||||||
SizedBox(width: 8.w), |
|
||||||
Expanded( |
|
||||||
child: Text( |
|
||||||
DateFormat( |
|
||||||
'yyyy-MM-dd HH:mm:ss', |
|
||||||
).format(problem.creationTime), |
|
||||||
style: TextStyle(fontSize: 12.sp, color: contentColor), |
|
||||||
), |
), |
||||||
), |
SizedBox(height: 12.h), |
||||||
], |
// const Divider(height: 1, color: Color(0xFFEEEEEE)), |
||||||
), |
// SizedBox(height: 10.h), |
||||||
SizedBox(height: 8.h), |
_buildLocationAndTime(contentColor), |
||||||
Row( |
], |
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
), |
||||||
children: <Widget>[ |
|
||||||
SizedBox(width: 16.w), |
|
||||||
Wrap( |
|
||||||
spacing: 8, |
|
||||||
children: [ |
|
||||||
...problem.syncStatus == ProblemSyncStatus.pendingDelete |
|
||||||
? [ |
|
||||||
TDTag( |
|
||||||
'服务器未删除', |
|
||||||
isLight: true, |
|
||||||
theme: TDTagTheme.defaultTheme, |
|
||||||
textColor: isDeleted ? Colors.grey[700] : null, |
|
||||||
// backgroundColor: isDeleted |
|
||||||
// ? Colors.grey[400] |
|
||||||
// : null, |
|
||||||
), |
|
||||||
] |
|
||||||
: [ |
|
||||||
problem.syncStatus == ProblemSyncStatus.synced |
|
||||||
? TDTag( |
|
||||||
'已上传', |
|
||||||
isLight: true, |
|
||||||
theme: TDTagTheme.success, |
|
||||||
) |
|
||||||
: TDTag( |
|
||||||
'未上传', |
|
||||||
isLight: true, |
|
||||||
theme: TDTagTheme.danger, |
|
||||||
), |
|
||||||
problem.bindData != null && |
|
||||||
problem.bindData!.isNotEmpty |
|
||||||
? TDTag( |
|
||||||
'已绑定', |
|
||||||
isLight: true, |
|
||||||
theme: TDTagTheme.primary, |
|
||||||
) |
|
||||||
: TDTag( |
|
||||||
'未绑定', |
|
||||||
isLight: true, |
|
||||||
theme: TDTagTheme.warning, |
|
||||||
), |
|
||||||
], |
|
||||||
], |
|
||||||
), |
|
||||||
const Spacer(), |
|
||||||
_buildBottomActions(isDeleted), |
|
||||||
], |
|
||||||
), |
), |
||||||
SizedBox(height: 8.h), |
// 如果有 actions,才显示分割线和底部行 |
||||||
|
if (actions != null) ...[ |
||||||
|
// const Divider(height: 1, color: Color(0xFFEEEEEE)), |
||||||
|
// SizedBox(height: 10.h), |
||||||
|
_buildBottomActionRow(), |
||||||
|
], |
||||||
], |
], |
||||||
), |
), |
||||||
), |
), |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
|
Widget _buildDescriptionAndCompany(Color contentColor) { |
||||||
|
return Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Text( |
||||||
|
'问题描述', |
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 9.sp), |
||||||
|
), |
||||||
|
SizedBox(height: 2.h), |
||||||
|
// FIX 3: 使用动态数据 |
||||||
|
Text( |
||||||
|
problemListItem.problemEntity.description, |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 12.5.sp, |
||||||
|
color: contentColor, |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
), |
||||||
|
maxLines: 1, |
||||||
|
overflow: TextOverflow.ellipsis, |
||||||
|
), |
||||||
|
Text( |
||||||
|
'企业名称', |
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 9.sp), |
||||||
|
), |
||||||
|
SizedBox(height: 2.h), |
||||||
|
// FIX 3: 使用动态数据 |
||||||
|
Text( |
||||||
|
problemListItem.enterpriseName, |
||||||
|
style: TextStyle( |
||||||
|
fontSize: 12.5.sp, |
||||||
|
color: contentColor, |
||||||
|
fontWeight: FontWeight.bold, |
||||||
|
), |
||||||
|
maxLines: 1, |
||||||
|
overflow: TextOverflow.ellipsis, |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
Widget _buildImageWidget(bool isDeleted) { |
Widget _buildImageWidget(bool isDeleted) { |
||||||
return AspectRatio( |
return SizedBox( |
||||||
aspectRatio: 1, // 强制正方形 |
width: 64.w, |
||||||
child: _buildImageContent(isDeleted), |
height: 64.w, |
||||||
|
child: ClipRRect( |
||||||
|
borderRadius: BorderRadius.circular(8.r), |
||||||
|
child: _buildImageContent(isDeleted), |
||||||
|
), |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
Widget _buildImageContent(bool isDeleted) { |
Widget _buildImageContent(bool isDeleted) { |
||||||
// 检查是否有图片路径 |
// FIX 1: 安全地访问图片列表 |
||||||
if (problem.imageUrls.isEmpty || problem.imageUrls[0].localPath.isEmpty) { |
final String? imagePath = problemListItem.problemEntity.imageUrls.isNotEmpty |
||||||
// 如果没有图片,显示默认占位图 |
? problemListItem.problemEntity.imageUrls.first |
||||||
return Image.asset( |
: null; |
||||||
'assets/images/problem_preview.png', |
|
||||||
fit: BoxFit.cover, // 使用 cover 来填充正方形区域 |
|
||||||
color: isDeleted ? Colors.grey[500] : null, |
|
||||||
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
final String imagePath = problem.imageUrls[0].localPath; |
final placeholder = Image.asset( |
||||||
|
'assets/images/problem_preview.png', |
||||||
|
fit: BoxFit.cover, |
||||||
|
color: isDeleted ? Colors.grey[500] : null, |
||||||
|
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
||||||
|
); |
||||||
|
|
||||||
// 检查文件是否存在 |
if (imagePath == null || imagePath.isEmpty) { |
||||||
final File imageFile = File(imagePath); |
return placeholder; |
||||||
if (!imageFile.existsSync()) { |
|
||||||
// 如果文件不存在,显示默认占位图 |
|
||||||
return Image.asset( |
|
||||||
'assets/images/problem_preview.png', |
|
||||||
fit: BoxFit.cover, |
|
||||||
color: isDeleted ? Colors.grey[500] : null, |
|
||||||
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
|
||||||
); |
|
||||||
} |
} |
||||||
|
|
||||||
// 如果文件存在,使用 Image.file 加载 |
// BEST PRACTICE 1: 移除 existsSync() |
||||||
return Image.file( |
return Image.file( |
||||||
imageFile, |
File(imagePath), |
||||||
fit: BoxFit.cover, // 使用 cover 来填充正方形区域 |
fit: BoxFit.cover, |
||||||
color: isDeleted ? Colors.grey[500] : null, |
color: isDeleted ? Colors.grey[500] : null, |
||||||
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
||||||
errorBuilder: (context, error, stackTrace) { |
errorBuilder: (context, error, stackTrace) { |
||||||
// 如果加载失败,显示默认图片 |
// 如果加载失败(包括文件不存在),显示默认图片 |
||||||
return Image.asset( |
return placeholder; |
||||||
'assets/images/problem_preview.png', |
|
||||||
fit: BoxFit.cover, |
|
||||||
color: isDeleted ? Colors.grey[500] : null, |
|
||||||
colorBlendMode: isDeleted ? BlendMode.saturation : null, |
|
||||||
); |
|
||||||
}, |
}, |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
Widget _buildBottomActions(bool isDeleted) { |
Widget _buildIconText(IconData icon, String text, Color color) { |
||||||
switch (viewType) { |
return Row( |
||||||
case ProblemCardViewType.buttons: |
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
return Row( |
mainAxisSize: MainAxisSize.min, // 让 Row 只占据必要的宽度 |
||||||
children: [ |
children: [ |
||||||
if (!isDeleted) |
Icon(icon, color: color, size: 16.r), |
||||||
CustomButton( |
SizedBox(width: 4.w), |
||||||
text: '修改', |
Text( |
||||||
onTap: () { |
text, |
||||||
onTap; |
style: TextStyle(fontSize: 12.sp, color: color), |
||||||
// controller.toProblemFormPageAndRefresh(problem: problem); |
overflow: TextOverflow.ellipsis, |
||||||
}, |
maxLines: 1, |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildLocationAndTime(Color contentColor) { |
||||||
|
return Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
Flexible( |
||||||
|
child: _buildIconText( |
||||||
|
Icons.location_on_outlined, |
||||||
|
problemListItem.problemEntity.location, |
||||||
|
contentColor, |
||||||
|
), |
||||||
|
), |
||||||
|
SizedBox(width: 12.w), |
||||||
|
_buildIconText( |
||||||
|
Icons.access_time_outlined, |
||||||
|
problemListItem.problemEntity.creationTime.toDateTimeString(), |
||||||
|
contentColor, |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildBottomActionRow() { |
||||||
|
return Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
Padding( |
||||||
|
padding: EdgeInsets.only(left: 12.r, bottom: 12.r), |
||||||
|
child: Row( |
||||||
|
children: [ |
||||||
|
TDTag( |
||||||
|
problemListItem.problemEntity.syncStatus.displayName, |
||||||
|
textColor: |
||||||
|
problemListItem.problemEntity.syncStatus.displayColor, |
||||||
|
backgroundColor: problemListItem |
||||||
|
.problemEntity |
||||||
|
.syncStatus |
||||||
|
.displayColor |
||||||
|
.withAlpha(20), |
||||||
), |
), |
||||||
if (!isDeleted) SizedBox(width: 8.w), |
SizedBox(width: 8.w), |
||||||
CustomButton( |
TDTag( |
||||||
text: '查看', |
problemListItem.boundStatus.displayName, |
||||||
onTap: () { |
textColor: problemListItem.boundStatus.displayColor, |
||||||
Get.toNamed( |
backgroundColor: problemListItem.boundStatus.displayColor |
||||||
AppRoutes.problemForm, |
.withAlpha(20), |
||||||
arguments: problem, |
), |
||||||
parameters: {'isReadOnly': 'true'}, |
], |
||||||
); |
|
||||||
}, |
|
||||||
), |
|
||||||
SizedBox(width: 16.w), |
|
||||||
], |
|
||||||
); |
|
||||||
case ProblemCardViewType.checkbox: |
|
||||||
return Padding( |
|
||||||
padding: EdgeInsets.only(right: 16.w), |
|
||||||
child: Checkbox( |
|
||||||
value: isSelected, |
|
||||||
onChanged: (bool? value) { |
|
||||||
if (value != null) { |
|
||||||
onChanged?.call(problem, value); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
), |
||||||
); |
), |
||||||
} |
|
||||||
|
const Spacer(), |
||||||
|
// FIX 2: 安全地处理 actions |
||||||
|
if (actions != null) actions!, |
||||||
|
], |
||||||
|
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue