2026.3.9 一级口门
parent
192d24cea5
commit
f4f2b3c566
|
|
@ -247,13 +247,17 @@ class _DoorareaCarAddPageState extends State<DoorareaCarAddPage> {
|
|||
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: '申请原因',
|
||||
|
|
|
|||
|
|
@ -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<FirstlevelCarAddPage> createState() => _FirstlevelCarAddPageState();
|
||||
}
|
||||
|
||||
class _FirstlevelCarAddPageState extends State<FirstlevelCarAddPage> {
|
||||
Map<String, dynamic> pd = {};
|
||||
bool _agreed = false;
|
||||
|
||||
// 部门列表
|
||||
List<dynamic> _deptList = [];
|
||||
List<Map> _personList = [];
|
||||
List<String> signImages = [];
|
||||
|
||||
late bool _isMyCompanyArea = false;
|
||||
late bool _isSelectCar = false;
|
||||
|
||||
|
||||
List<String> _vehicleImages = [];
|
||||
List<String> _vehicleLicenseImages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getDept();
|
||||
}
|
||||
|
||||
// 获取部门
|
||||
Future<void> _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<void> _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<File> 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<File> files) {
|
||||
// 上传图片 files
|
||||
_vehicleImages.clear();
|
||||
for(int i=0;i<files.length;i++){
|
||||
_vehicleImages.add(files[i].path);
|
||||
}
|
||||
},
|
||||
onAiIdentify: () {
|
||||
},
|
||||
),),
|
||||
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: RepairedPhotoSection(
|
||||
isRequired:true,
|
||||
title: "行驶证照片",
|
||||
maxCount: 2,
|
||||
mediaType: MediaType.image,
|
||||
isShowAI: false,
|
||||
onChanged: (List<File> files) {
|
||||
// 上传图片 files
|
||||
_vehicleLicenseImages.clear();
|
||||
for(int i=0;i<files.length;i++){
|
||||
_vehicleLicenseImages.add(files[i].path);
|
||||
}
|
||||
},
|
||||
onAiIdentify: () {
|
||||
},
|
||||
),),
|
||||
|
||||
|
||||
const SizedBox(height: 10),
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ class _DoorcarTabPageState extends State<DoorcarTabPage> {
|
|||
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<DoorcarTabPage> {
|
|||
},
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 5),
|
||||
child: Text('*申请封闭区域时,系统已自动包含进港口门权限,您无需再次申请进港口门权限。',
|
||||
style: const TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.bold,color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<DoorareaPersonApplyPage> {
|
|||
|
||||
// 部门列表
|
||||
List<dynamic> _deptList = [];
|
||||
List<Map> _personList = [];
|
||||
List<Person> _personList = [];
|
||||
List<String> signImages = [];
|
||||
|
||||
@override
|
||||
|
|
@ -89,55 +90,69 @@ class _DoorareaPersonApplyPageState extends State<DoorareaPersonApplyPage> {
|
|||
// }
|
||||
}
|
||||
|
||||
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<File> 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<DoorareaPersonApplyPage> {
|
|||
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<DoorareaPersonApplyPage> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
height: 35,
|
||||
backgroundColor: Colors.blue,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_personList.add({});
|
||||
_addPersonWight({});
|
||||
});
|
||||
onPressed: () async {
|
||||
final result = await Navigator.push<SelectionPersonResult>(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => PersonSelectionPage(_personList)),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
// 处理返回的选中结果
|
||||
_showSelectedResult(context, result);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
|
@ -453,6 +478,22 @@ class _DoorareaPersonApplyPageState extends State<DoorareaPersonApplyPage> {
|
|||
// }
|
||||
// }
|
||||
|
||||
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<result.selectedPersons.length;i++){
|
||||
_personList.add(result.selectedPersons[i]);
|
||||
_addPersonWight(result.selectedPersons[i]);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/car/doorArea_car_add_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/car/firstlevel_car_add_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/car/onlyLook_car_application.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/car/onlyLook_doorarea_car.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/person/doorArea_person_add_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/person/firstlevel_person_add_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/person/onlyLook_doorarea_person.dart';
|
||||
import 'package:qhd_prevention/pages/home/doorAndCar/person/onlyLook_person_application.dart';
|
||||
|
||||
|
|
@ -309,13 +311,13 @@ class _DoorareaPersonRecordPageState extends State<DoorareaPersonRecordPage> {
|
|||
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<DoorareaPersonRecordPage> {
|
|||
],),
|
||||
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()),
|
||||
|
|
|
|||
|
|
@ -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<FirstlevelPersonAddPage> createState() =>
|
||||
_FirstlevelPersonAddPageState();
|
||||
}
|
||||
|
||||
class _FirstlevelPersonAddPageState extends State<FirstlevelPersonAddPage> {
|
||||
Map<String, dynamic> pd = {};
|
||||
bool _agreed = false;
|
||||
|
||||
// 部门列表
|
||||
List<dynamic> _deptList = [];
|
||||
List<Person> _personList = [];
|
||||
List<String> signImages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getDept();
|
||||
}
|
||||
|
||||
// 获取部门
|
||||
Future<void> _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<void> _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<SelectionPersonResult>(
|
||||
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<void> _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<result.selectedPersons.length;i++){
|
||||
_personList.add(result.selectedPersons[i]);
|
||||
_addPersonWight(result.selectedPersons[i]);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
|
||||
// 人员模型类
|
||||
class Person {
|
||||
final String id;
|
||||
final String name;
|
||||
final String group;
|
||||
|
||||
Person({required this.id, required this.name, required this.group});
|
||||
}
|
||||
|
||||
// 选择结果类
|
||||
class SelectionPersonResult {
|
||||
final List<Person> selectedPersons;
|
||||
final Map<String, List<Person>> groupedSelected;
|
||||
|
||||
SelectionPersonResult({
|
||||
required this.selectedPersons,
|
||||
required this.groupedSelected,
|
||||
});
|
||||
}
|
||||
|
||||
class PersonSelectionPage extends StatefulWidget {
|
||||
const PersonSelectionPage(this.oldList, {super.key});
|
||||
|
||||
final List<Person> oldList;
|
||||
|
||||
@override
|
||||
State<PersonSelectionPage> createState() => _PersonSelectionPageState();
|
||||
}
|
||||
|
||||
class _PersonSelectionPageState extends State<PersonSelectionPage> {
|
||||
// 字母表(将#放在最后)
|
||||
final List<String> 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<String, List<Person>> groupedPersons;
|
||||
// 存储排序后的分组键列表
|
||||
late final List<String> sortedGroupKeys;
|
||||
// 存储每个分组的GlobalKey
|
||||
final Map<String, GlobalKey> groupKeys = {};
|
||||
|
||||
final List<Map<String, dynamic>> 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<String, bool> selectedStates = {};
|
||||
|
||||
// 分组选中状态
|
||||
final Map<String, bool> groupSelectedStates = {};
|
||||
|
||||
// 列表控制器
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
// 存储每个分组在列表中的位置索引
|
||||
late final Map<String, int> _groupIndexMap = {};
|
||||
|
||||
// 创建oldList的ID集合,用于快速查找
|
||||
late final Set<String> _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<String> 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<Person> selectedPersons = [];
|
||||
Map<String, List<Person>> 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<Person> 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<Person> 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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue