重点工程管理完

main
hs 2025-08-14 15:05:48 +08:00
parent 4e0bad563d
commit b756e4e809
22 changed files with 4592 additions and 224 deletions

View File

@ -169,7 +169,6 @@ class ListItemFactory {
} }
/// 6 /// 6
static Widget createTextVideoItem({ static Widget createTextVideoItem({
required String text, required String text,
required String videoUrl, required String videoUrl,
@ -195,6 +194,7 @@ class ListItemFactory {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
videoUrl.isNotEmpty ?
GestureDetector( GestureDetector(
onTap: onVideoTapped, onTap: onVideoTapped,
child: Container( child: Container(
@ -212,7 +212,7 @@ class ListItemFactory {
), ),
), ),
), ),
), ) : SizedBox(height: 10,)
], ],
), ),
); );
@ -255,6 +255,7 @@ class ListItemFactory {
double horizontalPadding = 10, double horizontalPadding = 10,
bool isEdit = true, bool isEdit = true,
String text = '', String text = '',
bool isRequired = false,
}) { }) {
return Padding( return Padding(
@ -273,13 +274,18 @@ class ListItemFactory {
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Text( child: Row(
children: [
if (isRequired && isEdit) Text('* ', style: TextStyle(color: Colors.red)),
Text(
title, title,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black, color: Colors.black,
), ),
)
],
), ),
), ),
if (isEdit) if (isEdit)

View File

@ -63,14 +63,14 @@ class BottomPicker {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
}, },
child: const Text('取消'), child: const Text('取消', style: TextStyle(color: Colors.black54, fontSize: 16),),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
Navigator.of(ctx).pop(selected); Navigator.of(ctx).pop(selected);
}, },
child: const Text('确定'), child: const Text('确定', style: TextStyle(color: Colors.blue, fontSize: 16),),
), ),
], ],
), ),

View File

