hs 2025-07-30 17:08:46 +08:00
parent e7fbd5e12c
commit 3a00844a57
11 changed files with 879 additions and 406 deletions

View File

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
@ -10,25 +9,32 @@ import 'ItemWidgetFactory.dart';
/// ///
enum MediaType { image, video } enum MediaType { image, video }
/// ///
/// 使 /// 使
/// MediaPickerRow( /// MediaPickerRow(
/// maxCount: 4, /// maxCount: 4,
/// mediaType: MediaType.video, /// mediaType: MediaType.video,
/// onChanged: (List<File> medias) { /// initialMediaPaths: ['https://...', '/local/path.png'],
/// // medias /// 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;
final List<String>? initialMediaPaths;
final ValueChanged<List<File>> onChanged; final ValueChanged<List<File>> onChanged;
final ValueChanged<String>? onMediaAdded;
final ValueChanged<String>? onMediaRemoved;
const MediaPickerRow({ const MediaPickerRow({
Key? key, Key? key,
this.maxCount = 4, this.maxCount = 4,
this.mediaType = MediaType.image, this.mediaType = MediaType.image,
this.initialMediaPaths,
required this.onChanged, required this.onChanged,
this.onMediaAdded,
this.onMediaRemoved,
}) : super(key: key); }) : super(key: key);
@override @override
@ -37,13 +43,24 @@ class MediaPickerRow extends StatefulWidget {
class _MediaPickerRowState extends State<MediaPickerRow> { class _MediaPickerRowState extends State<MediaPickerRow> {
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
final List<File> _files = []; late List<String> _mediaPaths;
@override
void initState() {
super.initState();
_mediaPaths = widget.initialMediaPaths != null
? widget.initialMediaPaths!.take(widget.maxCount).toList()
: [];
WidgetsBinding.instance.addPostFrameCallback((_) {
// File
widget.onChanged(_mediaPaths.map((p) => p.startsWith('http') ? File('') : File(p)).toList());
});
}
Future<void> _showPickerOptions() async { Future<void> _showPickerOptions() async {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: builder: (_) => SafeArea(
(_) => SafeArea(
child: Wrap( child: Wrap(
children: [ children: [
ListTile( ListTile(
@ -67,7 +84,9 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
: Icons.video_library, : Icons.video_library,
), ),
title: Text( title: Text(
widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频', widget.mediaType == MediaType.image
? '从相册选择'
: '从相册选择视频',
), ),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -86,7 +105,7 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
} }
Future<void> _pickCamera() async { Future<void> _pickCamera() async {
if (_files.length >= widget.maxCount) return; if (_mediaPaths.length >= widget.maxCount) return;
try { try {
XFile? picked; XFile? picked;
if (widget.mediaType == MediaType.image) { if (widget.mediaType == MediaType.image) {
@ -95,10 +114,10 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
picked = await _picker.pickVideo(source: ImageSource.camera); picked = await _picker.pickVideo(source: ImageSource.camera);
} }
if (picked != null) { if (picked != null) {
setState(() { final path = picked.path;
_files.add(File(picked!.path)); setState(() => _mediaPaths.add(path));
}); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onChanged(_files); widget.onMediaAdded?.call(path);
} }
} catch (e) { } catch (e) {
debugPrint('拍摄失败: $e'); debugPrint('拍摄失败: $e');
@ -106,22 +125,21 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
} }
Future<void> _pickGallery() async { Future<void> _pickGallery() async {
if (_files.length >= widget.maxCount) return; if (_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) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, const SnackBar(content: Text('请到设置中开启相册访问权限')),
).showSnackBar(const SnackBar(content: Text('请到设置中开启相册访问权限'))); );
return; return;
} }
try { try {
final remaining = widget.maxCount - _files.length; final remaining = widget.maxCount - _mediaPaths.length;
final List<AssetEntity>? assets = await AssetPicker.pickAssets( final List<AssetEntity>? assets = await AssetPicker.pickAssets(
context, context,
pickerConfig: AssetPickerConfig( pickerConfig: AssetPickerConfig(
requestType: requestType: widget.mediaType == MediaType.image
widget.mediaType == MediaType.image
? RequestType.image ? RequestType.image
: RequestType.video, : RequestType.video,
maxAssets: remaining, maxAssets: remaining,
@ -130,25 +148,27 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
); );
if (assets != null) { if (assets != null) {
for (final asset in assets) { for (final asset in assets) {
if (_files.length >= widget.maxCount) break; if (_mediaPaths.length >= widget.maxCount) break;
final file = await asset.file; final file = await asset.file;
if (file != null) { if (file != null) {
_files.add(file); final path = file.path;
_mediaPaths.add(path);
widget.onMediaAdded?.call(path);
} }
} }
setState(() {}); setState(() {});
widget.onChanged(_files); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
} }
} catch (e) { } catch (e) {
debugPrint('相册选择失败: $e'); debugPrint('相册选择失败: $e');
} }
} }
void _removeFile(int index) { void _removeMedia(int index) {
setState(() { final removed = _mediaPaths[index];
_files.removeAt(index); setState(() => _mediaPaths.removeAt(index));
}); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onChanged(_files); widget.onMediaRemoved?.call(removed);
} }
@override @override
@ -157,25 +177,24 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
height: 80, height: 80,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: itemCount: _mediaPaths.length < widget.maxCount
_files.length < widget.maxCount ? _mediaPaths.length + 1
? _files.length + 1
: widget.maxCount, : widget.maxCount,
separatorBuilder: (_, __) => const SizedBox(width: 8), separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index < _files.length) { if (index < _mediaPaths.length) {
final path = _mediaPaths[index];
final isNetwork = path.startsWith('http');
return Stack( return Stack(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
child: child: widget.mediaType == MediaType.image
widget.mediaType == MediaType.image ? (isNetwork
? Image.file( ? Image.network(path,
_files[index], width: 80, height: 80, fit: BoxFit.cover)
width: 80, : Image.file(File(path),
height: 80, width: 80, height: 80, fit: BoxFit.cover))
fit: BoxFit.cover,
)
: Container( : Container(
width: 80, width: 80,
height: 80, height: 80,
@ -189,11 +208,11 @@ class _MediaPickerRowState extends State<MediaPickerRow> {
), ),
), ),
Positioned( Positioned(
top: -6, top: -15,
right: -6, right: -15,
child: IconButton( child: IconButton(
icon: const Icon(Icons.cancel, size: 20, color: Colors.red), icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
onPressed: () => _removeFile(index), onPressed: () => _removeMedia(index),
), ),
), ),
], ],
@ -230,7 +249,10 @@ class RepairedPhotoSection extends StatelessWidget {
final int maxCount; final int maxCount;
final MediaType mediaType; final MediaType mediaType;
final String title; final String title;
final List<String>? initialMediaPaths;
final ValueChanged<List<File>> onChanged; final ValueChanged<List<File>> onChanged;
final ValueChanged<String>? onMediaAdded;
final ValueChanged<String>? onMediaRemoved;
final VoidCallback onAiIdentify; final VoidCallback onAiIdentify;
final bool isShowAI; final bool isShowAI;
final double horizontalPadding; final double horizontalPadding;
@ -240,10 +262,13 @@ class RepairedPhotoSection extends StatelessWidget {
this.maxCount = 4, this.maxCount = 4,
this.mediaType = MediaType.image, this.mediaType = MediaType.image,
required this.title, required this.title,
this.initialMediaPaths,
this.isShowAI = false, this.isShowAI = false,
required this.onChanged, required this.onChanged,
required this.onAiIdentify, required this.onAiIdentify,
this.horizontalPadding = 10, this.horizontalPadding = 10,
this.onMediaAdded,
this.onMediaRemoved,
}) : super(key: key); }) : super(key: key);
@override @override
@ -257,7 +282,7 @@ class RepairedPhotoSection extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: ListItemFactory.createRowSpaceBetweenItem( child: ListItemFactory.createRowSpaceBetweenItem(
leftText: title, leftText: title,
rightText: '0/$maxCount', rightText: '${initialMediaPaths?.length ?? 0}/$maxCount',
), ),
), ),
Padding( Padding(
@ -265,7 +290,10 @@ class RepairedPhotoSection extends StatelessWidget {
child: MediaPickerRow( child: MediaPickerRow(
maxCount: maxCount, maxCount: maxCount,
mediaType: mediaType, mediaType: mediaType,
initialMediaPaths: initialMediaPaths,
onChanged: onChanged, onChanged: onChanged,
onMediaAdded: onMediaAdded,
onMediaRemoved: onMediaRemoved,
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),

View File

@ -714,7 +714,7 @@ U6Hzm1ninpWeE+awIDAQAB
data: { data: {
"CORPINFO_ID":SessionService.instance.corpinfoId, "CORPINFO_ID":SessionService.instance.corpinfoId,
"HOTWORK_ID": homeWorkId, "HOTWORK_ID": homeWorkId,
"USER_ID":SessionService.instance.loginUserId, "CONFIRM_ID":SessionService.instance.loginUserId,
}, },
); );
} }
@ -801,6 +801,60 @@ U6Hzm1ninpWeE+awIDAQAB
); );
} }
///
static Future<Map<String, dynamic>> uploadSaveFile (
String filePath,
) async {
final Map<String, dynamic> data = {
"CORPINFO_ID":SessionService.instance.corpinfoId,
};
// MultipartFile
final path = filePath;
data['file'] = await MultipartFile.fromFile(
path,
filename: path.split(Platform.pathSeparator).last,
);
return HttpManager().uploadFaceImage(
baseUrl: basePath,
path: '/app/eightwork/saveFile',
fromData: data,
);
}
///
static Future<Map<String, dynamic>> deleteSaveFile(String FILE_PATH) {
return HttpManager().request(
basePath,
'/app/eightwork/deleteFile',
method: Method.post,
data: {
"FILE_PATH": FILE_PATH,
},
);
}
///
static Future<Map<String, dynamic>> saveDangerousOptionsFile (
List<String> filePaths,
) async {
// formData
final Map<String, dynamic> data = {
"CORPINFO_ID":SessionService.instance.corpinfoId,
};
// MultipartFile
for (var i = 0; i < filePaths.length; i++) {
final path = filePaths[i];
data['file$i'] = await MultipartFile.fromFile(
path,
filename: path.split(Platform.pathSeparator).last,
);
}
return HttpManager().uploadFaceImage(
baseUrl: basePath,
path: '/app/eightwork/saveDangerousOptionsFile',
fromData: data,
);
}
///TODO --------------------------------- --------------------------------- ///TODO --------------------------------- ---------------------------------

View File

@ -87,7 +87,7 @@ class MyApp extends StatelessWidget {
), ),
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
// //
scaffoldBackgroundColor: const Color(0xFFF5F7FA), // scaffoldBackgroundColor: const Color(0xFFF1F1F1), //
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
inputDecorationTheme: const InputDecorationTheme( inputDecorationTheme: const InputDecorationTheme(
border: InputBorder.none, border: InputBorder.none,

View File

@ -1,13 +1,17 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart';
import '../../../customWidget/single_image_viewer.dart';
import '../../../tools/tools.dart';
class ItemListWidget { class ItemListWidget {
static const Color detailtextColor = Colors.black54;
/// ///
/// - + TextField /// - + TextField
/// - + /// - +
static const Color detailtextColor = Colors.black54;
static Widget singleLineTitleText({ static Widget singleLineTitleText({
required String label, // required String label, //
required bool isEditable, // required bool isEditable, //

View File

@ -35,7 +35,13 @@ class WorkDetailFormWidget extends StatelessWidget {
this.relatedController, this.relatedController,
this.riskController, this.riskController,
}) : assert( }) : assert(
!isEditable || (contentController != null && locationController != null && methodController != null && hotworkPersonController != null && relatedController != null && riskController != null), !isEditable ||
(contentController != null &&
locationController != null &&
methodController != null &&
hotworkPersonController != null &&
relatedController != null &&
riskController != null),
'Editable mode requires all TextEditingController parameters', 'Editable mode requires all TextEditingController parameters',
), ),
super(key: key); super(key: key);
@ -43,7 +49,14 @@ class WorkDetailFormWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pd = this.pd; final pd = this.pd;
return Column( return
Container(
padding: EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
ItemListWidget.singleLineTitleText( ItemListWidget.singleLineTitleText(
@ -116,7 +129,8 @@ class WorkDetailFormWidget extends StatelessWidget {
onTap: () async { onTap: () async {
final val = await showDialog<String>( final val = await showDialog<String>(
context: context, context: context,
builder: (_) => SelectionPopup( builder:
(_) => SelectionPopup(
type: 'assignments', type: 'assignments',
initialValue: pd['SPECIAL_WORK'] ?? '', initialValue: pd['SPECIAL_WORK'] ?? '',
onConfirm: (v) => Navigator.of(context).pop(v), onConfirm: (v) => Navigator.of(context).pop(v),
@ -136,7 +150,8 @@ class WorkDetailFormWidget extends StatelessWidget {
onTap: () async { onTap: () async {
final val = await showDialog<String>( final val = await showDialog<String>(
context: context, context: context,
builder: (_) => SelectionPopup( builder:
(_) => SelectionPopup(
type: 'identification', type: 'identification',
initialValue: pd['RISK_IDENTIFICATION'] ?? '', initialValue: pd['RISK_IDENTIFICATION'] ?? '',
onConfirm: (v) => Navigator.of(context).pop(v), onConfirm: (v) => Navigator.of(context).pop(v),
@ -158,6 +173,9 @@ class WorkDetailFormWidget extends StatelessWidget {
), ),
], ],
], ],
),
); );
} }
} }

View File

@ -0,0 +1,264 @@
import 'dart:io';
import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/HttpManager.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import '../../../../../../customWidget/custom_button.dart';
import '../../../../../../customWidget/photo_picker_row.dart';
import '../../../../../../customWidget/single_image_viewer.dart';
import '../../../../../../tools/tools.dart';
import '../../../../../mine/mine_sign_page.dart';
/// + 线
class ImageData {
String localPath;
String? serverPath;
ImageData({required this.localPath, this.serverPath});
}
class DangerousOptionsPage extends StatefulWidget {
final int index;
final int status;
final String measures;
final List<ImageData> imgList;
final List<String> signImgList;
const DangerousOptionsPage({
Key? key,
required this.index,
this.status = 1,
this.measures = '',
this.imgList = const [],
this.signImgList = const [],
}) : super(key: key);
@override
_DangerousOptionsPageState createState() => _DangerousOptionsPageState();
}
class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
late int index;
late int status;
late String measures;
late List<ImageData> imgList;
late List<String> signImgList;
List<String> signTimes = [];
bool buttonLoading = false;
final Dio _dio = Dio();
final _picker = ImagePicker();
@override
void initState() {
super.initState();
index = widget.index;
status = widget.status;
measures = widget.measures;
imgList = List<ImageData>.from(widget.imgList);
signImgList = List<String>.from(widget.signImgList);
}
///
Future<void> _onImageAdded(String localPath) async {
//
final res = await ApiService.uploadSaveFile(localPath);
final url = res['FILE_PATH'] as String;
setState(() {
imgList.add(ImageData(localPath: localPath, serverPath: url));
});
}
///
Future<void> _onImageRemoved(ImageData item) async {
if (item.serverPath != null) {
await ApiService.deleteSaveFile(item.serverPath!);
}
setState(() {
imgList.remove(item);
});
}
Future<void> _submit() async {
if (signImgList.isEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('请签字')));
return;
}
await ApiService.saveDangerousOptionsFile(signImgList);
setState(() => buttonLoading = true);
Navigator.pop(context, {
'imgList': imgList.map((e) => {'local': e.localPath, 'remote': e.serverPath}).toList(),
'signImgList': signImgList,
'index': index,
'status': status,
});
}
Future<void> _sign() async {
final path = await Navigator.push(
context,
MaterialPageRoute(builder: (c) => MineSignPage()),
);
if (path != null) {
final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
setState(() {
signImgList.add(path);
signTimes.add(now);
});
FocusHelper.clearFocus(context);
}
}
Widget _signListWidget() {
return Column(
children: signImgList.map((path) {
final idx = signImgList.indexOf(path);
return Column(
children: [
const SizedBox(height: 10),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200, maxHeight: 150),
child: Image.file(File(path), fit: BoxFit.contain),
),
onTap: () => presentOpaque(SingleImageViewer(imageUrl: path), context),
),
Column(
children: [
CustomButton(
text: '删除',
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 10),
backgroundColor: Colors.red,
onPressed: () {
setState(() => signImgList.removeAt(idx));
},
),
const SizedBox(height: 80),
],
),
],
),
],
);
}).toList(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: '安全措施'),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
color: Colors.white,
child: ListView(
children: [
Table(
border: TableBorder.all(color: Colors.grey.shade300),
columnWidths: {0: FlexColumnWidth(3), 1: FlexColumnWidth(2)},
children: [
TableRow(
decoration: BoxDecoration(color: Colors.grey.shade200),
children: [
Padding(
padding: EdgeInsets.all(10),
child: Center(
child: Text('主要安全措施', style: TextStyle(fontWeight: FontWeight.bold)),
),
),
Padding(
padding: EdgeInsets.all(10),
child: Center(
child: Text('操作', style: TextStyle(fontWeight: FontWeight.bold)),
),
),
],
),
TableRow(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 5),
child: Text(measures),
),
Padding(
padding: const EdgeInsets.all(0),
child: Column(
children: [
RadioListTile<int>(
value: -1,
groupValue: status,
title: Text('不涉及'),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 8.0),
visualDensity: VisualDensity(vertical: -4, horizontal: 0),
onChanged: (v) => setState(() => status = v!),
),
RadioListTile<int>(
value: 1,
groupValue: status,
title: Text('涉及'),
contentPadding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
visualDensity: VisualDensity(vertical: -4, horizontal: 0),
onChanged: (v) => setState(() => status = v!),
),
],
),
),
],
),
],
),
const SizedBox(height: 20),
RepairedPhotoSection(
title: '上传图片',
maxCount: 2,
mediaType: MediaType.image,
initialMediaPaths: imgList.map((e) => e.localPath).toList(),
onChanged: (paths) {},
onMediaAdded: _onImageAdded,
onMediaRemoved: (path) {
final item = imgList.firstWhere((e) => e.localPath == path);
_onImageRemoved(item);
},
onAiIdentify: () {},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('签字:', style: TextStyle(fontSize: 16)),
CustomButton(
text: '新增手写签字',
height: 36,
backgroundColor: Colors.green,
onPressed: _sign,
),
],
),
if (signImgList.isNotEmpty) _signListWidget(),
const SizedBox(height: 30),
CustomButton(
text: '保存',
backgroundColor: Colors.blue,
onPressed: buttonLoading ? null : _submit,
),
],
),
),
),
);
}
}

