Compare commits

...

2 Commits

Author SHA1 Message Date
hs b5c31a5afe Merge remote-tracking branch 'origin/main' 2025-09-16 08:28:15 +08:00
hs a4072fd761 改bug 2025-09-16 08:28:08 +08:00
14 changed files with 361 additions and 356 deletions

View File

@ -24,9 +24,9 @@ class ApiService {
///
// static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb";
// static const String basePath = "http://192.168.20.240:8500/integrated_whb";//
// static const String basePath = "http://192.168.20.240:8500/integrated_whb";
static const String basePath = "http://192.168.20.240:8500/integrated_whb";
// static const String basePath = "http://192.168.0.25:28199";//
static const String basePath = "http://192.168.0.45:28199";//
// static const String basePath = "http://192.168.0.45:28199";//
///
static const String baseImgPath = "https://file.zcloudchina.com/YTHFile";

View File

@ -137,70 +137,48 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
List<Map<String, dynamic>> buildCoversFromRes(Map res) {
final list = <Map<String, dynamic>>[];
const String defaultIconAsset = 'assets/map/50.png';
final seen = <String>{}; // "lat|lng|type"
void tryAdd(double? lat, double? lng, String type, [dynamic data]) {
if (lat == null || lng == null) return;
final key = '${lat.toStringAsFixed(6)}|${lng.toStringAsFixed(6)}|$type';
if (seen.contains(key)) return;
seen.add(key);
list.add({
'latitude': lat,
'longitude': lng,
'icon': defaultIconAsset,
'data': {'type': type, 'item': data}
});
}
final cinfo = res['cinfo'];
if (cinfo != null &&
cinfo['LONGITUDE'] != null &&
cinfo['LATITUDE'] != null &&
cinfo['LONGITUDE'].toString().isNotEmpty &&
cinfo['LATITUDE'].toString().isNotEmpty) {
final lat = double.tryParse(cinfo['LATITUDE'].toString());
final lng = double.tryParse(cinfo['LONGITUDE'].toString());
if (lat != null && lng != null) {
list.add({
'latitude': lat,
'longitude': lng,
'icon': defaultIconAsset,
'data': {'type': 'cinfo'}
});
}
if (cinfo != null) {
final lat = double.tryParse(cinfo['LATITUDE']?.toString() ?? '');
final lng = double.tryParse(cinfo['LONGITUDE']?.toString() ?? '');
tryAdd(lat, lng, 'cinfo', cinfo);
}
final check = res['checkrecord'];
if (check != null &&
check['LONGITUDE'] != null &&
check['LATITUDE'] != null &&
check['LONGITUDE'].toString().isNotEmpty &&
check['LATITUDE'].toString().isNotEmpty) {
final lat = double.tryParse(check['LATITUDE'].toString());
final lng = double.tryParse(check['LONGITUDE'].toString());
if (lat != null && lng != null) {
list.add({
'latitude': lat,
'longitude': lng,
'icon': defaultIconAsset,
'data': {'type': 'checkrecord'}
});
}
}
final vlist = (res['varList'] as List?) ?? [];
for (final item in vlist) {
try {
if (item != null &&
item['LONGITUDE'] != null &&
item['LATITUDE'] != null &&
item['LONGITUDE'].toString().isNotEmpty &&
item['LATITUDE'].toString().isNotEmpty) {
final lat = double.tryParse(item['LATITUDE'].toString());
final lng = double.tryParse(item['LONGITUDE'].toString());
if (lat != null && lng != null) {
list.add({
'latitude': lat,
'longitude': lng,
'icon': defaultIconAsset,
'data': {'type': 'varList', 'item': item}
});
}
}
} catch (_) {
//
}
}
// final check = res['checkrecord'];
// if (check != null) {
// final lat = double.tryParse(check['LATITUDE']?.toString() ?? '');
// final lng = double.tryParse(check['LONGITUDE']?.toString() ?? '');
// tryAdd(lat, lng, 'checkrecord', check);
// }
//
// final vlist = (res['varList'] as List?) ?? [];
// for (final item in vlist) {
// try {
// final lat = double.tryParse(item['LATITUDE']?.toString() ?? '');
// final lng = double.tryParse(item['LONGITUDE']?.toString() ?? '');
// tryAdd(lat, lng, 'varList', item);
// } catch (_) {}
// }
return list;
}
// map
void _onBmfMapCreated(BMFMapController controller) async {
_mapController = controller;
@ -255,6 +233,18 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
}
Future<void> _updateMarkers() async {
bool cleared = false;
try {
final res = await _mapController!.cleanAllMarkers();
debugPrint('_updateMarkers: cleanAllMarkers returned: $res');
cleared = (res == true);
} catch (e) {
debugPrint('_updateMarkers: cleanAllMarkers exception: $e');
}
if (!cleared) {
debugPrint('_updateMarkers: warning - markers not cleared. proceeding but will guard duplicates.');
}
if (_mapController == null) {
debugPrint('_updateMarkers: mapController is null');
return;

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
@ -49,6 +50,7 @@ class SignImageData {
@override
String toString() => 'SignImageData(key:$key, filePath:$filePath, SIGNER_TIME:$SIGNER_TIME)';
}
class DangerousOptionsPage extends StatefulWidget {
final int index;
final int status;
@ -86,17 +88,21 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
status = widget.status;
measures = widget.measures;
imgList = List<ImageData>.from(widget.imgList);
signImgList =
widget.signImgList.map((map) => SignImageData.fromJson(map)).toList();
signImgList = widget.signImgList.map((map) => SignImageData.fromJson(map)).toList();
}
///
///
Future<void> _onImageAdded(String localPath) async {
//
if (!mounted) return;
LoadingDialogHelper.show();
final res = await ApiService.uploadSaveFile(localPath);
LoadingDialogHelper.hide();
try {
//
final res = await ApiService.uploadSaveFile(localPath).timeout(const Duration(seconds: 30));
LoadingDialogHelper.hide();
if (!mounted) return;
if (res['result'] == 'success') {
final url = res['FILE_PATH'] as String;
setState(() {
@ -104,25 +110,41 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
});
} else {
ToastUtil.showError(context, '上传失败,资源过大请重新选择');
//
setState(() {
imgList = [];
MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey));
});
MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey));
}
} catch (_) {
} on TimeoutException {
LoadingDialogHelper.hide();
if (!mounted) return;
ToastUtil.showError(context, '上传超时,请检查网络后重试');
} catch (e, st) {
LoadingDialogHelper.hide();
debugPrint('_onImageAdded error: $e\n$st');
if (!mounted) return;
ToastUtil.showError(context, '上传失败,资源过大请重新选择');
setState(() {
imgList = [];
MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey));
});
MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey));
}
}
///
Future<void> _onImageRemoved(ImageData item) async {
if (item.serverPath != null) {
await ApiService.deleteSaveFile(item.serverPath!);
try {
if (item.serverPath != null && item.serverPath.isNotEmpty) {
//
await ApiService.deleteSaveFile(item.serverPath!).timeout(const Duration(seconds: 15));
}
} catch (e, st) {
debugPrint('_onImageRemoved: delete api error: $e\n$st');
// 使 UI
}
if (!mounted) return;
setState(() {
imgList.remove(item);
});
@ -134,53 +156,70 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
return;
}
LoadingDialogHelper.show();
List<String> filePaths =
signImgList.map((img) => img.filePath ?? '').toList();
final result = await ApiService.saveDangerousOptionsFile(filePaths);
final List<dynamic> signList = result['FILE_PATH_LIST'];
List<Map<String, dynamic>> sineImageList = [];
for (SignImageData data in signImgList) {
for (Map<String, dynamic> img in signList) {
String imgName = 'file${data.key}';
if (data.filePath!.contains('uploadFiles')) {
final idata = {
'filePath': data.filePath,
'SIGNER_TIME': data.SIGNER_TIME,
'key': data.key,
};
sineImageList.add(idata);
}
if (imgName == img['key']) {
final idata = {
'filePath': img['filePath'] ?? '',
'SIGNER_TIME': data.SIGNER_TIME,
'key': data.key,
};
sineImageList.add(idata);
try {
//
List<String> filePaths = signImgList.map((img) => img.filePath ?? '').toList();
// API
final result = await ApiService.saveDangerousOptionsFile(filePaths).timeout(const Duration(seconds: 30));
final List<dynamic> signList = result['FILE_PATH_LIST'] ?? [];
List<Map<String, dynamic>> sineImageList = [];
for (SignImageData data in signImgList) {
for (Map<String, dynamic> img in signList) {
String imgName = 'file${data.key}';
if (data.filePath != null && data.filePath!.contains('uploadFiles')) {
final idata = {
'filePath': data.filePath,
'SIGNER_TIME': data.SIGNER_TIME,
'key': data.key,
};
sineImageList.add(idata);
}
if (imgName == (img['key'] ?? '')) {
final idata = {
'filePath': img['filePath'] ?? '',
'SIGNER_TIME': data.SIGNER_TIME,
'key': data.key,
};
sineImageList.add(idata);
}
}
}
if (!mounted) return;
setState(() => buttonLoading = true); //
LoadingDialogHelper.hide();
if (!mounted) return;
Navigator.pop(context, {
'imgList': imgList.map((e) => {'local': e.localPath, 'remote': e.serverPath}).toList(),
'signImgList': sineImageList,
'index': index,
'status': status,
});
} on TimeoutException {
LoadingDialogHelper.hide();
if (!mounted) return;
ToastUtil.showError(context, '保存超时,请稍后重试');
} catch (e, st) {
LoadingDialogHelper.hide();
debugPrint('_submit error: $e\n$st');
if (!mounted) return;
ToastUtil.showError(context, '提交失败,请重试');
}
setState(() => buttonLoading = true);
LoadingDialogHelper.hide();
Navigator.pop(context, {
'imgList':
imgList
.map((e) => {'local': e.localPath, 'remote': e.serverPath})
.toList(),
'signImgList': sineImageList,
'index': index,
'status': status,
});
}
Future<void> _sign() async {
await NativeOrientation.setLandscape();
final String path = await Navigator.push(
final String? path = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (c) => MineSignPage()),
);
await NativeOrientation.setPortrait();
if (path != null) {
if (!mounted) return;
if (path != null && path.isNotEmpty) {
final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
setState(() {
final imageData = SignImageData(
@ -191,63 +230,121 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
signImgList.add(imageData);
signTimes.add(now);
});
//FocusHelper.clearFocus(context);
}
}
Widget _signListWidget() {
// 使 ListView Column 线使 ResizeImage
return Column(
children:
signImgList.map((imgData) {
final idx = signImgList.indexOf(imgData);
return Column(
children: signImgList.map((imgData) {
final idx = signImgList.indexOf(imgData);
final rawPath = (imgData.filePath ?? '').toString();
final isNetwork = rawPath.startsWith('http://') || rawPath.startsWith('https://');
// UI
const targetWidth = 460; // * devicePixelRatio
const targetHeight = 300;
Widget imageWidget;
if (rawPath.isEmpty) {
imageWidget = Container(
width: 230,
height: 150,
color: Colors.grey.shade200,
child: const Center(child: Icon(Icons.broken_image, size: 28, color: Colors.grey)),
);
} else if (isNetwork) {
// width/height loadingBuilder
imageWidget = Image.network(
rawPath.startsWith('http') ? rawPath : ApiService.baseImgPath + rawPath,
width: 230,
height: 150,
fit: BoxFit.cover,
// loading
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: 230,
height: 150,
color: Colors.grey.shade200,
child: const Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
},
errorBuilder: (_, __, ___) => Container(
width: 230,
height: 150,
color: Colors.grey.shade200,
child: const Center(child: Icon(Icons.broken_image)),
),
);
} else {
// 使 ResizeImage FileImage线
final file = File(rawPath);
if (file.existsSync()) {
imageWidget = Image(
image: ResizeImage(
FileImage(file),
// pixel ratio
width: targetWidth,
height: targetHeight,
),
width: 230,
height: 150,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => Container(
width: 230,
height: 150,
color: Colors.grey.shade200,
child: const Center(child: Icon(Icons.broken_image)),
),
);
} else {
imageWidget = Container(
width: 230,
height: 150,
color: Colors.grey.shade200,
child: const Center(child: Icon(Icons.broken_image)),
);
}
}
return Column(
children: [
const SizedBox(height: 10),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(height: 10),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
GestureDetector(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 230,
maxHeight: 150,
),
child: imageWidget,
),
onTap: () => presentOpaque(SingleImageViewer(imageUrl: rawPath), context),
),
Column(
children: [
GestureDetector(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 230,
maxHeight: 150,
),
child:
(imgData.filePath ?? '').contains('uploadFiles')
? Image.network(
'${ApiService.baseImgPath}${imgData.filePath}',
)
: Image.file(
File(imgData.filePath ?? ''),
fit: BoxFit.contain,
),
),
onTap:
() => presentOpaque(
SingleImageViewer(imageUrl: imgData.filePath ?? ''),
context,
),
),
Column(
children: [
CustomButton(
text: '删除',
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 10),
backgroundColor: Colors.red,
onPressed: () {
setState(() => signImgList.removeAt(idx));
},
),
const SizedBox(height: 80),
],
CustomButton(
text: '删除',
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 10),
backgroundColor: Colors.red,
onPressed: () {
if (!mounted) return;
setState(() => signImgList.removeAt(idx));
},
),
const SizedBox(height: 80),
],
),
],
);
}).toList(),
),
],
);
}).toList(),
);
}
@ -258,19 +355,19 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
color: Colors.white,
child: ListView(
children: [
Table(
border: TableBorder.all(color: Colors.grey.shade300),
columnWidths: {0: FlexColumnWidth(3), 1: FlexColumnWidth(2)},
columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(2)},
children: [
TableRow(
decoration: BoxDecoration(color: Colors.grey.shade200),
children: [
Padding(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
'主要安全措施',
@ -279,7 +376,7 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
),
),
Padding(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
'操作',
@ -305,12 +402,12 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
RadioListTile<int>(
value: -1,
groupValue: status,
title: Text('不涉及'),
title: const Text('不涉及'),
contentPadding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 8.0,
),
visualDensity: VisualDensity(
visualDensity: const VisualDensity(
vertical: -4,
horizontal: 0,
),
@ -319,12 +416,12 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
RadioListTile<int>(
value: 1,
groupValue: status,
title: Text('涉及'),
title: const Text('涉及'),
contentPadding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 8.0,
),
visualDensity: VisualDensity(
visualDensity: const VisualDensity(
vertical: -4,
horizontal: 0,
),
@ -343,15 +440,17 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
maxCount: 2,
mediaType: MediaType.image,
initialMediaPaths:
imgList
.map((e) => '${ApiService.baseImgPath}${e.serverPath}')
.toList(),
imgList.map((e) => '${ApiService.baseImgPath}${e.serverPath}').toList(),
onChanged: (paths) {},
onMediaAdded: _onImageAdded,
onMediaRemoved: (path) {
print(path);
final item = imgList.firstWhere((e) => path.contains(e.localPath) );
_onImageRemoved(item);
// localPath
try {
final item = imgList.firstWhere((e) => path.contains(e.localPath));
_onImageRemoved(item);
} catch (e) {
debugPrint('onMediaRemoved: find item error: $e');
}
},
onAiIdentify: () {},
),
@ -359,7 +458,7 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('签字:', style: TextStyle(fontSize: 16)),
const Text('签字:', style: TextStyle(fontSize: 16)),
CustomButton(
text: '新增手写签字',
height: 36,

View File

@ -114,7 +114,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
_getHotWorkNameList();
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
@ -333,15 +333,6 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
Widget _card(Widget child) {

View File

@ -103,7 +103,7 @@ class _CutroadApplyDetailState extends State<CutroadApplyDetail> {
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
pd['WORK_REASON'] = _contentController.text.trim();
@ -504,14 +504,7 @@ class _CutroadApplyDetailState extends State<CutroadApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------

View File

@ -109,7 +109,7 @@ class _BreakgroundApplyDetailState extends State<BreakgroundApplyDetail> {
}
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
pd['JOB_CONTENT'] = _contentController.text.trim();
@ -275,14 +275,7 @@ class _BreakgroundApplyDetailState extends State<BreakgroundApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
Future<void> _chooseLevel() async {

View File

@ -105,7 +105,7 @@ class _HoistworkApplyDetailState extends State<HoistworkApplyDetail> {
}
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
pd['WORK_CONTENT'] = _contentController.text.trim();
@ -248,14 +248,7 @@ class _HoistworkApplyDetailState extends State<HoistworkApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
void set_pd_DEPARTMENT_ID(EditUserType type, String id) {

View File

@ -104,7 +104,7 @@ class _HighworkApplyDetailState extends State<HighworkApplyDetail> {
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
pd['WORK_CONTENT'] = _contentController.text.trim();
@ -245,14 +245,7 @@ class _HighworkApplyDetailState extends State<HighworkApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
void set_pd_DEPARTMENT_ID(EditUserType type, String id) {

View File

@ -111,7 +111,7 @@ class _ElectricityApplyDetailState extends State<ElectricityApplyDetail> {
}
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
pd['WORK_CONTENT'] = _contentController.text.trim();
@ -244,14 +244,7 @@ class _ElectricityApplyDetailState extends State<ElectricityApplyDetail> {
unitAllList = result['varList'] ?? [];
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
void set_pd_DEPARTMENT_ID(EditUserType type, String id) {
pd['${type.name}_DEPARTMENT_ID'] = id;

View File

@ -180,7 +180,7 @@ class _BlindboardCjryDetailState extends State<BlindboardCjryDetail> {
// formData['WORK_CONTENT'] = _contentController.text.trim();
formData['CONIMG_PATH'] = serverPathString;
formData['DESCR'] = FormUtils.hasValue(pd, 'DESCR') ? pd['DESCR'] : '';
formData['DESCR'] = '';
formData['BLINDBOARD_ID'] = pd['BLINDBOARD_ID'] ?? widget.BLINDBOARD_ID;
formData['SIGNTIME'] = signTimes.join(',');
formData['USER_ID'] = SessionService.instance.loginUserId;

View File

@ -110,7 +110,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
_getVideoList();
_getUnitListAll();
_getPlsList();
_nameController.addListener(() {
setState(() {
pd['NAME'] = _nameController.text.trim();
@ -234,14 +234,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
unitAllList = result['varList'] ?? [];
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
void set_pd_DEPARTMENT_ID(EditUserType type, String id) {
pd['${type.name}_DEPARTMENT_ID'] = id;

View File

@ -112,7 +112,7 @@ class _SpaceworkApplyDetailState extends State<SpaceworkApplyDetail> {
_getSpaceWorkNameList();
_getVideoList();
_getUnitListAll();
_getPlsList();
_contentController.addListener(() {
setState(() {
pd['WORK_CONTENT'] = _contentController.text.trim();
@ -245,14 +245,7 @@ class _SpaceworkApplyDetailState extends State<SpaceworkApplyDetail> {
});
}
///
Future<void> _getPlsList() async {
final result = await ApiService.getWorkAreaList();
setState(() {
final String zTreeNodes = result['zTreeNodes'] ?? '';
workAreaList = jsonDecode(zTreeNodes);
});
}
/// ------------------------------------------------------------
void set_pd_DEPARTMENT_ID(EditUserType type, String id) {

View File

@ -24,20 +24,18 @@ class Category {
final name = (json['name'] ?? '').toString();
final positions = (json['POSITIONS'] ?? '').toString();
// children null List
// children 使
final rawChildren = json['children'];
List<Category> childrenList = [];
if (rawChildren is List) {
childrenList = rawChildren
.where((e) => e != null)
.map((e) {
// e Map<String,dynamic> dynamic
if (e is Map<String, dynamic>) {
return Category.fromJson(e);
} else if (e is Map) {
return Category.fromJson(Map<String, dynamic>.from(e));
} else {
//
return null;
}
})
@ -54,9 +52,7 @@ class Category {
}
}
typedef DeptSelectCallback =
void Function(String id, String POSITIONS, String name);
typedef DeptSelectCallback = void Function(String id, String POSITIONS, String name);
class WorkAreaPicker extends StatefulWidget {
final DeptSelectCallback onSelected;
@ -72,8 +68,6 @@ class _WorkAreaPickerState extends State<WorkAreaPicker> {
String selectedName = '';
String selected_POSITIONS = '';
Set<String> expandedSet = {};
List<Category> original = [];
List<Category> filtered = [];
bool loading = true;
@ -83,11 +77,9 @@ class _WorkAreaPickerState extends State<WorkAreaPicker> {
@override
void initState() {
super.initState();
//
selectedId = '';
selectedName = '';
selected_POSITIONS = '';
expandedSet = {};
_searchController.addListener(_onSearchChanged);
_loadData();
}
@ -102,36 +94,60 @@ class _WorkAreaPickerState extends State<WorkAreaPicker> {
Future<void> _loadData() async {
try {
final result = await ApiService.getWorkAreaList();
final dynamic nodesField = result['zTreeNodes'];
List<dynamic> raw = result['varList'] ?? [];
// nodesField List StringJSON
List<dynamic> raw;
if (nodesField is String) {
raw = json.decode(nodesField) as List<dynamic>;
} else if (nodesField is List) {
raw = nodesField;
} else {
raw = [];
// Category children
final parsed = raw
.map((e) {
if (e is Map<String, dynamic>) return Category.fromJson(e);
if (e is Map) return Category.fromJson(Map<String, dynamic>.from(e));
return null;
})
.whereType<Category>()
.toList();
//
final List<Category> flat = [];
for (final c in parsed) {
flat.add(c);
if (c.children.isNotEmpty) {
flat.addAll(c.children);
}
}
// debug
// print('raw length = ${raw.length}');
// id ID id
// selectedId = ''
final List<Category> normalized = [];
for (var i = 0; i < flat.length; i++) {
final c = flat[i];
final rawId = (c.ELECTRONIC_FENCE_AREA_ID ?? '').toString().trim();
if (rawId.isEmpty) {
// id name hash
final generatedId = '__generated_${i}_${c.name.hashCode}';
normalized.add(Category(
ELECTRONIC_FENCE_AREA_ID: generatedId,
name: c.name,
POSITIONS: c.POSITIONS,
children: const [],
));
} else {
// id
normalized.add(Category(
ELECTRONIC_FENCE_AREA_ID: rawId,
name: c.name,
POSITIONS: c.POSITIONS,
children: const [],
));
}
}
setState(() {
original = raw
.map((e) {
if (e is Map<String, dynamic>) return Category.fromJson(e);
if (e is Map) return Category.fromJson(Map<String, dynamic>.from(e));
return null;
})
.whereType<Category>()
.toList();
original = normalized;
filtered = original;
loading = false;
});
} catch (e, st) {
// 便
// print('loadData error: $e\n$st');
debugPrint('WorkAreaPicker._loadData error: $e\n$st');
setState(() => loading = false);
}
}
@ -143,83 +159,40 @@ class _WorkAreaPickerState extends State<WorkAreaPicker> {
});
}
// name POSITIONS
List<Category> _filterCategories(List<Category> list, String query) {
List<Category> result = [];
for (var cat in list) {
final children = _filterCategories(cat.children, query);
if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) {
result.add(
Category(
ELECTRONIC_FENCE_AREA_ID: cat.ELECTRONIC_FENCE_AREA_ID,
name: cat.name,
children: children,
),
);
}
}
return result;
if (query.isEmpty) return list;
final q = query.toLowerCase();
return list.where((c) {
final name = (c.name ?? '').toLowerCase();
final pos = (c.POSITIONS ?? '').toLowerCase();
return name.contains(q) || pos.contains(q);
}).toList();
}
Widget _buildRow(Category cat, int indent) {
final hasChildren = cat.children.isNotEmpty;
final isExpanded = expandedSet.contains(cat.ELECTRONIC_FENCE_AREA_ID);
final isSelected = cat.ELECTRONIC_FENCE_AREA_ID == selectedId;
return Column(
children: [
InkWell(
onTap: () {
setState(() {
if (hasChildren) {
isExpanded
? expandedSet.remove(cat.ELECTRONIC_FENCE_AREA_ID)
: expandedSet.add(cat.ELECTRONIC_FENCE_AREA_ID);
}
selectedId = cat.ELECTRONIC_FENCE_AREA_ID;
selectedName = cat.name;
selected_POSITIONS = cat.POSITIONS;
});
},
child: Container(
color: Colors.white,
child: Row(
children: [
SizedBox(width: 16.0 * indent),
SizedBox(
width: 24,
child:
hasChildren
? Icon(
isExpanded
? Icons.arrow_drop_down_rounded
: Icons.arrow_right_rounded,
size: 35,
color: Colors.grey[600],
)
: const SizedBox.shrink(),
),
const SizedBox(width: 5),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(cat.name),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Icon(
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: Colors.green,
),
),
],
Widget _buildRow(Category cat) {
final isSelected = selectedId == cat.ELECTRONIC_FENCE_AREA_ID;
return InkWell(
onTap: () {
setState(() {
selectedId = cat.ELECTRONIC_FENCE_AREA_ID;
selectedName = cat.name;
selected_POSITIONS = cat.POSITIONS;
});
},
child: Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
child: Row(
children: [
Expanded(child: Text(cat.name)),
Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
color: isSelected ? Colors.green : Colors.grey,
),
),
],
),
if (hasChildren && isExpanded)
...cat.children.map((c) => _buildRow(c, indent + 1)),
],
),
);
}
@ -267,18 +240,19 @@ class _WorkAreaPickerState extends State<WorkAreaPicker> {
],
),
),
Divider(),
const Divider(height: 1),
Expanded(
child:
loading
? const Center(child: CircularProgressIndicator())
: Container(
color: Colors.white,
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0),
),
),
child: loading
? const Center(child: CircularProgressIndicator())
: (filtered.isEmpty
? const Center(child: Text('没有找到匹配的工作区域'))
: Container(
color: Colors.white,
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (ctx, idx) => _buildRow(filtered[idx]),
),
)),
),
],
),

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.1.2+8
version: 2.1.2+9
environment:
sdk: ^3.7.0