@ -10,15 +10,7 @@ import 'ItemWidgetFactory.dart';
enum MediaType { image, video } enum MediaType { image, video }
/// ///
/// 使 /// isEdit
/// MediaPickerGrid(
/// maxCount: 4,
/// mediaType: MediaType.video,
/// initialMediaPaths: ['https://...', '/local/path.png'],
/// onChanged: (List<File> medias) {},
/// onMediaAdded: (String path) {},
/// onMediaRemoved: (String path) {},
/// ),
class MediaPickerRow extends StatefulWidget { class MediaPickerRow extends StatefulWidget {
final int maxCount; final int maxCount;
final MediaType mediaType; final MediaType mediaType;
@ -26,6 +18,8 @@ class MediaPickerRow extends StatefulWidget {
final ValueChanged<List<File>> onChanged; final ValueChanged<List<File>> onChanged;
final ValueChanged<String>? onMediaAdded; final ValueChanged<String>? onMediaAdded;
final ValueChanged<String>? onMediaRemoved; final ValueChanged<String>? onMediaRemoved;
final ValueChanged<String>? onMediaTapped; //
final bool isEdit; //
const MediaPickerRow({ const MediaPickerRow({
Key? key, Key? key,
@ -35,6 +29,8 @@ class MediaPickerRow extends StatefulWidget {
required this.onChanged, required this.onChanged,
this.onMediaAdded, this.onMediaAdded,
this.onMediaRemoved, this.onMediaRemoved,
this.onMediaTapped, //
this.isEdit = true, //
}) : super(key: key); }) : super(key: key);
@override @override
@ -59,6 +55,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
} }
Future<void> _showPickerOptions() async { Future<void> _showPickerOptions() async {
if (!widget.isEdit) return; //
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -82,7 +80,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
), ),
ListTile( ListTile(
titleAlignment: ListTileTitleAlignment.center, titleAlignment: ListTileTitleAlignment.center,
leading: Icon( leading: Icon(
widget.mediaType == MediaType.image widget.mediaType == MediaType.image
? Icons.photo_library ? Icons.photo_library
@ -100,7 +97,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
), ),
ListTile( ListTile(
titleAlignment: ListTileTitleAlignment.center, titleAlignment: ListTileTitleAlignment.center,
leading: const Icon(Icons.close), leading: const Icon(Icons.close),
title: const Text('取消'), title: const Text('取消'),
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
@ -112,7 +108,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
} }
Future<void> _pickCamera() async { Future<void> _pickCamera() async {
if (_mediaPaths.length >= widget.maxCount) return; if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
try { try {
XFile? picked; XFile? picked;
if (widget.mediaType == MediaType.image) { if (widget.mediaType == MediaType.image) {
@ -132,7 +129,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
} }
Future<void> _pickGallery() async { Future<void> _pickGallery() async {
if (_mediaPaths.length >= widget.maxCount) return; if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
final permission = await PhotoManager.requestPermissionExtend(); final permission = await PhotoManager.requestPermissionExtend();
if (permission != PermissionState.authorized && if (permission != PermissionState.authorized &&
permission != PermissionState.limited) { permission != PermissionState.limited) {
@ -172,6 +170,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
} }
void _removeMedia(int index) { void _removeMedia(int index) {
if (!widget.isEdit) return; //
final removed = _mediaPaths[index]; final removed = _mediaPaths[index];
setState(() => _mediaPaths.removeAt(index)); setState(() => _mediaPaths.removeAt(index));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
@ -180,6 +180,9 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount;
final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0);
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -188,23 +191,24 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 1, childAspectRatio: 1,
mainAxisExtent: 80, // mainAxisExtent: 80,
), ),
itemCount: _mediaPaths.length < widget.maxCount itemCount: itemCount,
? _mediaPaths.length + 1
: widget.maxCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
//
if (index < _mediaPaths.length) { if (index < _mediaPaths.length) {
final path = _mediaPaths[index]; final path = _mediaPaths[index];
final isNetwork = path.startsWith('http'); final isNetwork = path.startsWith('http');
return Stack(
return GestureDetector(
onTap: () => widget.onMediaTapped?.call(path),
child: Stack(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
child: widget.mediaType == MediaType.image child: widget.mediaType == MediaType.image
? (isNetwork ? (isNetwork
? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,) ? Image.network(path, fit: BoxFit.cover, width: 80, height: 80)
: Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover)) : Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover))
: Container( : Container(
color: Colors.black12, color: Colors.black12,
@ -216,6 +220,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
), ),
), ),
), ),
//
if (widget.isEdit)
Positioned( Positioned(
top: -15, top: -15,
right: -15, right: -15,
@ -225,8 +231,11 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
), ),
), ),
], ],
),
); );
} else { }
//
else if (showAddButton) {
return GestureDetector( return GestureDetector(
onTap: _showPickerOptions, onTap: _showPickerOptions,
child: Container( child: Container(
@ -239,6 +248,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
), ),
), ),
); );
} else {
return const SizedBox.shrink();
} }
}, },
); );
@ -246,6 +257,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
} }
/// 使Grid /// 使Grid
/// isEdit
class RepairedPhotoSection extends StatefulWidget { class RepairedPhotoSection extends StatefulWidget {
final int maxCount; final int maxCount;
final MediaType mediaType; final MediaType mediaType;
@ -254,11 +266,13 @@ class RepairedPhotoSection extends StatefulWidget {
final ValueChanged<List<File>> onChanged; final ValueChanged<List<File>> onChanged;
final ValueChanged<String>? onMediaAdded; final ValueChanged<String>? onMediaAdded;
final ValueChanged<String>? onMediaRemoved; final ValueChanged<String>? onMediaRemoved;
final ValueChanged<String>? onMediaTapped; //
final VoidCallback onAiIdentify; final VoidCallback onAiIdentify;
final bool isShowAI; final bool isShowAI;
final double horizontalPadding; final double horizontalPadding;
final bool isRequired; final bool isRequired;
final bool isShowNum; final bool isShowNum;
final bool isEdit; //
const RepairedPhotoSection({ const RepairedPhotoSection({
Key? key, Key? key,
@ -272,8 +286,10 @@ class RepairedPhotoSection extends StatefulWidget {
this.horizontalPadding = 5, this.horizontalPadding = 5,
this.onMediaAdded, this.onMediaAdded,
this.onMediaRemoved, this.onMediaRemoved,
this.onMediaTapped, //
this.isRequired = false, this.isRequired = false,
this.isShowNum = true, this.isShowNum = true,
this.isEdit = true, //
}) : super(key: key); }) : super(key: key);
@override @override
@ -324,10 +340,12 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
}, },
onMediaAdded: widget.onMediaAdded, onMediaAdded: widget.onMediaAdded,
onMediaRemoved: widget.onMediaRemoved, onMediaRemoved: widget.onMediaRemoved,
onMediaTapped: widget.onMediaTapped, //
isEdit: widget.isEdit, //
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
if (widget.isShowAI) if (widget.isShowAI && widget.isEdit) // AI
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding),
child: GestureDetector( child: GestureDetector(

View File

@ -1007,7 +1007,7 @@ U6Hzm1ninpWeE+awIDAQAB
); );
} }
/// ///
static Future<Map<String, dynamic>> addSafeCheckReciord(String OUTSOURCED_ID) { static Future<Map<String, dynamic>> addSafeCheckRecord(String OUTSOURCED_ID) {
return HttpManager().request( return HttpManager().request(
basePath, basePath,
'/app/keyProjects/goEdit', '/app/keyProjects/goEdit',
@ -1018,6 +1018,17 @@ U6Hzm1ninpWeE+awIDAQAB
}, },
); );
} }
///
static Future<Map<String, dynamic>> getSafeCheckGoEdit(String KEYPROJECTCHECK_ID) {
return HttpManager().request(
basePath,
'/app/keyprojectcheck/goEdit',
method: Method.post,
data: {
"KEYPROJECTCHECK_ID":KEYPROJECTCHECK_ID,
},
);
}
/// ///
static Future<Map<String, dynamic>> getSafeCheckPersonList(String UNITS_ID, String NOMAIN) { static Future<Map<String, dynamic>> getSafeCheckPersonList(String UNITS_ID, String NOMAIN) {
return HttpManager().request( return HttpManager().request(
@ -1055,8 +1066,113 @@ U6Hzm1ninpWeE+awIDAQAB
}, },
); );
} }
///
static Future<Map<String, dynamic>> safeCheckPunishSubmit(Map data) {
return HttpManager().request(
basePath,
'/app/keyprojectpunish/add',
method: Method.post,
data: {
...data
},
);
}
///
static Future<Map<String, dynamic>> safeKeyprojectCheckSubmit(Map data) {
return HttpManager().request(
basePath,
'/app/keyprojectcheck/add',
method: Method.post,
data: {
...data
},
);
}
///
static Future<Map<String, dynamic>> getKeyprojectDangerList(String url, Map data) {
return HttpManager().request(
basePath,
url,
method: Method.post,
data: {
...data
},
);
}
///
static Future<Map<String, dynamic>> getKeyprojectDangerFindHidden(String HIDDEN_ID, String OUTSOURCED_ID) {
return HttpManager().request(
basePath,
'/app/keyprojectcheck/findHidden',
method: Method.post,
data: {
'HIDDEN_ID':HIDDEN_ID,
'OUTSOURCED_ID':OUTSOURCED_ID
},
);
}
///
static Future<Map<String, dynamic>> checkKeyprojectDanger(String HIDDEN_ID, Map data) {
return HttpManager().request(
basePath,
'/app/keyprojectcheck/check',
method: Method.post,
data: {
"CORPINFO_ID":SessionService.instance.corpinfoId,
"CREATOR":SessionService.instance.loginUserId,
"OPERATOR":SessionService.instance.loginUserId,
'HIDDEN_ID':HIDDEN_ID,
...data
},
);
}
///
static Future<Map<String, dynamic>> addNormalImgFiles(String imagePath, Map data) async {
final file = File(imagePath);
if (!await file.exists()) {
throw ApiException('file_not_found', '图片不存在:$imagePath');
}
final fileName = file.path.split(Platform.pathSeparator).last;
return HttpManager().uploadFaceImage(
baseUrl: basePath,
path: '/app/imgfiles/add',
fromData: {
...data,
'FFILE': await MultipartFile.fromFile(
file.path,
filename: fileName
),
}
);
}
///
static Future<Map<String, dynamic>> keyprojectpunishAdd(Map data) {
return HttpManager().request(
basePath,
'/app/keyprojectpunish/add',
method: Method.post,
data: {
"CORPINFO_ID":SessionService.instance.corpinfoId,
"CREATOR":SessionService.instance.loginUserId,
"OPERATOR":SessionService.instance.loginUserId,
...data
},
);
}
///
static Future<Map<String, dynamic>> keyprojectPunishEdit(Map data, String ISPUNISH) {
return HttpManager().request(
basePath,
'/app/keyprojectcheck/editHiddenIspunish',
method: Method.post,
data: {
"ISPUNISH":ISPUNISH,
"PUNISH_PERSON":SessionService.instance.loginUserId,
...data
},
);
}