View File

@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
@ -10,8 +9,8 @@ import 'package:qhd_prevention/customWidget/department_person_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart'; import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.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/home/tap/tabList/special_wrok/aqcs_work_detail/dangerous_options_page.dart';
import 'package:qhd_prevention/tools/tools.dart'; import 'package:qhd_prevention/tools/tools.dart';
import '../../../../../../customWidget/bottom_picker.dart';
import '../../../../../../customWidget/custom_alert_dialog.dart'; import '../../../../../../customWidget/custom_alert_dialog.dart';
import '../../../../../../customWidget/single_image_viewer.dart'; import '../../../../../../customWidget/single_image_viewer.dart';
import '../../../../../../http/ApiService.dart'; import '../../../../../../http/ApiService.dart';
@ -20,6 +19,7 @@ import '../../../../../my_appbar.dart';
import '../../special_Wrok/dh_work_detai/MeasuresListWidget.dart'; import '../../special_Wrok/dh_work_detai/MeasuresListWidget.dart';
import '../../special_Wrok/qtfx_work_detail/hotwork_gas_list.dart'; import '../../special_Wrok/qtfx_work_detail/hotwork_gas_list.dart';
import '../WorkDetailFormWidget.dart'; import '../WorkDetailFormWidget.dart';
/// ///
class HotworkSafeFuncSure extends StatefulWidget { class HotworkSafeFuncSure extends StatefulWidget {
const HotworkSafeFuncSure({ const HotworkSafeFuncSure({
@ -45,6 +45,8 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
/// ///
late Map<String, dynamic> pd = {}; late Map<String, dynamic> pd = {};
late List<Map<String, dynamic>> measuresList = []; late List<Map<String, dynamic>> measuresList = [];
///
final TextEditingController _otherController = TextEditingController();
/// ///
late List<dynamic> workUserList = []; late List<dynamic> workUserList = [];
@ -67,18 +69,6 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
return jsonEncode(jsonList); return jsonEncode(jsonList);
} }
Widget _card(Widget child) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: child,
);
}
/// ///
void chooseUnitHandle(MeasureItem item) { void chooseUnitHandle(MeasureItem item) {
showModalBottomSheet( showModalBottomSheet(
@ -87,7 +77,8 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
barrierColor: Colors.black54, barrierColor: Colors.black54,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: builder:
(_) => DepartmentPicker( (_) =>
DepartmentPicker(
onSelected: (id, name) async { onSelected: (id, name) async {
setState(() { setState(() {
item.DEPARTMENT_ID = id; item.DEPARTMENT_ID = id;
@ -265,7 +256,8 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
await showDialog<String>( await showDialog<String>(
context: context, context: context,
builder: builder:
(_) => CustomAlertDialog( (_) =>
CustomAlertDialog(
title: '作废原因', title: '作废原因',
mode: DialogMode.input, mode: DialogMode.input,
hintText: '请输入作废原因', hintText: '请输入作废原因',
@ -294,7 +286,8 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
await showDialog<String>( await showDialog<String>(
context: context, context: context,
builder: builder:
(_) => CustomAlertDialog( (_) =>
CustomAlertDialog(
title: '提示', title: '提示',
content: '请确认' + (status == '1' ? "通过" : "作废") + '本作业票?', content: '请确认' + (status == '1' ? "通过" : "作废") + '本作业票?',
cancelText: '取消', cancelText: '取消',
@ -380,6 +373,44 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
}); });
} }
Future<void> _itemToSign(Map<String, dynamic> measures, int index) async {
//
final List<String> signImgList = measures['SIGN_ITEM'] ?? [];
//
final List<ImageData> imgList = [];
if ((measures['IMG_PATH'] as String?)?.isNotEmpty ?? false) {
for (var path in (measures['IMG_PATH'] as String).split(',')) {
imgList.add(ImageData(localPath: path));
}
}
final result = await pushPage(DangerousOptionsPage(index: index,status: measures['STATUS'] ?? 1,
measures: measures['PROTECTIVE_MEASURES'] ?? '',
imgList: imgList,
signImgList: signImgList), context);
if (result != null) {
setState(() {
// local + remote
final returned = result['imgList'] as List<dynamic>;
measures['IMG_PATH'] = returned.map((m) {
return ImageData(
localPath: m['local'] as String,
serverPath: m['remote'] as String?,
);
}).toList();
//
measures['SIGN_ITEM'] = List<String>.from(result['signImgList'] as List);
//
measures['STATUS'] = result['status'];
index = result['index'] as int;
});
}
}
/// ///
Widget _setSafeDetailWidget() { Widget _setSafeDetailWidget() {
return Container( return Container(
@ -390,10 +421,10 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
padding: EdgeInsets.symmetric(horizontal: 5), padding: EdgeInsets.symmetric(horizontal: 5),
child: Column( child: Column(
children: [ children: [
if (measuresList.length > 0) if (measuresList.isNotEmpty)
Column( Column(
children: [ children: [
SizedBox(height: 20), SizedBox(height: 5),
ListItemFactory.createBuildSimpleSection('安全防护措施'), ListItemFactory.createBuildSimpleSection('安全防护措施'),
Container( Container(
color: Colors.white, color: Colors.white,
@ -401,10 +432,19 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
measuresList: measuresList:
measuresList, // List<Map<String, dynamic>> measuresList, // List<Map<String, dynamic>>
baseImgPath: ApiService.baseImgPath, baseImgPath: ApiService.baseImgPath,
isAllowEdit: true,
onSign: (item) {
_itemToSign(item, measuresList.indexOf(item));
},
), ),
), ),
], ],
), ),
ItemListWidget.singleLineTitleText(label: '其他安全措施:',
isEditable: true,
hintText: '请输入其他安全措施',
controller
:_otherController),
SizedBox(height: 20), SizedBox(height: 20),
Row( Row(
@ -468,13 +508,11 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
padding: EdgeInsets.all(12), padding: EdgeInsets.all(12),
child: Column( child: Column(
children: [ children: [
// _card(_defaultDetail()),
_card(
WorkDetailFormWidget( WorkDetailFormWidget(
pd: pd, pd: pd,
isEditable: false, isEditable: false,
onChooseLevel: (){}, onChooseLevel: () {},
onChooseHotworkUser: (){}, onChooseHotworkUser: () {},
onAnalyzeTap: () { onAnalyzeTap: () {
pushPage( pushPage(
HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID),
@ -482,7 +520,6 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
); );
}, },
), ),
),
SizedBox(height: 20), SizedBox(height: 20),
_setSafeDetailWidget(), _setSafeDetailWidget(),
SizedBox(height: 20), SizedBox(height: 20),
@ -514,7 +551,8 @@ class MeasureItem {
List<Map<String, dynamic>>? userList, List<Map<String, dynamic>>? userList,
this.userIndex = -1, this.userIndex = -1,
List<Map<String, dynamic>>? selectMeasures, List<Map<String, dynamic>>? selectMeasures,
}) : userList = userList ?? [], })
: userList = userList ?? [],
selectMeasures = selectMeasures ?? []; selectMeasures = selectMeasures ?? [];
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View File

@ -11,18 +11,31 @@ import '../../../../../../tools/tools.dart';
import 'hotwork_apply_detail.dart'; import 'hotwork_apply_detail.dart';
/// ///
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
///
class MeasuresListWidget extends StatelessWidget { class MeasuresListWidget extends StatelessWidget {
const MeasuresListWidget({
super.key,
required this.measuresList,
required this.baseImgPath,
required this.isAllowEdit,
this.onSign,
});
/// Map /// Map
final List<Map<String, dynamic>> measuresList; final List<Map<String, dynamic>> measuresList;
/// ///
final String baseImgPath; final String baseImgPath;
const MeasuresListWidget({ /// &
Key? key, final bool isAllowEdit;
required this.measuresList,
required this.baseImgPath, /// item
}) : super(key: key); final void Function(Map<String, dynamic> item)? onSign;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -98,30 +111,44 @@ class MeasuresListWidget extends StatelessWidget {
item['SIGN_TIME'] as String? ?? '', item['SIGN_TIME'] as String? ?? '',
), ),
// 14 + // 14 +
for (var i = 1; i <= 4; i++) for (var i = 1; i <= 4; i++)
if (item.containsKey('QUESTION$i')) if ((item['QUESTION$i'] as String?)?.isNotEmpty ?? false)
_buildQnA( _buildQnA(item, i),
item['QUESTION$i'] as String? ?? '',
item['ANSWER$i'] as String? ?? '0',
),
], ],
), ),
), ),
// + // / +
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Column( child: Column(
children: [ children: [
// //
Text( isAllowEdit
? TextButton(
onPressed: () {
if ((item['STATUS'] as String?) != '-1') {
onSign?.call(item);
}
},
child: Text(
(item['SIGN_ITEM'] ?? '').toString().isNotEmpty
? '已签字'
: '签字',
style: TextStyle(
color: (item['STATUS'] as String?) == '-1'
? Colors.grey
: Colors.blue,
),
),
)
: Text(
(item['STATUS'] as String?) == '-1' (item['STATUS'] as String?) == '-1'
? '不涉及' ? '不涉及'
: '涉及', : '涉及',
style: TextStyle( style: TextStyle(
color: color: (item['STATUS'] as String?) == '-1'
(item['STATUS'] as String?) == '-1'
? Colors.grey ? Colors.grey
: Colors.black, : Colors.black,
), ),
@ -148,27 +175,71 @@ class MeasuresListWidget extends StatelessWidget {
); );
} }
/// + /// +
Widget _buildQnA(String question, String answer) { Widget _buildQnA(Map<String, dynamic> item, int index) {
final question = item['QUESTION$index'] as String? ?? '';
final key = 'ANSWER$index';
final answer = item[key]?.toString() ?? '';
if (isAllowEdit) {
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
'$question:',
style: const TextStyle(fontWeight: FontWeight.w800),
),
),
Expanded(
flex: 1,
child: TextFormField(
initialValue: answer,
textAlign: TextAlign.center, //
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
//
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
),
),
onChanged: (val) {
item[key] = val;
},
),
),
],
),
);
}
//
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'$question: ', '$question:',
style: const TextStyle(fontWeight: FontWeight.w800), style: const TextStyle(fontWeight: FontWeight.w800),
), ),
Text(answer.isNotEmpty ? answer : '0'), Text(answer.isNotEmpty ? answer : '0'),
], ],
), ),
), ),
Divider(), const Divider(),
], ],
), ),
); );
@ -190,13 +261,12 @@ class MeasuresListWidget extends StatelessWidget {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
opaque: false, opaque: false,
pageBuilder: pageBuilder: (_, __, ___) =>
(_, __, ___) => SingleImageViewer(imageUrl: '$baseImgPath\$p'),
SingleImageViewer(imageUrl: '$baseImgPath$p'),
), ),
); );
}, },
child: Image.network('$baseImgPath$p', width: 80, height: 80), child: Image.network('$baseImgPath\$p', width: 80, height: 80),
), ),
if (time.isNotEmpty) ...[const SizedBox(width: 8), Text(time)], if (time.isNotEmpty) ...[const SizedBox(width: 8), Text(time)],
], ],
@ -206,6 +276,7 @@ class MeasuresListWidget extends StatelessWidget {
} }
} }
/// ///
class OtherMeasuresWidget extends StatelessWidget { class OtherMeasuresWidget extends StatelessWidget {
/// ///
@ -753,6 +824,7 @@ class _SelectionPopupState extends State<SelectionPopup> {
}); });
_getData(); _getData();
} }
void _determine() { void _determine() {
// //
final result = selectValue.join(','); final result = selectValue.join(',');

View File

@ -8,6 +8,7 @@ import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.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/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:qhd_prevention/tools/tools.dart'; import 'package:qhd_prevention/tools/tools.dart';
import '../../../../../../customWidget/bottom_picker.dart'; import '../../../../../../customWidget/bottom_picker.dart';
import '../../../../../../http/ApiService.dart'; import '../../../../../../http/ApiService.dart';
@ -188,6 +189,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
Widget _card(Widget child) { Widget _card(Widget child) {
return Container( return Container(
padding: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
@ -283,22 +285,23 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
ToastUtil.showNormal(context, '请先选择$unitName'); ToastUtil.showNormal(context, '请先选择$unitName');
return; return;
} }
if (personList.isEmpty) { //
await _getPersonListForUnitId(unitId, type);
final list = _personCache[type] ?? [];
if (list.isEmpty) { //
ToastUtil.showNormal(context, '暂无数据,请选择其他单位');
}else{
choosePersonHandle(type);
}
return;
}
if (personList.isEmpty) { if (personList.isEmpty) {
// //
ToastUtil.showNormal(context, '请先选择' + type.displayName); ToastUtil.showNormal(context, '请先选择' + type.displayName);
return; return;
} }
// if (personList.isEmpty) { //
// await _getPersonListForUnitId(unitId, type);
// final list = _personCache[type] ?? [];
//
// if (list.isEmpty) { //
// ToastUtil.showNormal(context, '暂无数据,请选择其他单位');
// }else{
// choosePersonHandle(type);
// }
// return;
// }
DepartmentPersonPicker.show( DepartmentPersonPicker.show(
context, context,
personsData: personList, personsData: personList,
@ -481,6 +484,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: h_backGroundColor(),
appBar: MyAppbar(title: '动火安全作业申请'), appBar: MyAppbar(title: '动火安全作业申请'),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -584,6 +588,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
measuresList: measuresList:
measuresList, // List<Map<String, dynamic>> measuresList, // List<Map<String, dynamic>>
baseImgPath: ApiService.baseImgPath, baseImgPath: ApiService.baseImgPath,
isAllowEdit: false,
), ),
), ),
], ],

View File

@ -68,15 +68,7 @@ class _HotworkSetSafeDetailState extends State<HotworkSetSafeDetail> {
return jsonEncode(jsonList); return jsonEncode(jsonList);
} }
Widget _card(Widget child) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: child,
);
}
Widget _chooseItem(MeasureItem item) { Widget _chooseItem(MeasureItem item) {
return Column( return Column(
@ -255,7 +247,7 @@ class _HotworkSetSafeDetailState extends State<HotworkSetSafeDetail> {
); );
} }
/// 1 0 /// -1 1
Future<void> _submit(String status) async { Future<void> _submit(String status) async {
if (imagePaths.isEmpty) { if (imagePaths.isEmpty) {
ToastUtil.showNormal(context, '请签字'); ToastUtil.showNormal(context, '请签字');
@ -572,7 +564,6 @@ class _HotworkSetSafeDetailState extends State<HotworkSetSafeDetail> {
child: Column( child: Column(
children: [ children: [
// _card(_defaultDetail()), // _card(_defaultDetail()),
_card(
WorkDetailFormWidget( WorkDetailFormWidget(
pd: pd, pd: pd,
isEditable: false, isEditable: false,
@ -585,7 +576,6 @@ class _HotworkSetSafeDetailState extends State<HotworkSetSafeDetail> {
); );
}, },
), ),
),
SizedBox(height: 20), SizedBox(height: 20),
_setSafeDetailWidget(), _setSafeDetailWidget(),
SizedBox(height: 20), SizedBox(height: 20),

View File

@ -1,7 +1,7 @@
import 'dart:ffi'; import 'dart:ffi';
import 'dart:ui'; import 'dart:ui';
Color h_backGroundColor() => Color(0xFFF5F7FA); Color h_backGroundColor() => Color(0xFFF1F1F1);
List<Color> riskLevelTextColors() { List<Color> riskLevelTextColors() {
return [Color(0xFFE54D42),Color(0xFFF37B1D),Color(0xFFF9BD08),Color(0xFF3281FF)]; return [Color(0xFFE54D42),Color(0xFFF37B1D),Color(0xFFF9BD08),Color(0xFF3281FF)];
} }