重点工程管理完

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

View File

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

View File

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

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

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),))
],),
body: Column(
body: SafeArea(child: Column(
children: [
// Filter bar
Container(
@ -231,7 +231,7 @@ class _CheckListPageState extends State<CheckListPage> {
// List
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,
);
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

View File

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

View File

@ -367,6 +367,14 @@ class FormUtils {
//
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 {