View File

@ -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&currentPage=$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()),
],
)),
);
}
}

View File

@ -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();
}
},
),
],
),
),
),
);
}
}

View File

@ -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&currentPage=$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),
);
},
),
),
],
),
),
);
}
}

View File

@ -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),
),
),
),
);
},
);
}

View File

@ -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&currentPage=$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()),
],
)),
);
}
}

View File

@ -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();
},
),
],
),
),
),
);
}
}

View File

@ -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&currentPage=$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),
);
},
),
),
],
),
),
);
}
}

View File

@ -204,7 +204,7 @@ class _CheckListPageState extends State<CheckListPage> {
}, child: Text('发起', style: TextStyle(color: Colors.white, fontSize: 16),)) }, child: Text('发起', style: TextStyle(color: Colors.white, fontSize: 16),))
],), ],),
body: Column( body: SafeArea(child: Column(
children: [ children: [
// Filter bar // Filter bar
Container( Container(
@ -231,7 +231,7 @@ class _CheckListPageState extends State<CheckListPage> {
// List // List
Expanded(child: _buildListContent()), Expanded(child: _buildListContent()),
], ],
), )),
); );
} }
} }

View File

@ -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),
),
),
),
],
),
);
}
}

View File

@ -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)),
],
),
),
);
}

View File

@ -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,
);
}
}

View File

@ -1,12 +1,19 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/customWidget/bottom_picker.dart'; import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart';
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart';
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart';
import 'package:qhd_prevention/pages/app/Danger_paicha/quick_report_page.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart'; import 'package:qhd_prevention/tools/tools.dart';
@ -28,114 +35,222 @@ class SafecheckDetail extends StatefulWidget {
} }
class _SafecheckDetailState extends State<SafecheckDetail> { class _SafecheckDetailState extends State<SafecheckDetail> {
late Map<String, dynamic> info = {};
/// ///
late List<dynamic> personList = []; late List<dynamic> personList = [];
/// ///
late List<dynamic> typeList = []; late List<dynamic> typeList = [];
/// ///
late List<dynamic> toCheckUnitList = []; late List<dynamic> toCheckUnitList = [];
bool? chooseTitleType = null; bool? chooseTitleType = null;
final TextEditingController _unitController = TextEditingController(); //
final TextEditingController _locationController = TextEditingController(); List<String> multiTexts = [];
final TextEditingController _personController = TextEditingController(); List<String> delInspectors = [];
List<String> delSituations = [];
List<String> delHiddens = [];
List<String> delHiddenFiles = [];
// rules : [{ 'name': 'INSPECTION_CATEGORY', 'message': '请填写检查题目' }, ...]
List<Map<String, String>> rules = [
{'name': 'INSPECTION_CATEGORY', 'message': '请选择检查题目'},
{'name': 'UNITS_ID', 'message': '请选择被检查单位'},
{'name': 'PERSONNELMANAGEMENT_ID', 'message': '请选择被检查单位现场负责人'},
{'name': 'INSPECTION_TYPE', 'message': '请选择检查类型不能为空'},
{'name': 'INSPECTION_PLACE', 'message': '请输入检查场所'},
{'name': 'INSPECTION_TIME_START', 'message': '请选择检查开始时间'},
{'name': 'INSPECTION_TIME_END', 'message': '请选择作业结束时间'},
{'name': 'INSPECTION_USERS', 'message': '请输入检查人员'},
];
Map<String, dynamic> form = {
'INSPECTION_USERS': '',
'KEYPROJECTCHECK_ID': '', // ID
'OUTSOURCED_ID': '', // ID
'INSPECTION_CATEGORY': '', //
'INSPECTION_SOURCE': '5', // 4- 5-
'INSPECTION_ORIGINATOR_ID': '', //
'UNITS_ID': '', //
'UNITS_NAME': '',
'PERSONNELMANAGEMENT_ID': '', //
'INSPECTED_SITEUSER_INDEX': '',
'PERSON_NAME': '',
'INSPECTED_EXPLAIN': '', //
'INSPECTED_SITEUSER_SIGN_IMG': '', //
'INSPECTED_SITEUSER_SIGN_TIME': '', //
'INSPECTION_TYPE': '', //
'INSPECTION_TYPE_NAME': '',
'INSPECTION_PLACE': '', //
'INSPECTION_TIME_START': '', //
'INSPECTION_TIME_END': '', //
'INSPECTION_STATUS': '0', //
'POSITIONDESC': '', //
'CREATTIME': '',
'inspectorList': [
{
'INSPECTION_INSPECTOR_ID': '', //
'INSPECTION_DEPARTMENT_ID': '', //ID
'INSPECTION_DEPARTMENT_NAME': '',
'INSPECTION_USER_ID': '', //ID
'INSPECTION_USER_INDEX': '',
'INSPECTION_USER_NAME': '',
},
],
'situationList': [
{'INSPECTION_SITUATION_ID': '', 'SITUATION': ''},
],
'hiddenList': [
{
'ISRELEVANT': '2',
'HIDDEN_ID': '', // ID
'HIDDENDESCR': '', //
'HIDDENPART': '', //
'HIDDENPART_NAME': '',
'HIDDENLEVEL': '', //
'HIDDENLEVEL_NAME': '',
'HIDDENTYPE': '', // 1
'HIDDENTYPE_NAME': '',
'HIDDENTYPE2': '', // 2
'HIDDENTYPE2_NAME': '',
'LONGITUDE': '', //
'LATITUDE': '', //
'DISCOVERYTIME': '', //
'HIDDENFINDDEPT': '', //
'HIDDENFINDDEPT_NAME': '',
'CREATOR': '', //
'CREATOR_INDEX': '',
'CREATOR_NAME': '',
'SOURCE': '5', //
'hiddenImgs': <String>[],
'zgImgs': <String>[],
'hiddenVideos': <String>[],
'RECTIFICATIONTYPE': '2',
'RECTIFICATIONDEADLINE': '',
'RECTIFYDESCR': '',
'RECTIFICATIONDEPT_NAME': '',
'RECTIFICATIONDEPT': '',
'RECTIFICATIONOR_INDEX': '',
'HIDDENLEVEL_INDEX': '',
'RECTIFICATIONOR_NAME': '',
'RECTIFICATIONOR': '',
'punishForm': null,
},
],
'INSPECTION_USER_SIGN_TIME': '',
'INSPECTION_USER_OPINION': '',
};
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
form['OUTSOURCED_ID'] = widget.OUTSOURCED_ID;
form['KEYPROJECTCHECK_ID'] = widget.KEYPROJECTCHECK_ID;
form['hiddenList'] = [];
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.KEYPROJECTCHECK_ID.isNotEmpty) {
_getData(); _getData();
} else {
// KEYPROJECTCHECK_ID
setState(() {
form['APPLY_DEPARTMENT_ID'] = SessionService.instance.deptId ?? '';
form['APPLY_DEPARTMENT_NAME'] =
SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? '';
form['APPLY_USER_ID'] = SessionService.instance.loginUserId ?? '';
form['APPLY_USER'] = SessionService.instance.username ?? '';
});
}
// /
_getAllRequest();
});
} }
Future<void> _getData() async { Future<void> _getData() async {
LoadingDialogHelper.show();
final result = await ApiService.addSafeCheckReciord(widget.OUTSOURCED_ID);
final personData = await ApiService.getSafeCheckPersonList(info['UNITS_ID']??'', '1');
final typeListData = await ApiService.getSafeCheckTypeList();
final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID);
try { try {
final result = await ApiService.getSafeCheckGoEdit(widget.KEYPROJECTCHECK_ID);
// await mounted pop setState
if (!mounted) return;
setState(() { setState(() {
info = result['pd'] ?? {}; form = result['pd'] ?? {};
personList = personData['varList']; _syncMultiTextsFromForm();
typeList = jsonDecode(typeListData['zTreeNodes']);
toCheckUnitList = toUnitListData['varList'];
if (!FormUtils.hasValue(info, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) {
info['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME'];
}
if (!FormUtils.hasValue(info, 'UNITS_ID') && toCheckUnitList.isNotEmpty) {
info['UNITS_ID'] = toCheckUnitList.first['UNITS_ID'];
}
}); });
} catch (e) { } catch (e, st) {
print('加载数据失败:$e'); print('加载单条数据失败: $e\n$st');
if (mounted) {
ToastUtil.showNormal(context, '加载数据失败:$e'); ToastUtil.showNormal(context, '加载数据失败:$e');
} }
}
LoadingDialogHelper.hide();
} }
Future<void> onUpdateState(String state) async { Future<void> _getAllRequest() async {
//
String content = '';
if (state == '1') {
content = '确定同意开工吗?';
} else if (state == '2') {
content = '确定同意结束吗?';
} else {
content = '确定操作吗?';
}
final bool confirmed =
await showDialog<bool>(
context: context,
builder:
(ctx) => CustomAlertDialog(
title: '提示',
content: content,
onConfirm: () => Navigator.of(ctx).pop(false),
),
) ??
false;
if (!confirmed) return;
LoadingDialogHelper.show();
try { try {
final response = await ApiService.sureKeyProjectState( // widget build
widget.OUTSOURCED_ID, LoadingDialogHelper.show();
state,
); final result = await ApiService.addSafeCheckRecord(widget.OUTSOURCED_ID);
//
if (!mounted) {
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
if (response['result'] == 'success') { return;
Navigator.of(context).pop();
} else {
ToastUtil.showNormal(context, '请求失败');
} }
setState(() {
Map pd = result['pd'] ?? {};
form['UNITS_NAME'] = pd['UNITS_NAME'] ?? '';
form['UNITS_ID'] = pd['UNITS_ID'] ?? '';
form['PERSONNELMANAGEMENT_ID'] = pd['PERSONNELMANAGEMENT_ID'] ?? '';
form['PERSON_NAME'] = pd['NAME'] ?? '';
});
try {
final personData = await ApiService.getSafeCheckPersonList(
form['UNITS_ID'] ?? '',
'1',
);
if (!mounted) return;
setState(() {
personList = personData['varList'] ?? [];
});
} catch (e) { } catch (e) {
ToastUtil.showNormal(context, '请求异常:$e'); print('加载 personList 失败: $e');
}
try {
final typeListData = await ApiService.getSafeCheckTypeList();
if (!mounted) return;
setState(() {
typeList = jsonDecode(typeListData['zTreeNodes'] ?? '[]');
});
} catch (e) {
print('加载 typeList 失败: $e');
}
try {
final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID);
if (!mounted) return;
setState(() {
toCheckUnitList = toUnitListData['varList'] ?? [];
//
if (!FormUtils.hasValue(form, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) {
form['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME'];
}
if (!FormUtils.hasValue(form, 'UNITS_ID') && toCheckUnitList.isNotEmpty) {
form['UNITS_ID'] = toCheckUnitList.first['UNITS_ID'];
}
});
} catch (e) {
print('加载 toCheckUnitList 失败: $e');
}
} catch (e, st) {
print('总的加载失败: $e\n$st');
if (mounted) ToastUtil.showNormal(context, '加载数据失败:$e');
} finally {
LoadingDialogHelper.hide();
} }
} }
Widget lineItem({
required String label,
required bool isEditable,
String? text,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ItemListWidget.singleLineTitleText(
label: label,
isEditable: isEditable,
text: text ?? '',
),
const Divider(height: 1, thickness: 1),
],
);
}
Future<void> _choosePerson() async { Future<void> _choosePerson() async {
final choice = await BottomPicker.show<String>( final choice = await BottomPicker.show<String>(
context, context,
@ -146,19 +261,443 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
if (choice != null) { if (choice != null) {
// choice // choice
setState(() { setState(() {
info['PERSON_NAME'] = choice; form['PERSON_NAME'] = choice;
final data = FormUtils.findMapForKeyValue(personList, 'NAME', choice);
form['PERSONNELMANAGEMENT_ID'] = data['PERSONNELMANAGEMENT_ID'];
form['INSPECTED_SITEUSER_INDEX'] = personList.indexOf(data);
FocusHelper.clearFocus(context); FocusHelper.clearFocus(context);
}); });
} }
} }
Future<void> _chooseType() async {
final choice = await BottomPicker.show<String>(
context,
items: typeList.map((val) => val['name'] as String).toList(),
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
// choice
setState(() {
form['INSPECTION_TYPE_NAME'] = choice;
final data = FormUtils.findMapForKeyValue(typeList, 'name', choice);
form['INSPECTION_TYPE'] = data['id'];
FocusHelper.clearFocus(context);
});
}
}
Future<void> _openDrawer(Map<String, dynamic> hiddenForm, int index) async {
try {
final result = await openCustomDrawer<Map>(
context,
SafeDrawerPage(
initialHidden: hiddenForm,
editType:
widget.isEdit
? (index < 0 ? SafeEditType.add : SafeEditType.edit)
: SafeEditType.see,
toCheckUnitList: toCheckUnitList,
),
);
if (result != null && mounted) {
setState(() {
if (index < 0) {
//
form['hiddenList'].add(result);
} else {
//
form['hiddenList'][index] = result;
}
});
}
} catch (e) {
print("打开抽屉失败: $e");
ToastUtil.showNormal(context, "打开抽屉失败");
}
}
Future<T?> openCustomDrawer<T>(BuildContext context, Widget child) {
return Navigator.of(context).push<T>(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
barrierColor: Colors.black54,
pageBuilder: (_, __, ___) {
return Align(
alignment: Alignment.centerRight,
child: FractionallySizedBox(
widthFactor: 4 / 5,
child: Material(color: Colors.white, child: child),
),
);
},
transitionsBuilder: (_, anim, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)),
child: child,
);
},
),
);
}
/// form['situationList'] List<String>
List<String> _situationListToStrings() {
final List<dynamic> cur = List<dynamic>.from(form['situationList'] ?? []);
if (cur.isEmpty) return ['']; // uni-app
return cur.map((e) {
if (e is Map && e['SITUATION'] != null) return e['SITUATION'].toString();
return '';
}).toList();
}
/// List<String> uni-app situationList INSPECTION_SITUATION_ID
List<Map<String, dynamic>> _stringsToSituationList(List<String> texts) {
final List<dynamic> existing = List<dynamic>.from(
form['situationList'] ?? [],
);
final List<Map<String, dynamic>> out = [];
for (int i = 0; i < texts.length; i++) {
final s = texts[i] ?? '';
if (i < existing.length && existing[i] is Map) {
// INSPECTION_SITUATION_ID SITUATION
final Map<String, dynamic> copy = Map<String, dynamic>.from(
existing[i],
);
copy['SITUATION'] = s;
// INSPECTION_SITUATION_ID uni-app
copy['INSPECTION_SITUATION_ID'] = copy['INSPECTION_SITUATION_ID'] ?? '';
out.add(copy);
} else {
//
out.add({'INSPECTION_SITUATION_ID': '', 'SITUATION': s});
}
}
// existing id delSituations
// ID delSituations便
if (existing.isNotEmpty && existing.length > texts.length) {
for (int j = texts.length; j < existing.length; j++) {
final ex = existing[j];
if (ex is Map) {
final id = (ex['INSPECTION_SITUATION_ID'] ?? '').toString();
if (id.isNotEmpty) {
delSituations.add(id);
}
}
}
}
return out;
}
/// form['situationList'] multiTexts MultiTextFieldWithTitle
void _syncMultiTextsFromForm() {
final List<String> arr = _situationListToStrings();
setState(() {
multiTexts = arr;
});
}
// ------------ ------------
Future<void> _submit() async {
if (!widget.isEdit) {
Navigator.of(context).pop();
return;
}
bool required = true;
// rules
for (final r in rules) {
final name = r['name'] ?? '';
final message = r['message'] ?? '请完善表单';
final v = form[name];
if (v == null || v.toString().isEmpty || v.toString() == '请选择') {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, message);
required = false;
break;
}
}
if (!required) return;
// situationList SITUATION
final situations = (form['situationList'] as List<dynamic>?) ?? [];
for (var i = 0; i < situations.length; i++) {
final s = Map<String, dynamic>.from(situations[i]);
if ((s['SITUATION'] ?? '').toString().trim().isEmpty) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '请填写第${i + 1}项检查情况');
return;
}
}
// inspectorList INSPECTION_USER_ID
final List<Map<String, String>> inspectors = form['inspectorList'] ?? [];
final seenIds = <String>{};
for (final it in inspectors) {
final id = (it as Map)['INSPECTION_USER_ID']?.toString() ?? '';
if (id.isNotEmpty) {
if (seenIds.contains(id)) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '检查人重复!请检查数据');
return;
}
seenIds.add(id);
}
}
// hiddenList
final origHiddenList = (form['hiddenList'] as List<dynamic>?) ?? [];
final List<List<Map<String, dynamic>>> hiddenFilesPerHidden = [];
for (var i = 0; i < origHiddenList.length; i++) {
final hidden = Map<String, dynamic>.from(origHiddenList[i] as Map);
final List<Map<String, dynamic>> fileList = [];
// hiddenImgs ()
final hiddenImgs = (hidden['hiddenImgs'] as List<dynamic>?) ?? [];
for (var j = 0; j < hiddenImgs.length; j++) {
final img = hiddenImgs[j];
//
if (img is String) {
fileList.add({'type': 3, 'FILEPATH': img});
} else if (img is Map) {
final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty;
if (!hasId) {
fileList.add({
'type': 3,
'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '',
});
}
}
}
// zgImgs ()
final zgImgs = (hidden['zgImgs'] as List<dynamic>?) ?? [];
for (var j = 0; j < zgImgs.length; j++) {
final img = zgImgs[j];
if (img is String) {
fileList.add({'type': 4, 'FILEPATH': img});
} else if (img is Map) {
final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty;
if (!hasId) {
fileList.add({
'type': 4,
'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '',
});
}
}
}
// hiddenVideos ()
final hiddenVideos = (hidden['hiddenVideos'] as List<dynamic>?) ?? [];
if (hiddenVideos.isNotEmpty) {
final v = hiddenVideos[0];
if (v is String) {
fileList.add({'type': 102, 'FILEPATH': v});
} else if (v is Map) {
final hasId = (v['IMGFILES_ID'] ?? '').toString().isNotEmpty;
if (!hasId) {
fileList.add({
'type': 102,
'FILEPATH': v['FILEPATH'] ?? v['path'] ?? '',
});
}
}
}
hiddenFilesPerHidden.add(fileList);
}
// inspectorList SessionService
final loginUser = SessionService.instance.loginUser ?? {};
final loginUserId = SessionService.instance.loginUserId ?? '';
final idx = inspectors.indexWhere((item) {
final m = Map<String, dynamic>.from(item as Map);
return (m['INSPECTION_USER_ID'] ?? '') ==
(loginUser['USER_ID'] ?? loginUserId);
});
if (idx < 0) {
inspectors.add({
'INSPECTION_INSPECTOR_ID': '',
'INSPECTION_DEPARTMENT_ID': loginUser['DEPARTMENT_ID'] ?? '',
'INSPECTION_DEPARTMENT_NAME': loginUser['DEPARTMENT_NAME'] ?? '',
'INSPECTION_USER_ID': loginUser['USER_ID'] ?? loginUserId,
'INSPECTION_USER_INDEX': '',
'INSPECTION_USER_NAME': loginUser['NAME'] ?? '',
});
}
// form JSON
form['INSPECTORJSON'] = jsonEncode(inspectors);
form['SITUATIONJSON'] = jsonEncode(situations);
form['HIDDENJSON'] = jsonEncode(origHiddenList);
form['delInspectors'] = delInspectors.join(',');
form['delSituations'] = delSituations.join(',');
form['delHiddens'] = delHiddens.join(',');
form['delHiddenFiles'] = delHiddenFiles.join(',');
form['CREATOR'] = loginUser['USER_ID'] ?? loginUserId;
form['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? '';
form['ACTION_USER'] = loginUser['NAME'] ?? '';
LoadingDialogHelper.show(); // loading
//
try {
final requestData = <String, dynamic>{
'CORPINFO_ID': form['CORPINFO_ID'],
...form,
};
final res = await ApiService.safeKeyprojectCheckSubmit(requestData);
// ApiService
if (res != null && res['result'] == 'success') {
final pd = res['pd'] ?? {};
final List<dynamic> returnedHiddenList = pd['hiddenList'] ?? [];
//
final hasFiles = hiddenFilesPerHidden.any((lst) => lst.isNotEmpty);
if (!hasFiles) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '提交成功');
Navigator.of(context).pop();
return;
}
// hidden punishForm
for (var i = 0; i < returnedHiddenList.length; i++) {
if (i < (form['hiddenList'] as List).length) {
final hidden = Map<String, dynamic>.from(
(form['hiddenList'] as List)[i],
);
final punishForm = hidden['punishForm'];
if (punishForm != null) {
final hid = (returnedHiddenList[i]['HIDDEN_ID'] ?? '').toString();
punishForm['HIDDEN_ID'] = hid;
// await
await fnSubmit(Map<String, dynamic>.from(punishForm));
}
}
}
// hiddenId
// hiddenList Map
final returnedHiddenMapList =
returnedHiddenList
.map((e) => Map<String, dynamic>.from(e))
.toList();
await uploadHiddenFiles(hiddenFilesPerHidden, returnedHiddenMapList);
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '提交成功');
Navigator.of(context).pop();
} else {
LoadingDialogHelper.hide();
final msg =
res != null
? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败')
: '提交失败';
ToastUtil.showNormal(context, msg);
}
} catch (e) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '提交异常:$e');
}
}
// ========== ==========
Future<void> uploadHiddenFiles(
List<List<Map<String, dynamic>>> hiddenFilesPerHidden,
List<Map<String, dynamic>> returnedHiddenList,
) async {
for (var i = 0; i < hiddenFilesPerHidden.length; i++) {
final filesForHidden = hiddenFilesPerHidden[i];
if (filesForHidden.isEmpty) continue;
final hiddenId =
i < returnedHiddenList.length
? (returnedHiddenList[i]['HIDDEN_ID']?.toString() ?? '')
: '';
if (hiddenId.isEmpty) continue;
for (final f in filesForHidden) {
final filePath = f['FILEPATH']?.toString() ?? '';
final type = f['type']?.toString() ?? '';
if (filePath.isEmpty) continue;
try {
await ApiService.addImgFiles(filePath, type, hiddenId);
} catch (e) {
//
print('上传文件失败: $e (path=$filePath)');
}
}
}
}
// ========== fnSubmit ==========
Future<bool> fnSubmit(Map<String, dynamic>? ordForm) async {
if (ordForm == null) return false;
final Map<String, String> punishRules = {
'REASON': '请填写处罚原因',
'AMOUT': '请填写处罚金额',
'DATE': '请选择下发处罚时间',
};
//
for (final entry in punishRules.entries) {
final key = entry.key;
final msg = entry.value;
final val = ordForm[key];
if (val == null || val.toString().trim().isEmpty) {
ToastUtil.showNormal(context, msg);
return false;
}
}
final requestData = Map<String, dynamic>.from(ordForm);
requestData['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? '';
requestData['CREATOR'] = SessionService.instance.loginUserId ?? '';
requestData['OPERATOR'] = SessionService.instance.loginUserId ?? '';
try {
LoadingDialogHelper.show();
final res = await ApiService.safeCheckPunishSubmit(requestData);
LoadingDialogHelper.hide();
if (FormUtils.hasValue(res, 'result') && res['result'] == 'success') {
return true;
} else {
final msg =
res != null
? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败')
: '提交失败';
ToastUtil.showNormal(context, msg);
return false;
}
} catch (e) {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '罚单提交异常:$e');
return false;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: MyAppbar(title: "安全检查发起"), appBar: MyAppbar(title: "安全检查发起", actions: []),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Column( child: form.isNotEmpty ? ListView(
children: [
Column(
children: [ children: [
ItemListWidget.itemContainer( ItemListWidget.itemContainer(
horizontal: 0, horizontal: 0,
@ -169,22 +708,29 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
groupValue: chooseTitleType, groupValue: chooseTitleType,
yesLabel: '安全', yesLabel: '安全',
noLabel: '综合', noLabel: '综合',
isEdit: widget.isEdit,
isRequired: true,
horizontalPadding: 5, horizontalPadding: 5,
verticalPadding: 0, verticalPadding: 0,
text: form['INSPECTION_CATEGORY'] ?? '',
onChanged: (val) { onChanged: (val) {
setState(() { setState(() {
chooseTitleType = val; chooseTitleType = val;
form['INSPECTION_CATEGORY'] =
val == true ? '安全' : '综合';
}); });
}, },
isEdit: true,
text: info['INSPECTION_CATEGORY'] ?? '',
), ),
const Divider(), const Divider(),
ItemListWidget.singleLineTitleText( ItemListWidget.singleLineTitleText(
label: '被检查单位:', label: '被检查单位:',
isEditable: false, isEditable: false,
text: info['UNITS_NAME'], text: form['UNITS_NAME'],
controller: _unitController, onChanged: (val) {
setState(() {
form['UNITS_NAME'] = val;
});
},
), ),
const Divider(), const Divider(),
@ -194,31 +740,152 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
onTap: () { onTap: () {
_choosePerson(); _choosePerson();
}, },
text: info['PERSON_NAME'] ?? '', text: form['PERSON_NAME'] ?? '',
), ),
const Divider(), const Divider(),
ItemListWidget.singleLineTitleText( ItemListWidget.singleLineTitleText(
label: '检查场所:', label: '检查场所:',
isEditable: widget.isEdit, isEditable: widget.isEdit,
text: info['INSPECTION_PLACE'], text: form['INSPECTION_PLACE'],
hintText: '请输入检查场所', hintText: '请输入检查场所',
controller: _locationController, onChanged: (val) {
setState(() {
form['INSPECTION_PLACE'] = val;
});
},
), ),
const Divider(), const Divider(),
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '检查类型:', label: '检查类型:',
onTap: () {
_chooseType();
},
isEditable: widget.isEdit, isEditable: widget.isEdit,
text: info['INSPECTION_TYPE_NAME'] ?? '', text: form['INSPECTION_TYPE_NAME'] ?? '',
), ),
const Divider(), const Divider(),
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '检查开始时间:', label: '检查开始时间:',
isEditable: widget.isEdit, isEditable: widget.isEdit,
text: info['INSPECTION_TIME_START'] ?? '', text: form['INSPECTION_TIME_START'] ?? '',
onTap: () async {
DateTime? picked =
await BottomDateTimePicker.showDate(context);
if (picked != null) {
setState(() {
form['INSPECTION_TIME_START'] = DateFormat(
'yyyy-MM-dd HH:mm',
).format(picked);
});
FocusHelper.clearFocus(context);
}
},
), ),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '检查结束时间:',
isEditable: widget.isEdit,
text: form['INSPECTION_TIME_END'] ?? '',
onTap: () async {
DateTime? picked =
await BottomDateTimePicker.showDate(context);
if (picked != null) {
setState(() {
form['INSPECTION_TIME_END'] = DateFormat(
'yyyy-MM-dd HH:mm',
).format(picked);
});
FocusHelper.clearFocus(context);
}
},
),
const Divider(),
MultiTextFieldWithTitle(
label: "检查情况",
//
isEditable: widget.isEdit,
// 使
hintText: "请输入检查情况...",
texts: multiTexts,
onTextsChanged: (texts) {
setState(() {
multiTexts = texts; //
form['situationList'] = _stringsToSituationList(
texts,
);
});
},
),
const Divider(),
ItemListWidget.multiLineTitleTextField(
label: '检查人员',
text: form['INSPECTION_USERS'] ?? '',
isEditable: widget.isEdit,
onChanged: (val) {
setState(() {
form['INSPECTION_USERS'] = val;
});
},
),
const Divider(),
ItemListWidget.itemContainer(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ListItemFactory.headerTitle('发现问题'),
if (widget.isEdit)
CustomButton(
text: " 添加 ",
height: 30,
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 5,
),
backgroundColor: Colors.blue,
onPressed: () {
_openDrawer(form, -1); // await
FocusHelper.clearFocus(context);
},
),
],
),
),
HiddenListTable(
hiddenList: form['hiddenList'] ?? [],
forbidEdit: widget.isEdit,
baseImgPath: ApiService.baseImgPath,
personSignImg: form['PERSON_SIGN_IMG'] ?? '',
personSignTime: form['PERSON_SIGN_TIME'] ?? '',
showHidden: (item, idx) {
_openDrawer(item, idx);
},
removeHidden: (item, idx) {
/* 删除逻辑 */
},
context: context,
),
if (!widget.isEdit)
Column(children: [
const Divider(),
ItemListWidget.twoRowTitleAndImages(
title: '签字',
onTapCallBack: (p) {
presentOpaque(SingleImageViewer(imageUrl: p), context);
},
imageUrls: [
'${form['PERSON_SIGN_IMG'] ?? ''}',
],
),
const Divider(),
ItemListWidget.singleLineTitleText(label: '签字时间', isEditable: false, text: form['PERSON_SIGN_TIME'] ?? '')
],)
], ],
), ),
), ),
@ -226,19 +893,37 @@ class _SafecheckDetailState extends State<SafecheckDetail> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (widget.isEdit)
SizedBox( SizedBox(
width: 200, width: 150,
child: CustomButton( child: CustomButton(
text: '提交', text: '返回',
textStyle: TextStyle(color: Colors.white, fontSize: 17), textStyle: TextStyle(
backgroundColor: Colors.blue, color: Colors.white,
fontSize: 17,
),
backgroundColor: Colors.black38,
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
), ),
], SizedBox(
width: 150,
child: CustomButton(
text: widget.isEdit ? '提交' : '返回',
textStyle: TextStyle(
color: Colors.white,
fontSize: 17,
),
backgroundColor: Colors.blue,
onPressed: _submit,
),
), ),
], ],
), ),
],
),
],
): SizedBox(),
), ),
), ),
); );

