diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f9be434..6505c47 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,6 +6,9 @@ PODS: - Flutter (1.0.0) - fluttertoast (0.0.2): - Flutter + - geolocator_apple (1.2.0): + - Flutter + - FlutterMacOS - image_picker_ios (0.0.1): - Flutter - mobile_scanner (7.0.0): @@ -26,6 +29,8 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -38,6 +43,7 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - nfc_manager (from `.symlinks/plugins/nfc_manager/ios`) @@ -46,6 +52,7 @@ DEPENDENCIES: - pdfx (from `.symlinks/plugins/pdfx/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) @@ -58,6 +65,8 @@ EXTERNAL SOURCES: :path: Flutter fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/darwin" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" mobile_scanner: @@ -74,6 +83,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/photo_manager/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" webview_flutter_wkwebview: @@ -84,6 +95,7 @@ SPEC CHECKSUMS: connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 nfc_manager: f6d5609c09b4640b914a3dc67479a2e392965fd0 @@ -92,6 +104,7 @@ SPEC CHECKSUMS: pdfx: 77f4dddc48361fbb01486fa2bdee4532cbb97ef3 photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 diff --git a/lib/customWidget/department_picker.dart b/lib/customWidget/department_picker.dart index 98f1d39..0cf5743 100644 --- a/lib/customWidget/department_picker.dart +++ b/lib/customWidget/department_picker.dart @@ -1,99 +1,152 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import '../tools/tools.dart'; // 包含 SessionService +// 数据模型 class Category { final String id; - final String title; + final String name; final List children; Category({ required this.id, - required this.title, + required this.name, this.children = const [], }); + + factory Category.fromJson(Map json) { + return Category( + id: json['id'] as String, + name: json['name'] as String, + children: (json['children'] as List) + .map((e) => Category.fromJson(e as Map)) + .toList(), + ); + } } -class DepartmentPicker extends StatefulWidget { - final List data; - final String? initialSelectedId; - final Set? initialExpandedSet; - final ValueChanged onSelected; +/// 弹窗回调签名:返回选中项的 id 和 name +typedef DeptSelectCallback = void Function(String id, String name); - const DepartmentPicker({ - Key? key, - required this.data, - this.initialSelectedId, - this.initialExpandedSet, - required this.onSelected, - }) : super(key: key); +class DepartmentPicker extends StatefulWidget { + /// 回调,返回选中部门 id 与 name + final DeptSelectCallback onSelected; + + const DepartmentPicker({Key? key, required this.onSelected}) : super(key: key); @override _DepartmentPickerState createState() => _DepartmentPickerState(); } class _DepartmentPickerState extends State { - late String? selectedId; - late Set expandedSet; + String selectedId = ''; + String selectedName = ''; + Set expandedSet = {}; + + List original = []; + List filtered = []; + bool loading = true; + + final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); - selectedId = widget.initialSelectedId; - expandedSet = Set.from(widget.initialExpandedSet ?? {}); + // 初始均为空 + selectedId = ''; + selectedName = ''; + expandedSet = {}; + _searchController.addListener(_onSearchChanged); + _loadData(); + } + + @override + void dispose() { + _searchController.removeListener(_onSearchChanged); + _searchController.dispose(); + super.dispose(); + } + + Future _loadData() async { + try { + List raw; + if (SessionService.instance.departmentJsonStr?.isNotEmpty ?? false) { + raw = json.decode(SessionService.instance.departmentJsonStr!) as List; + } else { + final result = await ApiService.getHiddenTreatmentListTree(); + final String nodes = result['zTreeNodes'] as String; + SessionService.instance.departmentJsonStr = nodes; + raw = json.decode(nodes) as List; + } + setState(() { + original = raw.map((e) => Category.fromJson(e as Map)).toList(); + filtered = original; + loading = false; + }); + } catch (e) { + setState(() => loading = false); + } + } + + void _onSearchChanged() { + final query = _searchController.text.toLowerCase().trim(); + setState(() { + filtered = query.isEmpty ? original : _filterCategories(original, query); + }); + } + + 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(id: cat.id, name: cat.name, children: children)); + } + } + return result; } Widget _buildRow(Category cat, int indent) { - final bool hasChildren = cat.children.isNotEmpty; - final bool isExpanded = expandedSet.contains(cat.id); - final bool isSelected = cat.id == selectedId; - + final hasChildren = cat.children.isNotEmpty; + final isExpanded = expandedSet.contains(cat.id); + final isSelected = cat.id == selectedId; return Column( children: [ InkWell( onTap: () { setState(() { if (hasChildren) { - if (isExpanded) { - expandedSet.remove(cat.id); - } else { - expandedSet.add(cat.id); - } + isExpanded ? expandedSet.remove(cat.id) : expandedSet.add(cat.id); } selectedId = cat.id; + selectedName = cat.name; }); }, child: Container( color: Colors.white, child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ - // 左侧缩进 SizedBox(width: 16.0 * indent), - // 展开/占位图标 SizedBox( width: 24, child: hasChildren - ? Icon( - isExpanded ? Icons.expand_less : Icons.expand_more, - size: 20, - color: Colors.grey[600], - ) + ? Icon(isExpanded ? Icons.arrow_drop_down_rounded : Icons.arrow_right_rounded, + size: 35, color: Colors.grey[600]) : const SizedBox.shrink(), ), - const SizedBox(width: 8), - // 标题 + const SizedBox(width: 5), Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), - child: Text(cat.title), + child: Text(cat.name), ), ), - // 单选圈保持右侧对齐 Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: Colors.green, ), ), @@ -103,7 +156,6 @@ class _DepartmentPickerState extends State { ), if (hasChildren && isExpanded) ...cat.children.map((c) => _buildRow(c, indent + 1)), - // const Divider(height: 1), ], ); } @@ -113,35 +165,49 @@ class _DepartmentPickerState extends State { return Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height * 0.7, - color: Colors.transparent, + color: Colors.white, child: Column( children: [ - // 顶部操作栏 Container( color: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => Navigator.of(context).pop(), child: const Text('取消', style: TextStyle(fontSize: 16)), ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SearchBarWidget( + + controller: _searchController, + isShowSearchButton: false, + onSearch: (keyboard) { + }, + ), + ), + ), GestureDetector( - onTap: () => Navigator.of(context).pop(selectedId), - child: const Text('确定', style: TextStyle(fontSize: 16, color: Colors.green),), + onTap: () { + Navigator.of(context).pop(); + widget.onSelected(selectedId, selectedName); + }, + child: const Text('确定', style: TextStyle(fontSize: 16, color: Colors.green)), ), ], ), ), - const Divider(height: 1), - // 列表区 + Divider(), Expanded( - child: Container( + child: loading + ? const Center(child: CircularProgressIndicator()) + : Container( color: Colors.white, child: ListView.builder( - itemCount: widget.data.length, - itemBuilder: (ctx, idx) => _buildRow(widget.data[idx], 0), + itemCount: filtered.length, + itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0), ), ), ), diff --git a/lib/customWidget/search_bar_widget.dart b/lib/customWidget/search_bar_widget.dart index ee095a0..c3b822e 100644 --- a/lib/customWidget/search_bar_widget.dart +++ b/lib/customWidget/search_bar_widget.dart @@ -12,6 +12,7 @@ class SearchBarWidget extends StatelessWidget { final bool isClickableOnly; final VoidCallback? onInputTap; final ValueChanged? onTextChanged; // 新增文本变化回调 + final bool isShowSearchButton; const SearchBarWidget({ Key? key, @@ -26,6 +27,7 @@ class SearchBarWidget extends StatelessWidget { this.isClickableOnly = false, this.onInputTap, this.onTextChanged, // 可选文本变化监听 + this.isShowSearchButton = true, }) : super(key: key); // 公共方法:更新输入框内容(可在外部调用) @@ -40,7 +42,6 @@ class SearchBarWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( children: [ Expanded( @@ -53,7 +54,8 @@ class SearchBarWidget extends StatelessWidget { autofocus: autoFocus, readOnly: isClickableOnly, style: const TextStyle(fontSize: 15), - onChanged: onTextChanged, // 监听文本变化 + onChanged: onTextChanged, + // 监听文本变化 decoration: InputDecoration( filled: true, fillColor: const Color(0xFFF5F5F5), @@ -65,7 +67,6 @@ class SearchBarWidget extends StatelessWidget { ), isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 8), - ), onSubmitted: onSearch, ), @@ -73,26 +74,24 @@ class SearchBarWidget extends StatelessWidget { ), ), const SizedBox(width: 10), - // 搜索按钮 - ElevatedButton( - onPressed: () => onSearch(controller.text.trim()), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), + if (isShowSearchButton) + // 搜索按钮 + ElevatedButton( + onPressed: () => onSearch(controller.text.trim()), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + elevation: 4, + shadowColor: Colors.black45, ), - elevation: 4, - shadowColor: Colors.black45, - ), - child: Text( - buttonText, - style: const TextStyle( - color: Colors.white, - fontSize: 16, + child: Text( + buttonText, + style: const TextStyle(color: Colors.white, fontSize: 16), ), ), - ), // 重置按钮 if (showResetButton) const SizedBox(width: 10), if (showResetButton) @@ -112,13 +111,10 @@ class SearchBarWidget extends StatelessWidget { ), child: Text( resetButtonText, - style: TextStyle( - color: Colors.white, - fontSize: 16, - ), + style: TextStyle(color: Colors.white, fontSize: 16), ), ), ], ); } -} \ No newline at end of file +} diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index 7c62049..c25ca32 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -925,7 +925,7 @@ U6Hzm1ninpWeE+awIDAQAB ); } - /// 获取隐患治理列表 + /// 获取部门列表 static Future> getHiddenTreatmentListTree() { return HttpManager().request( basePath, @@ -938,6 +938,21 @@ U6Hzm1ninpWeE+awIDAQAB }, ); } + /// 获取部门负责人列表 + static Future> getListTreePersonList(String DEPARTMENT_ID) { + return HttpManager().request( + basePath, + 'app/sys/listUser', + method: Method.post, + data: { + "tm":DateTime.now().millisecondsSinceEpoch.toString(), + "DEPARTMENT_ID": DEPARTMENT_ID, + "CORPINFO_ID": SessionService.instance.corpinfoId, + "USER_ID": SessionService.instance.loginUserId, + }, + ); + } + /// 获取隐患记录详情 static Future> getDangerDetail(String id) { diff --git a/lib/pages/app/Danger_paicha/check_record_list_page.dart b/lib/pages/app/Danger_paicha/check_record_list_page.dart index 18bf4e7..440bee7 100644 --- a/lib/pages/app/Danger_paicha/check_record_list_page.dart +++ b/lib/pages/app/Danger_paicha/check_record_list_page.dart @@ -25,28 +25,7 @@ class _CheckRecordListPageState extends State String time = '2025-06-${10 + i} 12:3${i}'; return NotificationItem(title, time); }); - final List data = [ - Category( - id: '1', - title: '分类一', - children: [ - Category(id: '1-1', title: '子项 1-1'), - Category(id: '1-2', title: '子项 1-2'), - ], - ), - Category(id: '2', title: '分类二'), - Category( - id: '3', - title: '分类三', - children: [ - Category( - id: '3-1', - title: '子项 3-1', - children: [Category(id: '3-1-1', title: '子项 3-1-1')], - ), - ], - ), - ]; + final TextEditingController _searchController = TextEditingController(); @override @@ -77,12 +56,9 @@ class _CheckRecordListPageState extends State barrierColor: Colors.black54, backgroundColor: Colors.transparent, builder: - (ctx) => DepartmentPicker( - data: data, - onSelected: (selectedId) { - setState(() {}); - }, - ), + (ctx) => DepartmentPicker(onSelected: (id, name) { + + }), ); } diff --git a/lib/pages/app/Danger_paicha/custom_record_drawer.dart b/lib/pages/app/Danger_paicha/custom_record_drawer.dart index 053cdaf..a32003b 100644 --- a/lib/pages/app/Danger_paicha/custom_record_drawer.dart +++ b/lib/pages/app/Danger_paicha/custom_record_drawer.dart @@ -30,26 +30,6 @@ class _CustomRecordDrawerState extends State { @override Widget build(BuildContext context) { - final List data = [ - Category( - id: '1', - title: '分类一1', - children: [ - Category(id: '1-1', title: '子项 1-1'), - Category(id: '1-2', title: '子项 1-2'), - ], - ), - Category(id: '2', title: '分类二'), - Category( - id: '3', - title: '分类三', - children: [ - Category(id: '3-1', title: '子项 3-1', children: [ - Category(id: '3-1-1', title: '子项 3-1-1'), - ]), - ], - ), - ]; Future showCategoryPicker(int type) async { if (type == 1) { @@ -58,14 +38,11 @@ class _CustomRecordDrawerState extends State { isScrollControlled: true, barrierColor: Colors.black54, backgroundColor: Colors.transparent, - builder: (ctx) => DepartmentPicker( - data: data, - onSelected: (selectedId) { - setState(() { - _selectedCategoryId = selectedId; - }); - }, - ), + builder: (ctx) => DepartmentPicker(onSelected: (id, name) { + setState(() { + _selectedCategoryId = id; + }); + }), ); } else if (type == 2) { final choice = await BottomPicker.show( diff --git a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart index a1eb47a..9f7fbdb 100644 --- a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/department_picker.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import '../../../../../../http/ApiService.dart'; import '../../../../../my_appbar.dart'; enum EditUserType { analyze, @@ -32,6 +34,8 @@ class HotworkApplyDetail extends StatefulWidget { class _HotworkApplyDetailState extends State { final bool isEditable = true; + late String treeJson=""; + Widget _defaultDetail() { return Column( @@ -123,7 +127,16 @@ class _HotworkApplyDetailState extends State { } /// 弹出单位 void chooseUnitHandle(EditUserType type) { - + showModalBottomSheet( + context: context, + isScrollControlled: true, + barrierColor: Colors.black54, + backgroundColor: Colors.transparent, + builder: (ctx) => DepartmentPicker(onSelected: (id, name) { + setState(() { + }); + }), + ); } /// 弹出单位对应的人员列表 void choosePersonHandle(EditUserType type) { @@ -203,4 +216,11 @@ class _HotworkApplyDetailState extends State { ), ); } + @override + void initState() { + // TODO: implement initState + super.initState(); + + } + } diff --git a/lib/pages/home/work/ai_alarm_page.dart b/lib/pages/home/work/ai_alarm_page.dart index 0e1a551..8fc7206 100644 --- a/lib/pages/home/work/ai_alarm_page.dart +++ b/lib/pages/home/work/ai_alarm_page.dart @@ -32,28 +32,7 @@ class _AiAlarmPageState extends State String time = '2025-06-${10 + i} 12:3${i}'; return NotificationItem(title, time); }); - final List data = [ - Category( - id: '1', - title: '分类一', - children: [ - Category(id: '1-1', title: '子项 1-1'), - Category(id: '1-2', title: '子项 1-2'), - ], - ), - Category(id: '2', title: '分类二'), - Category( - id: '3', - title: '分类三', - children: [ - Category( - id: '3-1', - title: '子项 3-1', - children: [Category(id: '3-1-1', title: '子项 3-1-1')], - ), - ], - ), - ]; + final TextEditingController _searchController = TextEditingController(); @override @@ -81,12 +60,9 @@ class _AiAlarmPageState extends State barrierColor: Colors.black54, backgroundColor: Colors.transparent, builder: - (ctx) => DepartmentPicker( - data: data, - onSelected: (selectedId) { - setState(() {}); - }, - ), + (ctx) => DepartmentPicker(onSelected: (id, name) { + + }), ); } diff --git a/lib/pages/home/work/danger_page.dart b/lib/pages/home/work/danger_page.dart index 3f6686d..2c70f4f 100644 --- a/lib/pages/home/work/danger_page.dart +++ b/lib/pages/home/work/danger_page.dart @@ -29,28 +29,7 @@ class _DangerPageState extends State String time = '2025-06-${10 + i} 12:3${i}'; return NotificationItem(title, time); }); - final List data = [ - Category( - id: '1', - title: '分类一', - children: [ - Category(id: '1-1', title: '子项 1-1'), - Category(id: '1-2', title: '子项 1-2'), - ], - ), - Category(id: '2', title: '分类二'), - Category( - id: '3', - title: '分类三', - children: [ - Category( - id: '3-1', - title: '子项 3-1', - children: [Category(id: '3-1-1', title: '子项 3-1-1')], - ), - ], - ), - ]; + final TextEditingController _searchController = TextEditingController(); @override @@ -83,12 +62,9 @@ class _DangerPageState extends State barrierColor: Colors.black54, backgroundColor: Colors.transparent, builder: - (ctx) => DepartmentPicker( - data: data, - onSelected: (selectedId) { - setState(() {}); - }, - ), + (ctx) => DepartmentPicker(onSelected: (id, name) { + + }), ); } diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index 660f0bf..831b3bd 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -157,6 +157,7 @@ class SessionService { bool updateInfo = false; String? dangerJson; String? riskJson; + String? departmentJsonStr; /// 如果以下任何一项为空,则跳转到登录页 void loginSession(BuildContext context) { @@ -194,6 +195,9 @@ class SessionService { void setRiskWaitInfo(String json) => riskJson = json; + void setDepartmentJsonStr(String json) => departmentJsonStr = json; + + }