344 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			344 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'dart:io';
 | ||
| import 'package:flutter/material.dart';
 | ||
| import 'package:qhd_prevention/customWidget/custom_button.dart';
 | ||
| import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
 | ||
| import 'package:qhd_prevention/pages/main_tab.dart';
 | ||
| import 'package:qhd_prevention/pages/mine/mine_sign_page.dart';
 | ||
| import 'package:qhd_prevention/pages/my_appbar.dart';
 | ||
| import 'package:qhd_prevention/customWidget/toast_util.dart';
 | ||
| import 'package:qhd_prevention/http/ApiService.dart';
 | ||
| import 'package:qhd_prevention/tools/tools.dart';
 | ||
| 
 | ||
| /// 完整翻译自你提供的 uniapp 页面逻辑。
 | ||
| /// 注意:下面的 ApiService 方法名(goEditII / editIsReadII / submitCorppromiseSign)
 | ||
| /// 仅为示例占位,与你项目中实际的方法名若不同请替换。
 | ||
| 
 | ||
| class OtherPromisePage extends StatefulWidget {
 | ||
|   const OtherPromisePage({
 | ||
|     super.key,
 | ||
|     required this.TabCur,
 | ||
|     required this.PROMISE_ID,
 | ||
|     required this.PROMISEPEOPLE_ID,
 | ||
|   });
 | ||
| 
 | ||
|   final int TabCur;
 | ||
|   final String PROMISE_ID;
 | ||
|   final String PROMISEPEOPLE_ID;
 | ||
| 
 | ||
|   @override
 | ||
|   State<OtherPromisePage> createState() => _OtherPromisePageState();
 | ||
| }
 | ||
| 
 | ||
| class _OtherPromisePageState extends State<OtherPromisePage> {
 | ||
|   bool _loading = true;
 | ||
|   Map<String, dynamic> info = {};
 | ||
|   String? baseImgPath = ApiService.baseImgPath;
 | ||
| 
 | ||
|   @override
 | ||
|   void initState() {
 | ||
|     super.initState();
 | ||
|     // 按照原 uniapp 的 onLoad:先获取数据
 | ||
|     _loadData();
 | ||
| 
 | ||
|     // 如果来自 TabCur == 1,需要标记为已读(uniapp 中是:if(option.TabCur=='1') this.setPromiseIsRead)
 | ||
|     if (widget.TabCur == 1) {
 | ||
|       _setPromiseIsRead();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _loadData() async {
 | ||
|     setState(() => _loading = true);
 | ||
|     try {
 | ||
|       // 请将下面的方法替换为你项目中实际的 ApiService 调用并传入 widget.PROMISE_ID / PROMISEPEOPLE_ID
 | ||
|       final res = await ApiService.getWorkshopSafetyOtherCommitmen(
 | ||
|         widget.PROMISE_ID,
 | ||
|         widget.PROMISEPEOPLE_ID,
 | ||
|       );
 | ||
| 
 | ||
|       final coll = (res['promistDetail'] as List?) ?? (res['promistdetail'] as List?) ?? [];
 | ||
|       final DETAIL = coll
 | ||
|           .map((e) => {
 | ||
|         'value': e?['COLLATERAL']?.toString() ?? e?['collateral']?.toString() ?? '',
 | ||
|         'id': e?['PROMISEDETAIL_ID']?.toString() ?? e?['promiseDetail_id']?.toString() ?? '',
 | ||
|       })
 | ||
|           .toList();
 | ||
| 
 | ||
|       final Map<String, dynamic> merged = {};
 | ||
| 
 | ||
|       if (res['varList'] is Map) merged.addAll(Map<String, dynamic>.from(res['varList']));
 | ||
|       if (res['people'] is Map) merged.addAll(Map<String, dynamic>.from(res['people']));
 | ||
|       merged['TEXT'] = res['TEXT']?.toString() ?? res['text']?.toString() ?? merged['TEXT'] ?? '';
 | ||
| 
 | ||
|       merged['DETAIL'] = DETAIL;
 | ||
| 
 | ||
|       if (res['coverpeople'] is List && (res['coverpeople'] as List).isNotEmpty) {
 | ||
|         merged['COVERPEOPLE'] = (res['coverpeople'][0]['USERNAME'] ?? res['coverpeople'][0]['username'])?.toString() ?? '';
 | ||
|       } else if (res['coverpeople'] is String) {
 | ||
|         merged['COVERPEOPLE'] = res['coverpeople'];
 | ||
|       }
 | ||
| 
 | ||
|       res.forEach((k, v) {
 | ||
|         if (!merged.containsKey(k)) merged[k] = v;
 | ||
|       });
 | ||
| 
 | ||
|       merged['PROMISEPEOPLE_ID'] = res['PROMISEPEOPLE_ID'] ?? widget.PROMISEPEOPLE_ID;
 | ||
| 
 | ||
|       merged['SIGNTIME'] = merged['SIGNTIME'] ?? merged['signtime'] ?? '';
 | ||
|       merged['CREATTIME'] = merged['CREATTIME'] ?? merged['creattime'] ?? merged['CREATTIME'] ?? '';
 | ||
| 
 | ||
|       setState(() {
 | ||
|         info = merged;
 | ||
|         _loading = false;
 | ||
|       });
 | ||
|     } catch (e, st) {
 | ||
|       debugPrint('加载承诺详情失败:$e\n$st');
 | ||
|       setState(() => _loading = false);
 | ||
|       ToastUtil.showNormal(context, '加载失败,请稍后重试');
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _setPromiseIsRead() async {
 | ||
|     try {
 | ||
|       await ApiService.getWorkshopSafetyOthercorppromise(widget.PROMISEPEOPLE_ID);
 | ||
|     } catch (e) {
 | ||
|       debugPrint('标记已读失败: $e');
 | ||
|       // 不打断用户流程,uniapp 中也是静默处理失败
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   String _formatDateShort(dynamic s) {
 | ||
|     if (s == null) return '';
 | ||
|     final str = s.toString();
 | ||
|     if (str.length >= 10) return str.substring(0, 10);
 | ||
|     return str;
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _sign() async {
 | ||
|     // 如果你的签字页面需要横屏,请在 MineSignPage 内或调用签字前后处理屏幕方向。
 | ||
|     // 下面直接跳转并等待返回签名图片路径(String),与原 uniapp 的行为一致。
 | ||
|     final result = await Navigator.push<String>(
 | ||
|       context,
 | ||
|       MaterialPageRoute(builder: (_) => const MineSignPage()),
 | ||
|     );
 | ||
| 
 | ||
|     if (result != null && result.isNotEmpty) {
 | ||
|       setState(() {
 | ||
|         info['FILEPATH'] = result;
 | ||
|       });
 | ||
|       // 清除焦点,防止键盘/输入问题
 | ||
|       FocusScope.of(context).unfocus();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> submitSignedPromise() async {
 | ||
|     final filePath = (info['FILEPATH'] ?? '').toString();
 | ||
|     if (filePath.isEmpty) {
 | ||
|       ToastUtil.showNormal(context, '请签字');
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     try {
 | ||
|       LoadingDialogHelper.show();
 | ||
|       // 请替换为你项目的实际提交方法和参数;下面示例使用 submitCorppromiseSign(filePath, info)
 | ||
|       final res = await ApiService.submitCorppromiseSign(filePath, info);
 | ||
|       LoadingDialogHelper.hide();
 | ||
| 
 | ||
|       if (res is Map && (res['result'] == 'success' || res['code'] == 0 || res['status'] == 'success')) {
 | ||
|         ToastUtil.showSuccess(context, '提交成功');
 | ||
|         // 和 uniapp 行为类似:提交后导航回主页面
 | ||
|         Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const MainPage()));
 | ||
|       } else {
 | ||
|         final msg = (res is Map) ? (res['message'] ?? res['msg'] ?? '提交失败') : '提交失败';
 | ||
|         ToastUtil.showNormal(context, msg.toString());
 | ||
|       }
 | ||
|     } catch (e, st) {
 | ||
|       LoadingDialogHelper.hide();
 | ||
|       debugPrint('提交签字失败:$e\n$st');
 | ||
|       ToastUtil.showNormal(context, '提交失败,请重试');
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildTitle() {
 | ||
|     final type = (info['TYPE'] ?? '').toString();
 | ||
|     final title = type == '0' ? '安全生产承诺书' : '安全生产责任状';
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.symmetric(vertical: 12.0),
 | ||
|       child: Text(
 | ||
|         title,
 | ||
|         style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
 | ||
|         textAlign: TextAlign.center,
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildHeaderName() {
 | ||
|     final type = (info['TYPE'] ?? '').toString();
 | ||
|     if (type == '0') {
 | ||
|       return Padding(
 | ||
|         padding: const EdgeInsets.symmetric(vertical: 8.0),
 | ||
|         child: Text(
 | ||
|           '${info['COVERPEOPLE'] ?? ''}:',
 | ||
|           style: const TextStyle(fontSize: 16),
 | ||
|         ),
 | ||
|       );
 | ||
|     }
 | ||
|     return const SizedBox.shrink();
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildParagraph(String text) {
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
 | ||
|       child: RichText(
 | ||
|         textAlign: TextAlign.justify,
 | ||
|         text: TextSpan(
 | ||
|           style: const TextStyle(
 | ||
|             height: 1.6,
 | ||
|             letterSpacing: 0.5,
 | ||
|             color: Colors.black,
 | ||
|           ),
 | ||
|           children: [
 | ||
|             const WidgetSpan(child: SizedBox(width: 28)),
 | ||
|             TextSpan(text: text),
 | ||
|           ],
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildCollateralList() {
 | ||
|     final detail = (info['DETAIL'] as List?) ?? [];
 | ||
|     if (detail.isEmpty) return const SizedBox.shrink();
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.only(left: 0, right: 8.0, top: 6.0),
 | ||
|       child: Column(
 | ||
|         crossAxisAlignment: CrossAxisAlignment.start,
 | ||
|         children: detail.map<Widget>((e) {
 | ||
|           return _buildParagraph(e['value']?.toString() ?? '');
 | ||
|         }).toList(),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildFooter() {
 | ||
|     final type = (info['TYPE'] ?? '').toString();
 | ||
|     final signImagePath = (info['FILEPATH'] ?? '').toString();
 | ||
|     final creatTime = (info['CREATTIME'] ?? '').toString();
 | ||
| 
 | ||
|     Widget signPreview;
 | ||
|     if (signImagePath.isNotEmpty && (signImagePath.startsWith('http') || signImagePath.startsWith('https'))) {
 | ||
|       signPreview = Image.network(
 | ||
|         signImagePath,
 | ||
|         width: 100,
 | ||
|         height: 50,
 | ||
|         fit: BoxFit.cover,
 | ||
|       );
 | ||
|     } else if (signImagePath.isNotEmpty && File(signImagePath).existsSync()) {
 | ||
|       signPreview = Image.file(
 | ||
|         File(signImagePath),
 | ||
|         width: 100,
 | ||
|         height: 50,
 | ||
|         fit: BoxFit.cover,
 | ||
|       );
 | ||
|     } else if (signImagePath.isNotEmpty && baseImgPath != null) {
 | ||
|       // 如果后端只给了相对路径,前端需要拼接 baseImgPath
 | ||
|       signPreview = Image.network(
 | ||
|         '${baseImgPath!}$signImagePath',
 | ||
|         width: 100,
 | ||
|         height: 50,
 | ||
|         fit: BoxFit.cover,
 | ||
|       );
 | ||
|     } else {
 | ||
|       signPreview = const SizedBox(width: 100, height: 50);
 | ||
|     }
 | ||
| 
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.only(top: 12.0, left: 8.0, right: 8.0, bottom: 20.0),
 | ||
|       child: Column(
 | ||
|         crossAxisAlignment: CrossAxisAlignment.stretch,
 | ||
|         children: [
 | ||
|           if (type == '0')
 | ||
|             const Align(alignment: Alignment.centerRight, child: Text('承诺单位(盖章):')),
 | ||
|           if (type == '1')
 | ||
|             Column(
 | ||
|               crossAxisAlignment: CrossAxisAlignment.end,
 | ||
|               children: [
 | ||
|                 Text('发状人:${info['COVERPEOPLE'] ?? ''}'),
 | ||
|                 const SizedBox(height: 6),
 | ||
|                 Text(creatTime.isNotEmpty ? _formatDateShort(creatTime) : ''),
 | ||
|               ],
 | ||
|             ),
 | ||
|           const SizedBox(height: 12),
 | ||
|           Row(
 | ||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | ||
|             children: [
 | ||
|               Expanded(
 | ||
|                 child: Row(
 | ||
|                   crossAxisAlignment: CrossAxisAlignment.end,
 | ||
|                   mainAxisAlignment: MainAxisAlignment.end,
 | ||
|                   children: [
 | ||
|                     Text(type == '0' ? '主要负责人签字:' : '受状人:'),
 | ||
|                     const SizedBox(width: 8),
 | ||
|                     GestureDetector(
 | ||
|                       onTap: () {
 | ||
|                         if ((info['FILEPATH'] ?? '').toString().isNotEmpty) {
 | ||
|                           presentOpaque(
 | ||
|                             SingleImageViewer(imageUrl: info['FILEPATH'] ?? ''),
 | ||
|                             context,
 | ||
|                           );
 | ||
|                         }
 | ||
|                       },
 | ||
|                       child: signPreview,
 | ||
|                     ),
 | ||
|                   ],
 | ||
|                 ),
 | ||
|               ),
 | ||
|             ],
 | ||
|           ),
 | ||
|         ],
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Widget build(BuildContext context) {
 | ||
|     return Scaffold(
 | ||
|       appBar: const MyAppbar(title: '安全承诺'),
 | ||
|       body: _loading
 | ||
|           ? const Center(child: CircularProgressIndicator())
 | ||
|           : SingleChildScrollView(
 | ||
|         child: Container(
 | ||
|           color: Colors.white,
 | ||
|           padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
 | ||
|           child: Column(
 | ||
|             crossAxisAlignment: CrossAxisAlignment.start,
 | ||
|             children: [
 | ||
|               Center(child: _buildTitle()),
 | ||
|               _buildHeaderName(),
 | ||
|               _buildParagraph(info['TEXT']?.toString() ?? ''),
 | ||
|               _buildCollateralList(),
 | ||
|               if ((info['TYPE'] ?? '').toString() == '0') ...[
 | ||
|                 _buildParagraph(
 | ||
|                   '若违反上述承诺和未履行安全生产职责,或发生责任事故的,接受政府或公司事故调查组做出的处罚决定。',
 | ||
|                 ),
 | ||
|                 _buildParagraph(
 | ||
|                   '承诺期限自${_formatDateShort(info['PROMISE_TERM_START'])}至${_formatDateShort(info['PROMISE_TERM_END'])}。',
 | ||
|                 ),
 | ||
|               ] else ...[
 | ||
|                 _buildParagraph(
 | ||
|                   '若未履行安全生产职责,或发生生产安全事故的,接受公司或政府事故调查组做出的处罚。',
 | ||
|                 ),
 | ||
|                 _buildParagraph(
 | ||
|                   '责任期限自${_formatDateShort(info['PROMISE_TERM_START'])}至${_formatDateShort(info['PROMISE_TERM_END'])}。',
 | ||
|                 ),
 | ||
|               ],
 | ||
|               const SizedBox(height: 8),
 | ||
|               _buildFooter(),
 | ||
| 
 | ||
|             ],
 | ||
|           ),
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| }
 |