重点工程管理完
parent
4e0bad563d
commit
b756e4e809
|
@ -169,7 +169,6 @@ class ListItemFactory {
|
|||
}
|
||||
|
||||
/// 类型6:文本和视频上下布局
|
||||
|
||||
static Widget createTextVideoItem({
|
||||
required String text,
|
||||
required String videoUrl,
|
||||
|
@ -195,6 +194,7 @@ class ListItemFactory {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
videoUrl.isNotEmpty ?
|
||||
GestureDetector(
|
||||
onTap: onVideoTapped,
|
||||
child: Container(
|
||||
|
@ -212,7 +212,7 @@ class ListItemFactory {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
) : SizedBox(height: 10,)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -255,6 +255,7 @@ class ListItemFactory {
|
|||
double horizontalPadding = 10,
|
||||
bool isEdit = true,
|
||||
String text = '',
|
||||
bool isRequired = false,
|
||||
|
||||
}) {
|
||||
return Padding(
|
||||
|
@ -273,13 +274,18 @@ class ListItemFactory {
|
|||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (isRequired && isEdit) Text('* ', style: TextStyle(color: Colors.red)),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isEdit)
|
||||
|
|
|
@ -63,14 +63,14 @@ class BottomPicker {
|
|||
FocusScope.of(context).unfocus();
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
child: const Text('取消', style: TextStyle(color: Colors.black54, fontSize: 16),),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
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 }
|
||||
|
||||
/// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示
|
||||
/// 使用示例:
|
||||
/// MediaPickerGrid(
|
||||
/// maxCount: 4,
|
||||
/// mediaType: MediaType.video,
|
||||
/// initialMediaPaths: ['https://...', '/local/path.png'],
|
||||
/// onChanged: (List<File> medias) {},
|
||||
/// onMediaAdded: (String path) {},
|
||||
/// onMediaRemoved: (String path) {},
|
||||
/// ),
|
||||
/// 新增 isEdit 属性控制编辑状态
|
||||
class MediaPickerRow extends StatefulWidget {
|
||||
final int maxCount;
|
||||
final MediaType mediaType;
|
||||
|
@ -26,6 +18,8 @@ class MediaPickerRow extends StatefulWidget {
|
|||
final ValueChanged<List<File>> onChanged;
|
||||
final ValueChanged<String>? onMediaAdded;
|
||||
final ValueChanged<String>? onMediaRemoved;
|
||||
final ValueChanged<String>? onMediaTapped; // 新增:媒体点击回调
|
||||
final bool isEdit; // 新增:控制编辑状态
|
||||
|
||||
const MediaPickerRow({
|
||||
Key? key,
|
||||
|
@ -35,6 +29,8 @@ class MediaPickerRow extends StatefulWidget {
|
|||
required this.onChanged,
|
||||
this.onMediaAdded,
|
||||
this.onMediaRemoved,
|
||||
this.onMediaTapped, // 新增
|
||||
this.isEdit = true, // 默认可编辑
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -59,6 +55,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
}
|
||||
|
||||
Future<void> _showPickerOptions() async {
|
||||
if (!widget.isEdit) return; // 不可编辑时直接返回
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.white,
|
||||
|
@ -82,7 +80,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
|
||||
leading: Icon(
|
||||
widget.mediaType == MediaType.image
|
||||
? Icons.photo_library
|
||||
|
@ -100,7 +97,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
|
||||
leading: const Icon(Icons.close),
|
||||
title: const Text('取消'),
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
|
@ -112,7 +108,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
}
|
||||
|
||||
Future<void> _pickCamera() async {
|
||||
if (_mediaPaths.length >= widget.maxCount) return;
|
||||
if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
|
||||
|
||||
try {
|
||||
XFile? picked;
|
||||
if (widget.mediaType == MediaType.image) {
|
||||
|
@ -132,7 +129,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
}
|
||||
|
||||
Future<void> _pickGallery() async {
|
||||
if (_mediaPaths.length >= widget.maxCount) return;
|
||||
if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
|
||||
|
||||
final permission = await PhotoManager.requestPermissionExtend();
|
||||
if (permission != PermissionState.authorized &&
|
||||
permission != PermissionState.limited) {
|
||||
|
@ -172,6 +170,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
}
|
||||
|
||||
void _removeMedia(int index) {
|
||||
if (!widget.isEdit) return; // 不可编辑时不允许删除
|
||||
|
||||
final removed = _mediaPaths[index];
|
||||
setState(() => _mediaPaths.removeAt(index));
|
||||
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
||||
|
@ -180,6 +180,9 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount;
|
||||
final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0);
|
||||
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
|
@ -188,45 +191,51 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
childAspectRatio: 1,
|
||||
mainAxisExtent: 80,
|
||||
|
||||
// mainAxisExtent: 80,
|
||||
),
|
||||
itemCount: _mediaPaths.length < widget.maxCount
|
||||
? _mediaPaths.length + 1
|
||||
: widget.maxCount,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// 显示媒体项
|
||||
if (index < _mediaPaths.length) {
|
||||
final path = _mediaPaths[index];
|
||||
final isNetwork = path.startsWith('http');
|
||||
return Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: widget.mediaType == MediaType.image
|
||||
? (isNetwork
|
||||
? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,)
|
||||
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
||||
: Container(
|
||||
color: Colors.black12,
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.videocam,
|
||||
color: Colors.white70,
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => widget.onMediaTapped?.call(path),
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: widget.mediaType == MediaType.image
|
||||
? (isNetwork
|
||||
? Image.network(path, fit: BoxFit.cover, width: 80, height: 80)
|
||||
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
|
||||
: Container(
|
||||
color: Colors.black12,
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.videocam,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: -15,
|
||||
right: -15,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
|
||||
onPressed: () => _removeMedia(index),
|
||||
),
|
||||
),
|
||||
],
|
||||
// 只在可编辑状态下显示删除按钮
|
||||
if (widget.isEdit)
|
||||
Positioned(
|
||||
top: -15,
|
||||
right: -15,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
|
||||
onPressed: () => _removeMedia(index),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
}
|
||||
// 显示添加按钮
|
||||
else if (showAddButton) {
|
||||
return GestureDetector(
|
||||
onTap: _showPickerOptions,
|
||||
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展示
|
||||
/// 新增 isEdit 属性控制编辑状态
|
||||
class RepairedPhotoSection extends StatefulWidget {
|
||||
final int maxCount;
|
||||
final MediaType mediaType;
|
||||
|
@ -254,11 +266,13 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
final ValueChanged<List<File>> onChanged;
|
||||
final ValueChanged<String>? onMediaAdded;
|
||||
final ValueChanged<String>? onMediaRemoved;
|
||||
final ValueChanged<String>? onMediaTapped; // 新增:媒体点击回调
|
||||
final VoidCallback onAiIdentify;
|
||||
final bool isShowAI;
|
||||
final double horizontalPadding;
|
||||
final bool isRequired;
|
||||
final bool isShowNum;
|
||||
final bool isEdit; // 新增:控制编辑状态
|
||||
|
||||
const RepairedPhotoSection({
|
||||
Key? key,
|
||||
|
@ -272,8 +286,10 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
this.horizontalPadding = 5,
|
||||
this.onMediaAdded,
|
||||
this.onMediaRemoved,
|
||||
this.onMediaTapped, // 新增
|
||||
this.isRequired = false,
|
||||
this.isShowNum = true,
|
||||
this.isEdit = true, // 默认可编辑
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -324,10 +340,12 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
},
|
||||
onMediaAdded: widget.onMediaAdded,
|
||||
onMediaRemoved: widget.onMediaRemoved,
|
||||
onMediaTapped: widget.onMediaTapped, // 传递点击回调
|
||||
isEdit: widget.isEdit, // 传递编辑状态
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (widget.isShowAI)
|
||||
if (widget.isShowAI && widget.isEdit) // 只在可编辑状态下显示AI按钮
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding),
|
||||
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(
|
||||
basePath,
|
||||
'/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) {
|
||||
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),))
|
||||
],),
|
||||
|
||||
body: Column(
|
||||
body: SafeArea(child: Column(
|
||||
children: [
|
||||
// Filter bar
|
||||
Container(
|
||||
|
@ -231,7 +231,7 @@ class _CheckListPageState extends State<CheckListPage> {
|
|||
// List
|
||||
Expanded(child: _buildListContent()),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
|
||||
class MultiTextFieldWithTitle extends StatefulWidget {
|
||||
final String label;
|
||||
final List<String> texts;
|
||||
final bool isEditable;
|
||||
final String hintText;
|
||||
final double fontSize;
|
||||
final bool isRequired;
|
||||
final ValueChanged<List<String>> onTextsChanged;
|
||||
|
||||
const MultiTextFieldWithTitle({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.isEditable,
|
||||
required this.hintText,
|
||||
required this.onTextsChanged,
|
||||
this.fontSize = 15,
|
||||
this.texts = const [],
|
||||
this.isRequired = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MultiTextFieldWithTitle> createState() =>
|
||||
_MultiTextFieldWithTitleState();
|
||||
}
|
||||
|
||||
class _MultiTextFieldWithTitleState extends State<MultiTextFieldWithTitle> {
|
||||
final List<TextEditingController> _controllers = [];
|
||||
final List<FocusNode> _focusNodes = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 延迟初始化,避免构建过程中调用 setState
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_addTextField();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (var controller in _controllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
for (var node in _focusNodes) {
|
||||
node.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _addTextField() {
|
||||
setState(() {
|
||||
final newController = TextEditingController();
|
||||
final newFocusNode = FocusNode();
|
||||
|
||||
newController.addListener(() {
|
||||
widget.onTextsChanged(_getAllTexts());
|
||||
});
|
||||
|
||||
_controllers.add(newController);
|
||||
_focusNodes.add(newFocusNode);
|
||||
widget.onTextsChanged(_getAllTexts());
|
||||
});
|
||||
}
|
||||
|
||||
void _removeTextField(int index) async {
|
||||
if (_controllers.length <= 1) return;
|
||||
await showDialog<String>(
|
||||
context: context,
|
||||
builder:
|
||||
(_) => CustomAlertDialog(
|
||||
title: '提示',
|
||||
mode: DialogMode.text,
|
||||
content: '确定删除检查情况吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
onConfirm: () {
|
||||
setState(() {
|
||||
_controllers[index].dispose();
|
||||
_focusNodes[index].dispose();
|
||||
|
||||
_controllers.removeAt(index);
|
||||
_focusNodes.removeAt(index);
|
||||
widget.onTextsChanged(_getAllTexts());
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _getAllTexts() {
|
||||
return _controllers.map((c) => c.text).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题行
|
||||
InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.isRequired && widget.isEditable)
|
||||
Text('* ', style: TextStyle(color: Colors.red)),
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.label,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (widget.isEditable)
|
||||
CustomButton(
|
||||
text: " 添加 ",
|
||||
height: 30,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
horizontal: 5,
|
||||
),
|
||||
backgroundColor: Colors.blue,
|
||||
onPressed: _addTextField,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 输入框区域 - 高度自适应
|
||||
Column(
|
||||
children: [
|
||||
// 可编辑状态
|
||||
if (widget.isEditable)
|
||||
..._controllers.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
return _buildTextFieldWithDelete(index);
|
||||
}).toList(),
|
||||
|
||||
// 不可编辑状态
|
||||
if (!widget.isEditable)
|
||||
...widget.texts.map((c) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
c,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextFieldWithDelete(int index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
// 输入框
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 7, vertical: 7),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: BoxBorder.all(color: Colors.grey.shade300, width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextField(
|
||||
controller: _controllers[index],
|
||||
decoration: InputDecoration(hintText: widget.hintText),
|
||||
focusNode: _focusNodes[index],
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: 3,
|
||||
minLines: 3,
|
||||
style: TextStyle(fontSize: widget.fontSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 删除按钮(叠加在左上角)
|
||||
if (index > 0)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () => _removeTextField(index),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: const Icon(Icons.close, size: 10, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget HiddenListTable({
|
||||
required List<dynamic> hiddenList,
|
||||
required bool forbidEdit,
|
||||
required String baseImgPath,
|
||||
required String personSignImg,
|
||||
required String personSignTime,
|
||||
required void Function(Map<String, dynamic> item, int index) showHidden,
|
||||
required void Function(Map<String, dynamic> item, int index) removeHidden,
|
||||
required BuildContext context,
|
||||
}) {
|
||||
Widget _buildCell(String text, {bool isHeader = false, Alignment alignment = Alignment.center, double minWidth = 50}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
|
||||
alignment: alignment,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: minWidth),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 14,
|
||||
color: isHeader ? Colors.black87 : Colors.black54,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 仍保留 Table 的 header 用法(用于有数据时)
|
||||
TableRow _buildHeader() {
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(color: Colors.grey.shade200),
|
||||
children: [
|
||||
_buildCell('序号', isHeader: true, alignment: Alignment.center),
|
||||
_buildCell('隐患部位', isHeader: true),
|
||||
_buildCell('隐患描述', isHeader: true),
|
||||
_buildCell('操作', isHeader: true, alignment: Alignment.center),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TableRow _buildRow(Map<String, dynamic> item, int index) {
|
||||
final partName = (item['HIDDENPART_NAME'] ?? '').toString();
|
||||
final part = (item['HIDDENPART'] ?? '').toString();
|
||||
final descr = (item['HIDDENDESCR'] ?? '').toString();
|
||||
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
_buildCell('${index + 1}', alignment: Alignment.center),
|
||||
_buildCell(partName.isNotEmpty ? partName : part),
|
||||
_buildCell(descr),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info_outline, size: 22, color: Colors.blue),
|
||||
onPressed: () => showHidden(item, index),
|
||||
tooltip: '查看',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
if (forbidEdit) // 注意:原逻辑保留,如需反向请改为 if (!forbidEdit)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline, size: 22, color: Colors.red),
|
||||
onPressed: () => removeHidden(item, index),
|
||||
tooltip: '删除',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 当 hiddenList 为空时,单独渲染表头(使用 Row + Expanded 来模仿 Table 的列宽)
|
||||
Widget _buildHeaderRowWidget() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.grey.shade200),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(flex: 2, child: _buildCell('序号', isHeader: true, alignment: Alignment.center)),
|
||||
Expanded(flex: 3, child: _buildCell('隐患部位', isHeader: true)),
|
||||
Expanded(flex: 3, child: _buildCell('隐患描述', isHeader: true)),
|
||||
Expanded(flex: 3, child: _buildCell('操作', isHeader: true, alignment: Alignment.center)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final tableWidth = MediaQuery.of(context).size.width - 50;
|
||||
|
||||
// 如果没有数据,返回“表头 + 跨列居中显示暂无数据”的布局
|
||||
if (hiddenList.isEmpty) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: tableWidth,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildHeaderRowWidget(),
|
||||
Container(
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black54),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 否则,使用原来的 Table 渲染有数据的行
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: tableWidth,
|
||||
child: Table(
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(2), // 序号
|
||||
1: FlexColumnWidth(3), // 隐患部位
|
||||
2: FlexColumnWidth(3), // 隐患描述
|
||||
3: FlexColumnWidth(3), // 操作按钮
|
||||
},
|
||||
border: TableBorder.symmetric(
|
||||
inside: BorderSide(color: Colors.grey.shade300, width: 0.5),
|
||||
),
|
||||
children: [
|
||||
_buildHeader(),
|
||||
...hiddenList.asMap().entries.map((e) => _buildRow(e.value, e.key)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,703 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
|
||||
import 'package:qhd_prevention/customWidget/bottom_picker_two.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
import 'package:qhd_prevention/customWidget/date_picker_dialog.dart';
|
||||
import 'package:qhd_prevention/customWidget/department_person_picker.dart';
|
||||
import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart';
|
||||
import 'package:qhd_prevention/customWidget/department_picker_two.dart';
|
||||
import 'package:qhd_prevention/customWidget/full_screen_video_page.dart';
|
||||
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
|
||||
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
import 'package:qhd_prevention/customWidget/video_player_widget.dart';
|
||||
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
import '../../../../customWidget/photo_picker_row.dart';
|
||||
import '../../../../http/ApiService.dart';
|
||||
|
||||
/// 添加 修改 查看
|
||||
enum SafeEditType { add, edit, see }
|
||||
|
||||
class SafeDrawerPage extends StatefulWidget {
|
||||
const SafeDrawerPage({
|
||||
super.key,
|
||||
required this.editType,
|
||||
this.initialHidden,
|
||||
required this.toCheckUnitList,
|
||||
});
|
||||
|
||||
final SafeEditType editType;
|
||||
final List<dynamic> toCheckUnitList;
|
||||
|
||||
/// 可选:传入已有的 hiddenForm(用于 edit / see)
|
||||
final Map<String, dynamic>? initialHidden;
|
||||
|
||||
@override
|
||||
State<SafeDrawerPage> createState() => _SafeDrawerPageState();
|
||||
}
|
||||
|
||||
class _SafeDrawerPageState extends State<SafeDrawerPage> {
|
||||
// Controllers
|
||||
final TextEditingController _descCtl = TextEditingController();
|
||||
final TextEditingController _partCtl = TextEditingController();
|
||||
final TextEditingController _rectifyCtl = TextEditingController();
|
||||
|
||||
bool _isEdit = false;
|
||||
|
||||
// Data lists
|
||||
List<dynamic> _hazardLevels = [];
|
||||
List<Map<String, dynamic>> _personCache = [];
|
||||
|
||||
// Selected / transient state
|
||||
Map<String, dynamic> hiddenForm = {};
|
||||
|
||||
bool _rectifyClickable = true; // 是否可切换整改方式
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isEdit = widget.editType != SafeEditType.see;
|
||||
_initHiddenForm();
|
||||
_loadHazardLevels();
|
||||
}
|
||||
|
||||
void _initHiddenForm() {
|
||||
hiddenForm = {
|
||||
'ISRELEVANT': '2',
|
||||
'SOURCE': '5',
|
||||
'hiddenImgs': <Map<String, dynamic>>[],
|
||||
'zgImgs': <Map<String, dynamic>>[],
|
||||
'hiddenVideos': <Map<String, dynamic>>[],
|
||||
'RECTIFICATIONTYPE': '2',
|
||||
};
|
||||
|
||||
// 如果传入了初始数据(编辑/查看),覆盖默认
|
||||
if (widget.initialHidden != null) {
|
||||
final Map<String, dynamic> m = Map<String, dynamic>.from(
|
||||
widget.initialHidden!,
|
||||
);
|
||||
// normalize lists to List<Map<String,dynamic>>
|
||||
m['hiddenImgs'] =
|
||||
(m['hiddenImgs'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) =>
|
||||
e is Map
|
||||
? Map<String, dynamic>.from(e)
|
||||
: {'FILEPATH': e.toString()},
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
m['zgImgs'] =
|
||||
(m['zgImgs'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) =>
|
||||
e is Map
|
||||
? Map<String, dynamic>.from(e)
|
||||
: {'FILEPATH': e.toString()},
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
m['hiddenVideos'] =
|
||||
(m['hiddenVideos'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) =>
|
||||
e is Map
|
||||
? Map<String, dynamic>.from(e)
|
||||
: {'FILEPATH': e.toString()},
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
hiddenForm.addAll(m);
|
||||
}
|
||||
// init controllers and flags
|
||||
_descCtl.text = hiddenForm['HIDDENDESCR'] ?? '';
|
||||
_partCtl.text = hiddenForm['HIDDENPART'] ?? '';
|
||||
_rectifyCtl.text = hiddenForm['RECTIFYDESCR'] ?? '';
|
||||
|
||||
// hidden level selected map is not stored directly; we can set HIDDENLEVEL_NAME/HIDDENLEVEL
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_descCtl.dispose();
|
||||
_partCtl.dispose();
|
||||
_rectifyCtl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadHazardLevels() async {
|
||||
try {
|
||||
final res = await ApiService.getHazardLevel();
|
||||
if (res['result'] == 'success') {
|
||||
setState(() => _hazardLevels = List<dynamic>.from(res['list'] ?? []));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
Widget _section(Widget child) => Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
child: child,
|
||||
);
|
||||
|
||||
List<String> _getSelectedImages() {
|
||||
return (hiddenForm['hiddenImgs'] as List<Map<String, dynamic>>)
|
||||
.map((e) {
|
||||
final p = '${e['FILEPATH']}';
|
||||
return p.contains('uploadFiles') ? '${ApiService.baseImgPath}$p' : p;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> _getSelectedVideos() {
|
||||
return (hiddenForm['hiddenVideos'] as List<Map<String, dynamic>>)
|
||||
.map((e) => '${e['FILEPATH']}')
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
color: Colors.grey[100],
|
||||
child: Column(
|
||||
children: [
|
||||
// scroll content
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// photos
|
||||
_section(
|
||||
RepairedPhotoSection(
|
||||
title: '隐患照片',
|
||||
maxCount: 4,
|
||||
mediaType: MediaType.image,
|
||||
isShowAI: true,
|
||||
isEdit: _isEdit,
|
||||
isRequired: _isEdit,
|
||||
initialMediaPaths: _getSelectedImages(),
|
||||
onMediaTapped: (p) {
|
||||
presentOpaque(SingleImageViewer(imageUrl: p), context);
|
||||
},
|
||||
onChanged:
|
||||
(files) => setState(() {
|
||||
hiddenForm['hiddenImgs'] =
|
||||
files
|
||||
.map((f) => {'FILEPATH': f.path})
|
||||
.toList();
|
||||
}),
|
||||
onAiIdentify: () {
|
||||
final imgs = List<Map<String, dynamic>>.from(
|
||||
hiddenForm['hiddenImgs'] ?? [],
|
||||
);
|
||||
if (imgs.isEmpty)
|
||||
return ToastUtil.showNormal(context, '请先上传一张图片');
|
||||
if (imgs.length > 1)
|
||||
return ToastUtil.showNormal(
|
||||
context,
|
||||
'识别暂时只能上传一张图片',
|
||||
);
|
||||
final path = imgs.first['FILEPATH']?.toString() ?? '';
|
||||
if (path.isNotEmpty) _identifyImage(path);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// videos
|
||||
_section(
|
||||
RepairedPhotoSection(
|
||||
title: '隐患视频',
|
||||
maxCount: 1,
|
||||
onMediaTapped: (p) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black54,
|
||||
builder: (_) => VideoPlayerPopup(videoUrl:p),
|
||||
);
|
||||
},
|
||||
isEdit: _isEdit,
|
||||
mediaType: MediaType.video,
|
||||
initialMediaPaths: _getSelectedVideos(),
|
||||
onChanged:
|
||||
(files) => setState(() {
|
||||
hiddenForm['hiddenVideos'] =
|
||||
files
|
||||
.map((f) => {'FILEPATH': f.path})
|
||||
.toList();
|
||||
}),
|
||||
onAiIdentify: () {},
|
||||
),
|
||||
),
|
||||
|
||||
// description
|
||||
|
||||
ItemListWidget.itemContainer(
|
||||
|
||||
ItemListWidget.multiLineTitleTextField(
|
||||
|
||||
label: '隐患描述',
|
||||
isEditable: _isEdit,
|
||||
text: hiddenForm['HIDDENDESCR'] ?? '',
|
||||
controller: _descCtl,
|
||||
),horizontal: 5),
|
||||
|
||||
ItemListWidget.itemContainer(
|
||||
|
||||
ItemListWidget.multiLineTitleTextField(
|
||||
|
||||
label: '隐患部位',
|
||||
isEditable: _isEdit,
|
||||
text: hiddenForm['HIDDENPART'] ?? '',
|
||||
controller: _partCtl,
|
||||
),horizontal: 5),
|
||||
// part
|
||||
|
||||
// hazard level
|
||||
GestureDetector(
|
||||
onTap: _isEdit ? _pickHazardLevel : null,
|
||||
child: _section(
|
||||
ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: '隐患级别',
|
||||
rightText:
|
||||
hiddenForm['HIDDENLEVEL_NAME']?.toString() ??
|
||||
'请选择',
|
||||
isRight: _isEdit,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// hazard type
|
||||
GestureDetector(
|
||||
onTap: _isEdit ? _pickHazardType : null,
|
||||
child: _section(
|
||||
ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: '隐患类型',
|
||||
rightText:
|
||||
hiddenForm['HIDDENTYPE_NAME']?.toString() ??
|
||||
'请选择',
|
||||
isRight: _isEdit,
|
||||
),
|
||||
),
|
||||
),
|
||||
// AI identify / disposition header
|
||||
_section(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ListItemFactory.headerTitle('隐患处置'),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
size: 20,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text(
|
||||
' 限期整改',
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// when immediate rectify
|
||||
if (hiddenForm['RECTIFICATIONTYPE'] == '1') ...[
|
||||
_section(
|
||||
ListItemFactory.createBuildMultilineInput(
|
||||
'整改描述',
|
||||
'请对隐患进行整改描述(必填项)',
|
||||
_rectifyCtl,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_section(
|
||||
RepairedPhotoSection(
|
||||
title: '整改后图片',
|
||||
maxCount: 4,
|
||||
mediaType: MediaType.image,
|
||||
onChanged:
|
||||
(files) => setState(() {
|
||||
hiddenForm['zgImgs'] =
|
||||
files
|
||||
.map((f) => {'FILEPATH': f.path})
|
||||
.toList();
|
||||
}),
|
||||
onAiIdentify: () {},
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
// limited rectify: department, person, date
|
||||
GestureDetector(
|
||||
onTap: _isEdit ? _pickDept : null,
|
||||
child: _section(
|
||||
ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: '整改部门',
|
||||
rightText:
|
||||
_isEdit
|
||||
? (hiddenForm['RECTIFICATIONDEPT_NAME']
|
||||
?.toString() ??
|
||||
'请选择')
|
||||
: hiddenForm['RECTIFICATIONDEPT_NAME']
|
||||
?.toString() ??
|
||||
'',
|
||||
isRight: _isEdit,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
GestureDetector(
|
||||
onTap: _isEdit ? _pickResponsible : null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: '整改人',
|
||||
rightText:
|
||||
hiddenForm['RECTIFICATIONOR_NAME']
|
||||
?.toString() ??
|
||||
'请选择',
|
||||
isRight: _isEdit,
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _isEdit ? () => _pickDate(context) : null,
|
||||
child: _section(
|
||||
ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: '整改期限',
|
||||
rightText:
|
||||
hiddenForm['RECTIFICATIONDEADLINE']
|
||||
?.toString() ??
|
||||
'请选择',
|
||||
isRight: _isEdit,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// buttons
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
onPressed: cancelHidden,
|
||||
text: '取消',
|
||||
textStyle: TextStyle(color: Colors.black87),
|
||||
backgroundColor: Colors.grey.shade300,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (_isEdit)
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
onPressed: saveHidden,
|
||||
text: '保存',
|
||||
backgroundColor: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------- Helpers -----------------
|
||||
|
||||
void _rectifyImgsFromFiles(List<File> files) =>
|
||||
hiddenForm['zgImgs'] = files.map((f) => {'FILEPATH': f.path}).toList();
|
||||
|
||||
Future<void> _pickHazardLevel() async {
|
||||
if (_hazardLevels.isEmpty) return ToastUtil.showNormal(context, '隐患级别数据为空');
|
||||
final choice = await BottomPickerTwo.show<String>(
|
||||
context,
|
||||
items: _hazardLevels,
|
||||
itemBuilder: (i) => Text(i['NAME'], textAlign: TextAlign.center),
|
||||
initialIndex: 0,
|
||||
);
|
||||
FocusHelper.clearFocus(context);
|
||||
if (choice == null) return;
|
||||
final found = _hazardLevels.firstWhere(
|
||||
(e) => e['NAME'] == choice,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (found != null) {
|
||||
setState(() {
|
||||
hiddenForm['HIDDENLEVEL'] = found['BIANMA'] ?? found['id'] ?? '';
|
||||
hiddenForm['HIDDENLEVEL_NAME'] = found['NAME'] ?? '';
|
||||
hiddenForm['HIDDENLEVEL_INDEX'] = found['id'] ?? '';
|
||||
_rectifyClickable =
|
||||
(found['DICTIONARIES_ID']?.toString() ?? '') !=
|
||||
'5ff9daf78e9a4fb1b40d77980656799d';
|
||||
if (!_rectifyClickable) {
|
||||
hiddenForm['RECTIFICATIONTYPE'] = '2';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickHazardType() async {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
barrierColor: Colors.black54,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder:
|
||||
(_) => DepartmentPickerHiddenType(
|
||||
onSelected: (result) {
|
||||
try {
|
||||
final Map m = Map.from(result);
|
||||
final ids = List<String>.from(m['id'] ?? []);
|
||||
final names = List<String>.from(m['name'] ?? []);
|
||||
setState(() {
|
||||
hiddenForm['HIDDENTYPE'] = ids.isNotEmpty ? ids.first : '';
|
||||
hiddenForm['HIDDENTYPE_NAME'] =
|
||||
names.isNotEmpty ? names.last : '';
|
||||
hiddenForm['HIDDENTYPE2'] = ids.length > 1 ? ids[1] : '';
|
||||
hiddenForm['HIDDENTYPE2_NAME'] =
|
||||
names.length > 1 ? names[1] : '';
|
||||
});
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickDept() async {
|
||||
final choice = await BottomPicker.show<String>(
|
||||
context,
|
||||
items:
|
||||
widget.toCheckUnitList
|
||||
.map((val) => val['UNITS_NAME'] as String)
|
||||
.toList(),
|
||||
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||
initialIndex: 0,
|
||||
);
|
||||
if (choice != null) {
|
||||
// 用户点击确定并选择了 choice
|
||||
setState(() {
|
||||
hiddenForm['RECTIFICATIONDEPT_NAME'] = choice;
|
||||
Map target = widget.toCheckUnitList.firstWhere(
|
||||
(item) => item['UNITS_NAME'] == choice,
|
||||
orElse: () => {},
|
||||
);
|
||||
hiddenForm['RECTIFICATIONDEPT'] = target?['UNITS_ID'] ?? '';
|
||||
_getUnitPerson();
|
||||
|
||||
FocusHelper.clearFocus(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getUnitPerson() async {
|
||||
final res = await ApiService.getSafeCheckPersonList(
|
||||
hiddenForm['RECTIFICATIONDEPT'],
|
||||
'1',
|
||||
);
|
||||
setState(
|
||||
() =>
|
||||
_personCache = List<Map<String, dynamic>>.from(res['varList'] ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickResponsible() async {
|
||||
if (_personCache.isEmpty) return ToastUtil.showNormal(context, '请先选择部门');
|
||||
|
||||
final choice = await BottomPicker.show<String>(
|
||||
context,
|
||||
items: _personCache.map((val) => val['NAME'] as String).toList(),
|
||||
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||
initialIndex: 0,
|
||||
);
|
||||
if (choice != null) {
|
||||
// 用户点击确定并选择了 choice
|
||||
setState(() {
|
||||
hiddenForm['RECTIFICATIONOR_NAME'] = choice;
|
||||
Map<String, dynamic>? target = widget.toCheckUnitList.firstWhere(
|
||||
(item) => item['RECTIFICATIONOR_NAME'] == choice,
|
||||
orElse: () => {},
|
||||
);
|
||||
hiddenForm['RECTIFICATIONOR'] = target?['PERSONNELMANAGEMENT_ID'];
|
||||
FocusHelper.clearFocus(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _pickDate(BuildContext ctx) async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(context);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
final selectData = DateFormat('yyyy-MM-dd HH:mm').format(picked);
|
||||
hiddenForm['RECTIFICATIONDEADLINE'] = selectData;
|
||||
});
|
||||
FocusHelper.clearFocus(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存隐患(这里只返回 hiddenForm 给上层调用者)
|
||||
void saveHidden() {
|
||||
// update hiddenForm from controllers before returning
|
||||
hiddenForm['HIDDENDESCR'] = _descCtl.text.trim();
|
||||
hiddenForm['HIDDENPART'] = _partCtl.text.trim();
|
||||
hiddenForm['RECTIFYDESCR'] = _rectifyCtl.text.trim();
|
||||
// basic validation example
|
||||
if ((hiddenForm['hiddenImgs'] as List).isEmpty)
|
||||
return ToastUtil.showNormal(context, '请上传隐患图片');
|
||||
if ((hiddenForm['HIDDENDESCR'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请填写隐患描述');
|
||||
if ((hiddenForm['HIDDENPART'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请填写隐患部位');
|
||||
if ((hiddenForm['HIDDENLEVEL_NAME'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请选择隐患级别');
|
||||
if ((hiddenForm['HIDDENTYPE_NAME'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请选择隐患类型');
|
||||
if ((hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请选择整改部门');
|
||||
if ((hiddenForm['RECTIFICATIONOR_NAME'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请选择整改人');
|
||||
if ((hiddenForm['RECTIFICATIONDEADLINE'] ?? '').toString().trim().isEmpty)
|
||||
return ToastUtil.showNormal(context, '请选择整改期限');
|
||||
|
||||
// 返回给调用方(抽屉关闭并返回数据)
|
||||
Navigator.of(context).pop(hiddenForm);
|
||||
}
|
||||
|
||||
void cancelHidden() {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
// 图片/视频查看与删除(简单实现)
|
||||
void viewImage(int index) {
|
||||
final imgs = List<Map<String, dynamic>>.from(
|
||||
hiddenForm['hiddenImgs'] ?? [],
|
||||
);
|
||||
if (index < 0 || index >= imgs.length) return;
|
||||
final path = imgs[index]['FILEPATH']?.toString() ?? '';
|
||||
|
||||
presentOpaque(
|
||||
SingleImageViewer(imageUrl: ApiService.baseImgPath + path),
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
void delImage(int index) {
|
||||
setState(() {
|
||||
final imgs = List<Map<String, dynamic>>.from(
|
||||
hiddenForm['hiddenImgs'] ?? [],
|
||||
);
|
||||
if (index >= 0 && index < imgs.length) {
|
||||
imgs.removeAt(index);
|
||||
hiddenForm['hiddenImgs'] = imgs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void viewVideo(int index) {
|
||||
final vids = List<Map<String, dynamic>>.from(
|
||||
hiddenForm['hiddenVideos'] ?? [],
|
||||
);
|
||||
if (index < 0 || index >= vids.length) return;
|
||||
final path = vids[index]['FILEPATH']?.toString() ?? '';
|
||||
ToastUtil.showNormal(context, '查看视频:$path');
|
||||
}
|
||||
|
||||
void delVideo(int index) {
|
||||
setState(() {
|
||||
final vids = List<Map<String, dynamic>>.from(
|
||||
hiddenForm['hiddenVideos'] ?? [],
|
||||
);
|
||||
if (index >= 0 && index < vids.length) {
|
||||
vids.removeAt(index);
|
||||
hiddenForm['hiddenVideos'] = vids;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _identifyImage(String path) async {
|
||||
try {
|
||||
LoadingDialogHelper.show();
|
||||
final raw = await ApiService.identifyImg(path);
|
||||
if (raw['result'] == 'success') {
|
||||
final aiList = raw['aiHiddens'] ?? [];
|
||||
String desc = '';
|
||||
String rectify = '';
|
||||
for (final item in aiList) {
|
||||
try {
|
||||
final m = jsonDecode(item);
|
||||
desc =
|
||||
desc.isEmpty
|
||||
? (m['hiddenDescr'] ?? '')
|
||||
: '$desc;${m['hiddenDescr'] ?? ''}';
|
||||
rectify =
|
||||
rectify.isEmpty
|
||||
? (m['rectificationSuggestions'] ?? '')
|
||||
: '$rectify;${m['rectificationSuggestions'] ?? ''}';
|
||||
} catch (_) {}
|
||||
}
|
||||
setState(() {
|
||||
_descCtl.text = desc;
|
||||
_rectifyCtl.text = rectify;
|
||||
hiddenForm['HIDDENDESCR'] = desc;
|
||||
hiddenForm['RECTIFYDESCR'] = rectify;
|
||||
hiddenForm['RECTIFICATIONTYPE'] = '1';
|
||||
});
|
||||
} else {
|
||||
ToastUtil.showNormal(context, '识别失败');
|
||||
}
|
||||
} catch (e) {
|
||||
ToastUtil.showNormal(context, '识别异常:$e');
|
||||
} finally {
|
||||
LoadingDialogHelper.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _uploadFile(String path, String type, String id) async {
|
||||
try {
|
||||
final r = await ApiService.addImgFiles(path, type, id);
|
||||
return r['result'] == 'success' ? (r['imgPath'] ?? '') : '';
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<Position> _determinePosition() async {
|
||||
if (!await Geolocator.isLocationServiceEnabled()) throw 'location disabled';
|
||||
var p = await Geolocator.checkPermission();
|
||||
if (p == LocationPermission.denied)
|
||||
p = await Geolocator.requestPermission();
|
||||
if (p == LocationPermission.denied || p == LocationPermission.deniedForever)
|
||||
throw 'permission denied';
|
||||
return await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -222,7 +222,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
|
|||
key: _scaffoldKey,
|
||||
appBar: MyAppbar(title: widget.flow),
|
||||
|
||||
body: Column(
|
||||
body: SafeArea(child: Column(
|
||||
children: [
|
||||
// Filter bar
|
||||
Container(
|
||||
|
@ -249,7 +249,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
|
|||
// List
|
||||
Expanded(child: _buildListContent()),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
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/Punishment/punishment_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';
|
||||
|
@ -89,8 +91,17 @@ class _KeyprojectsTabListState extends State<KeyprojectsTabList> {
|
|||
await pushPage(SafecheckListPage(flow: title), context);
|
||||
|
||||
} break;
|
||||
case 2: title = '隐患管理'; break;
|
||||
case 3: title = '处罚管理'; break;
|
||||
case 2: {
|
||||
title = '隐患管理';
|
||||
await pushPage(DangerListPage(flow: title), context);
|
||||
|
||||
} break;
|
||||
case 3: {
|
||||
title = '处罚管理';
|
||||
await pushPage(PunishmentListPage(flow: title), context);
|
||||
|
||||
} break;
|
||||
|
||||
default:
|
||||
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/badge_manager.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/low_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
|
||||
|
@ -342,6 +343,9 @@ class _HomePageState extends State<HomePage> {
|
|||
case 7:
|
||||
pushPage(StudyGardenPage(), context);
|
||||
break;
|
||||
case 8: // 安全检查
|
||||
await pushPage(SafecheckTabList(), context);
|
||||
break;
|
||||
case 10:
|
||||
pushPage(SafetyMeetingListPage(), context);
|
||||
break;
|
||||
|
|
|
@ -22,6 +22,7 @@ class ItemListWidget {
|
|||
bool isRequired = true,
|
||||
bool strongRequired = false,
|
||||
ValueChanged<String>? onChanged,
|
||||
ValueChanged<String>? onFieldSubmitted,
|
||||
|
||||
/// 强制必选 不受是否可以编译和是否必选影响
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
|
@ -86,6 +87,8 @@ class ItemListWidget {
|
|||
double height = 110, // 整体高度
|
||||
bool isRequired = true,
|
||||
String hintText = '请输入',
|
||||
ValueChanged<String>? onChanged,
|
||||
|
||||
}) {
|
||||
return Container(
|
||||
// 统一左右 padding,保证标题和内容在同一左侧基线
|
||||
|
@ -117,6 +120,7 @@ class ItemListWidget {
|
|||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
onChanged: onChanged,
|
||||
// 垂直顶部对齐
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
|
@ -693,8 +697,8 @@ class ItemListWidget {
|
|||
return Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
color: Colors.grey[200],
|
||||
child: const Icon(Icons.broken_image, size: 40),
|
||||
color: Colors.transparent,
|
||||
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}) {
|
||||
|
||||
|
|
|
@ -367,6 +367,14 @@ class FormUtils {
|
|||
// 数字、布尔等其它非空即可
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue