重点工程管理完
parent
4e0bad563d
commit
b756e4e809
|
|
@ -169,7 +169,6 @@ class ListItemFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 类型6:文本和视频上下布局
|
/// 类型6:文本和视频上下布局
|
||||||
|
|
||||||
static Widget createTextVideoItem({
|
static Widget createTextVideoItem({
|
||||||
required String text,
|
required String text,
|
||||||
required String videoUrl,
|
required String videoUrl,
|
||||||
|
|
@ -195,6 +194,7 @@ class ListItemFactory {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
videoUrl.isNotEmpty ?
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: onVideoTapped,
|
onTap: onVideoTapped,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -212,7 +212,7 @@ class ListItemFactory {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
) : SizedBox(height: 10,)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -255,6 +255,7 @@ class ListItemFactory {
|
||||||
double horizontalPadding = 10,
|
double horizontalPadding = 10,
|
||||||
bool isEdit = true,
|
bool isEdit = true,
|
||||||
String text = '',
|
String text = '',
|
||||||
|
bool isRequired = false,
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
@ -273,13 +274,18 @@ class ListItemFactory {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Row(
|
||||||
title,
|
children: [
|
||||||
style: TextStyle(
|
if (isRequired && isEdit) Text('* ', style: TextStyle(color: Colors.red)),
|
||||||
fontSize: 15,
|
Text(
|
||||||
fontWeight: FontWeight.bold,
|
title,
|
||||||
color: Colors.black,
|
style: TextStyle(
|
||||||
),
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isEdit)
|
if (isEdit)
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,14 @@ class BottomPicker {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.of(ctx).pop();
|
Navigator.of(ctx).pop();
|
||||||
},
|
},
|
||||||
child: const Text('取消'),
|
child: const Text('取消', style: TextStyle(color: Colors.black54, fontSize: 16),),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.of(ctx).pop(selected);
|
Navigator.of(ctx).pop(selected);
|
||||||
},
|
},
|
||||||
child: const Text('确定'),
|
child: const Text('确定', style: TextStyle(color: Colors.blue, fontSize: 16),),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,7 @@ import 'ItemWidgetFactory.dart';
|
||||||
enum MediaType { image, video }
|
enum MediaType { image, video }
|
||||||
|
|
||||||
/// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示
|
/// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示
|
||||||
/// 使用示例:
|
/// 新增 isEdit 属性控制编辑状态
|
||||||
/// MediaPickerGrid(
|
|
||||||
/// maxCount: 4,
|
|
||||||
/// mediaType: MediaType.video,
|
|
||||||
/// initialMediaPaths: ['https://...', '/local/path.png'],
|
|
||||||
/// onChanged: (List<File> medias) {},
|
|
||||||
/// onMediaAdded: (String path) {},
|
|
||||||
/// onMediaRemoved: (String path) {},
|
|
||||||
/// ),
|
|
||||||
class MediaPickerRow extends StatefulWidget {
|
class MediaPickerRow extends StatefulWidget {
|
||||||
final int maxCount;
|
final int maxCount;
|
||||||
final MediaType mediaType;
|
final MediaType mediaType;
|
||||||
|
|
@ -26,6 +18,8 @@ class MediaPickerRow extends StatefulWidget {
|
||||||
final ValueChanged<List<File>> onChanged;
|
final ValueChanged<List<File>> onChanged;
|
||||||
final ValueChanged<String>? onMediaAdded;
|
final ValueChanged<String>? onMediaAdded;
|
||||||
final ValueChanged<String>? onMediaRemoved;
|
final ValueChanged<String>? onMediaRemoved;
|
||||||
|
final ValueChanged<String>? onMediaTapped; // 新增:媒体点击回调
|
||||||
|
final bool isEdit; // 新增:控制编辑状态
|
||||||
|
|
||||||
const MediaPickerRow({
|
const MediaPickerRow({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
|
@ -35,6 +29,8 @@ class MediaPickerRow extends StatefulWidget {
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
this.onMediaAdded,
|
this.onMediaAdded,
|
||||||
this.onMediaRemoved,
|
this.onMediaRemoved,
|
||||||
|
this.onMediaTapped, // 新增
|
||||||
|
this.isEdit = true, // 默认可编辑
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -59,6 +55,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showPickerOptions() async {
|
Future<void> _showPickerOptions() async {
|
||||||
|
if (!widget.isEdit) return; // 不可编辑时直接返回
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
|
@ -82,7 +80,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
titleAlignment: ListTileTitleAlignment.center,
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
widget.mediaType == MediaType.image
|
widget.mediaType == MediaType.image
|
||||||
? Icons.photo_library
|
? Icons.photo_library
|
||||||
|
|
@ -100,7 +97,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
titleAlignment: ListTileTitleAlignment.center,
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
|
||||||
leading: const Icon(Icons.close),
|
leading: const Icon(Icons.close),
|
||||||
title: const Text('取消'),
|
title: const Text('取消'),
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
|
@ -112,7 +108,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pickCamera() async {
|
Future<void> _pickCamera() async {
|
||||||
if (_mediaPaths.length >= widget.maxCount) return;
|
if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
XFile? picked;
|
XFile? picked;
|
||||||
if (widget.mediaType == MediaType.image) {
|
if (widget.mediaType == MediaType.image) {
|
||||||
|
|
@ -132,7 +129,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pickGallery() async {
|
Future<void> _pickGallery() async {
|
||||||
if (_mediaPaths.length >= widget.maxCount) return;
|
if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
|
||||||
|
|
||||||
final permission = await PhotoManager.requestPermissionExtend();
|
final permission = await PhotoManager.requestPermissionExtend();
|
||||||
if (permission != PermissionState.authorized &&
|
if (permission != PermissionState.authorized &&
|
||||||
permission != PermissionState.limited) {
|
permission != PermissionState.limited) {
|
||||||
|
|
@ -172,6 +170,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeMedia(int index) {
|
void _removeMedia(int index) {
|
||||||
|
if (!widget.isEdit) return; // 不可编辑时不允许删除
|
||||||
|
|
||||||
final removed = _mediaPaths[index];
|
final removed = _mediaPaths[index];
|
||||||
setState(() => _mediaPaths.removeAt(index));
|
setState(() => _mediaPaths.removeAt(index));
|
||||||
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
||||||
|
|
@ -180,6 +180,9 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount;
|
||||||
|
final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0);
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
|
@ -188,45 +191,51 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
childAspectRatio: 1,
|
childAspectRatio: 1,
|
||||||
mainAxisExtent: 80,
|
// mainAxisExtent: 80,
|
||||||
|
|
||||||
),
|
),
|
||||||
itemCount: _mediaPaths.length < widget.maxCount
|
itemCount: itemCount,
|
||||||
? _mediaPaths.length + 1
|
|
||||||
: widget.maxCount,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
// 显示媒体项
|
||||||
if (index < _mediaPaths.length) {
|
if (index < _mediaPaths.length) {
|
||||||
final path = _mediaPaths[index];
|
final path = _mediaPaths[index];
|
||||||
final isNetwork = path.startsWith('http');
|
final isNetwork = path.startsWith('http');
|
||||||
return Stack(
|
|
||||||
children: [
|
return GestureDetector(
|
||||||
ClipRRect(
|
onTap: () => widget.onMediaTapped?.call(path),
|
||||||
borderRadius: BorderRadius.circular(5),
|
child: Stack(
|
||||||
child: widget.mediaType == MediaType.image
|
children: [
|
||||||
? (isNetwork
|
ClipRRect(
|
||||||
? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,)
|
borderRadius: BorderRadius.circular(5),
|
||||||
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
child: widget.mediaType == MediaType.image
|
||||||
: Container(
|
? (isNetwork
|
||||||
color: Colors.black12,
|
? Image.network(path, fit: BoxFit.cover, width: 80, height: 80)
|
||||||
child: const Center(
|
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
||||||
child: Icon(
|
: Container(
|
||||||
Icons.videocam,
|
color: Colors.black12,
|
||||||
color: Colors.white70,
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.videocam,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 只在可编辑状态下显示删除按钮
|
||||||
Positioned(
|
if (widget.isEdit)
|
||||||
top: -15,
|
Positioned(
|
||||||
right: -15,
|
top: -15,
|
||||||
child: IconButton(
|
right: -15,
|
||||||
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
|
child: IconButton(
|
||||||
onPressed: () => _removeMedia(index),
|
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
|
||||||
),
|
onPressed: () => _removeMedia(index),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
|
// 显示添加按钮
|
||||||
|
else if (showAddButton) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: _showPickerOptions,
|
onTap: _showPickerOptions,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -239,6 +248,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -246,6 +257,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 照片上传区域组件,使用纵向四列Grid展示
|
/// 照片上传区域组件,使用纵向四列Grid展示
|
||||||
|
/// 新增 isEdit 属性控制编辑状态
|
||||||
class RepairedPhotoSection extends StatefulWidget {
|
class RepairedPhotoSection extends StatefulWidget {
|
||||||
final int maxCount;
|
final int maxCount;
|
||||||
final MediaType mediaType;
|
final MediaType mediaType;
|
||||||
|
|
@ -254,11 +266,13 @@ class RepairedPhotoSection extends StatefulWidget {
|
||||||
final ValueChanged<List<File>> onChanged;
|
final ValueChanged<List<File>> onChanged;
|
||||||
final ValueChanged<String>? onMediaAdded;
|
final ValueChanged<String>? onMediaAdded;
|
||||||
final ValueChanged<String>? onMediaRemoved;
|
final ValueChanged<String>? onMediaRemoved;
|
||||||
|
final ValueChanged<String>? onMediaTapped; // 新增:媒体点击回调
|
||||||
final VoidCallback onAiIdentify;
|
final VoidCallback onAiIdentify;
|
||||||
final bool isShowAI;
|
final bool isShowAI;
|
||||||
final double horizontalPadding;
|
final double horizontalPadding;
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
final bool isShowNum;
|
final bool isShowNum;
|
||||||
|
final bool isEdit; // 新增:控制编辑状态
|
||||||
|
|
||||||
const RepairedPhotoSection({
|
const RepairedPhotoSection({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
|
@ -272,8 +286,10 @@ class RepairedPhotoSection extends StatefulWidget {
|
||||||
this.horizontalPadding = 5,
|
this.horizontalPadding = 5,
|
||||||
this.onMediaAdded,
|
this.onMediaAdded,
|
||||||
this.onMediaRemoved,
|
this.onMediaRemoved,
|
||||||
|
this.onMediaTapped, // 新增
|
||||||
this.isRequired = false,
|
this.isRequired = false,
|
||||||
this.isShowNum = true,
|
this.isShowNum = true,
|
||||||
|
this.isEdit = true, // 默认可编辑
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -324,10 +340,12 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
||||||
},
|
},
|
||||||
onMediaAdded: widget.onMediaAdded,
|
onMediaAdded: widget.onMediaAdded,
|
||||||
onMediaRemoved: widget.onMediaRemoved,
|
onMediaRemoved: widget.onMediaRemoved,
|
||||||
|
onMediaTapped: widget.onMediaTapped, // 传递点击回调
|
||||||
|
isEdit: widget.isEdit, // 传递编辑状态
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (widget.isShowAI)
|
if (widget.isShowAI && widget.isEdit) // 只在可编辑状态下显示AI按钮
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding),
|
padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
|
|
||||||
|
|
@ -1007,7 +1007,7 @@ U6Hzm1ninpWeE+awIDAQAB
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/// 安全检查发起
|
/// 安全检查发起
|
||||||
static Future<Map<String, dynamic>> addSafeCheckReciord(String OUTSOURCED_ID) {
|
static Future<Map<String, dynamic>> addSafeCheckRecord(String OUTSOURCED_ID) {
|
||||||
return HttpManager().request(
|
return HttpManager().request(
|
||||||
basePath,
|
basePath,
|
||||||
'/app/keyProjects/goEdit',
|
'/app/keyProjects/goEdit',
|
||||||
|
|
@ -1018,6 +1018,17 @@ U6Hzm1ninpWeE+awIDAQAB
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// 安全检查
|
||||||
|
static Future<Map<String, dynamic>> getSafeCheckGoEdit(String KEYPROJECTCHECK_ID) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectcheck/goEdit',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
"KEYPROJECTCHECK_ID":KEYPROJECTCHECK_ID,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
/// 安全检查单位现场负责人列表
|
/// 安全检查单位现场负责人列表
|
||||||
static Future<Map<String, dynamic>> getSafeCheckPersonList(String UNITS_ID, String NOMAIN) {
|
static Future<Map<String, dynamic>> getSafeCheckPersonList(String UNITS_ID, String NOMAIN) {
|
||||||
return HttpManager().request(
|
return HttpManager().request(
|
||||||
|
|
@ -1055,8 +1066,113 @@ U6Hzm1ninpWeE+awIDAQAB
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// 安全检查发起提交
|
||||||
|
static Future<Map<String, dynamic>> safeCheckPunishSubmit(Map data) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectpunish/add',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 安全检查发起提交
|
||||||
|
static Future<Map<String, dynamic>> safeKeyprojectCheckSubmit(Map data) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectcheck/add',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 重点工程隐患管理列表
|
||||||
|
static Future<Map<String, dynamic>> getKeyprojectDangerList(String url, Map data) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
url,
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 重点工程隐患详情
|
||||||
|
static Future<Map<String, dynamic>> getKeyprojectDangerFindHidden(String HIDDEN_ID, String OUTSOURCED_ID) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectcheck/findHidden',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
'HIDDEN_ID':HIDDEN_ID,
|
||||||
|
'OUTSOURCED_ID':OUTSOURCED_ID
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 重点工程隐患验收
|
||||||
|
static Future<Map<String, dynamic>> checkKeyprojectDanger(String HIDDEN_ID, Map data) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectcheck/check',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
"CORPINFO_ID":SessionService.instance.corpinfoId,
|
||||||
|
"CREATOR":SessionService.instance.loginUserId,
|
||||||
|
"OPERATOR":SessionService.instance.loginUserId,
|
||||||
|
'HIDDEN_ID':HIDDEN_ID,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传图片
|
||||||
|
static Future<Map<String, dynamic>> addNormalImgFiles(String imagePath, Map data) async {
|
||||||
|
final file = File(imagePath);
|
||||||
|
if (!await file.exists()) {
|
||||||
|
throw ApiException('file_not_found', '图片不存在:$imagePath');
|
||||||
|
}
|
||||||
|
final fileName = file.path.split(Platform.pathSeparator).last;
|
||||||
|
return HttpManager().uploadFaceImage(
|
||||||
|
baseUrl: basePath,
|
||||||
|
path: '/app/imgfiles/add',
|
||||||
|
fromData: {
|
||||||
|
...data,
|
||||||
|
'FFILE': await MultipartFile.fromFile(
|
||||||
|
file.path,
|
||||||
|
filename: fileName
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 重点工程处罚
|
||||||
|
static Future<Map<String, dynamic>> keyprojectpunishAdd(Map data) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectpunish/add',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
"CORPINFO_ID":SessionService.instance.corpinfoId,
|
||||||
|
"CREATOR":SessionService.instance.loginUserId,
|
||||||
|
"OPERATOR":SessionService.instance.loginUserId,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 重点工程处罚修改隐患
|
||||||
|
static Future<Map<String, dynamic>> keyprojectPunishEdit(Map data, String ISPUNISH) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/keyprojectcheck/editHiddenIspunish',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
"ISPUNISH":ISPUNISH,
|
||||||
|
"PUNISH_PERSON":SessionService.instance.loginUserId,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_detail.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
|
||||||
|
class DangerListPage extends StatefulWidget {
|
||||||
|
final String flow;
|
||||||
|
|
||||||
|
const DangerListPage({Key? key, required this.flow}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DangerListPageState createState() => _DangerListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DangerListPageState extends State<DangerListPage> {
|
||||||
|
// Data and state variables
|
||||||
|
List<dynamic> list = [];
|
||||||
|
int currentPage = 1;
|
||||||
|
int rows = 10;
|
||||||
|
int totalPage = 1;
|
||||||
|
bool isLoading = false;
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> stepList = [];
|
||||||
|
int sindex = 0;
|
||||||
|
String searchKeywords = '';
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchData();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScroll() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent &&
|
||||||
|
!isLoading) {
|
||||||
|
if (currentPage < totalPage) {
|
||||||
|
currentPage++;
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchData() async {
|
||||||
|
if (isLoading) return;
|
||||||
|
setState(() => isLoading = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final data = {'KEYWORDS': searchKeywords};
|
||||||
|
final url =
|
||||||
|
'/app/keyProjects/listOutsourced?showCount=10¤tPage=$currentPage';
|
||||||
|
final response = await ApiService.getKeyProjectList(data, url);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
list = response['varList'];
|
||||||
|
} else {
|
||||||
|
list.addAll(response['varList']);
|
||||||
|
}
|
||||||
|
Map<String, dynamic> page = response['page'];
|
||||||
|
totalPage = page['totalPage'] ?? 1;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching data: $e');
|
||||||
|
setState(() => isLoading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _search() {
|
||||||
|
searchKeywords = _searchController.text.trim();
|
||||||
|
currentPage = 1;
|
||||||
|
list.clear();
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToDetail(Map<String, dynamic> item) async {
|
||||||
|
await pushPage(DangerManagerPage(OUTSOURCED_ID: item['OUTSOURCED_ID'] ?? ''),context);
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListItem(Map<String, dynamic> item) {
|
||||||
|
bool showBadge =
|
||||||
|
(item['STATE'] == '-1' || item['STATE'] == '-2') &&
|
||||||
|
(item['CREATOR'] != null &&
|
||||||
|
item['CREATOR'].toString() == SessionService.instance.loginUserId);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
margin: const EdgeInsets.all(8.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _goToDetail(item),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 标题 + 红点
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${item['OUTSOURCED_NAME'] ?? ''}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showBadge)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(left: 8.0),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 2.0,
|
||||||
|
),
|
||||||
|
height: 18,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFdd514c),
|
||||||
|
borderRadius: BorderRadius.circular(96),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'1',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 根据 IS_CORP_TYPE 显示不同的主管/监管信息(对应 vue 逻辑)
|
||||||
|
if (item['IS_CORP_TYPE'] == '1') ...[
|
||||||
|
Text("主管部门:${item['Q_COMPETENT_DEPT_NAME'] ?? ''}"),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text("监督部门:${item['DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
] else if (item['IS_CORP_TYPE'] == '0') ...[
|
||||||
|
Text("主管部门:${item['MANAGER_DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text("监管部门:${item['DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 相关方单位负责人 + 电话(两列)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"相关方单位负责人:${item['UNITS_PIC_NAME'] ?? ''}",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"电话:${item['UNITS_PHONE'] ?? ''}",
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(),
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () { _goToDetail(item);},
|
||||||
|
text: '查看',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||||
|
textStyle: TextStyle(fontSize: 13, color: Colors.white),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListContent() {
|
||||||
|
if (isLoading && list.isEmpty) {
|
||||||
|
// 初始加载时显示居中的加载指示器
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
} else if (list.isEmpty) {
|
||||||
|
// 没有数据
|
||||||
|
return NoDataWidget.show();
|
||||||
|
} else {
|
||||||
|
// 有数据或加载更多
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
|
||||||
|
controller: _scrollController,
|
||||||
|
itemCount: list.length + (isLoading ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index >= list.length) {
|
||||||
|
// 加载更多时在列表底部显示加载指示器
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildListItem(list[index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
appBar: MyAppbar(title: widget.flow),
|
||||||
|
|
||||||
|
body: SafeArea(child: Column(
|
||||||
|
children: [
|
||||||
|
// Filter bar
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: SearchBarWidget(
|
||||||
|
showResetButton: false,
|
||||||
|
hintText: "请输入关键字",
|
||||||
|
// isClickableOnly: true,
|
||||||
|
onSearch: (text) {
|
||||||
|
_search();
|
||||||
|
},
|
||||||
|
controller: _searchController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// List
|
||||||
|
Expanded(child: _buildListContent()),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,398 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/full_screen_video_page.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/photo_picker_row.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
class DangerManagerDetailPage extends StatefulWidget {
|
||||||
|
const DangerManagerDetailPage({super.key, required this.info});
|
||||||
|
|
||||||
|
final Map<String, dynamic> info;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DangerManagerDetailPage> createState() =>
|
||||||
|
_DangerManagerDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DangerManagerDetailPageState extends State<DangerManagerDetailPage> {
|
||||||
|
Map<String, dynamic> hiddenForm = {};
|
||||||
|
Map<String, dynamic> punishForm = {};
|
||||||
|
List<File> ysImages = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
_getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getData() async {
|
||||||
|
final result = await ApiService.getKeyprojectDangerFindHidden(
|
||||||
|
widget.info['HIDDEN_ID'] ?? '',
|
||||||
|
widget.info['OUTSOURCED_ID'] ?? '',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
hiddenForm = result['pd'] ?? {};
|
||||||
|
if (FormUtils.hasValue(hiddenForm, 'punishForm')) {
|
||||||
|
punishForm = hiddenForm['punishForm'];
|
||||||
|
}
|
||||||
|
print(hiddenForm);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.showNormal(context, '$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getServerPath(List paths) {
|
||||||
|
List<String> p = paths.map((val) => '${val['FILEPATH']}').toList();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getServerVideoPath(List paths) {
|
||||||
|
List<String> p = paths.map((val) => '${val['FILEPATH']}').toList();
|
||||||
|
if (p.isNotEmpty) {
|
||||||
|
return '${ApiService.baseImgPath}${p[0]}';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _punishFormWidget() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListItemFactory.createBuildSimpleSection('处罚信息'),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚原因:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['REASON'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚金额:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['AMOUT'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '被处罚单位:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['UNITS_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '被处罚人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['PERSON_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '下发人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['CREATOR_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '下发处罚时间:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['DATE'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (FormUtils.hasValue(punishForm, 'HANDLE_IMG'))
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '罚款缴纳单',
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(punishForm['HANDLE_IMG'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrls: punishForm['HANDLE_IMG'],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '是否缴纳罚款:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['已缴'] == '1' ? '已缴' : '未缴',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (punishForm['HANDLED'].toString() == '1')
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚处理人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['PERSON_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚处理时间:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['HANLDE_TIME'] ?? '',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
|
||||||
|
if (!FormUtils.hasValue(hiddenForm, 'CHECKTIME')) {
|
||||||
|
ToastUtil.showNormal(context, '请选择验收时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
final result = await ApiService.checkKeyprojectDanger(widget.info['HIDDEN_ID'] ?? '', hiddenForm);
|
||||||
|
if (result['result'] == 'success') {
|
||||||
|
final List<String> files = _getServerPath(hiddenForm['ysImgs']);
|
||||||
|
for (String p in files) {
|
||||||
|
final upResult = await ApiService.addNormalImgFiles(p,{'TYPE':'5','FOREIGN_KEY':widget.info['HIDDEN_ID']});
|
||||||
|
if (FormUtils.hasValue(upResult, 'network_error')) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '上传文件出错');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: MyAppbar(title: '隐患管理'),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
child:
|
||||||
|
hiddenForm.isNotEmpty
|
||||||
|
? ListView(
|
||||||
|
children: [
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '隐患照片',
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(hiddenForm['hiddenImgs'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrls: _getServerPath(
|
||||||
|
hiddenForm['hiddenImgs'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
ListItemFactory.createTextVideoItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
onVideoTapped: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
builder:
|
||||||
|
(_) => VideoPlayerPopup(
|
||||||
|
videoUrl: _getServerVideoPath(
|
||||||
|
hiddenForm['hiddenVideos'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
text: '隐患视频',
|
||||||
|
videoUrl: _getServerVideoPath(
|
||||||
|
hiddenForm['hiddenVideos'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '隐患描述',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENDESCR'],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患部位',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENPART'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患级别',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENLEVEL_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患类型',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENTYPE_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患处置',
|
||||||
|
isEditable: false,
|
||||||
|
text: '限期整改',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改期限',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONDEADLINE'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改部门',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改人',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONOR_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (hiddenForm['STATE'].toString() == '2' ||
|
||||||
|
hiddenForm['STATE'].toString() == '4')
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改时间',
|
||||||
|
isEditable: false,
|
||||||
|
text:
|
||||||
|
hiddenForm['RECTIFICATIONTIME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '整改照片',
|
||||||
|
imageUrls: _getServerPath(
|
||||||
|
hiddenForm['zgImgs'],
|
||||||
|
),
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(hiddenForm['zgImgs'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (hiddenForm['STATE'].toString() == '4' ||
|
||||||
|
widget.info['TabCur'].toString() == '1')
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
|
label: '验收时间',
|
||||||
|
isEditable:
|
||||||
|
widget.info['TabCur'] == '1'
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
text: hiddenForm['CHECKTIME'] ?? '',
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? picked =
|
||||||
|
await BottomDateTimePicker.showDate(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['CHECKTIME'] =
|
||||||
|
DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(picked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
Padding(padding: EdgeInsets.symmetric(horizontal: 8),child: RepairedPhotoSection(
|
||||||
|
title: '验收照片',
|
||||||
|
maxCount: 4,
|
||||||
|
mediaType: MediaType.image,
|
||||||
|
isShowAI: false,
|
||||||
|
isEdit: widget.info['TabCur'] == '1'
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
isRequired:false,
|
||||||
|
initialMediaPaths: _getServerPath(
|
||||||
|
hiddenForm['ysImgs'],
|
||||||
|
).map((path) => '${ApiService.baseImgPath}$path').toList(),
|
||||||
|
onMediaTapped: (p) {
|
||||||
|
presentOpaque(SingleImageViewer(imageUrl: p), context);
|
||||||
|
},
|
||||||
|
onChanged:
|
||||||
|
(files) => setState(() {
|
||||||
|
hiddenForm['ysImgs'] =
|
||||||
|
files
|
||||||
|
.map((f) => {'FILEPATH': f.path})
|
||||||
|
.toList();
|
||||||
|
}), onAiIdentify: () { },
|
||||||
|
|
||||||
|
),)
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (FormUtils.hasValue(hiddenForm, 'punishForm'))
|
||||||
|
_punishFormWidget(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox(width: double.maxFinite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
CustomButton(
|
||||||
|
text: widget.info['TabCur'] == '1' ? '验收' : '返回',
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
onPressed: () {
|
||||||
|
if (widget.info['TabCur'] == '1') {
|
||||||
|
_submit();
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/danner_repain_item.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_detail_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
class DangerManagerPage extends StatefulWidget {
|
||||||
|
const DangerManagerPage({super.key, required this.OUTSOURCED_ID});
|
||||||
|
|
||||||
|
final String OUTSOURCED_ID;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DangerManagerPage> createState() => _DangerManagerPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DangerManagerPageState extends State<DangerManagerPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late TabController _tabController;
|
||||||
|
late int _selectedTab = 0;
|
||||||
|
late List listDates = [];
|
||||||
|
late int totalPage = 0;
|
||||||
|
int currentPage = 1;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
|
||||||
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
|
_tabController.addListener(() {
|
||||||
|
if (_tabController.indexIsChanging) {
|
||||||
|
setState(() {
|
||||||
|
_selectedTab = _tabController.index;
|
||||||
|
currentPage = 1;
|
||||||
|
});
|
||||||
|
print('切换到标签:${_tabController.index}');
|
||||||
|
|
||||||
|
_getDataWithIndex(_tabController.index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
|
||||||
|
}
|
||||||
|
void _onScroll() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent) {
|
||||||
|
if (currentPage < totalPage) {
|
||||||
|
currentPage++;
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Future<void> _getDataWithIndex(int index) async {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
try {
|
||||||
|
final data = {
|
||||||
|
'KEYWORDS': '',
|
||||||
|
'OUTSOURCED_ID': widget.OUTSOURCED_ID,
|
||||||
|
'CREATOR': SessionService.instance.loginUserId,
|
||||||
|
'ISCHECK': index + 1,
|
||||||
|
};
|
||||||
|
final url =
|
||||||
|
'/app/keyprojectcheck/listHidden?showCount=10¤tPage=$currentPage';
|
||||||
|
final response = await ApiService.getKeyprojectDangerList(url, data);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
listDates = response['varList'];
|
||||||
|
} else {
|
||||||
|
listDates.addAll(response['varList']);
|
||||||
|
}
|
||||||
|
Map<String, dynamic> page = response['page'];
|
||||||
|
totalPage = page['totalPage'] ?? 1;
|
||||||
|
});
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching data: $e');
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getState(String state) {
|
||||||
|
final info = {'1': "未整改",
|
||||||
|
'2': "已整改",
|
||||||
|
'4': "已验收",};
|
||||||
|
return info[state] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToDetail(Map<String, dynamic> item, String tabCur) async {
|
||||||
|
item['TabCur'] = tabCur;
|
||||||
|
await pushPage(DangerManagerDetailPage(info: item), context);
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _itemCell(Map<String, dynamic> item) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_goToDetail(item, '2');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(top: 12, left: 12, right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListItemFactory.headerTitle(item['OUTSOURCED_NAME'] ?? ''),
|
||||||
|
const SizedBox(height: 5,),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患来源: ${item['SOURCE'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患描述: ${item['HIDDENDESCR'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患发现人: ${item['CREATOR_NAME'] ?? item['CREATOR_NAMES']}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患发现时间: ${item['CREATTIME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患整改人: ${item['RECTIFICATIONOR_NAME'] ?? ''}"),
|
||||||
|
Text("整改时间: ${item['RECTIFICATIONTIME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患验收人: ${item['CREATOR_NAME'] ?? ''}"),
|
||||||
|
Text("验收时间: ${item['CHECKTIME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("隐患状态: ${_getState(item['STATE'].toString())}"),
|
||||||
|
Text("是否处罚: ${FormUtils.hasValue(item, 'ISPUNISH') ? (item['ISPUNISH'] == '1' ? '是' : '否'):''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5,),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (item['CREATOR'] == SessionService.instance.loginUserId && _selectedTab == 0)
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () { _goToDetail(item, '1');},
|
||||||
|
text: '验收',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||||
|
textStyle: TextStyle(fontSize: 13, color: Colors.white),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () { _goToDetail(item, '2');},
|
||||||
|
text: '查看',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||||
|
textStyle: TextStyle(fontSize: 13, color: Colors.white),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10,),
|
||||||
|
const Divider(height: 1,)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleItemTap(Map item, int index) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: MyAppbar(title: '隐患管理'),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
labelStyle: TextStyle(fontSize: 16),
|
||||||
|
indicator: UnderlineTabIndicator(
|
||||||
|
borderSide: BorderSide(width: 3.0, color: Colors.blue),
|
||||||
|
insets: EdgeInsets.symmetric(horizontal: 100.0),
|
||||||
|
),
|
||||||
|
labelColor: Colors.blue,
|
||||||
|
unselectedLabelColor: Colors.grey,
|
||||||
|
tabs: const [Tab(text: '待验收隐患'), Tab(text: '已验收隐患')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
listDates.isEmpty
|
||||||
|
? NoDataWidget.show()
|
||||||
|
: ListView.separated(
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
itemCount: listDates.length,
|
||||||
|
controller: _scrollController,
|
||||||
|
separatorBuilder: (_, __) => const SizedBox(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = listDates[index];
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => _handleItemTap(item, index),
|
||||||
|
child: _itemCell(item),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,335 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
|
|
||||||
|
/// punlish_modal.dart
|
||||||
|
/// 把你提供的 uniapp 弹窗翻译为 Flutter 版本。
|
||||||
|
/// 提供:
|
||||||
|
/// - 可复用的 PunishModal Widget
|
||||||
|
/// - showPunishDialog 辅助函数
|
||||||
|
/// - 一个最小的 main() 示例展示如何调用
|
||||||
|
|
||||||
|
class PunishModal extends StatefulWidget {
|
||||||
|
/// 初始数据(可选),key 与原 uniapp 对应:
|
||||||
|
/// "ISPUNISH" : '1' 或 '2'
|
||||||
|
/// "REASON"
|
||||||
|
/// "AMOUT"
|
||||||
|
/// "RECTIFICATIONDEPT_NAME"
|
||||||
|
/// "RECTIFICATIONOR_NAME"
|
||||||
|
/// "DATE" (字符串)
|
||||||
|
final Map<String, String>? initial;
|
||||||
|
final void Function(Map<String, String> form) onSubmit;
|
||||||
|
|
||||||
|
const PunishModal({Key? key, this.initial, required this.onSubmit})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PunishModalState createState() => _PunishModalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PunishModalState extends State<PunishModal> {
|
||||||
|
late bool isp = false; // 默认否
|
||||||
|
late TextEditingController reasonController;
|
||||||
|
late TextEditingController amountController;
|
||||||
|
late TextEditingController deptController;
|
||||||
|
late TextEditingController personController;
|
||||||
|
String? dateText;
|
||||||
|
|
||||||
|
final FocusNode _amountFocus = FocusNode();
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
reasonController = TextEditingController(
|
||||||
|
text: widget.initial?['REASON'] ?? '',
|
||||||
|
);
|
||||||
|
amountController = TextEditingController(
|
||||||
|
text: widget.initial?['AMOUT'] ?? '',
|
||||||
|
);
|
||||||
|
deptController = TextEditingController(
|
||||||
|
text: widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '',
|
||||||
|
);
|
||||||
|
personController = TextEditingController(
|
||||||
|
text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '',
|
||||||
|
);
|
||||||
|
dateText = widget.initial?['DATE'];
|
||||||
|
isp = widget.initial?['ISPUNISH'] == '1' ? true : false;
|
||||||
|
|
||||||
|
// 相当于 @blur=\"checkNumber\",监听焦点失去时做检查
|
||||||
|
_amountFocus.addListener(() {
|
||||||
|
if (!_amountFocus.hasFocus) {
|
||||||
|
_checkNumber();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
reasonController.dispose();
|
||||||
|
amountController.dispose();
|
||||||
|
deptController.dispose();
|
||||||
|
personController.dispose();
|
||||||
|
_amountFocus.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkNumber() {
|
||||||
|
final text = amountController.text.trim();
|
||||||
|
if (text.isEmpty) return;
|
||||||
|
// 允许整数或两位小数
|
||||||
|
final reg = RegExp(r"^\d+(?:\.\d{1,2})?$");
|
||||||
|
if (!reg.hasMatch(text)) {
|
||||||
|
// 显示错误提示,并做简单修正(可按需改变)
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(const SnackBar(content: Text('请输入有效的金额(最多两位小数)')));
|
||||||
|
// 尝试提取数字部分
|
||||||
|
final match = RegExp(r"\d+(?:\.\d{1,2})?").firstMatch(text);
|
||||||
|
if (match != null) {
|
||||||
|
amountController.text = match.group(0)!;
|
||||||
|
} else {
|
||||||
|
amountController.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onConfirm() {
|
||||||
|
if (isp) {
|
||||||
|
if (reasonController.text.trim().isEmpty) {
|
||||||
|
ToastUtil.showNormal(context, '请填写处罚原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (amountController.text.trim().isEmpty) {
|
||||||
|
ToastUtil.showNormal(context, '请填写处罚金额');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dateText?.length == 0) {
|
||||||
|
ToastUtil.showNormal(context, '请选择下发处罚时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = {
|
||||||
|
'ISPUNISH': isp ? "1" : "2",
|
||||||
|
'REASON': reasonController.text.trim(),
|
||||||
|
'AMOUT': amountController.text.trim(),
|
||||||
|
'RECTIFICATIONDEPT_NAME': deptController.text.trim(),
|
||||||
|
'RECTIFICATIONOR_NAME': personController.text.trim(),
|
||||||
|
'DATE': dateText ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.onSubmit(result);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// 我们改为根据内容自动伸缩,但当内容过高时仍然限制最大高度并可滚动。
|
||||||
|
final maxBodyHeight = MediaQuery.of(context).size.height * 0.6; // 可按需调整
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
// 不再强制固定高度,使用 Column 的 min size,让弹窗高度随内容变化
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'处罚',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: const Icon(Icons.close, color: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Body (表单)
|
||||||
|
// 使用 ConstrainedBox 限制最大高度,内部使用 SingleChildScrollView,这样当字段少时弹窗高度自动变小,字段多时会出现滚动。
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: maxBodyHeight),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 是否进行罚款
|
||||||
|
ListItemFactory.createYesNoSection(
|
||||||
|
title: '是否进行罚款',
|
||||||
|
horizontalPadding: 0,
|
||||||
|
groupValue: isp,
|
||||||
|
onChanged: (v) => setState(() => isp = v),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 以下内容仅在 isp == '1' 时显示
|
||||||
|
if (isp) ...[
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '处罚原因',
|
||||||
|
isEditable: true,
|
||||||
|
controller: reasonController,
|
||||||
|
hintText: '请输入处罚原因',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '处罚金额(元)',
|
||||||
|
isEditable: true,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(
|
||||||
|
decimal: true,
|
||||||
|
),
|
||||||
|
controller: amountController,
|
||||||
|
hintText: '请输入处罚金额',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '被处罚单位',
|
||||||
|
isEditable: false,
|
||||||
|
text:
|
||||||
|
widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '被处罚人',
|
||||||
|
isEditable: false,
|
||||||
|
text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
|
label: '下发处罚时间',
|
||||||
|
isEditable: true,
|
||||||
|
text: '',
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? picked =
|
||||||
|
await BottomDateTimePicker.showDate(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
dateText =
|
||||||
|
DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(picked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Footer 按钮
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('关闭'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.green,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
side: const BorderSide(color: Colors.green),
|
||||||
|
),
|
||||||
|
onPressed: _onConfirm,
|
||||||
|
child: const Text(
|
||||||
|
'确认',
|
||||||
|
style: TextStyle(color: Colors.green),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLabel(String text) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 6),
|
||||||
|
child: Text(text, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示showPunishDialog
|
||||||
|
Future<void> showPunishDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
Map<String, String>? initial,
|
||||||
|
required void Function(Map<String, String>) onSubmit,
|
||||||
|
}) {
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) {
|
||||||
|
final viewInsets = MediaQuery.of(context).viewInsets;
|
||||||
|
return AnimatedPadding(
|
||||||
|
padding: viewInsets + const EdgeInsets.all(16),
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.decelerate,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Container(
|
||||||
|
// 保持原来 Dialog 的圆角和背景
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||||
|
child: PunishModal(initial: initial, onSubmit: onSubmit),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_detail.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_manager_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
|
||||||
|
class PunishmentListPage extends StatefulWidget {
|
||||||
|
final String flow;
|
||||||
|
|
||||||
|
const PunishmentListPage({Key? key, required this.flow}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PunishmentListPageState createState() => _PunishmentListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PunishmentListPageState extends State<PunishmentListPage> {
|
||||||
|
// Data and state variables
|
||||||
|
List<dynamic> list = [];
|
||||||
|
int currentPage = 1;
|
||||||
|
int rows = 10;
|
||||||
|
int totalPage = 1;
|
||||||
|
bool isLoading = false;
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> stepList = [];
|
||||||
|
int sindex = 0;
|
||||||
|
String searchKeywords = '';
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchData();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScroll() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent &&
|
||||||
|
!isLoading) {
|
||||||
|
if (currentPage < totalPage) {
|
||||||
|
currentPage++;
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchData() async {
|
||||||
|
if (isLoading) return;
|
||||||
|
setState(() => isLoading = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final data = {'KEYWORDS': searchKeywords, 'PUNISHUser':SessionService.instance.loginUserId,};
|
||||||
|
final url =
|
||||||
|
'/app/keyProjects/getPUNISHlist?showCount=10¤tPage=$currentPage';
|
||||||
|
final response = await ApiService.getKeyProjectList(data, url);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
list = response['varList'];
|
||||||
|
} else {
|
||||||
|
list.addAll(response['varList']);
|
||||||
|
}
|
||||||
|
Map<String, dynamic> page = response['page'];
|
||||||
|
totalPage = page['totalPage'] ?? 1;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching data: $e');
|
||||||
|
setState(() => isLoading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _search() {
|
||||||
|
searchKeywords = _searchController.text.trim();
|
||||||
|
currentPage = 1;
|
||||||
|
list.clear();
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToDetail(Map<String, dynamic> item) async {
|
||||||
|
await pushPage(PunishmentManagerPage(OUTSOURCED_ID: item['OUTSOURCED_ID'] ?? ''),context);
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListItem(Map<String, dynamic> item) {
|
||||||
|
bool showBadge = item['cfcount'] > 0;
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
margin: const EdgeInsets.all(8.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _goToDetail(item),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 标题 + 红点
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${item['OUTSOURCED_NAME'] ?? ''}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showBadge)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(left: 8.0),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 2.0,
|
||||||
|
),
|
||||||
|
height: 18,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFdd514c),
|
||||||
|
borderRadius: BorderRadius.circular(96),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'1',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 根据 IS_CORP_TYPE 显示不同的主管/监管信息(对应 vue 逻辑)
|
||||||
|
if (item['IS_CORP_TYPE'] == '1') ...[
|
||||||
|
Text("主管部门:${item['Q_COMPETENT_DEPT_NAME'] ?? ''}"),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text("监督部门:${item['DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
] else if (item['IS_CORP_TYPE'] == '0') ...[
|
||||||
|
Text("主管部门:${item['MANAGER_DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text("监管部门:${item['DEPARTMENT_NAME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 相关方单位负责人 + 电话(两列)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"相关方单位负责人:${item['UNITS_PIC_NAME'] ?? ''}",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"电话:${item['UNITS_PHONE'] ?? ''}",
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(),
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () { _goToDetail(item);},
|
||||||
|
text: '查看',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||||
|
textStyle: TextStyle(fontSize: 13, color: Colors.white),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListContent() {
|
||||||
|
if (isLoading && list.isEmpty) {
|
||||||
|
// 初始加载时显示居中的加载指示器
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
} else if (list.isEmpty) {
|
||||||
|
// 没有数据
|
||||||
|
return NoDataWidget.show();
|
||||||
|
} else {
|
||||||
|
// 有数据或加载更多
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
|
||||||
|
controller: _scrollController,
|
||||||
|
itemCount: list.length + (isLoading ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index >= list.length) {
|
||||||
|
// 加载更多时在列表底部显示加载指示器
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildListItem(list[index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
appBar: MyAppbar(title: widget.flow),
|
||||||
|
|
||||||
|
body: SafeArea(child: Column(
|
||||||
|
children: [
|
||||||
|
// Filter bar
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: SearchBarWidget(
|
||||||
|
showResetButton: false,
|
||||||
|
hintText: "请输入关键字",
|
||||||
|
// isClickableOnly: true,
|
||||||
|
onSearch: (text) {
|
||||||
|
_search();
|
||||||
|
},
|
||||||
|
controller: _searchController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// List
|
||||||
|
Expanded(child: _buildListContent()),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,396 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/full_screen_video_page.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/photo_picker_row.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
class PunishmentManagerDetailPage extends StatefulWidget {
|
||||||
|
const PunishmentManagerDetailPage({super.key, required this.info});
|
||||||
|
|
||||||
|
final Map<String, dynamic> info;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PunishmentManagerDetailPage> createState() =>
|
||||||
|
_PunishmentManagerDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PunishmentManagerDetailPageState extends State<PunishmentManagerDetailPage> {
|
||||||
|
Map<String, dynamic> hiddenForm = {};
|
||||||
|
Map<String, dynamic> punishForm = {};
|
||||||
|
List<File> ysImages = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
_getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getData() async {
|
||||||
|
final result = await ApiService.getKeyprojectDangerFindHidden(
|
||||||
|
widget.info['HIDDEN_ID'] ?? '',
|
||||||
|
widget.info['OUTSOURCED_ID'] ?? '',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
hiddenForm = result['pd'] ?? {};
|
||||||
|
if (FormUtils.hasValue(hiddenForm, 'punishForm')) {
|
||||||
|
punishForm = hiddenForm['punishForm'];
|
||||||
|
}
|
||||||
|
print(hiddenForm);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.showNormal(context, '$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getServerPath(List paths) {
|
||||||
|
List<String> p = paths.map((val) => '${val['FILEPATH']}').toList();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getServerVideoPath(List paths) {
|
||||||
|
List<String> p = paths.map((val) => '${val['FILEPATH']}').toList();
|
||||||
|
if (p.isNotEmpty) {
|
||||||
|
return '${ApiService.baseImgPath}${p[0]}';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _punishFormWidget() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListItemFactory.createBuildSimpleSection('处罚信息'),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚原因:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['REASON'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚金额:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['AMOUT'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '被处罚单位:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['UNITS_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '被处罚人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['PERSON_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '下发人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['CREATOR_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '下发处罚时间:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['DATE'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (FormUtils.hasValue(punishForm, 'HANDLE_IMG'))
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '罚款缴纳单',
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(punishForm['HANDLE_IMG'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrls: punishForm['HANDLE_IMG'],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '是否缴纳罚款:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['已缴'] == '1' ? '已缴' : '未缴',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (punishForm['HANDLED'].toString() == '1')
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚处理人:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['PERSON_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '处罚处理时间:',
|
||||||
|
isEditable: false,
|
||||||
|
text: punishForm['HANLDE_TIME'] ?? '',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
|
||||||
|
if (!FormUtils.hasValue(hiddenForm, 'CHECKTIME')) {
|
||||||
|
ToastUtil.showNormal(context, '请选择验收时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
final result = await ApiService.checkKeyprojectDanger(widget.info['HIDDEN_ID'] ?? '', hiddenForm);
|
||||||
|
if (result['result'] == 'success') {
|
||||||
|
final List<String> files = _getServerPath(hiddenForm['ysImgs']);
|
||||||
|
for (String p in files) {
|
||||||
|
final upResult = await ApiService.addNormalImgFiles(p,{'TYPE':'5','FOREIGN_KEY':widget.info['HIDDEN_ID']});
|
||||||
|
if (FormUtils.hasValue(upResult, 'network_error')) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '上传文件出错');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: MyAppbar(title: '隐患管理'),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
child:
|
||||||
|
hiddenForm.isNotEmpty
|
||||||
|
? ListView(
|
||||||
|
children: [
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '隐患照片',
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(hiddenForm['hiddenImgs'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrls: _getServerPath(
|
||||||
|
hiddenForm['hiddenImgs'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
ListItemFactory.createTextVideoItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
onVideoTapped: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
builder:
|
||||||
|
(_) => VideoPlayerPopup(
|
||||||
|
videoUrl: _getServerVideoPath(
|
||||||
|
hiddenForm['hiddenVideos'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
text: '隐患视频',
|
||||||
|
videoUrl: _getServerVideoPath(
|
||||||
|
hiddenForm['hiddenVideos'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '隐患描述',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENDESCR'],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患部位',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENPART'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患级别',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENLEVEL_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患类型',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['HIDDENTYPE_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '隐患处置',
|
||||||
|
isEditable: false,
|
||||||
|
text: '限期整改',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改期限',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONDEADLINE'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改部门',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改人',
|
||||||
|
isEditable: false,
|
||||||
|
text: hiddenForm['RECTIFICATIONOR_NAME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
if (hiddenForm['STATE'].toString() == '2' ||
|
||||||
|
hiddenForm['STATE'].toString() == '4')
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ItemListWidget.singleLineTitleText(
|
||||||
|
label: '整改时间',
|
||||||
|
isEditable: false,
|
||||||
|
text:
|
||||||
|
hiddenForm['RECTIFICATIONTIME'] ?? '',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListItemFactory.createTextImageItem(
|
||||||
|
horizontalPadding: 12,
|
||||||
|
text: '整改照片',
|
||||||
|
imageUrls: _getServerPath(
|
||||||
|
hiddenForm['zgImgs'],
|
||||||
|
),
|
||||||
|
onImageTapped: (index) {
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(
|
||||||
|
imageUrl:
|
||||||
|
'${ApiService.baseImgPath}${_getServerPath(hiddenForm['zgImgs'])[index]}',
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (hiddenForm['STATE'].toString() == '4' ||
|
||||||
|
widget.info['TabCur'].toString() == '1')
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
|
label: '验收时间',
|
||||||
|
isEditable:
|
||||||
|
widget.info['TabCur'] == '1'
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
text: hiddenForm['CHECKTIME'] ?? '',
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? picked =
|
||||||
|
await BottomDateTimePicker.showDate(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['CHECKTIME'] =
|
||||||
|
DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(picked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
Padding(padding: EdgeInsets.symmetric(horizontal: 8),child: RepairedPhotoSection(
|
||||||
|
title: '验收照片',
|
||||||
|
maxCount: 4,
|
||||||
|
mediaType: MediaType.image,
|
||||||
|
isShowAI: false,
|
||||||
|
isEdit: widget.info['TabCur'] == '1'
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
isRequired:false,
|
||||||
|
initialMediaPaths: _getServerPath(
|
||||||
|
hiddenForm['ysImgs'],
|
||||||
|
).map((path) => '${ApiService.baseImgPath}$path').toList(),
|
||||||
|
onMediaTapped: (p) {
|
||||||
|
presentOpaque(SingleImageViewer(imageUrl: p), context);
|
||||||
|
},
|
||||||
|
onChanged:
|
||||||
|
(files) => setState(() {
|
||||||
|
hiddenForm['ysImgs'] =
|
||||||
|
files
|
||||||
|
.map((f) => {'FILEPATH': f.path})
|
||||||
|
.toList();
|
||||||
|
}), onAiIdentify: () { },
|
||||||
|
|
||||||
|
),)
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (FormUtils.hasValue(hiddenForm, 'punishForm'))
|
||||||
|
_punishFormWidget(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox(width: double.maxFinite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
CustomButton(
|
||||||
|
text: '返回',
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/danner_repain_item.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_detail_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Punishment/PunishmentModalAlert.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
class PunishmentManagerPage extends StatefulWidget {
|
||||||
|
const PunishmentManagerPage({super.key, required this.OUTSOURCED_ID});
|
||||||
|
|
||||||
|
final String OUTSOURCED_ID;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PunishmentManagerPage> createState() => _PunishmentManagerPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PunishmentManagerPageState extends State<PunishmentManagerPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late TabController _tabController;
|
||||||
|
late int _selectedTab = 0;
|
||||||
|
late List listDates = [];
|
||||||
|
late int totalPage = 0;
|
||||||
|
int currentPage = 1;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
|
||||||
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
|
_tabController.addListener(() {
|
||||||
|
if (_tabController.indexIsChanging) {
|
||||||
|
setState(() {
|
||||||
|
_selectedTab = _tabController.index;
|
||||||
|
currentPage = 1;
|
||||||
|
});
|
||||||
|
print('切换到标签:${_tabController.index}');
|
||||||
|
|
||||||
|
_getDataWithIndex(_tabController.index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScroll() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent) {
|
||||||
|
if (currentPage < totalPage) {
|
||||||
|
currentPage++;
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getDataWithIndex(int index) async {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
try {
|
||||||
|
final data = {
|
||||||
|
'KEYWORDS': '',
|
||||||
|
'OUTSOURCED_ID': widget.OUTSOURCED_ID,
|
||||||
|
'CREATOR': SessionService.instance.loginUserId,
|
||||||
|
'HANDLED': index + 1,
|
||||||
|
};
|
||||||
|
final url =
|
||||||
|
'/app/keyprojectcheck/punishlist?showCount=10¤tPage=$currentPage';
|
||||||
|
final response = await ApiService.getKeyprojectDangerList(url, data);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
listDates = response['varList'];
|
||||||
|
} else {
|
||||||
|
listDates.addAll(response['varList']);
|
||||||
|
}
|
||||||
|
Map<String, dynamic> page = response['page'];
|
||||||
|
totalPage = page['totalPage'] ?? 1;
|
||||||
|
});
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching data: $e');
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _punishment(Map item) {
|
||||||
|
showPunishDialog(
|
||||||
|
context,
|
||||||
|
initial: {
|
||||||
|
'ISPUNISH': '2',
|
||||||
|
'RECTIFICATIONDEPT_NAME': item['UNITS_NAME'],
|
||||||
|
'RECTIFICATIONOR_NAME': item['RECTIFICATIONOR_NAME'],
|
||||||
|
},
|
||||||
|
onSubmit: (form) {
|
||||||
|
// 这里接收用户确认后的表单数据
|
||||||
|
form['HIDDEN_ID'] = item['HIDDEN_ID'];
|
||||||
|
_keyprojectPunishAdd(form);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _keyprojectPunishAdd(Map<String, dynamic> form) async {
|
||||||
|
try {
|
||||||
|
if (form['ISPUNISH'] == 1) {
|
||||||
|
final result = await ApiService.keyprojectpunishAdd(form);
|
||||||
|
if (result['result'] == 'success') {
|
||||||
|
await ApiService.keyprojectPunishEdit(form, '1');
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
await ApiService.keyprojectPunishEdit(form, '2');
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.showNormal(context, '$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToDetail(Map<String, dynamic> item, String tabCur) async {
|
||||||
|
item['TabCur'] = tabCur;
|
||||||
|
await pushPage(PunishmentManagerDetailPage(info: item), context);
|
||||||
|
_getDataWithIndex(_selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _itemCell(Map<String, dynamic> item) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_goToDetail(item, '2');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(top: 12, left: 12, right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [Text('${item['HIDDENDESCR'] ?? ''}')],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("被处罚单位: ${item['UNITS_NAME'] ?? ''}"),
|
||||||
|
if (item['ISPUNISH'].toString() == '1' &&
|
||||||
|
item['HANDLED'].toString() == '0')
|
||||||
|
Text("被处罚人: ${item['PERSON_NAME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (item['ISPUNISH'].toString() == '1')
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [Text("处罚原因: ${item['REASON'] ?? ''}")],
|
||||||
|
),
|
||||||
|
if (FormUtils.hasValue(item, 'ISPUNISH'))
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("下发人: ${item['CREATOR_NAME'] ?? ''}"),
|
||||||
|
Text("是否处罚: ${item['ISPUNISH'] == '2' ? "否" : "是"}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"处罚处理状态: ${item['ISPUNISH'] == '2'
|
||||||
|
? "不处罚"
|
||||||
|
: item['HANDLED'] == '1'
|
||||||
|
? '已完成'
|
||||||
|
: item['ISPUNISH'] == "1"
|
||||||
|
? "待反馈"
|
||||||
|
: "待处罚"}",
|
||||||
|
),
|
||||||
|
// Text("整改时间: ${item['RECTIFICATIONTIME'] ?? ''}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (!FormUtils.hasValue(item, 'ISPUNISH'))
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () {
|
||||||
|
_punishment(item);
|
||||||
|
},
|
||||||
|
text: '处罚',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 5,
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
CustomButton(
|
||||||
|
onPressed: () {
|
||||||
|
_goToDetail(item, '2');
|
||||||
|
},
|
||||||
|
text: '查看',
|
||||||
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 5,
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(fontSize: 13, color: Colors.white),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
const Divider(height: 1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleItemTap(Map item, int index) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: MyAppbar(title: '处罚管理'),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
labelStyle: TextStyle(fontSize: 16),
|
||||||
|
indicator: UnderlineTabIndicator(
|
||||||
|
borderSide: BorderSide(width: 3.0, color: Colors.blue),
|
||||||
|
insets: EdgeInsets.symmetric(horizontal: 100.0),
|
||||||
|
),
|
||||||
|
labelColor: Colors.blue,
|
||||||
|
unselectedLabelColor: Colors.grey,
|
||||||
|
tabs: const [Tab(text: '待反馈处罚'), Tab(text: '已完成处罚')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
listDates.isEmpty
|
||||||
|
? NoDataWidget.show()
|
||||||
|
: ListView.separated(
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
itemCount: listDates.length,
|
||||||
|
controller: _scrollController,
|
||||||
|
separatorBuilder: (_, __) => const SizedBox(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = listDates[index];
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => _handleItemTap(item, index),
|
||||||
|
child: _itemCell(item),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ class _CheckListPageState extends State<CheckListPage> {
|
||||||
}, child: Text('发起', style: TextStyle(color: Colors.white, fontSize: 16),))
|
}, child: Text('发起', style: TextStyle(color: Colors.white, fontSize: 16),))
|
||||||
],),
|
],),
|
||||||
|
|
||||||
body: Column(
|
body: SafeArea(child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Filter bar
|
// Filter bar
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -231,7 +231,7 @@ class _CheckListPageState extends State<CheckListPage> {
|
||||||
// List
|
// List
|
||||||
Expanded(child: _buildListContent()),
|
Expanded(child: _buildListContent()),
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
|
||||||
|
class MultiTextFieldWithTitle extends StatefulWidget {
|
||||||
|
final String label;
|
||||||
|
final List<String> texts;
|
||||||
|
final bool isEditable;
|
||||||
|
final String hintText;
|
||||||
|
final double fontSize;
|
||||||
|
final bool isRequired;
|
||||||
|
final ValueChanged<List<String>> onTextsChanged;
|
||||||
|
|
||||||
|
const MultiTextFieldWithTitle({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.isEditable,
|
||||||
|
required this.hintText,
|
||||||
|
required this.onTextsChanged,
|
||||||
|
this.fontSize = 15,
|
||||||
|
this.texts = const [],
|
||||||
|
this.isRequired = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MultiTextFieldWithTitle> createState() =>
|
||||||
|
_MultiTextFieldWithTitleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MultiTextFieldWithTitleState extends State<MultiTextFieldWithTitle> {
|
||||||
|
final List<TextEditingController> _controllers = [];
|
||||||
|
final List<FocusNode> _focusNodes = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 延迟初始化,避免构建过程中调用 setState
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_addTextField();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (var controller in _controllers) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
for (var node in _focusNodes) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addTextField() {
|
||||||
|
setState(() {
|
||||||
|
final newController = TextEditingController();
|
||||||
|
final newFocusNode = FocusNode();
|
||||||
|
|
||||||
|
newController.addListener(() {
|
||||||
|
widget.onTextsChanged(_getAllTexts());
|
||||||
|
});
|
||||||
|
|
||||||
|
_controllers.add(newController);
|
||||||
|
_focusNodes.add(newFocusNode);
|
||||||
|
widget.onTextsChanged(_getAllTexts());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeTextField(int index) async {
|
||||||
|
if (_controllers.length <= 1) return;
|
||||||
|
await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(_) => CustomAlertDialog(
|
||||||
|
title: '提示',
|
||||||
|
mode: DialogMode.text,
|
||||||
|
content: '确定删除检查情况吗?',
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
onConfirm: () {
|
||||||
|
setState(() {
|
||||||
|
_controllers[index].dispose();
|
||||||
|
_focusNodes[index].dispose();
|
||||||
|
|
||||||
|
_controllers.removeAt(index);
|
||||||
|
_focusNodes.removeAt(index);
|
||||||
|
widget.onTextsChanged(_getAllTexts());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getAllTexts() {
|
||||||
|
return _controllers.map((c) => c.text).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 标题行
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (widget.isRequired && widget.isEditable)
|
||||||
|
Text('* ', style: TextStyle(color: Colors.red)),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
widget.label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: widget.fontSize,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (widget.isEditable)
|
||||||
|
CustomButton(
|
||||||
|
text: " 添加 ",
|
||||||
|
height: 30,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 2,
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
onPressed: _addTextField,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 输入框区域 - 高度自适应
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// 可编辑状态
|
||||||
|
if (widget.isEditable)
|
||||||
|
..._controllers.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
return _buildTextFieldWithDelete(index);
|
||||||
|
}).toList(),
|
||||||
|
|
||||||
|
// 不可编辑状态
|
||||||
|
if (!widget.isEditable)
|
||||||
|
...widget.texts.map((c) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
width: double.maxFinite,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
c,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: widget.fontSize,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextFieldWithDelete(int index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// 输入框
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 7, vertical: 7),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: BoxBorder.all(color: Colors.grey.shade300, width: 1),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: TextField(
|
||||||
|
controller: _controllers[index],
|
||||||
|
decoration: InputDecoration(hintText: widget.hintText),
|
||||||
|
focusNode: _focusNodes[index],
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: 3,
|
||||||
|
minLines: 3,
|
||||||
|
style: TextStyle(fontSize: widget.fontSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 删除按钮(叠加在左上角)
|
||||||
|
if (index > 0)
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _removeTextField(index),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: const Icon(Icons.close, size: 10, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Widget HiddenListTable({
|
||||||
|
required List<dynamic> hiddenList,
|
||||||
|
required bool forbidEdit,
|
||||||
|
required String baseImgPath,
|
||||||
|
required String personSignImg,
|
||||||
|
required String personSignTime,
|
||||||
|
required void Function(Map<String, dynamic> item, int index) showHidden,
|
||||||
|
required void Function(Map<String, dynamic> item, int index) removeHidden,
|
||||||
|
required BuildContext context,
|
||||||
|
}) {
|
||||||
|
Widget _buildCell(String text, {bool isHeader = false, Alignment alignment = Alignment.center, double minWidth = 50}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
|
||||||
|
alignment: alignment,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minWidth: minWidth),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
|
||||||
|
fontSize: 14,
|
||||||
|
color: isHeader ? Colors.black87 : Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仍保留 Table 的 header 用法(用于有数据时)
|
||||||
|
TableRow _buildHeader() {
|
||||||
|
return TableRow(
|
||||||
|
decoration: BoxDecoration(color: Colors.grey.shade200),
|
||||||
|
children: [
|
||||||
|
_buildCell('序号', isHeader: true, alignment: Alignment.center),
|
||||||
|
_buildCell('隐患部位', isHeader: true),
|
||||||
|
_buildCell('隐患描述', isHeader: true),
|
||||||
|
_buildCell('操作', isHeader: true, alignment: Alignment.center),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRow _buildRow(Map<String, dynamic> item, int index) {
|
||||||
|
final partName = (item['HIDDENPART_NAME'] ?? '').toString();
|
||||||
|
final part = (item['HIDDENPART'] ?? '').toString();
|
||||||
|
final descr = (item['HIDDENDESCR'] ?? '').toString();
|
||||||
|
|
||||||
|
return TableRow(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
_buildCell('${index + 1}', alignment: Alignment.center),
|
||||||
|
_buildCell(partName.isNotEmpty ? partName : part),
|
||||||
|
_buildCell(descr),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.info_outline, size: 22, color: Colors.blue),
|
||||||
|
onPressed: () => showHidden(item, index),
|
||||||
|
tooltip: '查看',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
if (forbidEdit) // 注意:原逻辑保留,如需反向请改为 if (!forbidEdit)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 22, color: Colors.red),
|
||||||
|
onPressed: () => removeHidden(item, index),
|
||||||
|
tooltip: '删除',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当 hiddenList 为空时,单独渲染表头(使用 Row + Expanded 来模仿 Table 的列宽)
|
||||||
|
Widget _buildHeaderRowWidget() {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(color: Colors.grey.shade200),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 2, child: _buildCell('序号', isHeader: true, alignment: Alignment.center)),
|
||||||
|
Expanded(flex: 3, child: _buildCell('隐患部位', isHeader: true)),
|
||||||
|
Expanded(flex: 3, child: _buildCell('隐患描述', isHeader: true)),
|
||||||
|
Expanded(flex: 3, child: _buildCell('操作', isHeader: true, alignment: Alignment.center)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final tableWidth = MediaQuery.of(context).size.width - 50;
|
||||||
|
|
||||||
|
// 如果没有数据,返回“表头 + 跨列居中显示暂无数据”的布局
|
||||||
|
if (hiddenList.isEmpty) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: tableWidth,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildHeaderRowWidget(),
|
||||||
|
Container(
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'暂无数据',
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.black54),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则,使用原来的 Table 渲染有数据的行
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: tableWidth,
|
||||||
|
child: Table(
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
columnWidths: const {
|
||||||
|
0: FlexColumnWidth(2), // 序号
|
||||||
|
1: FlexColumnWidth(3), // 隐患部位
|
||||||
|
2: FlexColumnWidth(3), // 隐患描述
|
||||||
|
3: FlexColumnWidth(3), // 操作按钮
|
||||||
|
},
|
||||||
|
border: TableBorder.symmetric(
|
||||||
|
inside: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
...hiddenList.asMap().entries.map((e) => _buildRow(e.value, e.key)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,703 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/bottom_picker_two.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/date_picker_dialog.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/department_person_picker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/department_picker_two.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/full_screen_video_page.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/video_player_widget.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
import '../../../../customWidget/photo_picker_row.dart';
|
||||||
|
import '../../../../http/ApiService.dart';
|
||||||
|
|
||||||
|
/// 添加 修改 查看
|
||||||
|
enum SafeEditType { add, edit, see }
|
||||||
|
|
||||||
|
class SafeDrawerPage extends StatefulWidget {
|
||||||
|
const SafeDrawerPage({
|
||||||
|
super.key,
|
||||||
|
required this.editType,
|
||||||
|
this.initialHidden,
|
||||||
|
required this.toCheckUnitList,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SafeEditType editType;
|
||||||
|
final List<dynamic> toCheckUnitList;
|
||||||
|
|
||||||
|
/// 可选:传入已有的 hiddenForm(用于 edit / see)
|
||||||
|
final Map<String, dynamic>? initialHidden;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SafeDrawerPage> createState() => _SafeDrawerPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SafeDrawerPageState extends State<SafeDrawerPage> {
|
||||||
|
// Controllers
|
||||||
|
final TextEditingController _descCtl = TextEditingController();
|
||||||
|
final TextEditingController _partCtl = TextEditingController();
|
||||||
|
final TextEditingController _rectifyCtl = TextEditingController();
|
||||||
|
|
||||||
|
bool _isEdit = false;
|
||||||
|
|
||||||
|
// Data lists
|
||||||
|
List<dynamic> _hazardLevels = [];
|
||||||
|
List<Map<String, dynamic>> _personCache = [];
|
||||||
|
|
||||||
|
// Selected / transient state
|
||||||
|
Map<String, dynamic> hiddenForm = {};
|
||||||
|
|
||||||
|
bool _rectifyClickable = true; // 是否可切换整改方式
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_isEdit = widget.editType != SafeEditType.see;
|
||||||
|
_initHiddenForm();
|
||||||
|
_loadHazardLevels();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initHiddenForm() {
|
||||||
|
hiddenForm = {
|
||||||
|
'ISRELEVANT': '2',
|
||||||
|
'SOURCE': '5',
|
||||||
|
'hiddenImgs': <Map<String, dynamic>>[],
|
||||||
|
'zgImgs': <Map<String, dynamic>>[],
|
||||||
|
'hiddenVideos': <Map<String, dynamic>>[],
|
||||||
|
'RECTIFICATIONTYPE': '2',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果传入了初始数据(编辑/查看),覆盖默认
|
||||||
|
if (widget.initialHidden != null) {
|
||||||
|
final Map<String, dynamic> m = Map<String, dynamic>.from(
|
||||||
|
widget.initialHidden!,
|
||||||
|
);
|
||||||
|
// normalize lists to List<Map<String,dynamic>>
|
||||||
|
m['hiddenImgs'] =
|
||||||
|
(m['hiddenImgs'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) =>
|
||||||
|
e is Map
|
||||||
|
? Map<String, dynamic>.from(e)
|
||||||
|
: {'FILEPATH': e.toString()},
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
m['zgImgs'] =
|
||||||
|
(m['zgImgs'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) =>
|
||||||
|
e is Map
|
||||||
|
? Map<String, dynamic>.from(e)
|
||||||
|
: {'FILEPATH': e.toString()},
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
m['hiddenVideos'] =
|
||||||
|
(m['hiddenVideos'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) =>
|
||||||
|
e is Map
|
||||||
|
? Map<String, dynamic>.from(e)
|
||||||
|
: {'FILEPATH': e.toString()},
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
hiddenForm.addAll(m);
|
||||||
|
}
|
||||||
|
// init controllers and flags
|
||||||
|
_descCtl.text = hiddenForm['HIDDENDESCR'] ?? '';
|
||||||
|
_partCtl.text = hiddenForm['HIDDENPART'] ?? '';
|
||||||
|
_rectifyCtl.text = hiddenForm['RECTIFYDESCR'] ?? '';
|
||||||
|
|
||||||
|
// hidden level selected map is not stored directly; we can set HIDDENLEVEL_NAME/HIDDENLEVEL
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_descCtl.dispose();
|
||||||
|
_partCtl.dispose();
|
||||||
|
_rectifyCtl.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadHazardLevels() async {
|
||||||
|
try {
|
||||||
|
final res = await ApiService.getHazardLevel();
|
||||||
|
if (res['result'] == 'success') {
|
||||||
|
setState(() => _hazardLevels = List<dynamic>.from(res['list'] ?? []));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _section(Widget child) => Container(
|
||||||
|
margin: const EdgeInsets.only(top: 10),
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> _getSelectedImages() {
|
||||||
|
return (hiddenForm['hiddenImgs'] as List<Map<String, dynamic>>)
|
||||||
|
.map((e) {
|
||||||
|
final p = '${e['FILEPATH']}';
|
||||||
|
return p.contains('uploadFiles') ? '${ApiService.baseImgPath}$p' : p;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getSelectedVideos() {
|
||||||
|
return (hiddenForm['hiddenVideos'] as List<Map<String, dynamic>>)
|
||||||
|
.map((e) => '${e['FILEPATH']}')
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// scroll content
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// photos
|
||||||
|
_section(
|
||||||
|
RepairedPhotoSection(
|
||||||
|
title: '隐患照片',
|
||||||
|
maxCount: 4,
|
||||||
|
mediaType: MediaType.image,
|
||||||
|
isShowAI: true,
|
||||||
|
isEdit: _isEdit,
|
||||||
|
isRequired: _isEdit,
|
||||||
|
initialMediaPaths: _getSelectedImages(),
|
||||||
|
onMediaTapped: (p) {
|
||||||
|
presentOpaque(SingleImageViewer(imageUrl: p), context);
|
||||||
|
},
|
||||||
|
onChanged:
|
||||||
|
(files) => setState(() {
|
||||||
|
hiddenForm['hiddenImgs'] =
|
||||||
|
files
|
||||||
|
.map((f) => {'FILEPATH': f.path})
|
||||||
|
.toList();
|
||||||
|
}),
|
||||||
|
onAiIdentify: () {
|
||||||
|
final imgs = List<Map<String, dynamic>>.from(
|
||||||
|
hiddenForm['hiddenImgs'] ?? [],
|
||||||
|
);
|
||||||
|
if (imgs.isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请先上传一张图片');
|
||||||
|
if (imgs.length > 1)
|
||||||
|
return ToastUtil.showNormal(
|
||||||
|
context,
|
||||||
|
'识别暂时只能上传一张图片',
|
||||||
|
);
|
||||||
|
final path = imgs.first['FILEPATH']?.toString() ?? '';
|
||||||
|
if (path.isNotEmpty) _identifyImage(path);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// videos
|
||||||
|
_section(
|
||||||
|
RepairedPhotoSection(
|
||||||
|
title: '隐患视频',
|
||||||
|
maxCount: 1,
|
||||||
|
onMediaTapped: (p) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
builder: (_) => VideoPlayerPopup(videoUrl:p),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isEdit: _isEdit,
|
||||||
|
mediaType: MediaType.video,
|
||||||
|
initialMediaPaths: _getSelectedVideos(),
|
||||||
|
onChanged:
|
||||||
|
(files) => setState(() {
|
||||||
|
hiddenForm['hiddenVideos'] =
|
||||||
|
files
|
||||||
|
.map((f) => {'FILEPATH': f.path})
|
||||||
|
.toList();
|
||||||
|
}),
|
||||||
|
onAiIdentify: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// description
|
||||||
|
|
||||||
|
ItemListWidget.itemContainer(
|
||||||
|
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
|
||||||
|
label: '隐患描述',
|
||||||
|
isEditable: _isEdit,
|
||||||
|
text: hiddenForm['HIDDENDESCR'] ?? '',
|
||||||
|
controller: _descCtl,
|
||||||
|
),horizontal: 5),
|
||||||
|
|
||||||
|
ItemListWidget.itemContainer(
|
||||||
|
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
|
||||||
|
label: '隐患部位',
|
||||||
|
isEditable: _isEdit,
|
||||||
|
text: hiddenForm['HIDDENPART'] ?? '',
|
||||||
|
controller: _partCtl,
|
||||||
|
),horizontal: 5),
|
||||||
|
// part
|
||||||
|
|
||||||
|
// hazard level
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _isEdit ? _pickHazardLevel : null,
|
||||||
|
child: _section(
|
||||||
|
ListItemFactory.createRowSpaceBetweenItem(
|
||||||
|
leftText: '隐患级别',
|
||||||
|
rightText:
|
||||||
|
hiddenForm['HIDDENLEVEL_NAME']?.toString() ??
|
||||||
|
'请选择',
|
||||||
|
isRight: _isEdit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// hazard type
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _isEdit ? _pickHazardType : null,
|
||||||
|
child: _section(
|
||||||
|
ListItemFactory.createRowSpaceBetweenItem(
|
||||||
|
leftText: '隐患类型',
|
||||||
|
rightText:
|
||||||
|
hiddenForm['HIDDENTYPE_NAME']?.toString() ??
|
||||||
|
'请选择',
|
||||||
|
isRight: _isEdit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// AI identify / disposition header
|
||||||
|
_section(
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
ListItemFactory.headerTitle('隐患处置'),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' 限期整改',
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// when immediate rectify
|
||||||
|
if (hiddenForm['RECTIFICATIONTYPE'] == '1') ...[
|
||||||
|
_section(
|
||||||
|
ListItemFactory.createBuildMultilineInput(
|
||||||
|
'整改描述',
|
||||||
|
'请对隐患进行整改描述(必填项)',
|
||||||
|
_rectifyCtl,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_section(
|
||||||
|
RepairedPhotoSection(
|
||||||
|
title: '整改后图片',
|
||||||
|
maxCount: 4,
|
||||||
|
mediaType: MediaType.image,
|
||||||
|
onChanged:
|
||||||
|
(files) => setState(() {
|
||||||
|
hiddenForm['zgImgs'] =
|
||||||
|
files
|
||||||
|
.map((f) => {'FILEPATH': f.path})
|
||||||
|
.toList();
|
||||||
|
}),
|
||||||
|
onAiIdentify: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
// limited rectify: department, person, date
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _isEdit ? _pickDept : null,
|
||||||
|
child: _section(
|
||||||
|
ListItemFactory.createRowSpaceBetweenItem(
|
||||||
|
leftText: '整改部门',
|
||||||
|
rightText:
|
||||||
|
_isEdit
|
||||||
|
? (hiddenForm['RECTIFICATIONDEPT_NAME']
|
||||||
|
?.toString() ??
|
||||||
|
'请选择')
|
||||||
|
: hiddenForm['RECTIFICATIONDEPT_NAME']
|
||||||
|
?.toString() ??
|
||||||
|
'',
|
||||||
|
isRight: _isEdit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _isEdit ? _pickResponsible : null,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
child: ListItemFactory.createRowSpaceBetweenItem(
|
||||||
|
leftText: '整改人',
|
||||||
|
rightText:
|
||||||
|
hiddenForm['RECTIFICATIONOR_NAME']
|
||||||
|
?.toString() ??
|
||||||
|
'请选择',
|
||||||
|
isRight: _isEdit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _isEdit ? () => _pickDate(context) : null,
|
||||||
|
child: _section(
|
||||||
|
ListItemFactory.createRowSpaceBetweenItem(
|
||||||
|
leftText: '整改期限',
|
||||||
|
rightText:
|
||||||
|
hiddenForm['RECTIFICATIONDEADLINE']
|
||||||
|
?.toString() ??
|
||||||
|
'请选择',
|
||||||
|
isRight: _isEdit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// buttons
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomButton(
|
||||||
|
onPressed: cancelHidden,
|
||||||
|
text: '取消',
|
||||||
|
textStyle: TextStyle(color: Colors.black87),
|
||||||
|
backgroundColor: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
if (_isEdit)
|
||||||
|
Expanded(
|
||||||
|
child: CustomButton(
|
||||||
|
onPressed: saveHidden,
|
||||||
|
text: '保存',
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Helpers -----------------
|
||||||
|
|
||||||
|
void _rectifyImgsFromFiles(List<File> files) =>
|
||||||
|
hiddenForm['zgImgs'] = files.map((f) => {'FILEPATH': f.path}).toList();
|
||||||
|
|
||||||
|
Future<void> _pickHazardLevel() async {
|
||||||
|
if (_hazardLevels.isEmpty) return ToastUtil.showNormal(context, '隐患级别数据为空');
|
||||||
|
final choice = await BottomPickerTwo.show<String>(
|
||||||
|
context,
|
||||||
|
items: _hazardLevels,
|
||||||
|
itemBuilder: (i) => Text(i['NAME'], textAlign: TextAlign.center),
|
||||||
|
initialIndex: 0,
|
||||||
|
);
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
if (choice == null) return;
|
||||||
|
final found = _hazardLevels.firstWhere(
|
||||||
|
(e) => e['NAME'] == choice,
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (found != null) {
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['HIDDENLEVEL'] = found['BIANMA'] ?? found['id'] ?? '';
|
||||||
|
hiddenForm['HIDDENLEVEL_NAME'] = found['NAME'] ?? '';
|
||||||
|
hiddenForm['HIDDENLEVEL_INDEX'] = found['id'] ?? '';
|
||||||
|
_rectifyClickable =
|
||||||
|
(found['DICTIONARIES_ID']?.toString() ?? '') !=
|
||||||
|
'5ff9daf78e9a4fb1b40d77980656799d';
|
||||||
|
if (!_rectifyClickable) {
|
||||||
|
hiddenForm['RECTIFICATIONTYPE'] = '2';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickHazardType() async {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder:
|
||||||
|
(_) => DepartmentPickerHiddenType(
|
||||||
|
onSelected: (result) {
|
||||||
|
try {
|
||||||
|
final Map m = Map.from(result);
|
||||||
|
final ids = List<String>.from(m['id'] ?? []);
|
||||||
|
final names = List<String>.from(m['name'] ?? []);
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['HIDDENTYPE'] = ids.isNotEmpty ? ids.first : '';
|
||||||
|
hiddenForm['HIDDENTYPE_NAME'] =
|
||||||
|
names.isNotEmpty ? names.last : '';
|
||||||
|
hiddenForm['HIDDENTYPE2'] = ids.length > 1 ? ids[1] : '';
|
||||||
|
hiddenForm['HIDDENTYPE2_NAME'] =
|
||||||
|
names.length > 1 ? names[1] : '';
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickDept() async {
|
||||||
|
final choice = await BottomPicker.show<String>(
|
||||||
|
context,
|
||||||
|
items:
|
||||||
|
widget.toCheckUnitList
|
||||||
|
.map((val) => val['UNITS_NAME'] as String)
|
||||||
|
.toList(),
|
||||||
|
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||||
|
initialIndex: 0,
|
||||||
|
);
|
||||||
|
if (choice != null) {
|
||||||
|
// 用户点击确定并选择了 choice
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['RECTIFICATIONDEPT_NAME'] = choice;
|
||||||
|
Map target = widget.toCheckUnitList.firstWhere(
|
||||||
|
(item) => item['UNITS_NAME'] == choice,
|
||||||
|
orElse: () => {},
|
||||||
|
);
|
||||||
|
hiddenForm['RECTIFICATIONDEPT'] = target?['UNITS_ID'] ?? '';
|
||||||
|
_getUnitPerson();
|
||||||
|
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getUnitPerson() async {
|
||||||
|
final res = await ApiService.getSafeCheckPersonList(
|
||||||
|
hiddenForm['RECTIFICATIONDEPT'],
|
||||||
|
'1',
|
||||||
|
);
|
||||||
|
setState(
|
||||||
|
() =>
|
||||||
|
_personCache = List<Map<String, dynamic>>.from(res['varList'] ?? []),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickResponsible() async {
|
||||||
|
if (_personCache.isEmpty) return ToastUtil.showNormal(context, '请先选择部门');
|
||||||
|
|
||||||
|
final choice = await BottomPicker.show<String>(
|
||||||
|
context,
|
||||||
|
items: _personCache.map((val) => val['NAME'] as String).toList(),
|
||||||
|
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||||
|
initialIndex: 0,
|
||||||
|
);
|
||||||
|
if (choice != null) {
|
||||||
|
// 用户点击确定并选择了 choice
|
||||||
|
setState(() {
|
||||||
|
hiddenForm['RECTIFICATIONOR_NAME'] = choice;
|
||||||
|
Map<String, dynamic>? target = widget.toCheckUnitList.firstWhere(
|
||||||
|
(item) => item['RECTIFICATIONOR_NAME'] == choice,
|
||||||
|
orElse: () => {},
|
||||||
|
);
|
||||||
|
hiddenForm['RECTIFICATIONOR'] = target?['PERSONNELMANAGEMENT_ID'];
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pickDate(BuildContext ctx) async {
|
||||||
|
DateTime? picked = await BottomDateTimePicker.showDate(context);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
final selectData = DateFormat('yyyy-MM-dd HH:mm').format(picked);
|
||||||
|
hiddenForm['RECTIFICATIONDEADLINE'] = selectData;
|
||||||
|
});
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 保存隐患(这里只返回 hiddenForm 给上层调用者)
|
||||||
|
void saveHidden() {
|
||||||
|
// update hiddenForm from controllers before returning
|
||||||
|
hiddenForm['HIDDENDESCR'] = _descCtl.text.trim();
|
||||||
|
hiddenForm['HIDDENPART'] = _partCtl.text.trim();
|
||||||
|
hiddenForm['RECTIFYDESCR'] = _rectifyCtl.text.trim();
|
||||||
|
// basic validation example
|
||||||
|
if ((hiddenForm['hiddenImgs'] as List).isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请上传隐患图片');
|
||||||
|
if ((hiddenForm['HIDDENDESCR'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请填写隐患描述');
|
||||||
|
if ((hiddenForm['HIDDENPART'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请填写隐患部位');
|
||||||
|
if ((hiddenForm['HIDDENLEVEL_NAME'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请选择隐患级别');
|
||||||
|
if ((hiddenForm['HIDDENTYPE_NAME'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请选择隐患类型');
|
||||||
|
if ((hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请选择整改部门');
|
||||||
|
if ((hiddenForm['RECTIFICATIONOR_NAME'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请选择整改人');
|
||||||
|
if ((hiddenForm['RECTIFICATIONDEADLINE'] ?? '').toString().trim().isEmpty)
|
||||||
|
return ToastUtil.showNormal(context, '请选择整改期限');
|
||||||
|
|
||||||
|
// 返回给调用方(抽屉关闭并返回数据)
|
||||||
|
Navigator.of(context).pop(hiddenForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelHidden() {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片/视频查看与删除(简单实现)
|
||||||
|
void viewImage(int index) {
|
||||||
|
final imgs = List<Map<String, dynamic>>.from(
|
||||||
|
hiddenForm['hiddenImgs'] ?? [],
|
||||||
|
);
|
||||||
|
if (index < 0 || index >= imgs.length) return;
|
||||||
|
final path = imgs[index]['FILEPATH']?.toString() ?? '';
|
||||||
|
|
||||||
|
presentOpaque(
|
||||||
|
SingleImageViewer(imageUrl: ApiService.baseImgPath + path),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void delImage(int index) {
|
||||||
|
setState(() {
|
||||||
|
final imgs = List<Map<String, dynamic>>.from(
|
||||||
|
hiddenForm['hiddenImgs'] ?? [],
|
||||||
|
);
|
||||||
|
if (index >= 0 && index < imgs.length) {
|
||||||
|
imgs.removeAt(index);
|
||||||
|
hiddenForm['hiddenImgs'] = imgs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void viewVideo(int index) {
|
||||||
|
final vids = List<Map<String, dynamic>>.from(
|
||||||
|
hiddenForm['hiddenVideos'] ?? [],
|
||||||
|
);
|
||||||
|
if (index < 0 || index >= vids.length) return;
|
||||||
|
final path = vids[index]['FILEPATH']?.toString() ?? '';
|
||||||
|
ToastUtil.showNormal(context, '查看视频:$path');
|
||||||
|
}
|
||||||
|
|
||||||
|
void delVideo(int index) {
|
||||||
|
setState(() {
|
||||||
|
final vids = List<Map<String, dynamic>>.from(
|
||||||
|
hiddenForm['hiddenVideos'] ?? [],
|
||||||
|
);
|
||||||
|
if (index >= 0 && index < vids.length) {
|
||||||
|
vids.removeAt(index);
|
||||||
|
hiddenForm['hiddenVideos'] = vids;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _identifyImage(String path) async {
|
||||||
|
try {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
final raw = await ApiService.identifyImg(path);
|
||||||
|
if (raw['result'] == 'success') {
|
||||||
|
final aiList = raw['aiHiddens'] ?? [];
|
||||||
|
String desc = '';
|
||||||
|
String rectify = '';
|
||||||
|
for (final item in aiList) {
|
||||||
|
try {
|
||||||
|
final m = jsonDecode(item);
|
||||||
|
desc =
|
||||||
|
desc.isEmpty
|
||||||
|
? (m['hiddenDescr'] ?? '')
|
||||||
|
: '$desc;${m['hiddenDescr'] ?? ''}';
|
||||||
|
rectify =
|
||||||
|
rectify.isEmpty
|
||||||
|
? (m['rectificationSuggestions'] ?? '')
|
||||||
|
: '$rectify;${m['rectificationSuggestions'] ?? ''}';
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_descCtl.text = desc;
|
||||||
|
_rectifyCtl.text = rectify;
|
||||||
|
hiddenForm['HIDDENDESCR'] = desc;
|
||||||
|
hiddenForm['RECTIFYDESCR'] = rectify;
|
||||||
|
hiddenForm['RECTIFICATIONTYPE'] = '1';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ToastUtil.showNormal(context, '识别失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ToastUtil.showNormal(context, '识别异常:$e');
|
||||||
|
} finally {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _uploadFile(String path, String type, String id) async {
|
||||||
|
try {
|
||||||
|
final r = await ApiService.addImgFiles(path, type, id);
|
||||||
|
return r['result'] == 'success' ? (r['imgPath'] ?? '') : '';
|
||||||
|
} catch (_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Position> _determinePosition() async {
|
||||||
|
if (!await Geolocator.isLocationServiceEnabled()) throw 'location disabled';
|
||||||
|
var p = await Geolocator.checkPermission();
|
||||||
|
if (p == LocationPermission.denied)
|
||||||
|
p = await Geolocator.requestPermission();
|
||||||
|
if (p == LocationPermission.denied || p == LocationPermission.deniedForever)
|
||||||
|
throw 'permission denied';
|
||||||
|
return await Geolocator.getCurrentPosition(
|
||||||
|
desiredAccuracy: LocationAccuracy.high,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -222,7 +222,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
appBar: MyAppbar(title: widget.flow),
|
appBar: MyAppbar(title: widget.flow),
|
||||||
|
|
||||||
body: Column(
|
body: SafeArea(child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Filter bar
|
// Filter bar
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -249,7 +249,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
|
||||||
// List
|
// List
|
||||||
Expanded(child: _buildListContent()),
|
Expanded(child: _buildListContent()),
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
import 'package:qhd_prevention/http/ApiService.dart';
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart';
|
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart';
|
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart';
|
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart';
|
||||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
|
@ -89,8 +91,17 @@ class _KeyprojectsTabListState extends State<KeyprojectsTabList> {
|
||||||
await pushPage(SafecheckListPage(flow: title), context);
|
await pushPage(SafecheckListPage(flow: title), context);
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
case 2: title = '隐患管理'; break;
|
case 2: {
|
||||||
case 3: title = '处罚管理'; break;
|
title = '隐患管理';
|
||||||
|
await pushPage(DangerListPage(flow: title), context);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case 3: {
|
||||||
|
title = '处罚管理';
|
||||||
|
await pushPage(PunishmentListPage(flow: title), context);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print("按钮 $index 被点击");
|
print("按钮 $index 被点击");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_list_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart';
|
||||||
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class SafecheckTabList extends StatefulWidget {
|
||||||
|
const SafecheckTabList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SafecheckTabList> createState() => _SafecheckTabListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SafecheckTabListState extends State<SafecheckTabList> {
|
||||||
|
late List<Map<String, dynamic>> buttonInfos = [
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-1.png",
|
||||||
|
"title": "安全检查\n发起",
|
||||||
|
"unreadCount": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-4.png",
|
||||||
|
"title": "检查人\n确认",
|
||||||
|
"unreadCount": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-2.png",
|
||||||
|
"title": "被检查人\n签字/申辩",
|
||||||
|
"unreadCount":'0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-2.png",
|
||||||
|
"title": "隐患指派\n及验收",
|
||||||
|
"unreadCount":'0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-2.png",
|
||||||
|
"title": "申辩记录",
|
||||||
|
"unreadCount":'0',
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getData();
|
||||||
|
}
|
||||||
|
Future<void> _getData() async {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
final data = await ApiService.getKeyProjectCount();
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
setState(() {
|
||||||
|
final eight_work_count = data['pd'] ?? {};
|
||||||
|
buttonInfos = [
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-1.png",
|
||||||
|
"title": "重点工程管理",
|
||||||
|
"unreadCount": eight_work_count['GC_COUNT'] ?? '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-1.png",
|
||||||
|
"title": "安全检查管理",
|
||||||
|
"unreadCount": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-1.png",
|
||||||
|
"title": "隐患管理",
|
||||||
|
"unreadCount": eight_work_count['CF_COUNT'] ?? '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/icon-yxkj-1.png",
|
||||||
|
"title": "处罚管理",
|
||||||
|
"unreadCount": eight_work_count['HIDDEN_COUNT'] ?? '0',
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void _handleItemPressed(int index) async {
|
||||||
|
// 根据索引执行不同的导航逻辑
|
||||||
|
String title = '';
|
||||||
|
switch (index) {
|
||||||
|
case 0: {
|
||||||
|
title = '安全检查发起';
|
||||||
|
// await pushPage(KeyprojectListPage(flow: title), context);
|
||||||
|
} break;
|
||||||
|
case 1: {
|
||||||
|
title = '安全检查核实';
|
||||||
|
// await pushPage(SafecheckListPage(flow: title), context);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case 2: {
|
||||||
|
title = '安全检查确认';
|
||||||
|
// await pushPage(DangerListPage(flow: title), context);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case 3: title = '隐患指派及验收'; break;
|
||||||
|
case 4: title = '申辩记录'; break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
print("按钮 $index 被点击");
|
||||||
|
}
|
||||||
|
|
||||||
|
_getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: MyAppbar(title: '安全检查管理'),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListItemFactory.createBuildSimpleSection('安全检查管理'),
|
||||||
|
WorkTabIconGrid(
|
||||||
|
buttonInfos: buttonInfos,
|
||||||
|
onItemPressed: _handleItemPressed,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
|
import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
|
||||||
import 'package:qhd_prevention/pages/badge_manager.dart';
|
import 'package:qhd_prevention/pages/badge_manager.dart';
|
||||||
import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
|
import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart';
|
||||||
import 'package:qhd_prevention/pages/home/home_danger_page.dart';
|
import 'package:qhd_prevention/pages/home/home_danger_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/low_page.dart';
|
import 'package:qhd_prevention/pages/home/low_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
|
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
|
||||||
|
|
@ -342,6 +343,9 @@ class _HomePageState extends State<HomePage> {
|
||||||
case 7:
|
case 7:
|
||||||
pushPage(StudyGardenPage(), context);
|
pushPage(StudyGardenPage(), context);
|
||||||
break;
|
break;
|
||||||
|
case 8: // 安全检查
|
||||||
|
await pushPage(SafecheckTabList(), context);
|
||||||
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
pushPage(SafetyMeetingListPage(), context);
|
pushPage(SafetyMeetingListPage(), context);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class ItemListWidget {
|
||||||
bool isRequired = true,
|
bool isRequired = true,
|
||||||
bool strongRequired = false,
|
bool strongRequired = false,
|
||||||
ValueChanged<String>? onChanged,
|
ValueChanged<String>? onChanged,
|
||||||
|
ValueChanged<String>? onFieldSubmitted,
|
||||||
|
|
||||||
/// 强制必选 不受是否可以编译和是否必选影响
|
/// 强制必选 不受是否可以编译和是否必选影响
|
||||||
TextInputType keyboardType = TextInputType.text,
|
TextInputType keyboardType = TextInputType.text,
|
||||||
|
|
@ -86,6 +87,8 @@ class ItemListWidget {
|
||||||
double height = 110, // 整体高度
|
double height = 110, // 整体高度
|
||||||
bool isRequired = true,
|
bool isRequired = true,
|
||||||
String hintText = '请输入',
|
String hintText = '请输入',
|
||||||
|
ValueChanged<String>? onChanged,
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
// 统一左右 padding,保证标题和内容在同一左侧基线
|
// 统一左右 padding,保证标题和内容在同一左侧基线
|
||||||
|
|
@ -117,6 +120,7 @@ class ItemListWidget {
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
expands: true,
|
expands: true,
|
||||||
|
onChanged: onChanged,
|
||||||
// 垂直顶部对齐
|
// 垂直顶部对齐
|
||||||
textAlignVertical: TextAlignVertical.top,
|
textAlignVertical: TextAlignVertical.top,
|
||||||
style: TextStyle(fontSize: fontSize),
|
style: TextStyle(fontSize: fontSize),
|
||||||
|
|
@ -693,8 +697,8 @@ class ItemListWidget {
|
||||||
return Container(
|
return Container(
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
color: Colors.grey[200],
|
color: Colors.transparent,
|
||||||
child: const Icon(Icons.broken_image, size: 40),
|
child: SizedBox(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -707,6 +711,99 @@ class ItemListWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// 多行垂直布局:
|
||||||
|
/// 标题+按钮
|
||||||
|
/// 编辑框列表,多个编辑框可删除
|
||||||
|
static Widget mulRowTitleAndTextField({
|
||||||
|
required String label, // 第一行标题
|
||||||
|
required bool isEditable, // 是否可编辑
|
||||||
|
required String text, // 显示内容或提示
|
||||||
|
TextEditingController? controller, // 第二行编辑控制器
|
||||||
|
required VoidCallback? onTap, // 第一行点击回调
|
||||||
|
required String hintText,
|
||||||
|
double fontSize = 15, // 字体大小
|
||||||
|
double row2Height = 80, // 第二行高度
|
||||||
|
bool isRequired = true,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 第一行:标题 + 按钮
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose, // loose 模式下它可以比最大宽度更小
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (isRequired && isEditable)
|
||||||
|
Text('* ', style: TextStyle(color: Colors.red)),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (isEditable)
|
||||||
|
CustomButton(
|
||||||
|
text: "选择其他",
|
||||||
|
height: 30,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 2,
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
onPressed: onTap,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
height: row2Height,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child:
|
||||||
|
isEditable
|
||||||
|
? TextField(
|
||||||
|
autofocus: false,
|
||||||
|
controller: controller,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
style: TextStyle(fontSize: fontSize),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
//contentPadding: EdgeInsets.zero,
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: detailtextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Widget itemContainer(Widget child, {double horizontal = 12}) {
|
static Widget itemContainer(Widget child, {double horizontal = 12}) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,14 @@ class FormUtils {
|
||||||
// 数字、布尔等其它非空即可
|
// 数字、布尔等其它非空即可
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
/// 在list中根据一个 key,value,找到对应的map
|
||||||
|
static Map findMapForKeyValue(List list,String key, String value) {
|
||||||
|
Map target = list.firstWhere(
|
||||||
|
(item) => item[key] == value,
|
||||||
|
orElse: () => {},
|
||||||
|
);
|
||||||
|
return target ?? {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoDataWidget {
|
class NoDataWidget {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue