From a4072fd76139a3208366c0afd1a63f44009481a3 Mon Sep 17 00:00:00 2001 From: hs <873121290@qq.com> Date: Tue, 16 Sep 2025 08:28:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/ApiService.dart | 4 +- .../check_record_detail_page.dart | 102 +++--- .../special_wrok/dangerous_options_page.dart | 317 ++++++++++++------ .../dh_work_detai/hotwork_apply_detail.dart | 11 +- .../dl_work_detai/cutroad_apply_detail.dart | 11 +- .../breakground_apply_detail.dart | 11 +- .../dz_work_detai/hoistwork_apply_detail.dart | 11 +- .../gc_work_detai/highwork_apply_detail.dart | 11 +- .../electricity_apply_detail.dart | 11 +- .../blindboard_cjry_detail.dart | 2 +- .../blindboard_apply_detail.dart | 11 +- .../spacework_apply_detail.dart | 11 +- lib/pages/home/tap/workArea_picker.dart | 202 +++++------ pubspec.yaml | 2 +- 14 files changed, 361 insertions(+), 356 deletions(-) diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index 4a08048..3509db0 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -24,9 +24,9 @@ class ApiService { /// 登录及其他管理后台接口 // static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb"; // static const String basePath = "http://192.168.20.240:8500/integrated_whb";//测试服务器 - // static const String basePath = "http://192.168.20.240:8500/integrated_whb"; + static const String basePath = "http://192.168.20.240:8500/integrated_whb"; // static const String basePath = "http://192.168.0.25:28199";//王轩服务器 - static const String basePath = "http://192.168.0.45:28199";//长久服务器 + // static const String basePath = "http://192.168.0.45:28199";//长久服务器 /// 图片文件服务 static const String baseImgPath = "https://file.zcloudchina.com/YTHFile"; diff --git a/lib/pages/app/Danger_paicha/check_record_detail_page.dart b/lib/pages/app/Danger_paicha/check_record_detail_page.dart index 4ea4713..4accc77 100644 --- a/lib/pages/app/Danger_paicha/check_record_detail_page.dart +++ b/lib/pages/app/Danger_paicha/check_record_detail_page.dart @@ -137,70 +137,48 @@ class _CheckRecordDetailPageState extends State { List> buildCoversFromRes(Map res) { final list = >[]; const String defaultIconAsset = 'assets/map/50.png'; + final seen = {}; // "lat|lng|type" 去重 + + void tryAdd(double? lat, double? lng, String type, [dynamic data]) { + if (lat == null || lng == null) return; + final key = '${lat.toStringAsFixed(6)}|${lng.toStringAsFixed(6)}|$type'; + if (seen.contains(key)) return; + seen.add(key); + list.add({ + 'latitude': lat, + 'longitude': lng, + 'icon': defaultIconAsset, + 'data': {'type': type, 'item': data} + }); + } final cinfo = res['cinfo']; - if (cinfo != null && - cinfo['LONGITUDE'] != null && - cinfo['LATITUDE'] != null && - cinfo['LONGITUDE'].toString().isNotEmpty && - cinfo['LATITUDE'].toString().isNotEmpty) { - final lat = double.tryParse(cinfo['LATITUDE'].toString()); - final lng = double.tryParse(cinfo['LONGITUDE'].toString()); - if (lat != null && lng != null) { - list.add({ - 'latitude': lat, - 'longitude': lng, - 'icon': defaultIconAsset, - 'data': {'type': 'cinfo'} - }); - } + if (cinfo != null) { + final lat = double.tryParse(cinfo['LATITUDE']?.toString() ?? ''); + final lng = double.tryParse(cinfo['LONGITUDE']?.toString() ?? ''); + tryAdd(lat, lng, 'cinfo', cinfo); } - final check = res['checkrecord']; - if (check != null && - check['LONGITUDE'] != null && - check['LATITUDE'] != null && - check['LONGITUDE'].toString().isNotEmpty && - check['LATITUDE'].toString().isNotEmpty) { - final lat = double.tryParse(check['LATITUDE'].toString()); - final lng = double.tryParse(check['LONGITUDE'].toString()); - if (lat != null && lng != null) { - list.add({ - 'latitude': lat, - 'longitude': lng, - 'icon': defaultIconAsset, - 'data': {'type': 'checkrecord'} - }); - } - } - - final vlist = (res['varList'] as List?) ?? []; - for (final item in vlist) { - try { - if (item != null && - item['LONGITUDE'] != null && - item['LATITUDE'] != null && - item['LONGITUDE'].toString().isNotEmpty && - item['LATITUDE'].toString().isNotEmpty) { - final lat = double.tryParse(item['LATITUDE'].toString()); - final lng = double.tryParse(item['LONGITUDE'].toString()); - if (lat != null && lng != null) { - list.add({ - 'latitude': lat, - 'longitude': lng, - 'icon': defaultIconAsset, - 'data': {'type': 'varList', 'item': item} - }); - } - } - } catch (_) { - // 忽略单条解析错误 - } - } + // final check = res['checkrecord']; + // if (check != null) { + // final lat = double.tryParse(check['LATITUDE']?.toString() ?? ''); + // final lng = double.tryParse(check['LONGITUDE']?.toString() ?? ''); + // tryAdd(lat, lng, 'checkrecord', check); + // } + // + // final vlist = (res['varList'] as List?) ?? []; + // for (final item in vlist) { + // try { + // final lat = double.tryParse(item['LATITUDE']?.toString() ?? ''); + // final lng = double.tryParse(item['LONGITUDE']?.toString() ?? ''); + // tryAdd(lat, lng, 'varList', item); + // } catch (_) {} + // } return list; } + // 当 map 创建完成后会回调这里 void _onBmfMapCreated(BMFMapController controller) async { _mapController = controller; @@ -255,6 +233,18 @@ class _CheckRecordDetailPageState extends State { } Future _updateMarkers() async { + bool cleared = false; + try { + final res = await _mapController!.cleanAllMarkers(); + debugPrint('_updateMarkers: cleanAllMarkers returned: $res'); + cleared = (res == true); + } catch (e) { + debugPrint('_updateMarkers: cleanAllMarkers exception: $e'); + } + + if (!cleared) { + debugPrint('_updateMarkers: warning - markers not cleared. proceeding but will guard duplicates.'); + } if (_mapController == null) { debugPrint('_updateMarkers: mapController is null'); return; diff --git a/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart b/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart index aac3fce..93a76d9 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart @@ -1,5 +1,6 @@ +import 'dart:async'; import 'dart:io'; -import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; @@ -49,6 +50,7 @@ class SignImageData { @override String toString() => 'SignImageData(key:$key, filePath:$filePath, SIGNER_TIME:$SIGNER_TIME)'; } + class DangerousOptionsPage extends StatefulWidget { final int index; final int status; @@ -86,17 +88,21 @@ class _DangerousOptionsPageState extends State { status = widget.status; measures = widget.measures; imgList = List.from(widget.imgList); - signImgList = - widget.signImgList.map((map) => SignImageData.fromJson(map)).toList(); + signImgList = widget.signImgList.map((map) => SignImageData.fromJson(map)).toList(); } - /// 拍照或选图后的回调 + /// 拍照或选图后的回调(上传) Future _onImageAdded(String localPath) async { - // 上传到服务器 + if (!mounted) return; LoadingDialogHelper.show(); - final res = await ApiService.uploadSaveFile(localPath); - LoadingDialogHelper.hide(); + try { + // 给网络请求设置超时,避免长时间卡住 + final res = await ApiService.uploadSaveFile(localPath).timeout(const Duration(seconds: 30)); + LoadingDialogHelper.hide(); + + if (!mounted) return; + if (res['result'] == 'success') { final url = res['FILE_PATH'] as String; setState(() { @@ -104,25 +110,41 @@ class _DangerousOptionsPageState extends State { }); } else { ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + // 保持与原逻辑一致:清空并通知 setState(() { imgList = []; - MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); }); + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); } - } catch (_) { + } on TimeoutException { + LoadingDialogHelper.hide(); + if (!mounted) return; + ToastUtil.showError(context, '上传超时,请检查网络后重试'); + } catch (e, st) { + LoadingDialogHelper.hide(); + debugPrint('_onImageAdded error: $e\n$st'); + if (!mounted) return; ToastUtil.showError(context, '上传失败,资源过大请重新选择'); setState(() { imgList = []; - MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); }); + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); } } /// 删除图片处理:调用删除接口并更新列表 Future _onImageRemoved(ImageData item) async { - if (item.serverPath != null) { - await ApiService.deleteSaveFile(item.serverPath!); + try { + if (item.serverPath != null && item.serverPath.isNotEmpty) { + // 不等待过久:加超时保护 + await ApiService.deleteSaveFile(item.serverPath!).timeout(const Duration(seconds: 15)); + } + } catch (e, st) { + debugPrint('_onImageRemoved: delete api error: $e\n$st'); + // 不阻塞删除流程:即使删除接口失败,也从本地 UI 中移除 } + + if (!mounted) return; setState(() { imgList.remove(item); }); @@ -134,53 +156,70 @@ class _DangerousOptionsPageState extends State { return; } LoadingDialogHelper.show(); - List filePaths = - signImgList.map((img) => img.filePath ?? '').toList(); - final result = await ApiService.saveDangerousOptionsFile(filePaths); - final List signList = result['FILE_PATH_LIST']; - List> sineImageList = []; - for (SignImageData data in signImgList) { - for (Map img in signList) { - String imgName = 'file${data.key}'; - if (data.filePath!.contains('uploadFiles')) { - final idata = { - 'filePath': data.filePath, - 'SIGNER_TIME': data.SIGNER_TIME, - 'key': data.key, - }; - sineImageList.add(idata); - } - if (imgName == img['key']) { - final idata = { - 'filePath': img['filePath'] ?? '', - 'SIGNER_TIME': data.SIGNER_TIME, - 'key': data.key, - }; - sineImageList.add(idata); + try { + // 组织要上传的路径(以原逻辑为准) + List filePaths = signImgList.map((img) => img.filePath ?? '').toList(); + + // 保存文件 API,增加超时保护 + final result = await ApiService.saveDangerousOptionsFile(filePaths).timeout(const Duration(seconds: 30)); + + final List signList = result['FILE_PATH_LIST'] ?? []; + List> sineImageList = []; + for (SignImageData data in signImgList) { + for (Map img in signList) { + String imgName = 'file${data.key}'; + if (data.filePath != null && data.filePath!.contains('uploadFiles')) { + final idata = { + 'filePath': data.filePath, + 'SIGNER_TIME': data.SIGNER_TIME, + 'key': data.key, + }; + sineImageList.add(idata); + } + if (imgName == (img['key'] ?? '')) { + final idata = { + 'filePath': img['filePath'] ?? '', + 'SIGNER_TIME': data.SIGNER_TIME, + 'key': data.key, + }; + sineImageList.add(idata); + } } } + + if (!mounted) return; + setState(() => buttonLoading = true); // 保持原行为 + LoadingDialogHelper.hide(); + + if (!mounted) return; + Navigator.pop(context, { + 'imgList': imgList.map((e) => {'local': e.localPath, 'remote': e.serverPath}).toList(), + 'signImgList': sineImageList, + 'index': index, + 'status': status, + }); + } on TimeoutException { + LoadingDialogHelper.hide(); + if (!mounted) return; + ToastUtil.showError(context, '保存超时,请稍后重试'); + } catch (e, st) { + LoadingDialogHelper.hide(); + debugPrint('_submit error: $e\n$st'); + if (!mounted) return; + ToastUtil.showError(context, '提交失败,请重试'); } - setState(() => buttonLoading = true); - LoadingDialogHelper.hide(); - Navigator.pop(context, { - 'imgList': - imgList - .map((e) => {'local': e.localPath, 'remote': e.serverPath}) - .toList(), - 'signImgList': sineImageList, - 'index': index, - 'status': status, - }); } Future _sign() async { await NativeOrientation.setLandscape(); - final String path = await Navigator.push( + final String? path = await Navigator.push( context, MaterialPageRoute(builder: (c) => MineSignPage()), ); await NativeOrientation.setPortrait(); - if (path != null) { + + if (!mounted) return; + if (path != null && path.isNotEmpty) { final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now()); setState(() { final imageData = SignImageData( @@ -191,63 +230,121 @@ class _DangerousOptionsPageState extends State { signImgList.add(imageData); signTimes.add(now); }); - //FocusHelper.clearFocus(context); } } Widget _signListWidget() { + // 使用 ListView 或 Column 的时候,注意图片的解码在主线程可能造成阻塞。这里限制尺寸并使用 ResizeImage 去解码。 return Column( - children: - signImgList.map((imgData) { - final idx = signImgList.indexOf(imgData); - return Column( + children: signImgList.map((imgData) { + final idx = signImgList.indexOf(imgData); + final rawPath = (imgData.filePath ?? '').toString(); + final isNetwork = rawPath.startsWith('http://') || rawPath.startsWith('https://'); + + // 目标尺寸(尽量与 UI 展示一致,避免解码过大) + const targetWidth = 460; // 约为显示宽度 * devicePixelRatio 的一个合理值 + const targetHeight = 300; + + Widget imageWidget; + if (rawPath.isEmpty) { + imageWidget = Container( + width: 230, + height: 150, + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image, size: 28, color: Colors.grey)), + ); + } else if (isNetwork) { + // 网络图片:加上 width/height 与 loadingBuilder,避免阻塞 + imageWidget = Image.network( + rawPath.startsWith('http') ? rawPath : ApiService.baseImgPath + rawPath, + width: 230, + height: 150, + fit: BoxFit.cover, + // 显示 loading 占位,避免白屏卡顿 + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + width: 230, + height: 150, + color: Colors.grey.shade200, + child: const Center(child: CircularProgressIndicator(strokeWidth: 2)), + ); + }, + errorBuilder: (_, __, ___) => Container( + width: 230, + height: 150, + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ), + ); + } else { + // 本地文件:使用 ResizeImage 包装 FileImage,按目标像素解码,避免解码超大图片堵主线程 + final file = File(rawPath); + if (file.existsSync()) { + imageWidget = Image( + image: ResizeImage( + FileImage(file), + // 这里给出一个合理的解码尺寸(可根据实际设备 pixel ratio 适当调整) + width: targetWidth, + height: targetHeight, + ), + width: 230, + height: 150, + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => Container( + width: 230, + height: 150, + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ), + ); + } else { + imageWidget = Container( + width: 230, + height: 150, + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ); + } + } + + return Column( + children: [ + const SizedBox(height: 10), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const SizedBox(height: 10), - const Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + GestureDetector( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 230, + maxHeight: 150, + ), + child: imageWidget, + ), + onTap: () => presentOpaque(SingleImageViewer(imageUrl: rawPath), context), + ), + Column( children: [ - GestureDetector( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 230, - maxHeight: 150, - ), - child: - (imgData.filePath ?? '').contains('uploadFiles') - ? Image.network( - '${ApiService.baseImgPath}${imgData.filePath}', - ) - : Image.file( - File(imgData.filePath ?? ''), - fit: BoxFit.contain, - ), - ), - onTap: - () => presentOpaque( - SingleImageViewer(imageUrl: imgData.filePath ?? ''), - 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), - ], + CustomButton( + text: '删除', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + if (!mounted) return; + setState(() => signImgList.removeAt(idx)); + }, ), + const SizedBox(height: 80), ], ), ], - ); - }).toList(), + ), + ], + ); + }).toList(), ); } @@ -258,19 +355,19 @@ class _DangerousOptionsPageState extends State { body: Padding( padding: const EdgeInsets.all(12.0), child: Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const 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)}, + columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(2)}, children: [ TableRow( decoration: BoxDecoration(color: Colors.grey.shade200), children: [ Padding( - padding: EdgeInsets.all(10), + padding: const EdgeInsets.all(10), child: Center( child: Text( '主要安全措施', @@ -279,7 +376,7 @@ class _DangerousOptionsPageState extends State { ), ), Padding( - padding: EdgeInsets.all(10), + padding: const EdgeInsets.all(10), child: Center( child: Text( '操作', @@ -305,12 +402,12 @@ class _DangerousOptionsPageState extends State { RadioListTile( value: -1, groupValue: status, - title: Text('不涉及'), + title: const Text('不涉及'), contentPadding: const EdgeInsets.symmetric( vertical: 0, horizontal: 8.0, ), - visualDensity: VisualDensity( + visualDensity: const VisualDensity( vertical: -4, horizontal: 0, ), @@ -319,12 +416,12 @@ class _DangerousOptionsPageState extends State { RadioListTile( value: 1, groupValue: status, - title: Text('涉及'), + title: const Text('涉及'), contentPadding: const EdgeInsets.symmetric( vertical: 4.0, horizontal: 8.0, ), - visualDensity: VisualDensity( + visualDensity: const VisualDensity( vertical: -4, horizontal: 0, ), @@ -343,15 +440,17 @@ class _DangerousOptionsPageState extends State { maxCount: 2, mediaType: MediaType.image, initialMediaPaths: - imgList - .map((e) => '${ApiService.baseImgPath}${e.serverPath}') - .toList(), + imgList.map((e) => '${ApiService.baseImgPath}${e.serverPath}').toList(), onChanged: (paths) {}, onMediaAdded: _onImageAdded, onMediaRemoved: (path) { - print(path); - final item = imgList.firstWhere((e) => path.contains(e.localPath) ); - _onImageRemoved(item); + // 原逻辑保持:通过包含 localPath 的方式匹配 + try { + final item = imgList.firstWhere((e) => path.contains(e.localPath)); + _onImageRemoved(item); + } catch (e) { + debugPrint('onMediaRemoved: find item error: $e'); + } }, onAiIdentify: () {}, ), @@ -359,7 +458,7 @@ class _DangerousOptionsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('签字:', style: TextStyle(fontSize: 16)), + const Text('签字:', style: TextStyle(fontSize: 16)), CustomButton( text: '新增手写签字', height: 36, diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart index 68b0b58..8a6d3c0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart @@ -114,7 +114,7 @@ class _HotworkApplyDetailState extends State { _getHotWorkNameList(); _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { @@ -333,15 +333,6 @@ class _HotworkApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } - /// ------------------------------------------------------------ Widget _card(Widget child) { diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart index e7d14ab..d9c2b52 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart @@ -103,7 +103,7 @@ class _CutroadApplyDetailState extends State { _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { pd['WORK_REASON'] = _contentController.text.trim(); @@ -504,14 +504,7 @@ class _CutroadApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart index d67c470..65f8bd7 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart @@ -109,7 +109,7 @@ class _BreakgroundApplyDetailState extends State { } _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { pd['JOB_CONTENT'] = _contentController.text.trim(); @@ -275,14 +275,7 @@ class _BreakgroundApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ Future _chooseLevel() async { diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart index 946f03a..19a92fd 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart @@ -105,7 +105,7 @@ class _HoistworkApplyDetailState extends State { } _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { pd['WORK_CONTENT'] = _contentController.text.trim(); @@ -248,14 +248,7 @@ class _HoistworkApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ void set_pd_DEPARTMENT_ID(EditUserType type, String id) { diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart index b5fc862..965ee54 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart @@ -104,7 +104,7 @@ class _HighworkApplyDetailState extends State { _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { pd['WORK_CONTENT'] = _contentController.text.trim(); @@ -245,14 +245,7 @@ class _HighworkApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ void set_pd_DEPARTMENT_ID(EditUserType type, String id) { diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart index 4b528d3..c4fe199 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart @@ -111,7 +111,7 @@ class _ElectricityApplyDetailState extends State { } _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { pd['WORK_CONTENT'] = _contentController.text.trim(); @@ -244,14 +244,7 @@ class _ElectricityApplyDetailState extends State { unitAllList = result['varList'] ?? []; }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ void set_pd_DEPARTMENT_ID(EditUserType type, String id) { pd['${type.name}_DEPARTMENT_ID'] = id; diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart index 746d097..31dffca 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart @@ -180,7 +180,7 @@ class _BlindboardCjryDetailState extends State { // formData['WORK_CONTENT'] = _contentController.text.trim(); formData['CONIMG_PATH'] = serverPathString; - formData['DESCR'] = FormUtils.hasValue(pd, 'DESCR') ? pd['DESCR'] : '无'; + formData['DESCR'] = ''; formData['BLINDBOARD_ID'] = pd['BLINDBOARD_ID'] ?? widget.BLINDBOARD_ID; formData['SIGNTIME'] = signTimes.join(','); formData['USER_ID'] = SessionService.instance.loginUserId; diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart index ed564d5..512f757 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart @@ -110,7 +110,7 @@ class _BlindboardApplyDetailState extends State { _getVideoList(); _getUnitListAll(); - _getPlsList(); + _nameController.addListener(() { setState(() { pd['NAME'] = _nameController.text.trim(); @@ -234,14 +234,7 @@ class _BlindboardApplyDetailState extends State { unitAllList = result['varList'] ?? []; }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ void set_pd_DEPARTMENT_ID(EditUserType type, String id) { pd['${type.name}_DEPARTMENT_ID'] = id; diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart index 68a6ae0..8d8d6e6 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart @@ -112,7 +112,7 @@ class _SpaceworkApplyDetailState extends State { _getSpaceWorkNameList(); _getVideoList(); _getUnitListAll(); - _getPlsList(); + _contentController.addListener(() { setState(() { pd['WORK_CONTENT'] = _contentController.text.trim(); @@ -245,14 +245,7 @@ class _SpaceworkApplyDetailState extends State { }); } - /// 作业区域列表 - Future _getPlsList() async { - final result = await ApiService.getWorkAreaList(); - setState(() { - final String zTreeNodes = result['zTreeNodes'] ?? ''; - workAreaList = jsonDecode(zTreeNodes); - }); - } + /// ------------------------------------------------------------ void set_pd_DEPARTMENT_ID(EditUserType type, String id) { diff --git a/lib/pages/home/tap/workArea_picker.dart b/lib/pages/home/tap/workArea_picker.dart index 0d38a5e..7a55ccf 100644 --- a/lib/pages/home/tap/workArea_picker.dart +++ b/lib/pages/home/tap/workArea_picker.dart @@ -24,20 +24,18 @@ class Category { final name = (json['name'] ?? '').toString(); final positions = (json['POSITIONS'] ?? '').toString(); - // children 可能不存在、为 null、或已经是 List + // children 仍然尝试解析(兼容旧结构),但在扁平模式下不会使用 final rawChildren = json['children']; List childrenList = []; if (rawChildren is List) { childrenList = rawChildren .where((e) => e != null) .map((e) { - // e 可能已经是 Map 或 dynamic if (e is Map) { return Category.fromJson(e); } else if (e is Map) { return Category.fromJson(Map.from(e)); } else { - // 无法解析的项跳过,或返回空占位 return null; } }) @@ -54,9 +52,7 @@ class Category { } } - -typedef DeptSelectCallback = - void Function(String id, String POSITIONS, String name); +typedef DeptSelectCallback = void Function(String id, String POSITIONS, String name); class WorkAreaPicker extends StatefulWidget { final DeptSelectCallback onSelected; @@ -72,8 +68,6 @@ class _WorkAreaPickerState extends State { String selectedName = ''; String selected_POSITIONS = ''; - Set expandedSet = {}; - List original = []; List filtered = []; bool loading = true; @@ -83,11 +77,9 @@ class _WorkAreaPickerState extends State { @override void initState() { super.initState(); - // 初始均为空 selectedId = ''; selectedName = ''; selected_POSITIONS = ''; - expandedSet = {}; _searchController.addListener(_onSearchChanged); _loadData(); } @@ -102,36 +94,60 @@ class _WorkAreaPickerState extends State { Future _loadData() async { try { final result = await ApiService.getWorkAreaList(); - final dynamic nodesField = result['zTreeNodes']; + List raw = result['varList'] ?? []; - // nodesField 可能是已经解析过的 List 或者是 String(JSON 字符串) - List raw; - if (nodesField is String) { - raw = json.decode(nodesField) as List; - } else if (nodesField is List) { - raw = nodesField; - } else { - raw = []; + // 解析为 Category 列表(可能包含 children) + final parsed = raw + .map((e) { + if (e is Map) return Category.fromJson(e); + if (e is Map) return Category.fromJson(Map.from(e)); + return null; + }) + .whereType() + .toList(); + + // 扁平化:父与子都放到同一列表中(以兼容后端可能还是两层结构) + final List flat = []; + for (final c in parsed) { + flat.add(c); + if (c.children.isNotEmpty) { + flat.addAll(c.children); + } } - // debug 打印(运行中可查看控制台) - // print('raw length = ${raw.length}'); + // 规范化 id:如果后端 ID 为空,则为该项赋予一个独一无二的占位 id, + // 避免与默认 selectedId = '' 冲突导致“全部被选中”的问题。 + final List normalized = []; + for (var i = 0; i < flat.length; i++) { + final c = flat[i]; + final rawId = (c.ELECTRONIC_FENCE_AREA_ID ?? '').toString().trim(); + if (rawId.isEmpty) { + // 生成占位 id(基于索引和 name hash,足够唯一且不会是空字符串) + final generatedId = '__generated_${i}_${c.name.hashCode}'; + normalized.add(Category( + ELECTRONIC_FENCE_AREA_ID: generatedId, + name: c.name, + POSITIONS: c.POSITIONS, + children: const [], + )); + } else { + // 保持原 id + normalized.add(Category( + ELECTRONIC_FENCE_AREA_ID: rawId, + name: c.name, + POSITIONS: c.POSITIONS, + children: const [], + )); + } + } setState(() { - original = raw - .map((e) { - if (e is Map) return Category.fromJson(e); - if (e is Map) return Category.fromJson(Map.from(e)); - return null; - }) - .whereType() - .toList(); + original = normalized; filtered = original; loading = false; }); } catch (e, st) { - // 打印错误以便调试 - // print('loadData error: $e\n$st'); + debugPrint('WorkAreaPicker._loadData error: $e\n$st'); setState(() => loading = false); } } @@ -143,83 +159,40 @@ class _WorkAreaPickerState extends State { }); } + // 扁平过滤:在 name 或 POSITIONS 中搜索 List _filterCategories(List list, String query) { - List result = []; - for (var cat in list) { - final children = _filterCategories(cat.children, query); - if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) { - result.add( - Category( - ELECTRONIC_FENCE_AREA_ID: cat.ELECTRONIC_FENCE_AREA_ID, - name: cat.name, - children: children, - ), - ); - } - } - return result; + if (query.isEmpty) return list; + final q = query.toLowerCase(); + return list.where((c) { + final name = (c.name ?? '').toLowerCase(); + final pos = (c.POSITIONS ?? '').toLowerCase(); + return name.contains(q) || pos.contains(q); + }).toList(); } - Widget _buildRow(Category cat, int indent) { - final hasChildren = cat.children.isNotEmpty; - final isExpanded = expandedSet.contains(cat.ELECTRONIC_FENCE_AREA_ID); - final isSelected = cat.ELECTRONIC_FENCE_AREA_ID == selectedId; - return Column( - children: [ - InkWell( - onTap: () { - setState(() { - if (hasChildren) { - isExpanded - ? expandedSet.remove(cat.ELECTRONIC_FENCE_AREA_ID) - : expandedSet.add(cat.ELECTRONIC_FENCE_AREA_ID); - } - selectedId = cat.ELECTRONIC_FENCE_AREA_ID; - selectedName = cat.name; - selected_POSITIONS = cat.POSITIONS; - }); - }, - child: Container( - color: Colors.white, - child: Row( - children: [ - SizedBox(width: 16.0 * indent), - SizedBox( - width: 24, - child: - hasChildren - ? Icon( - isExpanded - ? Icons.arrow_drop_down_rounded - : Icons.arrow_right_rounded, - size: 35, - color: Colors.grey[600], - ) - : const SizedBox.shrink(), - ), - const SizedBox(width: 5), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Text(cat.name), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, - color: Colors.green, - ), - ), - ], + Widget _buildRow(Category cat) { + final isSelected = selectedId == cat.ELECTRONIC_FENCE_AREA_ID; + return InkWell( + onTap: () { + setState(() { + selectedId = cat.ELECTRONIC_FENCE_AREA_ID; + selectedName = cat.name; + selected_POSITIONS = cat.POSITIONS; + }); + }, + child: Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), + child: Row( + children: [ + Expanded(child: Text(cat.name)), + Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + color: isSelected ? Colors.green : Colors.grey, ), - ), + ], ), - if (hasChildren && isExpanded) - ...cat.children.map((c) => _buildRow(c, indent + 1)), - ], + ), ); } @@ -267,18 +240,19 @@ class _WorkAreaPickerState extends State { ], ), ), - Divider(), + const Divider(height: 1), Expanded( - child: - loading - ? const Center(child: CircularProgressIndicator()) - : Container( - color: Colors.white, - child: ListView.builder( - itemCount: filtered.length, - itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0), - ), - ), + child: loading + ? const Center(child: CircularProgressIndicator()) + : (filtered.isEmpty + ? const Center(child: Text('没有找到匹配的工作区域')) + : Container( + color: Colors.white, + child: ListView.builder( + itemCount: filtered.length, + itemBuilder: (ctx, idx) => _buildRow(filtered[idx]), + ), + )), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index aee7d80..8bc9ce1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.1.2+8 +version: 2.1.2+9 environment: sdk: ^3.7.0