From f4f2b3c566d933ed580dd1a59cb47541e2f13516 Mon Sep 17 00:00:00 2001 From: xufei <727302827@qq.com> Date: Mon, 9 Mar 2026 11:28:49 +0800 Subject: [PATCH] =?UTF-8?q?2026.3.9=20=E4=B8=80=E7=BA=A7=E5=8F=A3=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorAndCar/car/doorArea_car_add_page.dart | 16 +- .../car/firstlevel_car_add_page.dart | 528 ++++++++++++++++++ .../home/doorAndCar/doorCar_tab_page.dart | 9 +- .../person/doorArea_person_add_page.dart | 151 +++-- .../person/doorArea_person_record_page.dart | 72 ++- .../person/firstlevel_person_add_page.dart | 469 ++++++++++++++++ .../doorAndCar/person_selection_page.dart | 496 ++++++++++++++++ 7 files changed, 1662 insertions(+), 79 deletions(-) create mode 100644 lib/pages/home/doorAndCar/car/firstlevel_car_add_page.dart create mode 100644 lib/pages/home/doorAndCar/person/firstlevel_person_add_page.dart create mode 100644 lib/pages/home/doorAndCar/person_selection_page.dart diff --git a/lib/pages/home/doorAndCar/car/doorArea_car_add_page.dart b/lib/pages/home/doorAndCar/car/doorArea_car_add_page.dart index 3f38319..ffb8730 100644 --- a/lib/pages/home/doorAndCar/car/doorArea_car_add_page.dart +++ b/lib/pages/home/doorAndCar/car/doorArea_car_add_page.dart @@ -247,13 +247,17 @@ class _DoorareaCarAddPageState extends State { onTap: () async {}, ), const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '选择时间:', - isEditable: true, - text: pd['departmentName'] ?? '请选择', - isRequired: true, - onTap: () async {}, + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '选择时间:', + isEditable: false, + text: pd['departmentName'] ?? '', + isRequired: true, + onTap: () async {}, + ), ), + const Divider(), ItemListWidget.multiLineTitleTextField( label: '申请原因', diff --git a/lib/pages/home/doorAndCar/car/firstlevel_car_add_page.dart b/lib/pages/home/doorAndCar/car/firstlevel_car_add_page.dart new file mode 100644 index 0000000..ac46765 --- /dev/null +++ b/lib/pages/home/doorAndCar/car/firstlevel_car_add_page.dart @@ -0,0 +1,528 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.dart'; +import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/item_list_widget.dart'; +import 'package:qhd_prevention/customWidget/photo_picker_row.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/http/modules/basic_info_api.dart'; + +import 'package:qhd_prevention/pages/home/doorAndCar/sign_instructions_webView.dart'; +import 'package:qhd_prevention/pages/mine/mine_sign_page.dart'; +import 'package:qhd_prevention/pages/mine/webViewPage.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/services/SessionService.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:flutter/gestures.dart'; + +class FirstlevelCarAddPage extends StatefulWidget { + const FirstlevelCarAddPage({super.key}); + + @override + State createState() => _FirstlevelCarAddPageState(); +} + +class _FirstlevelCarAddPageState extends State { + Map pd = {}; + bool _agreed = false; + + // 部门列表 + List _deptList = []; + List _personList = []; + List signImages = []; + + late bool _isMyCompanyArea = false; + late bool _isSelectCar = false; + + + List _vehicleImages = []; + List _vehicleLicenseImages = []; + + @override + void initState() { + super.initState(); + _getDept(); + } + + // 获取部门 + Future _getDept() async { + // try { + // final data = { + // 'eqCorpinfoId': widget.scanData['id'], + // // 'eqParentId': widget.scanData['corpinfoId'], + // }; + // final result = await BasicInfoApi.getDeptTree(data); + // if (result['success'] == true) { + // final list = result['data'] ?? []; + // if (list.length > 0) { + // setState(() { + // _deptList = list[0]['childrenList'] ?? []; + // }); + // } + // } + // } catch (e) {} + } + + // 提交 + Future _saveSuccess() async { + if (!FormUtils.hasValue(pd, 'corpinfoId')) { + ToastUtil.showNormal(context, '请选择部门'); + return; + } + if (!FormUtils.hasValue(pd, 'postName')) { + ToastUtil.showNormal(context, '请输入岗位'); + return; + } + // try { + // final result = await BasicInfoApi.userFirmEntry(pd); + // LoadingDialogHelper.hide(); + // if (result['success'] == true) { + // ToastUtil.showNormal(context, '申请成功'); + // _relogin(); + // } else { + // ToastUtil.showNormal(context, result['errMessage']); + // } + // } catch (e) { + // LoadingDialogHelper.hide(); + // ToastUtil.showNormal(context, '操作失败,请重试'); + // } + } + + Widget _addPersonWight(Map personData) { + return Stack( + children: [ + Padding(padding: const EdgeInsets.only(top: 5),child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12), + // padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.white, + // 边框 + border: Border.all( + color: Colors.grey.shade300, + width: 1.0, + style: BorderStyle.solid, + ), + borderRadius: BorderRadius.circular(5), + ), + child: Column( + children: [ + const SizedBox(height: 10), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择部门:', + isEditable: true, + text: personData['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择人员:', + isEditable: true, + text: personData['postName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.itemContainer(RepairedPhotoSection( + isRequired: true, + title: "人员照片", + maxCount: 1, + horizontalPadding: 0, + mediaType: MediaType.image, + isShowAI: false, + onChanged: (List files) {}, + onAiIdentify: () {}, + ),), + const SizedBox(height: 10) + ], + ) + ),), + Positioned( + top: 0, + right: 2, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.red, + ), + + child: GestureDetector( + child: const Icon(Icons.close, size: 14, color: Colors.white,), + onTap: () { + setState(() { + _personList.remove(personData); + }); + }, + ), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '车辆申请'), + body: SafeArea( + child: ItemListWidget.itemContainer( + horizontal: 5, + vertical: 12, + ListView( + children: [ + + + ListItemFactory.createBuildSimpleSection('申请信息'), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择项目名称:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '审核人员:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '选择时间:', + isEditable: false, + text: pd['departmentName'] ?? '', + isRequired: true, + onTap: () async {}, + ), + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '访问港区:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择范围:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + // ItemListWidget.multiLineTitleTextField( + // label: '申请原因', + // isEditable: true, + // ), + + + + + ///车辆信息 + ListItemFactory.createBuildSimpleSection('车辆信息'), + + const Divider(), + + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '驾驶人部门', + isEditable: !_isSelectCar, + onTap: () { + + }, + text: '', + ), + ), + const Divider(height: 1,), + + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '驾驶人', + isEditable: !_isSelectCar, + onTap: () { + + }, + text: '', + ), + ), + const Divider(height: 1,), + + + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '车牌类型', + isEditable: !_isSelectCar, + onTap: () { + + }, + text: '', + ), + ), + + const Divider(height: 1,), + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '车辆类型', + isEditable: !_isSelectCar, + onTap: () { + + }, + text: '', + ), + ), + + const Divider(height: 1,), + Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '车牌号', + isEditable: !_isSelectCar, + onTap: () { + + }, + text: '', + ), + ), + // ItemListWidget.singleLineTitleText( + // label: '车牌号', + // isEditable: !_isSelectCar, + // text: '', + // onChanged: (value) { + // + // }, + // ), + + + const Divider(), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: RepairedPhotoSection( + isRequired:true, + title: "车辆照片", + maxCount: 4, + mediaType: MediaType.image, + isShowAI: false, + onChanged: (List files) { + // 上传图片 files + _vehicleImages.clear(); + for(int i=0;i files) { + // 上传图片 files + _vehicleLicenseImages.clear(); + for(int i=0;i SignInstructionsWebview( + name: "安全进港须知", + url: + 'http://47.92.102.56:7811/file/xieyi/zsyhxy.htm', + )), + ); + await NativeOrientation.setPortrait(); + if (path != null) { + setState(() { + setState(() { + _agreed = true; + signImages = []; + signImages.add(path); + }); + }); + } + + // pushPage( + // const SignInstructionsWebview( + // name: "安全进港须知", + // url: + // 'http://47.92.102.56:7811/file/xieyi/zsyhxy.htm', + // ), + // context, + // ); + }, + ), + ], + ), + ), + ), + ], + ), + + + // Container( + // padding: EdgeInsets.symmetric(horizontal: 12), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ListItemFactory.headerTitle('签字:', isRequired: true), + // CustomButton( + // text: signImages.isNotEmpty ? '重新签字' : '手写签字', + // height: 36, + // backgroundColor: Colors.blue, + // onPressed: _sign, + // ), + // ], + // ), + // ), + if (signImages.isNotEmpty) _signListWidget(), + + SizedBox(height: 10,), + + CustomButton( + text: '提交申请', + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + height: 40, + backgroundColor: Colors.blue, + onPressed: () { + _saveSuccess(); + }, + ), + ], + ), + ), + ), + ); + } + + + + Widget _signListWidget() { + return Column( + children: + signImages.map((path) { + return Column( + children: [ + const SizedBox(height: 15), + // const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + signImages.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], + ), + ], + ), + ], + ); + }).toList(), + ); + } + + + + + +} diff --git a/lib/pages/home/doorAndCar/doorCar_tab_page.dart b/lib/pages/home/doorAndCar/doorCar_tab_page.dart index 1e28098..516da3d 100644 --- a/lib/pages/home/doorAndCar/doorCar_tab_page.dart +++ b/lib/pages/home/doorAndCar/doorCar_tab_page.dart @@ -222,7 +222,7 @@ class _DoorcarTabPageState extends State { builder: (context, constraints) { const spacing = 20.0; final totalWidth = constraints.maxWidth; - final itemWidth = (totalWidth - spacing * 2) / 3; + final itemWidth = (totalWidth - spacing * 2) / 2; return Wrap( spacing: spacing, runSpacing: spacing, @@ -240,6 +240,13 @@ class _DoorcarTabPageState extends State { }, ), ), + + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 10, 5), + child: Text('*申请封闭区域时,系统已自动包含进港口门权限,您无需再次申请进港口门权限。', + style: const TextStyle( + fontSize: 12, fontWeight: FontWeight.bold,color: Colors.red)), + ), ], ), ), diff --git a/lib/pages/home/doorAndCar/person/doorArea_person_add_page.dart b/lib/pages/home/doorAndCar/person/doorArea_person_add_page.dart index 9ee0ae3..d221df7 100644 --- a/lib/pages/home/doorAndCar/person/doorArea_person_add_page.dart +++ b/lib/pages/home/doorAndCar/person/doorArea_person_add_page.dart @@ -12,6 +12,7 @@ import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/http/modules/basic_info_api.dart'; +import 'package:qhd_prevention/pages/home/doorAndCar/person_selection_page.dart'; import 'package:qhd_prevention/pages/home/doorAndCar/sign_instructions_webView.dart'; @@ -36,7 +37,7 @@ class _DoorareaPersonApplyPageState extends State { // 部门列表 List _deptList = []; - List _personList = []; + List _personList = []; List signImages = []; @override @@ -89,55 +90,69 @@ class _DoorareaPersonApplyPageState extends State { // } } - Widget _addPersonWight(Map personData) { + Widget _addPersonWight(Person personData) { return Stack( children: [ - Padding(padding: const EdgeInsets.only(top: 5),child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12), - // padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: Colors.white, - // 边框 - border: Border.all( - color: Colors.grey.shade300, - width: 1.0, - style: BorderStyle.solid, - ), - borderRadius: BorderRadius.circular(5), - ), + Card( + margin: EdgeInsets.only(bottom: 12), + color: Colors.white, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(16), child: Column( children: [ - const SizedBox(height: 10), - ItemListWidget.selectableLineTitleTextRightButton( - label: '选择部门:', - isEditable: true, - text: personData['departmentName'] ?? '请选择', - isRequired: true, - onTap: () async {}, + // 第一行:姓名和部门 + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + '姓名: ${personData.name??''}', + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + ), + ), + ), + // _buildInfoItem('姓名:', person['姓名'] ?? '',), + SizedBox(width: 24), + Expanded( + child: Text( + '部门: ${personData.group??''}', + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + ), + ), + ), + + // _buildInfoItem('部门:', person['部门'] ?? ''), + ], ), - const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '选择人员:', - isEditable: true, - text: personData['postName'] ?? '请选择', - isRequired: true, - onTap: () async {}, - ), - const Divider(), - ItemListWidget.itemContainer(RepairedPhotoSection( - isRequired: true, - title: "人员照片", - maxCount: 1, - horizontalPadding: 0, - mediaType: MediaType.image, - isShowAI: false, - onChanged: (List files) {}, - onAiIdentify: () {}, - ),), - const SizedBox(height: 10) + // SizedBox(height: 8), + + // 第二行:删除 + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // CustomButton( + // height: 35, + // onPressed: () async { + // + // }, + // backgroundColor: Colors.red, + // textStyle: const TextStyle(color: Colors.black), + // buttonStyle:ButtonStyleType.primary, + // text: '删除') + // ], + // ), ], - ) - ),), + ), + ), + ), Positioned( top: 0, right: 2, @@ -236,12 +251,15 @@ class _DoorareaPersonApplyPageState extends State { onTap: () async {}, ), const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '选择时间:', - isEditable: true, - text: pd['departmentName'] ?? '请选择', - isRequired: true, - onTap: () async {}, + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '选择时间:', + isEditable: false, + text: pd['departmentName'] ?? '', + isRequired: true, + onTap: () async {}, + ), ), const Divider(), ItemListWidget.multiLineTitleTextField( @@ -266,11 +284,18 @@ class _DoorareaPersonApplyPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 20), height: 35, backgroundColor: Colors.blue, - onPressed: () { - setState(() { - _personList.add({}); - _addPersonWight({}); - }); + onPressed: () async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => PersonSelectionPage(_personList)), + ); + + if (result != null) { + // 处理返回的选中结果 + _showSelectedResult(context, result); + } + + }, ), const SizedBox(width: 10), @@ -453,6 +478,22 @@ class _DoorareaPersonApplyPageState extends State { // } // } + void _showSelectedResult(BuildContext context, SelectionPersonResult result) { + // String message = '选中了 ${result.selectedPersons.length} 人:\n'; + // result.groupedSelected.forEach((group, persons) { + // message += '组${group}: ${persons.map((p) => p.name).join(', ')}\n'; + // }); + + setState(() { + _personList.clear(); + for(int i=0;i { await pushPage(DoorareaPersonApplyPage(), context); break; case 3: - + await pushPage(FirstlevelPersonAddPage(), context); break; case 5: await pushPage(DoorareaCarAddPage(), context); break; case 7: - + await pushPage(FirstlevelCarAddPage(), context); break; } _fetchData(); @@ -326,26 +328,62 @@ class _DoorareaPersonRecordPageState extends State { ],), body: Column( children: [ - // Filter bar + // 搜索框区域(带筛选图标) Container( color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), - child: SearchBarWidget( - showResetButton: true, - hintText: '请输入关键字', - resetButtonText: '重置', - onReset: () { - FocusScope.of(context).unfocus(); - _searchController.text = ''; - _search(); - }, - onSearch: (text) { - FocusScope.of(context).unfocus(); - _search(); - }, - controller: _searchController, + child: Row( + children: [ + // 可点击的筛选图标 + GestureDetector( + onTap: () { + // 筛选逻辑 + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + children: [ + Text( + '筛选', + style: TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + const SizedBox(width: 2), + Icon( + Icons.expand_more, + size: 20, + color: Colors.black, + ), + + ], + ), + ), + ), + const SizedBox(width: 8), + // 搜索框 + Expanded( + child: SearchBarWidget( + showResetButton: true, + hintText: '请输入关键字', + resetButtonText: '重置', + onReset: () { + FocusScope.of(context).unfocus(); + _searchController.text = ''; + _search(); + }, + onSearch: (text) { + FocusScope.of(context).unfocus(); + _search(); + }, + controller: _searchController, + ), + ), + ], ), ), + const Divider(height: 1), // List Expanded(child: _buildListContent()), diff --git a/lib/pages/home/doorAndCar/person/firstlevel_person_add_page.dart b/lib/pages/home/doorAndCar/person/firstlevel_person_add_page.dart new file mode 100644 index 0000000..bbd9fce --- /dev/null +++ b/lib/pages/home/doorAndCar/person/firstlevel_person_add_page.dart @@ -0,0 +1,469 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.dart'; +import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/item_list_widget.dart'; +import 'package:qhd_prevention/customWidget/photo_picker_row.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/http/modules/basic_info_api.dart'; +import 'package:qhd_prevention/pages/home/doorAndCar/person_selection_page.dart'; +import 'package:qhd_prevention/pages/home/doorAndCar/sign_instructions_webView.dart'; + + +import 'package:qhd_prevention/pages/mine/mine_sign_page.dart'; +import 'package:qhd_prevention/pages/mine/webViewPage.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/services/SessionService.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:flutter/gestures.dart'; + +class FirstlevelPersonAddPage extends StatefulWidget { + const FirstlevelPersonAddPage({super.key}); + + @override + State createState() => + _FirstlevelPersonAddPageState(); +} + +class _FirstlevelPersonAddPageState extends State { + Map pd = {}; + bool _agreed = false; + + // 部门列表 + List _deptList = []; + List _personList = []; + List signImages = []; + + @override + void initState() { + super.initState(); + _getDept(); + } + + // 获取部门 + Future _getDept() async { + // try { + // final data = { + // 'eqCorpinfoId': widget.scanData['id'], + // // 'eqParentId': widget.scanData['corpinfoId'], + // }; + // final result = await BasicInfoApi.getDeptTree(data); + // if (result['success'] == true) { + // final list = result['data'] ?? []; + // if (list.length > 0) { + // setState(() { + // _deptList = list[0]['childrenList'] ?? []; + // }); + // } + // } + // } catch (e) {} + } + + // 提交 + Future _saveSuccess() async { + if (!FormUtils.hasValue(pd, 'corpinfoId')) { + ToastUtil.showNormal(context, '请选择部门'); + return; + } + if (!FormUtils.hasValue(pd, 'postName')) { + ToastUtil.showNormal(context, '请输入岗位'); + return; + } + // try { + // final result = await BasicInfoApi.userFirmEntry(pd); + // LoadingDialogHelper.hide(); + // if (result['success'] == true) { + // ToastUtil.showNormal(context, '申请成功'); + // _relogin(); + // } else { + // ToastUtil.showNormal(context, result['errMessage']); + // } + // } catch (e) { + // LoadingDialogHelper.hide(); + // ToastUtil.showNormal(context, '操作失败,请重试'); + // } + } + + Widget _addPersonWight(Person personData) { + return Stack( + children: [ + Card( + margin: EdgeInsets.only(bottom: 12), + color: Colors.white, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + // 第一行:姓名和部门 + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + '姓名: ${personData.name??''}', + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + ), + ), + ), + // _buildInfoItem('姓名:', person['姓名'] ?? '',), + SizedBox(width: 24), + Expanded( + child: Text( + '部门: ${personData.group??''}', + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + ), + ), + ), + + // _buildInfoItem('部门:', person['部门'] ?? ''), + ], + ), + // SizedBox(height: 8), + + // 第二行:删除 + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // CustomButton( + // height: 35, + // onPressed: () async { + // + // }, + // backgroundColor: Colors.red, + // textStyle: const TextStyle(color: Colors.black), + // buttonStyle:ButtonStyleType.primary, + // text: '删除') + // ], + // ), + ], + ), + ), + ), + Positioned( + top: 0, + right: 2, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.red, + ), + + child: GestureDetector( + child: const Icon(Icons.close, size: 14, color: Colors.white,), + onTap: () { + setState(() { + _personList.remove(personData); + }); + }, + ), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '人员申请'), + body: SafeArea( + child: ItemListWidget.itemContainer( + horizontal: 5, + vertical: 12, + ListView( + children: [ + ListItemFactory.createBuildSimpleSection('申请信息'), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择项目名称:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '审核人员:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: ItemListWidget.selectableLineTitleTextRightButton( + label: '选择时间:', + isEditable: false, + text: pd['departmentName'] ?? '', + isRequired: true, + onTap: () async {}, + ), + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '访问港区:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '选择范围:', + isEditable: true, + text: pd['departmentName'] ?? '请选择', + isRequired: true, + onTap: () async {}, + ), + // const Divider(), + // ItemListWidget.multiLineTitleTextField( + // label: '申请原因', + // isEditable: true, + // ), + + const Divider(), + if (_personList.isNotEmpty) + ...[ + ListItemFactory.createBuildSimpleSection('人员信息'), + // const Divider(), + for (var i = 0; i < _personList.length; i++) + _addPersonWight(_personList[i]), + ], + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: '添加人员', + padding: const EdgeInsets.symmetric(horizontal: 20), + height: 35, + backgroundColor: Colors.blue, + onPressed: () async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => PersonSelectionPage(_personList)), + ); + + if (result != null) { + // 处理返回的选中结果 + _showSelectedResult(context, result); + } + + + }, + ), + const SizedBox(width: 10), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + value: _agreed, + activeColor: Colors.blue, + checkColor: Colors.white, + side: const BorderSide(color: Colors.grey), + onChanged: (value) { + setState(() { + _agreed = value ?? false; + }); + }, + ), + Flexible( + child: RichText( + text: TextSpan( + children: [ + const TextSpan( + text: '我已阅读并同意', + style: TextStyle(color: Colors.black, fontSize: 12), + ), + TextSpan( + text: '《安全进港须知》', + style: const TextStyle( + color: Colors.blue, + fontSize: 12, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () async { + final path = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => SignInstructionsWebview( + name: "安全进港须知", + url: + 'http://47.92.102.56:7811/file/xieyi/zsyhxy.htm', + )), + ); + await NativeOrientation.setPortrait(); + if (path != null) { + setState(() { + setState(() { + _agreed = true; + signImages = []; + signImages.add(path); + }); + }); + } + + }, + ), + ], + ), + ), + ), + ], + ), + + + // Container( + // padding: EdgeInsets.symmetric(horizontal: 12), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ListItemFactory.headerTitle('签字:', isRequired: true), + // CustomButton( + // text: signImages.isNotEmpty ? '重新签字' : '手写签字', + // height: 36, + // backgroundColor: Colors.blue, + // onPressed: _sign, + // ), + // ], + // ), + // ), + if (signImages.isNotEmpty) _signListWidget(), + + SizedBox(height: 10,), + + CustomButton( + text: '提交申请', + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + height: 40, + backgroundColor: Colors.blue, + onPressed: () { + _saveSuccess(); + }, + ), + ], + ), + ), + ), + ); + } + + + + Widget _signListWidget() { + return Column( + children: + signImages.map((path) { + return Column( + children: [ + const SizedBox(height: 15), + // const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + signImages.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], + ), + ], + ), + ], + ); + }).toList(), + ); + } + + /// 签字 + // Future _sign() async { + // final name = SessionService.instance.name ?? ''; + // await NativeOrientation.setLandscape(); + // final path = await Navigator.push( + // context, + // MaterialPageRoute(builder: (context) => MineSignPage(personName: name,)), + // ); + // await NativeOrientation.setPortrait(); + // if (path != null) { + // setState(() { + // signImages = []; + // signImages.add(path); + // }); + // } + // } + + void _showSelectedResult(BuildContext context, SelectionPersonResult result) { + // String message = '选中了 ${result.selectedPersons.length} 人:\n'; + // result.groupedSelected.forEach((group, persons) { + // message += '组${group}: ${persons.map((p) => p.name).join(', ')}\n'; + // }); + + setState(() { + _personList.clear(); + for(int i=0;i selectedPersons; + final Map> groupedSelected; + + SelectionPersonResult({ + required this.selectedPersons, + required this.groupedSelected, + }); +} + +class PersonSelectionPage extends StatefulWidget { + const PersonSelectionPage(this.oldList, {super.key}); + + final List oldList; + + @override + State createState() => _PersonSelectionPageState(); +} + +class _PersonSelectionPageState extends State { + // 字母表(将#放在最后) + final List alphabet = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#' + ]; + + // 人员数据 + late final Map> groupedPersons; + // 存储排序后的分组键列表 + late final List sortedGroupKeys; + // 存储每个分组的GlobalKey + final Map groupKeys = {}; + + final List> list = [ + { + 'id': 'p1', + 'name': '赵', + }, + { + 'id': 'p2', + 'name': '钱', + }, + { + 'id': 'p3', + 'name': '算', + }, + { + 'id': 'p4', + 'name': '里', + }, + { + 'id': 'p5', + 'name': '周', + }, + { + 'id': 'p6', + 'name': '吴', + }, + { + 'id': 'p7', + 'name': '啊', + }, + { + 'id': 'p8', + 'name': '到', + }, + { + 'id': 'p9', + 'name': '噢', + }, + { + 'id': 'p10', + 'name': '饿', + }, + { + 'id': 'p11', + 'name': '不', + }, + { + 'id': 'p12', + 'name': '吃', + }, + { + 'id': 'p13', + 'name': '123', // 测试数字 + }, + { + 'id': 'p14', + 'name': '@@@', // 测试特殊字符 + }, + ]; + + // 选中状态 + final Map selectedStates = {}; + + // 分组选中状态 + final Map groupSelectedStates = {}; + + // 列表控制器 + final ScrollController _scrollController = ScrollController(); + final TextEditingController _searchController = TextEditingController(); + + // 存储每个分组在列表中的位置索引 + late final Map _groupIndexMap = {}; + + // 创建oldList的ID集合,用于快速查找 + late final Set _oldSelectedIds; + + @override + void initState() { + super.initState(); + // 创建oldList的ID集合 + _oldSelectedIds = widget.oldList.map((person) => person.id).toSet(); + _initData(); + } + + void _initData() { + // 根据姓名首字母分组 + groupedPersons = {}; + + for (var item in list) { + String name = item['name']; + String id = item['id']; + + // 使用lpinyin获取姓名首字母 + String firstLetter = _getFirstLetter(name); + + // 创建Person对象 + Person person = Person( + id: id, + name: name, + group: firstLetter, + ); + + // 添加到对应分组 + if (!groupedPersons.containsKey(firstLetter)) { + groupedPersons[firstLetter] = []; + } + groupedPersons[firstLetter]!.add(person); + } + + // 对每个分组内的人员按姓名排序 + groupedPersons.forEach((key, value) { + value.sort((a, b) => a.name.compareTo(b.name)); + }); + + // 对分组键进行排序(确保#在最后) + sortedGroupKeys = groupedPersons.keys.toList()..sort((a, b) { + // 如果a是#,a应该在后面 + if (a == '#') return 1; + // 如果b是#,b应该在后面 + if (b == '#') return -1; + // 其他情况正常排序 + return a.compareTo(b); + }); + + // 构建分组索引映射 + for (int i = 0; i < sortedGroupKeys.length; i++) { + _groupIndexMap[sortedGroupKeys[i]] = i; + } + + // 初始化选中状态和为每个分组创建GlobalKey + for (var group in groupedPersons.keys) { + for (var person in groupedPersons[group]!) { + // 检查当前人员是否在oldList中,如果在则设置为true + selectedStates[person.id] = _oldSelectedIds.contains(person.id); + } + // 初始化分组选中状态 + groupSelectedStates[group] = false; + groupKeys[group] = GlobalKey(); + } + + // 更新每个分组的全选状态 + for (var group in groupedPersons.keys) { + _updateGroupSelection(group); + } + } + + // 使用lpinyin获取姓名的首字母 + String _getFirstLetter(String name) { + if (name.isEmpty) return '#'; + + try { + // 检查第一个字符是否是英文字母 + String firstChar = name.substring(0, 1); + RegExp englishLetter = RegExp(r'^[A-Za-z]$'); + if (englishLetter.hasMatch(firstChar)) { + return firstChar.toUpperCase(); + } + + // 检查第一个字符是否是数字或特殊字符 + RegExp nonChinese = RegExp(r'^[0-9!@#\$%^&*()_+\-=\[\]{};:"\\|,.<>\/?~`]'); + if (nonChinese.hasMatch(firstChar)) { + return '#'; + } + + // 获取拼音 - 使用WITHOUT_TONE格式获取完整拼音 + String pinyin = PinyinHelper.getPinyin(name, + separator: ' ', format: PinyinFormat.WITHOUT_TONE); + + if (pinyin.isNotEmpty) { + // 获取第一个字的拼音首字母 + List pinyinParts = pinyin.split(' '); + if (pinyinParts.isNotEmpty) { + String firstCharPinyin = pinyinParts[0]; + if (firstCharPinyin.isNotEmpty) { + String firstLetter = firstCharPinyin.substring(0, 1).toUpperCase(); + + // 检查是否是英文字母 + if (englishLetter.hasMatch(firstLetter)) { + return firstLetter; + } + } + } + } + } catch (e) { + // 异常处理,返回# + print('拼音转换错误: $e'); + } + + // 如果无法识别,返回# + return '#'; + } + + // 滚动到指定分组 + void _scrollToGroup(String letter) { + if (!groupedPersons.containsKey(letter)) return; + + // 方法1:使用索引跳转(更精确) + int? index = _groupIndexMap[letter]; + if (index != null && _scrollController.hasClients) { + // 计算大概的滚动位置(每个分组标题高度 + 每个人item高度) + double position = 0; + for (int i = 0; i < index; i++) { + String group = sortedGroupKeys[i]; + // 分组标题高度56,每个人item高度56 + position += 56; // 分组标题高度 + position += (groupedPersons[group]?.length ?? 0) * 56; // 人员列表高度 + } + + _scrollController.animateTo( + position, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + return; + } + + // 方法2:使用GlobalKey(备选方案) + final key = groupKeys[letter]; + if (key?.currentContext != null) { + Scrollable.ensureVisible( + key!.currentContext!, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + alignment: 0, // 滚动到顶部 + ); + } + } + + // 更新分组的全选状态 + void _updateGroupSelection(String group) { + final groupPersons = groupedPersons[group]!; + final allSelected = groupPersons.every((person) => selectedStates[person.id] == true); + final anySelected = groupPersons.any((person) => selectedStates[person.id] == true); + + setState(() { + groupSelectedStates[group] = allSelected; + }); + } + + // 切换分组的全选/全不选 + void _toggleGroupSelection(String group) { + final newState = !(groupSelectedStates[group] ?? false); + final groupPersons = groupedPersons[group]!; + + setState(() { + groupSelectedStates[group] = newState; + for (var person in groupPersons) { + selectedStates[person.id] = newState; + } + }); + } + + // 获取选中的人员 + SelectionPersonResult _getSelectedResult() { + List selectedPersons = []; + Map> groupedSelected = {}; + + for (var group in groupedPersons.keys) { + final groupPersons = groupedPersons[group]!; + final selectedInGroup = groupPersons + .where((person) => selectedStates[person.id] == true) + .toList(); + + if (selectedInGroup.isNotEmpty) { + groupedSelected[group] = selectedInGroup; + selectedPersons.addAll(selectedInGroup); + } + } + + return SelectionPersonResult( + selectedPersons: selectedPersons, + groupedSelected: groupedSelected, + ); + } + + // 确认添加并返回 + void _confirmSelection() { + final result = _getSelectedResult(); + Navigator.pop(context, result); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: MyAppbar(title: '选择人员', actions: [ + + TextButton( + onPressed: () { + _confirmSelection(); + }, + child: Text( + "确认添加", + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + + ],), + body: Column( + children: [ + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: SearchBarWidget( + showResetButton: true, + hintText: '请输入关键字', + resetButtonText: '重置', + onReset: () { + FocusScope.of(context).unfocus(); + _searchController.text = ''; + _search(); + }, + onSearch: (text) { + FocusScope.of(context).unfocus(); + _search(); + }, + controller: _searchController, + ), + ), + + + // 人员列表 + Expanded( + child: Row( + children: [ + // 左侧人员列表 + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: sortedGroupKeys.length, + itemBuilder: (context, index) { + String group = sortedGroupKeys[index]; + List persons = groupedPersons[group]!; + + return _buildGroupSection(group, persons); + }, + ), + ), + + // 右侧字母导航 + Container( + width: 30, + color: Colors.grey.shade50, + child: ListView.builder( + itemCount: alphabet.length, + itemBuilder: (context, index) { + final letter = alphabet[index]; + final hasGroup = groupedPersons.containsKey(letter); + + return GestureDetector( + onTap: hasGroup ? () => _scrollToGroup(letter) : null, + child: Container( + height: 30, + alignment: Alignment.center, + child: Text( + letter, + style: TextStyle( + fontSize: 12, + color: hasGroup + ? Colors.blue + : Colors.grey.shade400, + fontWeight: hasGroup + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ); + } + + // 构建分组区域 + Widget _buildGroupSection(String group, List persons) { + return Container( + key: groupKeys[group], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 分组标题 + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + color: Colors.grey.shade100, + height: 56, // 固定高度便于计算 + child: Row( + children: [ + Text( + group, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + ], + ), + ), + // 人员列表 + ...persons.map((person) => _buildPersonItem(person)), + ], + ), + ); + } + + // 构建人员项 + Widget _buildPersonItem(Person person) { + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.grey.shade200, + width: 0.5, + ), + ), + ), + height: 56, // 固定高度便于计算 + child: CheckboxListTile( + title: Text( + person.name, + style: const TextStyle(fontSize: 14), + ), + value: selectedStates[person.id] ?? false, + onChanged: (bool? value) { + setState(() { + selectedStates[person.id] = value ?? false; + _updateGroupSelection(person.group); + }); + }, + activeColor: Colors.blue, + controlAffinity: ListTileControlAffinity.leading, + contentPadding: const EdgeInsets.symmetric(horizontal: 0), + ), + ); + } + + void _search() { + // searchKeywords = _searchController.text.trim(); + // currentPage = 1; + // list.clear(); + // _fetchData(); + } +} \ No newline at end of file