View File

@ -222,7 +222,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
key: _scaffoldKey, key: _scaffoldKey,
appBar: MyAppbar(title: widget.flow), appBar: MyAppbar(title: widget.flow),
body: Column( body: SafeArea(child: Column(
children: [ children: [
// Filter bar // Filter bar
Container( Container(
@ -249,7 +249,7 @@ class _SafecheckListPageState extends State<SafecheckListPage> {
// List // List
Expanded(child: _buildListContent()), Expanded(child: _buildListContent()),
], ],
), )),
); );
} }
} }

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_list_page.dart';
import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart'; import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart';
import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_list_page.dart';
import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart'; import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart';
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart';
import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/pages/my_appbar.dart';
@ -89,8 +91,17 @@ class _KeyprojectsTabListState extends State<KeyprojectsTabList> {
await pushPage(SafecheckListPage(flow: title), context); await pushPage(SafecheckListPage(flow: title), context);
} break; } break;
case 2: title = '隐患管理'; break; case 2: {
case 3: title = '处罚管理'; break; title = '隐患管理';
await pushPage(DangerListPage(flow: title), context);
} break;
case 3: {
title = '处罚管理';
await pushPage(PunishmentListPage(flow: title), context);
} break;
default: default:
print("按钮 $index 被点击"); print("按钮 $index 被点击");
} }

View File

@ -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,
),
],
)
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart'; import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
import 'package:qhd_prevention/pages/badge_manager.dart'; import 'package:qhd_prevention/pages/badge_manager.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart'; import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart';
import 'package:qhd_prevention/pages/home/home_danger_page.dart'; import 'package:qhd_prevention/pages/home/home_danger_page.dart';
import 'package:qhd_prevention/pages/home/low_page.dart'; import 'package:qhd_prevention/pages/home/low_page.dart';
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart'; import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
@ -342,6 +343,9 @@ class _HomePageState extends State<HomePage> {
case 7: case 7:
pushPage(StudyGardenPage(), context); pushPage(StudyGardenPage(), context);
break; break;
case 8: //
await pushPage(SafecheckTabList(), context);
break;
case 10: case 10:
pushPage(SafetyMeetingListPage(), context); pushPage(SafetyMeetingListPage(), context);
break; break;

View File

@ -22,6 +22,7 @@ class ItemListWidget {
bool isRequired = true, bool isRequired = true,
bool strongRequired = false, bool strongRequired = false,
ValueChanged<String>? onChanged, ValueChanged<String>? onChanged,
ValueChanged<String>? onFieldSubmitted,
/// ///
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
@ -86,6 +87,8 @@ class ItemListWidget {
double height = 110, // double height = 110, //
bool isRequired = true, bool isRequired = true,
String hintText = '请输入', String hintText = '请输入',
ValueChanged<String>? onChanged,
}) { }) {
return Container( return Container(
// padding线 // padding线
@ -117,6 +120,7 @@ class ItemListWidget {
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
expands: true, expands: true,
onChanged: onChanged,
// //
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
@ -693,8 +697,8 @@ class ItemListWidget {
return Container( return Container(
width: 80, width: 80,
height: 80, height: 80,
color: Colors.grey[200], color: Colors.transparent,
child: const Icon(Icons.broken_image, size: 40), child: SizedBox(),
); );
}, },
), ),
@ -707,6 +711,99 @@ class ItemListWidget {
], ],
); );
} }
///
/// +
///
static Widget mulRowTitleAndTextField({
required String label, //
required bool isEditable, //
required String text, //
TextEditingController? controller, //
required VoidCallback? onTap, //
required String hintText,
double fontSize = 15, //
double row2Height = 80, //
bool isRequired = true,
}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// +
InkWell(
child: Row(
children: [
Flexible(
fit: FlexFit.loose, // loose
child: Row(
children: [
if (isRequired && isEditable)
Text('* ', style: TextStyle(color: Colors.red)),
Flexible(
child: Text(
label,
style: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(width: 8),
if (isEditable)
CustomButton(
text: "选择其他",
height: 30,
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 5,
),
backgroundColor: Colors.green,
onPressed: onTap,
),
],
),
),
const SizedBox(height: 8),
Container(
height: row2Height,
padding: const EdgeInsets.symmetric(vertical: 8),
child:
isEditable
? TextField(
autofocus: false,
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
expands: true,
style: TextStyle(fontSize: fontSize),
decoration: InputDecoration(
hintText: hintText,
//contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
)
: SingleChildScrollView(
padding: EdgeInsets.zero,
child: Text(
text,
style: TextStyle(
fontSize: fontSize,
color: detailtextColor,
),
),
),
),
],
),
);
}
static Widget itemContainer(Widget child, {double horizontal = 12}) { static Widget itemContainer(Widget child, {double horizontal = 12}) {

View File

@ -367,6 +367,14 @@ class FormUtils {
// //
return true; return true;
} }
/// list keyvaluemap
static Map findMapForKeyValue(List list,String key, String value) {
Map target = list.firstWhere(
(item) => item[key] == value,
orElse: () => {},
);
return target ?? {};
}
} }
class NoDataWidget { class NoDataWidget {