重点工程管理完
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(
|
||||||
|
children: [
|
||||||
|
if (isRequired && isEdit) Text('* ', style: TextStyle(color: Colors.red)),
|
||||||
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black,
|
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,23 +191,24 @@ 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(
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => widget.onMediaTapped?.call(path),
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
child: widget.mediaType == MediaType.image
|
child: widget.mediaType == MediaType.image
|
||||||
? (isNetwork
|
? (isNetwork
|
||||||
? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,)
|
? Image.network(path, fit: BoxFit.cover, width: 80, height: 80)
|
||||||
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
||||||
: Container(
|
: Container(
|
||||||
color: Colors.black12,
|
color: Colors.black12,
|
||||||
|
|
@ -216,6 +220,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// 只在可编辑状态下显示删除按钮
|
||||||
|
if (widget.isEdit)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: -15,
|
top: -15,
|
||||||
right: -15,
|
right: -15,
|
||||||
|
|
@ -225,8 +231,11 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
|
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
|
||||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
import 'package:qhd_prevention/customWidget/custom_button.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/toast_util.dart';
|
||||||
import 'package:qhd_prevention/http/ApiService.dart';
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart';
|
||||||
|
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart';
|
||||||
|
import 'package:qhd_prevention/pages/app/Danger_paicha/quick_report_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
import 'package:qhd_prevention/tools/tools.dart';
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
@ -28,114 +35,222 @@ class SafecheckDetail extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SafecheckDetailState extends State<SafecheckDetail> {
|
class _SafecheckDetailState extends State<SafecheckDetail> {
|
||||||
late Map<String, dynamic> info = {};
|
|
||||||
/// 被检查单位负责人
|
/// 被检查单位负责人
|
||||||
late List<dynamic> personList = [];
|
late List<dynamic> personList = [];
|
||||||
|
|
||||||
/// 检查类型
|
/// 检查类型
|
||||||
late List<dynamic> typeList = [];
|
late List<dynamic> typeList = [];
|
||||||
|
|
||||||
/// 被检查单位
|
/// 被检查单位
|
||||||
late List<dynamic> toCheckUnitList = [];
|
late List<dynamic> toCheckUnitList = [];
|
||||||
|
|
||||||
bool? chooseTitleType = null;
|
bool? chooseTitleType = null;
|
||||||
|
|
||||||
final TextEditingController _unitController = TextEditingController();
|
// 存储多行输入的内容
|
||||||
final TextEditingController _locationController = TextEditingController();
|
List<String> multiTexts = [];
|
||||||
|
|
||||||
final TextEditingController _personController = TextEditingController();
|
List<String> delInspectors = [];
|
||||||
|
List<String> delSituations = [];
|
||||||
|
List<String> delHiddens = [];
|
||||||
|
List<String> delHiddenFiles = [];
|
||||||
|
|
||||||
|
// rules 格式: [{ 'name': 'INSPECTION_CATEGORY', 'message': '请填写检查题目' }, ...]
|
||||||
|
List<Map<String, String>> rules = [
|
||||||
|
{'name': 'INSPECTION_CATEGORY', 'message': '请选择检查题目'},
|
||||||
|
{'name': 'UNITS_ID', 'message': '请选择被检查单位'},
|
||||||
|
{'name': 'PERSONNELMANAGEMENT_ID', 'message': '请选择被检查单位现场负责人'},
|
||||||
|
{'name': 'INSPECTION_TYPE', 'message': '请选择检查类型不能为空'},
|
||||||
|
{'name': 'INSPECTION_PLACE', 'message': '请输入检查场所'},
|
||||||
|
{'name': 'INSPECTION_TIME_START', 'message': '请选择检查开始时间'},
|
||||||
|
{'name': 'INSPECTION_TIME_END', 'message': '请选择作业结束时间'},
|
||||||
|
{'name': 'INSPECTION_USERS', 'message': '请输入检查人员'},
|
||||||
|
];
|
||||||
|
|
||||||
|
Map<String, dynamic> form = {
|
||||||
|
'INSPECTION_USERS': '',
|
||||||
|
'KEYPROJECTCHECK_ID': '', // 检查ID
|
||||||
|
'OUTSOURCED_ID': '', // 检查ID
|
||||||
|
'INSPECTION_CATEGORY': '', // 检查标题
|
||||||
|
'INSPECTION_SOURCE': '5', // 检查来源(4-监管端 5-企业端)
|
||||||
|
'INSPECTION_ORIGINATOR_ID': '', // 检查发起人
|
||||||
|
'UNITS_ID': '', // 被检查单位
|
||||||
|
'UNITS_NAME': '',
|
||||||
|
'PERSONNELMANAGEMENT_ID': '', // 被检查单位现场负责人
|
||||||
|
'INSPECTED_SITEUSER_INDEX': '',
|
||||||
|
'PERSON_NAME': '',
|
||||||
|
'INSPECTED_EXPLAIN': '', // 申辩内容
|
||||||
|
'INSPECTED_SITEUSER_SIGN_IMG': '', // 被检查单位现场负责人签字
|
||||||
|
'INSPECTED_SITEUSER_SIGN_TIME': '', // 被检查单位现场负责人签字时间
|
||||||
|
'INSPECTION_TYPE': '', // 检查类型
|
||||||
|
'INSPECTION_TYPE_NAME': '',
|
||||||
|
'INSPECTION_PLACE': '', // 检查场所
|
||||||
|
'INSPECTION_TIME_START': '', // 检查开始时间
|
||||||
|
'INSPECTION_TIME_END': '', // 检查结束时间
|
||||||
|
'INSPECTION_STATUS': '0', // 状态
|
||||||
|
'POSITIONDESC': '', // 隐患位置描述
|
||||||
|
'CREATTIME': '',
|
||||||
|
'inspectorList': [
|
||||||
|
{
|
||||||
|
'INSPECTION_INSPECTOR_ID': '', //检查人员主键
|
||||||
|
'INSPECTION_DEPARTMENT_ID': '', //检查人员部门ID
|
||||||
|
'INSPECTION_DEPARTMENT_NAME': '',
|
||||||
|
'INSPECTION_USER_ID': '', //检查人员ID
|
||||||
|
'INSPECTION_USER_INDEX': '',
|
||||||
|
'INSPECTION_USER_NAME': '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'situationList': [
|
||||||
|
{'INSPECTION_SITUATION_ID': '', 'SITUATION': ''},
|
||||||
|
],
|
||||||
|
'hiddenList': [
|
||||||
|
{
|
||||||
|
'ISRELEVANT': '2',
|
||||||
|
'HIDDEN_ID': '', // 隐患ID
|
||||||
|
'HIDDENDESCR': '', // 隐患描述
|
||||||
|
'HIDDENPART': '', // 隐患部位
|
||||||
|
'HIDDENPART_NAME': '',
|
||||||
|
'HIDDENLEVEL': '', // 隐患级别
|
||||||
|
'HIDDENLEVEL_NAME': '',
|
||||||
|
'HIDDENTYPE': '', // 隐患类型1
|
||||||
|
'HIDDENTYPE_NAME': '',
|
||||||
|
'HIDDENTYPE2': '', // 隐患类型2
|
||||||
|
'HIDDENTYPE2_NAME': '',
|
||||||
|
'LONGITUDE': '', // 隐患位置经度
|
||||||
|
'LATITUDE': '', // 隐患位置纬度
|
||||||
|
'DISCOVERYTIME': '', // 隐患发现时间
|
||||||
|
'HIDDENFINDDEPT': '', // 隐患发现部门(隐患责任人部门)
|
||||||
|
'HIDDENFINDDEPT_NAME': '',
|
||||||
|
'CREATOR': '', // 发现人(隐患责任人)
|
||||||
|
'CREATOR_INDEX': '',
|
||||||
|
'CREATOR_NAME': '',
|
||||||
|
'SOURCE': '5', // 隐患来源
|
||||||
|
'hiddenImgs': <String>[],
|
||||||
|
'zgImgs': <String>[],
|
||||||
|
'hiddenVideos': <String>[],
|
||||||
|
'RECTIFICATIONTYPE': '2',
|
||||||
|
'RECTIFICATIONDEADLINE': '',
|
||||||
|
'RECTIFYDESCR': '',
|
||||||
|
'RECTIFICATIONDEPT_NAME': '',
|
||||||
|
'RECTIFICATIONDEPT': '',
|
||||||
|
'RECTIFICATIONOR_INDEX': '',
|
||||||
|
'HIDDENLEVEL_INDEX': '',
|
||||||
|
'RECTIFICATIONOR_NAME': '',
|
||||||
|
'RECTIFICATIONOR': '',
|
||||||
|
'punishForm': null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'INSPECTION_USER_SIGN_TIME': '',
|
||||||
|
'INSPECTION_USER_OPINION': '',
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
form['OUTSOURCED_ID'] = widget.OUTSOURCED_ID;
|
||||||
|
form['KEYPROJECTCHECK_ID'] = widget.KEYPROJECTCHECK_ID;
|
||||||
|
form['hiddenList'] = [];
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (widget.KEYPROJECTCHECK_ID.isNotEmpty) {
|
||||||
_getData();
|
_getData();
|
||||||
|
} else {
|
||||||
|
// 只有在没有 KEYPROJECTCHECK_ID 时,才做本地的初始值设置
|
||||||
|
setState(() {
|
||||||
|
form['APPLY_DEPARTMENT_ID'] = SessionService.instance.deptId ?? '';
|
||||||
|
form['APPLY_DEPARTMENT_NAME'] =
|
||||||
|
SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? '';
|
||||||
|
form['APPLY_USER_ID'] = SessionService.instance.loginUserId ?? '';
|
||||||
|
form['APPLY_USER'] = SessionService.instance.username ?? '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 无论如何都去拉取下拉/基础数据
|
||||||
|
_getAllRequest();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getData() async {
|
Future<void> _getData() async {
|
||||||
LoadingDialogHelper.show();
|
|
||||||
final result = await ApiService.addSafeCheckReciord(widget.OUTSOURCED_ID);
|
|
||||||
final personData = await ApiService.getSafeCheckPersonList(info['UNITS_ID']??'', '1');
|
|
||||||
final typeListData = await ApiService.getSafeCheckTypeList();
|
|
||||||
final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID);
|
|
||||||
try {
|
try {
|
||||||
|
final result = await ApiService.getSafeCheckGoEdit(widget.KEYPROJECTCHECK_ID);
|
||||||
|
// 在 await 之后检查 mounted,避免页面已经被 pop 导致 setState 报错
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
info = result['pd'] ?? {};
|
form = result['pd'] ?? {};
|
||||||
personList = personData['varList'];
|
_syncMultiTextsFromForm();
|
||||||
typeList = jsonDecode(typeListData['zTreeNodes']);
|
|
||||||
toCheckUnitList = toUnitListData['varList'];
|
|
||||||
if (!FormUtils.hasValue(info, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) {
|
|
||||||
info['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME'];
|
|
||||||
}
|
|
||||||
if (!FormUtils.hasValue(info, 'UNITS_ID') && toCheckUnitList.isNotEmpty) {
|
|
||||||
info['UNITS_ID'] = toCheckUnitList.first['UNITS_ID'];
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
print('加载数据失败:$e');
|
print('加载单条数据失败: $e\n$st');
|
||||||
|
if (mounted) {
|
||||||
ToastUtil.showNormal(context, '加载数据失败:$e');
|
ToastUtil.showNormal(context, '加载数据失败:$e');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LoadingDialogHelper.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onUpdateState(String state) async {
|
Future<void> _getAllRequest() async {
|
||||||
// 提示文案
|
|
||||||
String content = '';
|
|
||||||
if (state == '1') {
|
|
||||||
content = '确定同意开工吗?';
|
|
||||||
} else if (state == '2') {
|
|
||||||
content = '确定同意结束吗?';
|
|
||||||
} else {
|
|
||||||
content = '确定操作吗?';
|
|
||||||
}
|
|
||||||
final bool confirmed =
|
|
||||||
await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(ctx) => CustomAlertDialog(
|
|
||||||
title: '提示',
|
|
||||||
content: content,
|
|
||||||
onConfirm: () => Navigator.of(ctx).pop(false),
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
|
|
||||||
if (!confirmed) return;
|
|
||||||
LoadingDialogHelper.show();
|
|
||||||
try {
|
try {
|
||||||
final response = await ApiService.sureKeyProjectState(
|
// 在网络请求前确保 widget 已经 build 完毕
|
||||||
widget.OUTSOURCED_ID,
|
LoadingDialogHelper.show();
|
||||||
state,
|
|
||||||
);
|
final result = await ApiService.addSafeCheckRecord(widget.OUTSOURCED_ID);
|
||||||
|
// 若页面已被销毁,就直接返回
|
||||||
|
if (!mounted) {
|
||||||
LoadingDialogHelper.hide();
|
LoadingDialogHelper.hide();
|
||||||
if (response['result'] == 'success') {
|
return;
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else {
|
|
||||||
ToastUtil.showNormal(context, '请求失败');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
Map pd = result['pd'] ?? {};
|
||||||
|
form['UNITS_NAME'] = pd['UNITS_NAME'] ?? '';
|
||||||
|
form['UNITS_ID'] = pd['UNITS_ID'] ?? '';
|
||||||
|
form['PERSONNELMANAGEMENT_ID'] = pd['PERSONNELMANAGEMENT_ID'] ?? '';
|
||||||
|
form['PERSON_NAME'] = pd['NAME'] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final personData = await ApiService.getSafeCheckPersonList(
|
||||||
|
form['UNITS_ID'] ?? '',
|
||||||
|
'1',
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
personList = personData['varList'] ?? [];
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastUtil.showNormal(context, '请求异常:$e');
|
print('加载 personList 失败: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final typeListData = await ApiService.getSafeCheckTypeList();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
typeList = jsonDecode(typeListData['zTreeNodes'] ?? '[]');
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('加载 typeList 失败: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
toCheckUnitList = toUnitListData['varList'] ?? [];
|
||||||
|
// 仅在没有选择时自动回填
|
||||||
|
if (!FormUtils.hasValue(form, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) {
|
||||||
|
form['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME'];
|
||||||
|
}
|
||||||
|
if (!FormUtils.hasValue(form, 'UNITS_ID') && toCheckUnitList.isNotEmpty) {
|
||||||
|
form['UNITS_ID'] = toCheckUnitList.first['UNITS_ID'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('加载 toCheckUnitList 失败: $e');
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
print('总的加载失败: $e\n$st');
|
||||||
|
if (mounted) ToastUtil.showNormal(context, '加载数据失败:$e');
|
||||||
|
} finally {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget lineItem({
|
|
||||||
required String label,
|
|
||||||
required bool isEditable,
|
|
||||||
String? text,
|
|
||||||
}) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ItemListWidget.singleLineTitleText(
|
|
||||||
label: label,
|
|
||||||
isEditable: isEditable,
|
|
||||||
text: text ?? '',
|
|
||||||
),
|
|
||||||
const Divider(height: 1, thickness: 1),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Future<void> _choosePerson() async {
|
Future<void> _choosePerson() async {
|
||||||
final choice = await BottomPicker.show<String>(
|
final choice = await BottomPicker.show<String>(
|
||||||
context,
|
context,
|
||||||
|
|
@ -146,19 +261,443 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
|
||||||
if (choice != null) {
|
if (choice != null) {
|
||||||
// 用户点击确定并选择了 choice
|
// 用户点击确定并选择了 choice
|
||||||
setState(() {
|
setState(() {
|
||||||
info['PERSON_NAME'] = choice;
|
form['PERSON_NAME'] = choice;
|
||||||
|
final data = FormUtils.findMapForKeyValue(personList, 'NAME', choice);
|
||||||
|
form['PERSONNELMANAGEMENT_ID'] = data['PERSONNELMANAGEMENT_ID'];
|
||||||
|
form['INSPECTED_SITEUSER_INDEX'] = personList.indexOf(data);
|
||||||
|
|
||||||
FocusHelper.clearFocus(context);
|
FocusHelper.clearFocus(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _chooseType() async {
|
||||||
|
final choice = await BottomPicker.show<String>(
|
||||||
|
context,
|
||||||
|
items: typeList.map((val) => val['name'] as String).toList(),
|
||||||
|
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||||
|
initialIndex: 0,
|
||||||
|
);
|
||||||
|
if (choice != null) {
|
||||||
|
// 用户点击确定并选择了 choice
|
||||||
|
setState(() {
|
||||||
|
form['INSPECTION_TYPE_NAME'] = choice;
|
||||||
|
final data = FormUtils.findMapForKeyValue(typeList, 'name', choice);
|
||||||
|
form['INSPECTION_TYPE'] = data['id'];
|
||||||
|
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openDrawer(Map<String, dynamic> hiddenForm, int index) async {
|
||||||
|
try {
|
||||||
|
final result = await openCustomDrawer<Map>(
|
||||||
|
context,
|
||||||
|
SafeDrawerPage(
|
||||||
|
initialHidden: hiddenForm,
|
||||||
|
editType:
|
||||||
|
widget.isEdit
|
||||||
|
? (index < 0 ? SafeEditType.add : SafeEditType.edit)
|
||||||
|
: SafeEditType.see,
|
||||||
|
toCheckUnitList: toCheckUnitList,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != null && mounted) {
|
||||||
|
setState(() {
|
||||||
|
if (index < 0) {
|
||||||
|
// 新增
|
||||||
|
form['hiddenList'].add(result);
|
||||||
|
} else {
|
||||||
|
// 修改
|
||||||
|
form['hiddenList'][index] = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("打开抽屉失败: $e");
|
||||||
|
ToastUtil.showNormal(context, "打开抽屉失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T?> openCustomDrawer<T>(BuildContext context, Widget child) {
|
||||||
|
return Navigator.of(context).push<T>(
|
||||||
|
PageRouteBuilder(
|
||||||
|
opaque: false,
|
||||||
|
barrierDismissible: true,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
pageBuilder: (_, __, ___) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 4 / 5,
|
||||||
|
child: Material(color: Colors.white, child: child),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
transitionsBuilder: (_, anim, __, child) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween(
|
||||||
|
begin: const Offset(1, 0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将 form['situationList'](若存在)转换为 List<String>
|
||||||
|
List<String> _situationListToStrings() {
|
||||||
|
final List<dynamic> cur = List<dynamic>.from(form['situationList'] ?? []);
|
||||||
|
if (cur.isEmpty) return ['']; // 保持至少一行,和 uni-app 行为一致
|
||||||
|
return cur.map((e) {
|
||||||
|
if (e is Map && e['SITUATION'] != null) return e['SITUATION'].toString();
|
||||||
|
return '';
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将 List<String> 转换成 uni-app 风格的 situationList(保留已存在的 INSPECTION_SITUATION_ID)
|
||||||
|
List<Map<String, dynamic>> _stringsToSituationList(List<String> texts) {
|
||||||
|
final List<dynamic> existing = List<dynamic>.from(
|
||||||
|
form['situationList'] ?? [],
|
||||||
|
);
|
||||||
|
final List<Map<String, dynamic>> out = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < texts.length; i++) {
|
||||||
|
final s = texts[i] ?? '';
|
||||||
|
if (i < existing.length && existing[i] is Map) {
|
||||||
|
// 保留已有项的其他字段(如 INSPECTION_SITUATION_ID),只覆盖 SITUATION
|
||||||
|
final Map<String, dynamic> copy = Map<String, dynamic>.from(
|
||||||
|
existing[i],
|
||||||
|
);
|
||||||
|
copy['SITUATION'] = s;
|
||||||
|
// 若没有 INSPECTION_SITUATION_ID 字段,确保它存在(保持原 uni-app 结构)
|
||||||
|
copy['INSPECTION_SITUATION_ID'] = copy['INSPECTION_SITUATION_ID'] ?? '';
|
||||||
|
out.add(copy);
|
||||||
|
} else {
|
||||||
|
// 新增项
|
||||||
|
out.add({'INSPECTION_SITUATION_ID': '', 'SITUATION': s});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户删除了行,要把超出的 existing id 记录到 delSituations(可选)
|
||||||
|
// 下面为示例:把被删除的旧 ID 收集到 delSituations,便于编辑时提交删除列表
|
||||||
|
if (existing.isNotEmpty && existing.length > texts.length) {
|
||||||
|
for (int j = texts.length; j < existing.length; j++) {
|
||||||
|
final ex = existing[j];
|
||||||
|
if (ex is Map) {
|
||||||
|
final id = (ex['INSPECTION_SITUATION_ID'] ?? '').toString();
|
||||||
|
if (id.isNotEmpty) {
|
||||||
|
delSituations.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在加载数据之后调用:把已存在的 form['situationList'] 同步到 multiTexts(用于 MultiTextFieldWithTitle)
|
||||||
|
void _syncMultiTextsFromForm() {
|
||||||
|
final List<String> arr = _situationListToStrings();
|
||||||
|
setState(() {
|
||||||
|
multiTexts = arr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------ 提交入口 ------------
|
||||||
|
Future<void> _submit() async {
|
||||||
|
if (!widget.isEdit) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool required = true;
|
||||||
|
// 基于 rules 验证
|
||||||
|
for (final r in rules) {
|
||||||
|
final name = r['name'] ?? '';
|
||||||
|
final message = r['message'] ?? '请完善表单';
|
||||||
|
final v = form[name];
|
||||||
|
if (v == null || v.toString().isEmpty || v.toString() == '请选择') {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, message);
|
||||||
|
required = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!required) return;
|
||||||
|
|
||||||
|
// situationList 每项 SITUATION 非空
|
||||||
|
final situations = (form['situationList'] as List<dynamic>?) ?? [];
|
||||||
|
for (var i = 0; i < situations.length; i++) {
|
||||||
|
final s = Map<String, dynamic>.from(situations[i]);
|
||||||
|
if ((s['SITUATION'] ?? '').toString().trim().isEmpty) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '请填写第${i + 1}项检查情况');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 inspectorList 中是否有重复 INSPECTION_USER_ID
|
||||||
|
final List<Map<String, String>> inspectors = form['inspectorList'] ?? [];
|
||||||
|
final seenIds = <String>{};
|
||||||
|
for (final it in inspectors) {
|
||||||
|
final id = (it as Map)['INSPECTION_USER_ID']?.toString() ?? '';
|
||||||
|
if (id.isNotEmpty) {
|
||||||
|
if (seenIds.contains(id)) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '检查人重复!请检查数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seenIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据 hiddenList 构建需要上传的文件数组
|
||||||
|
final origHiddenList = (form['hiddenList'] as List<dynamic>?) ?? [];
|
||||||
|
final List<List<Map<String, dynamic>>> hiddenFilesPerHidden = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < origHiddenList.length; i++) {
|
||||||
|
final hidden = Map<String, dynamic>.from(origHiddenList[i] as Map);
|
||||||
|
final List<Map<String, dynamic>> fileList = [];
|
||||||
|
|
||||||
|
// hiddenImgs (多张)
|
||||||
|
final hiddenImgs = (hidden['hiddenImgs'] as List<dynamic>?) ?? [];
|
||||||
|
for (var j = 0; j < hiddenImgs.length; j++) {
|
||||||
|
final img = hiddenImgs[j];
|
||||||
|
// 如果是字符串路径
|
||||||
|
if (img is String) {
|
||||||
|
fileList.add({'type': 3, 'FILEPATH': img});
|
||||||
|
} else if (img is Map) {
|
||||||
|
final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty;
|
||||||
|
if (!hasId) {
|
||||||
|
fileList.add({
|
||||||
|
'type': 3,
|
||||||
|
'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zgImgs (整改图)
|
||||||
|
final zgImgs = (hidden['zgImgs'] as List<dynamic>?) ?? [];
|
||||||
|
for (var j = 0; j < zgImgs.length; j++) {
|
||||||
|
final img = zgImgs[j];
|
||||||
|
if (img is String) {
|
||||||
|
fileList.add({'type': 4, 'FILEPATH': img});
|
||||||
|
} else if (img is Map) {
|
||||||
|
final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty;
|
||||||
|
if (!hasId) {
|
||||||
|
fileList.add({
|
||||||
|
'type': 4,
|
||||||
|
'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hiddenVideos (只取第一个)
|
||||||
|
final hiddenVideos = (hidden['hiddenVideos'] as List<dynamic>?) ?? [];
|
||||||
|
if (hiddenVideos.isNotEmpty) {
|
||||||
|
final v = hiddenVideos[0];
|
||||||
|
if (v is String) {
|
||||||
|
fileList.add({'type': 102, 'FILEPATH': v});
|
||||||
|
} else if (v is Map) {
|
||||||
|
final hasId = (v['IMGFILES_ID'] ?? '').toString().isNotEmpty;
|
||||||
|
if (!hasId) {
|
||||||
|
fileList.add({
|
||||||
|
'type': 102,
|
||||||
|
'FILEPATH': v['FILEPATH'] ?? v['path'] ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hiddenFilesPerHidden.add(fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保当前登录用户在 inspectorList 中(依据 SessionService)
|
||||||
|
final loginUser = SessionService.instance.loginUser ?? {};
|
||||||
|
final loginUserId = SessionService.instance.loginUserId ?? '';
|
||||||
|
final idx = inspectors.indexWhere((item) {
|
||||||
|
final m = Map<String, dynamic>.from(item as Map);
|
||||||
|
return (m['INSPECTION_USER_ID'] ?? '') ==
|
||||||
|
(loginUser['USER_ID'] ?? loginUserId);
|
||||||
|
});
|
||||||
|
if (idx < 0) {
|
||||||
|
inspectors.add({
|
||||||
|
'INSPECTION_INSPECTOR_ID': '',
|
||||||
|
'INSPECTION_DEPARTMENT_ID': loginUser['DEPARTMENT_ID'] ?? '',
|
||||||
|
'INSPECTION_DEPARTMENT_NAME': loginUser['DEPARTMENT_NAME'] ?? '',
|
||||||
|
'INSPECTION_USER_ID': loginUser['USER_ID'] ?? loginUserId,
|
||||||
|
'INSPECTION_USER_INDEX': '',
|
||||||
|
'INSPECTION_USER_NAME': loginUser['NAME'] ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备 form 字段(JSON 字符串等)
|
||||||
|
form['INSPECTORJSON'] = jsonEncode(inspectors);
|
||||||
|
form['SITUATIONJSON'] = jsonEncode(situations);
|
||||||
|
form['HIDDENJSON'] = jsonEncode(origHiddenList);
|
||||||
|
form['delInspectors'] = delInspectors.join(',');
|
||||||
|
form['delSituations'] = delSituations.join(',');
|
||||||
|
form['delHiddens'] = delHiddens.join(',');
|
||||||
|
form['delHiddenFiles'] = delHiddenFiles.join(',');
|
||||||
|
form['CREATOR'] = loginUser['USER_ID'] ?? loginUserId;
|
||||||
|
form['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? '';
|
||||||
|
form['ACTION_USER'] = loginUser['NAME'] ?? '';
|
||||||
|
LoadingDialogHelper.show(); // 显示 loading
|
||||||
|
|
||||||
|
// 提交主表
|
||||||
|
try {
|
||||||
|
final requestData = <String, dynamic>{
|
||||||
|
'CORPINFO_ID': form['CORPINFO_ID'],
|
||||||
|
...form,
|
||||||
|
};
|
||||||
|
final res = await ApiService.safeKeyprojectCheckSubmit(requestData);
|
||||||
|
// 如果你的 ApiService 返回结构不同,请按实际调整判断
|
||||||
|
if (res != null && res['result'] == 'success') {
|
||||||
|
final pd = res['pd'] ?? {};
|
||||||
|
final List<dynamic> returnedHiddenList = pd['hiddenList'] ?? [];
|
||||||
|
|
||||||
|
// 如果没有附件需要上传,直接完成
|
||||||
|
final hasFiles = hiddenFilesPerHidden.any((lst) => lst.isNotEmpty);
|
||||||
|
if (!hasFiles) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '提交成功');
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若每个 hidden 有 punishForm,需要先提交罚单
|
||||||
|
for (var i = 0; i < returnedHiddenList.length; i++) {
|
||||||
|
if (i < (form['hiddenList'] as List).length) {
|
||||||
|
final hidden = Map<String, dynamic>.from(
|
||||||
|
(form['hiddenList'] as List)[i],
|
||||||
|
);
|
||||||
|
final punishForm = hidden['punishForm'];
|
||||||
|
if (punishForm != null) {
|
||||||
|
final hid = (returnedHiddenList[i]['HIDDEN_ID'] ?? '').toString();
|
||||||
|
punishForm['HIDDEN_ID'] = hid;
|
||||||
|
// await 调用罚单提交(在下面实现)
|
||||||
|
await fnSubmit(Map<String, dynamic>.from(punishForm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传所有附件(按隐患对应的 hiddenId)
|
||||||
|
// 把返回的 hiddenList 转为 Map 列表,确保索引一致
|
||||||
|
final returnedHiddenMapList =
|
||||||
|
returnedHiddenList
|
||||||
|
.map((e) => Map<String, dynamic>.from(e))
|
||||||
|
.toList();
|
||||||
|
await uploadHiddenFiles(hiddenFilesPerHidden, returnedHiddenMapList);
|
||||||
|
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '提交成功');
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
final msg =
|
||||||
|
res != null
|
||||||
|
? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败')
|
||||||
|
: '提交失败';
|
||||||
|
ToastUtil.showNormal(context, msg);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '提交异常:$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 上传附件的方法 ==========
|
||||||
|
Future<void> uploadHiddenFiles(
|
||||||
|
List<List<Map<String, dynamic>>> hiddenFilesPerHidden,
|
||||||
|
List<Map<String, dynamic>> returnedHiddenList,
|
||||||
|
) async {
|
||||||
|
for (var i = 0; i < hiddenFilesPerHidden.length; i++) {
|
||||||
|
final filesForHidden = hiddenFilesPerHidden[i];
|
||||||
|
if (filesForHidden.isEmpty) continue;
|
||||||
|
final hiddenId =
|
||||||
|
i < returnedHiddenList.length
|
||||||
|
? (returnedHiddenList[i]['HIDDEN_ID']?.toString() ?? '')
|
||||||
|
: '';
|
||||||
|
if (hiddenId.isEmpty) continue;
|
||||||
|
|
||||||
|
for (final f in filesForHidden) {
|
||||||
|
final filePath = f['FILEPATH']?.toString() ?? '';
|
||||||
|
final type = f['type']?.toString() ?? '';
|
||||||
|
if (filePath.isEmpty) continue;
|
||||||
|
try {
|
||||||
|
await ApiService.addImgFiles(filePath, type, hiddenId);
|
||||||
|
} catch (e) {
|
||||||
|
// 你可以记录失败项或重试,这里先忽略单文件错误
|
||||||
|
print('上传文件失败: $e (path=$filePath)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 罚单提交方法(对应原 fnSubmit) ==========
|
||||||
|
Future<bool> fnSubmit(Map<String, dynamic>? ordForm) async {
|
||||||
|
if (ordForm == null) return false;
|
||||||
|
|
||||||
|
final Map<String, String> punishRules = {
|
||||||
|
'REASON': '请填写处罚原因',
|
||||||
|
'AMOUT': '请填写处罚金额',
|
||||||
|
'DATE': '请选择下发处罚时间',
|
||||||
|
};
|
||||||
|
// 校验
|
||||||
|
for (final entry in punishRules.entries) {
|
||||||
|
final key = entry.key;
|
||||||
|
final msg = entry.value;
|
||||||
|
final val = ordForm[key];
|
||||||
|
if (val == null || val.toString().trim().isEmpty) {
|
||||||
|
ToastUtil.showNormal(context, msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final requestData = Map<String, dynamic>.from(ordForm);
|
||||||
|
requestData['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? '';
|
||||||
|
requestData['CREATOR'] = SessionService.instance.loginUserId ?? '';
|
||||||
|
requestData['OPERATOR'] = SessionService.instance.loginUserId ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
final res = await ApiService.safeCheckPunishSubmit(requestData);
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
if (FormUtils.hasValue(res, 'result') && res['result'] == 'success') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
final msg =
|
||||||
|
res != null
|
||||||
|
? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败')
|
||||||
|
: '提交失败';
|
||||||
|
ToastUtil.showNormal(context, msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
LoadingDialogHelper.hide();
|
||||||
|
ToastUtil.showNormal(context, '罚单提交异常:$e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: MyAppbar(title: "安全检查发起"),
|
appBar: MyAppbar(title: "安全检查发起", actions: []),
|
||||||
|
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
child: Column(
|
child: form.isNotEmpty ? ListView(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
children: [
|
children: [
|
||||||
ItemListWidget.itemContainer(
|
ItemListWidget.itemContainer(
|
||||||
horizontal: 0,
|
horizontal: 0,
|
||||||
|
|
@ -169,22 +708,29 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
|
||||||
groupValue: chooseTitleType,
|
groupValue: chooseTitleType,
|
||||||
yesLabel: '安全',
|
yesLabel: '安全',
|
||||||
noLabel: '综合',
|
noLabel: '综合',
|
||||||
|
isEdit: widget.isEdit,
|
||||||
|
isRequired: true,
|
||||||
horizontalPadding: 5,
|
horizontalPadding: 5,
|
||||||
verticalPadding: 0,
|
verticalPadding: 0,
|
||||||
|
text: form['INSPECTION_CATEGORY'] ?? '',
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
setState(() {
|
setState(() {
|
||||||
chooseTitleType = val;
|
chooseTitleType = val;
|
||||||
|
form['INSPECTION_CATEGORY'] =
|
||||||
|
val == true ? '安全' : '综合';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEdit: true,
|
|
||||||
text: info['INSPECTION_CATEGORY'] ?? '',
|
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
ItemListWidget.singleLineTitleText(
|
ItemListWidget.singleLineTitleText(
|
||||||
label: '被检查单位:',
|
label: '被检查单位:',
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
text: info['UNITS_NAME'],
|
text: form['UNITS_NAME'],
|
||||||
controller: _unitController,
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
form['UNITS_NAME'] = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
|
|
@ -194,31 +740,152 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_choosePerson();
|
_choosePerson();
|
||||||
},
|
},
|
||||||
text: info['PERSON_NAME'] ?? '',
|
text: form['PERSON_NAME'] ?? '',
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
ItemListWidget.singleLineTitleText(
|
ItemListWidget.singleLineTitleText(
|
||||||
label: '检查场所:',
|
label: '检查场所:',
|
||||||
isEditable: widget.isEdit,
|
isEditable: widget.isEdit,
|
||||||
text: info['INSPECTION_PLACE'],
|
text: form['INSPECTION_PLACE'],
|
||||||
hintText: '请输入检查场所',
|
hintText: '请输入检查场所',
|
||||||
controller: _locationController,
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
form['INSPECTION_PLACE'] = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
ItemListWidget.selectableLineTitleTextRightButton(
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
label: '检查类型:',
|
label: '检查类型:',
|
||||||
|
onTap: () {
|
||||||
|
_chooseType();
|
||||||
|
},
|
||||||
isEditable: widget.isEdit,
|
isEditable: widget.isEdit,
|
||||||
text: info['INSPECTION_TYPE_NAME'] ?? '',
|
text: form['INSPECTION_TYPE_NAME'] ?? '',
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
ItemListWidget.selectableLineTitleTextRightButton(
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
label: '检查开始时间:',
|
label: '检查开始时间:',
|
||||||
isEditable: widget.isEdit,
|
isEditable: widget.isEdit,
|
||||||
text: info['INSPECTION_TIME_START'] ?? '',
|
text: form['INSPECTION_TIME_START'] ?? '',
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? picked =
|
||||||
|
await BottomDateTimePicker.showDate(context);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
form['INSPECTION_TIME_START'] = DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(picked);
|
||||||
|
});
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
ItemListWidget.selectableLineTitleTextRightButton(
|
||||||
|
label: '检查结束时间:',
|
||||||
|
isEditable: widget.isEdit,
|
||||||
|
text: form['INSPECTION_TIME_END'] ?? '',
|
||||||
|
onTap: () async {
|
||||||
|
DateTime? picked =
|
||||||
|
await BottomDateTimePicker.showDate(context);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
form['INSPECTION_TIME_END'] = DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(picked);
|
||||||
|
});
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
MultiTextFieldWithTitle(
|
||||||
|
label: "检查情况",
|
||||||
|
// 更合适的标题
|
||||||
|
isEditable: widget.isEdit,
|
||||||
|
// 使用父组件的编辑状态
|
||||||
|
hintText: "请输入检查情况...",
|
||||||
|
texts: multiTexts,
|
||||||
|
onTextsChanged: (texts) {
|
||||||
|
setState(() {
|
||||||
|
multiTexts = texts; // 保存到状态变量
|
||||||
|
form['situationList'] = _stringsToSituationList(
|
||||||
|
texts,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.multiLineTitleTextField(
|
||||||
|
label: '检查人员',
|
||||||
|
text: form['INSPECTION_USERS'] ?? '',
|
||||||
|
isEditable: widget.isEdit,
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
form['INSPECTION_USERS'] = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.itemContainer(
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
ListItemFactory.headerTitle('发现问题'),
|
||||||
|
if (widget.isEdit)
|
||||||
|
CustomButton(
|
||||||
|
text: " 添加 ",
|
||||||
|
height: 30,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 2,
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
onPressed: () {
|
||||||
|
_openDrawer(form, -1); // 添加括号和 await
|
||||||
|
FocusHelper.clearFocus(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
HiddenListTable(
|
||||||
|
hiddenList: form['hiddenList'] ?? [],
|
||||||
|
forbidEdit: widget.isEdit,
|
||||||
|
baseImgPath: ApiService.baseImgPath,
|
||||||
|
personSignImg: form['PERSON_SIGN_IMG'] ?? '',
|
||||||
|
personSignTime: form['PERSON_SIGN_TIME'] ?? '',
|
||||||
|
showHidden: (item, idx) {
|
||||||
|
_openDrawer(item, idx);
|
||||||
|
},
|
||||||
|
removeHidden: (item, idx) {
|
||||||
|
/* 删除逻辑 */
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
|
||||||
|
if (!widget.isEdit)
|
||||||
|
Column(children: [
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.twoRowTitleAndImages(
|
||||||
|
title: '签字',
|
||||||
|
onTapCallBack: (p) {
|
||||||
|
presentOpaque(SingleImageViewer(imageUrl: p), context);
|
||||||
|
},
|
||||||
|
imageUrls: [
|
||||||
|
'${form['PERSON_SIGN_IMG'] ?? ''}',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ItemListWidget.singleLineTitleText(label: '签字时间', isEditable: false, text: form['PERSON_SIGN_TIME'] ?? '')
|
||||||
|
],)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -226,19 +893,37 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.isEdit)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 200,
|
width: 150,
|
||||||
child: CustomButton(
|
child: CustomButton(
|
||||||
text: '提交',
|
text: '返回',
|
||||||
textStyle: TextStyle(color: Colors.white, fontSize: 17),
|
textStyle: TextStyle(
|
||||||
backgroundColor: Colors.blue,
|
color: Colors.white,
|
||||||
|
fontSize: 17,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black38,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: CustomButton(
|
||||||
|
text: widget.isEdit ? '提交' : '返回',
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 17,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
onPressed: _submit,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
): SizedBox(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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