nfc巡检暂存

main
hs 2025-08-27 16:14:50 +08:00
parent 566ab85147
commit b5992b94ab
18 changed files with 3383 additions and 1568 deletions

View File

@ -491,9 +491,11 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -504,6 +506,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -682,9 +685,11 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -695,6 +700,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -710,9 +716,11 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -723,6 +731,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@ -1,8 +1,8 @@
import UIKit
import Flutter
import UIKit
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate {
@main
@objc class AppDelegate: FlutterAppDelegate {
//
static var orientationMask: UIInterfaceOrientationMask = .portrait
@ -74,4 +74,4 @@ import Flutter
-> UIInterfaceOrientationMask {
return AppDelegate.orientationMask
}
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDisplayName</key>
@ -25,11 +25,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NFCReaderUsageDescription</key>
<string>用于读取 NFC 标签</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<string>需要NFC权限来读取和写入标签</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
@ -65,10 +61,16 @@
<string>app需要发送通知提醒重要信息</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
@ -83,7 +85,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
</dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
</dict>
</plist>

View File

@ -4,7 +4,10 @@
<dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
</dict>
</plist>

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
///
/// /
class CustomButton extends StatelessWidget {
final String text; //
final Color backgroundColor; //
@ -11,6 +10,16 @@ class CustomButton extends StatelessWidget {
final double? height; //
final TextStyle? textStyle; //
/// true false
/// onPressed null
final bool enabled;
///
final Color? disabledBackgroundColor;
///
final Color? disabledTextColor;
const CustomButton({
super.key,
required this.text,
@ -21,27 +30,56 @@ class CustomButton extends StatelessWidget {
this.margin,
this.height,
this.textStyle,
this.enabled = true,
this.disabledBackgroundColor,
this.disabledTextColor,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
// enabled false onPressed null
final bool isEnabled = enabled && onPressed != null;
//
final Color bgColor = isEnabled
? backgroundColor
: (disabledBackgroundColor ?? Colors.grey.shade400);
TextStyle finalTextStyle;
if (textStyle != null) {
finalTextStyle = isEnabled
? textStyle!
: textStyle!.copyWith(
color: disabledTextColor ?? textStyle!.color?.withOpacity(0.8) ?? Colors.white70,
);
} else {
finalTextStyle = TextStyle(
color: isEnabled ? Colors.white : (disabledTextColor ?? Colors.white70),
fontSize: 15,
fontWeight: FontWeight.bold,
);
}
// +
return Opacity(
opacity: isEnabled ? 1.0 : 0.65,
child: AbsorbPointer(
absorbing: !isEnabled,
child: GestureDetector(
onTap: isEnabled ? onPressed : null,
child: Container(
height: height ?? 45, // 45
padding: padding ?? const EdgeInsets.all(8), //
margin: margin ?? const EdgeInsets.symmetric(horizontal: 5), //
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: backgroundColor,
color: bgColor,
),
child: Center(
child: Text(
text,
style: textStyle ?? const TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.bold,
style: finalTextStyle,
),
),
),
),

View File

@ -20,6 +20,8 @@ class MediaPickerRow extends StatefulWidget {
final ValueChanged<String>? onMediaRemoved;
final ValueChanged<String>? onMediaTapped; //
final bool isEdit; //
final bool isCamera; //
const MediaPickerRow({
Key? key,
@ -31,6 +33,7 @@ class MediaPickerRow extends StatefulWidget {
this.onMediaRemoved,
this.onMediaTapped, //
this.isEdit = true, //
this.isCamera = false,
}) : super(key: key);
@override
@ -53,7 +56,16 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
);
});
}
Future<void> _cameraAction() async {
XFile? picked = await _picker.pickImage(source: ImageSource.camera);
if (picked != null) {
final path = picked.path;
setState(() => _mediaPaths.add(path));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onMediaAdded?.call(path);
}
}
Future<void> _showPickerOptions() async {
if (!widget.isEdit) return; //
@ -237,7 +249,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
//
else if (showAddButton) {
return GestureDetector(
onTap: _showPickerOptions,
onTap: widget.isCamera?_cameraAction:_showPickerOptions,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
@ -273,6 +285,8 @@ class RepairedPhotoSection extends StatefulWidget {
final bool isRequired;
final bool isShowNum;
final bool isEdit; //
final bool isCamera; //
const RepairedPhotoSection({
Key? key,
@ -290,6 +304,7 @@ class RepairedPhotoSection extends StatefulWidget {
this.isRequired = false,
this.isShowNum = true,
this.isEdit = true, //
this.isCamera = false,
}) : super(key: key);
@override
@ -331,6 +346,7 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
maxCount: widget.maxCount,
mediaType: widget.mediaType,
initialMediaPaths: _mediaPaths,
isCamera: widget.isCamera,
onChanged: (files) {
final newPaths = files.map((f) => f.path).toList();
setState(() {

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
// main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/badge_manager.dart';
import 'package:qhd_prevention/services/auth_service.dart';
@ -8,6 +10,7 @@ import './pages/main_tab.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'http/HttpManager.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter/services.dart'; // for TextInput.hide
//
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -28,6 +31,36 @@ class GlobalMessage {
}
}
/// helper
Future<T?> showDialogAfterUnfocus<T>(BuildContext context, Widget dialog) async {
//
FocusScope.of(context).unfocus();
try {
await SystemChannels.textInput.invokeMethod('TextInput.hide');
} catch (_) {}
// 100-200ms
await Future.delayed(const Duration(milliseconds: 150));
return showDialog<T>(context: context, builder: (_) => dialog);
}
///
Future<T?> showModalBottomSheetAfterUnfocus<T>({
required BuildContext context,
required WidgetBuilder builder,
bool isScrollControlled = false,
}) async {
FocusScope.of(context).unfocus();
try {
await SystemChannels.textInput.invokeMethod('TextInput.hide');
} catch (_) {}
await Future.delayed(const Duration(milliseconds: 150));
return showModalBottomSheet<T>(
context: context,
isScrollControlled: isScrollControlled,
builder: builder,
);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -70,8 +103,6 @@ void main() async {
// token
try {
isLoggedIn = await AuthService.isLoggedIn();
// AuthService.isLoggedIn()
// false
} catch (e) {
isLoggedIn = false;
}
@ -80,6 +111,7 @@ void main() async {
runApp(MyApp(isLoggedIn: isLoggedIn));
}
/// MyApp Stateless viewInsets
class MyApp extends StatelessWidget {
final bool isLoggedIn;
@ -90,13 +122,15 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: '',
navigatorKey: navigatorKey,
// push/pop TextField
navigatorObservers: [KeyboardUnfocusNavigatorObserver()],
builder: (context, child) {
return EasyLoading.init(
builder: (context, widget) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
// FocusScope.of(context).unfocus();
//
FocusHelper.clearFocus(context);
},
child: widget,
@ -136,3 +170,38 @@ class MyApp extends StatelessWidget {
);
}
}
/// NavigatorObserver push/pop/remove/replace
class KeyboardUnfocusNavigatorObserver extends NavigatorObserver {
void _unfocus() {
try {
FocusManager.instance.primaryFocus?.unfocus();
} catch (e) {
debugPrint('NavigatorObserver unfocus error: $e');
}
}
@override
void didPush(Route route, Route? previousRoute) {
_unfocus();
super.didPush(route, previousRoute);
}
@override
void didPop(Route route, Route? previousRoute) {
_unfocus();
super.didPop(route, previousRoute);
}
@override
void didRemove(Route route, Route? previousRoute) {
_unfocus();
super.didRemove(route, previousRoute);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
_unfocus();
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
}
}

View File

@ -526,11 +526,12 @@ class _QuickReportPageState extends State<QuickReportPage> {
String latitude=position.latitude.toString();
try {
Map data = {};
final result = await ApiService.addRiskListCheckApp(
hazardDescription, partDescription, latitude, longitude,
dangerDetail, dataTime, type, responsibleId,
yinHuanTypeIds, hazardLeve, buMenId, buMenPDId,
yinHuanTypeNames, hiddenType1, hiddenType2, hiddenType3,);
yinHuanTypeNames, hiddenType1, hiddenType2, hiddenType3,data);
if (result['result'] == 'success') {
String hiddenId = result['pd']['HIDDEN_ID'] ;

View File

@ -124,6 +124,27 @@ class _HomeNfcAddPageState extends State<HomeNfcAddPage> {
);
if (confirmed) {
LoadingDialogHelper.show(message: '等待手机靠近NFC标签');
NfcService.instance.startScanOnceWithCallback(
onResult: (uid, parsedText, rawMsg) async {
final result = await ApiService.nfcWriteCheck(uid);
if (result['result'] == 'success') {
_writeNFCInfoRequest();
}else{
ToastUtil.showError(context, result['result'] ?? '');
}
LoadingDialogHelper.hide();
},
onError: (err) {
ToastUtil.showNormal(context, '$err');
LoadingDialogHelper.hide();
},
timeout: Duration(seconds: 12),
);
}
}
Future<void> _writeNFCInfoRequest() async{
await NfcService.instance.writeText(
mapToCompactJson({'PIPELINE_AREA_ID':pd['PIPELINE_AREA_ID'],'EQUIPMENT_PIPELINE_ID':pd['EQUIPMENT_PIPELINE_ID']}),
timeout: Duration(seconds: 12),
@ -137,12 +158,17 @@ class _HomeNfcAddPageState extends State<HomeNfcAddPage> {
}else{
ToastUtil.showError(context, '写入失败,请重试');
}
}else{
ToastUtil.showError(context, '$msg');
}
LoadingDialogHelper.hide();
},
);
LoadingDialogHelper.hide();
}
}
String mapToCompactJson(Map<String, dynamic> map) {
// 使 jsonEncode
String jsonStr = jsonEncode(map);

View File

@ -1,17 +1,207 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_detail_page.dart';
import 'package:qhd_prevention/pages/home/NFC/nfc_check_danger_detail.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
/// OptionData
class OptionData {
final String value;
final String label;
final IconData icon;
final Color color;
const OptionData({
required this.value,
required this.label,
required this.icon,
required this.color,
});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is OptionData &&
runtimeType == other.runtimeType &&
value == other.value &&
label == other.label &&
icon == other.icon &&
color == other.color;
@override
int get hashCode =>
value.hashCode ^ label.hashCode ^ icon.hashCode ^ color.hashCode;
}
class HomeNfcCheckDangerPage extends StatefulWidget {
const HomeNfcCheckDangerPage({super.key});
const HomeNfcCheckDangerPage({
super.key,
required this.info,
required this.facebookImages,
required this.isNfcError
});
final Map info;
final List<String> facebookImages;
// nfc
final bool isNfcError;
@override
State<HomeNfcCheckDangerPage> createState() => _HomeNfcCheckDangerPageState();
}
class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
late Map<String, dynamic> pd = {};
OptionData? selectType; // null
final List<OptionData> _options = const [
OptionData(
value: "option1",
label: "合格",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
OptionData(
value: "option2",
label: "不合格",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
OptionData(
value: "option3",
label: "不涉及",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
];
@override
void initState() {
// TODO: implement initState
super.initState();
final bool unchecked = widget.info['INSPECTED_FLAG'] == '0';
if (!unchecked) { //
for (OptionData data in _options) {
if (data.label == widget.info['INSPECTION_RESULT']) {
selectType = data;
}
}
}
if (selectType != null && selectType?.label == '不合格') { //
_getCheckRecord();
}
Widget _pendingTopCard(Map<String, String> item) {
}
Future<void> _getCheckRecord() async{
Map data = {'PATROL_RECORD_DETAIL_ID': widget.info['PATROL_RECORD_DETAIL_ID']};
final result = await ApiService.nfcDangerRecord(data);
if (result['result'] == 'success') {
setState(() {
pd = result['pd'];
});
}
}
Future<void> _submit() async {
//
if (selectType == null) {
//
ToastUtil.showNormal(context, '请先选择检查结果');
return;
}
Map data = {
...widget.info,
'INSPECTION_RESULT': selectType?.label ?? '',
'TYPE': '0', // (0- 1-)
};
LoadingDialogHelper.show();
// ,
if (selectType?.label == '不合格') {
List imgList = pd['imgList'] ?? [];
List _videos = pd['videoList'] ?? [];
List zgImgList = pd['gzImageList'] ?? [];
for (int i = 0; i < imgList.length; i++) {
await _reloadFeedBack(imgList[i], '3');
}
for (int i = 0; i < _videos.length; i++) {
await _reloadFeedBack(_videos[i], '3');
}
for (int i = 0; i < zgImgList.length; i++) {
await _reloadFeedBack(zgImgList[i], '4');
}
data = {...data,...pd};
}
if (widget.facebookImages.isNotEmpty) {
// nfc
final List<String> uploaded = [];
for (int i = 0; i < widget.facebookImages.length; i++) {
String imagePath = await _reloadFeedBack(widget.facebookImages[i], '30');
if (imagePath.isNotEmpty) {
uploaded.add(imagePath);
}
}
if (uploaded.isNotEmpty) {
data['PHOTO_URL'] = uploaded.join(',');
}
}
data['CHECK_CONTENT'] = widget.info['INSPECTION_CONTENT'];
final result = await ApiService.nfcChekSubmit(data);
LoadingDialogHelper.hide();
if (result['result'] == 'success') {
if (widget.isNfcError) { // nfc退
Navigator.of(context).pop();
}
Navigator.of(context).pop();
} else {
//
ToastUtil.showNormal(context, result['result']?.toString() ?? '提交失败');
}
}
Future<String> _reloadFeedBack(String imagePath, String type) async {
try {
Map data = {
'TYPE': type,
'FOREIGN_KEY': widget.info['EQUIPMENT_PIPELINE_ID'],
};
final raw = await ApiService.addNormalImgFiles(imagePath, data);
if (raw['result'] == 'success') {
Map pd = raw['pd'];
return pd['FILEPATH'] ?? "";
} else {
return "";
}
} catch (e) {
// Toast
debugPrint('加载首页数据失败:$e');
return "";
}
}
void _pushDangerDetail() async {
// pushPage pd pd
final result = await pushPage<Map<String, dynamic>>(
NfcCheckDangerDetail(info: pd),
context,
);
if (result != null && result.isNotEmpty) {
setState(() {
//
selectType = _options[1];
pd = result;
});
}
}
Widget _pendingTopCard(Map item) {
return SizedBox(
height: 180,
child: Stack(
@ -32,7 +222,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
top: 12,
left: 12,
child: Text(
item['title']!,
item['TASK_NAME']?.toString() ?? '',
style: const TextStyle(
color: Colors.black87,
fontSize: 18,
@ -52,7 +242,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
),
child: Center(
child: Text(
item['status']!,
item['PATROL_TYPE_NAME'] ?? '',
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
@ -66,11 +256,11 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
top: 50, //
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 0),
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
@ -86,32 +276,126 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
);
}
///
Widget _buildInfoGrid(Map<String, String> item) {
return Row(
// option OptionData
Widget _buildOptionButton({
required BuildContext context,
required OptionData option,
required double screenWidth,
required dynamic item,
VoidCallback? onImageTap,
}) {
final String value = option.value;
final String label = option.label;
final icon = option.icon;
final color = option.color;
final bool isSelected = selectType?.value == option.value;
final buttonWidth = (screenWidth - 60) / 3 - 10; //
return GestureDetector(
onTap: () {
setState(() {
if (value != "option2") {
selectType = option;
} else {
//
_pushDangerDetail();
}
});
},
behavior: HitTestBehavior.opaque,
child: Container(
width: buttonWidth,
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
SizedBox(
height: 30,
width: 90,
child: Row(
children: [
Icon(icon, color: isSelected ? color : Colors.grey, size: 30),
const SizedBox(width: 8),
Flexible(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? color : Colors.grey[600],
),
),
),
],
),
),
const SizedBox(width: 6),
if ((value == "option1" && item["REFERENCE_BASIS"] == "option1") ||
(value == "option2" &&
item["REFERENCE_BASIS"] == "option2" &&
item.containsKey("ids") &&
item["ids"].toString().isNotEmpty))
GestureDetector(
onTap: () {
if (onImageTap != null) {
onImageTap();
}
},
behavior: HitTestBehavior.opaque,
child: Transform.translate(
offset: const Offset(0, -6),
child: Image.asset(
"assets/images/gantan-blue.png",
width: 15,
height: 15,
),
),
),
],
),
),
);
}
///
Widget _buildInfoGrid(Map item) {
final screenWidth = MediaQuery.of(context).size.width;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('负责部门:${item['department']}'),
const SizedBox(height: 8),
Text('负责人:${item['owner']}'),
const SizedBox(height: 8),
Text('UN件类型${item['unType']}'),
],
ItemListWidget.singleLineTitleText(
label: '检查项:',
isEditable: false,
text: item['EQUIPMENT_NAME'] ?? '',
),
ItemListWidget.singleLineTitleText(
label: '检查内容:',
isEditable: false,
text: item['INSPECTION_CONTENT'] ?? '',
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('巡检周期:${item['cycle']}'),
const SizedBox(height: 8),
Text('已巡点位:${item['points']}'),
Text('涉及管道区域:${item['department']}')
],
),
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _options.map((option) {
return _buildOptionButton(
context: context,
option: option,
screenWidth: screenWidth,
item: item,
onImageTap: () {
if (item["REFERENCE_BASIS"] == "option1") {
// _getAlreadyUpImages(item);
} else if (item["REFERENCE_BASIS"] == "option2") {
// _goUnqualifiedPage(item);
}
},
);
}).toList(),
),
],
);
@ -119,18 +403,28 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
@override
Widget build(BuildContext context) {
final bool canSubmit = selectType != null; //
return Scaffold(
appBar: MyAppbar(title: '检查项'),
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_pendingTopCard({}),
]
)
)
)
_pendingTopCard(widget.info),
const Spacer(),
CustomButton(
enabled: canSubmit,
text: '提交',
backgroundColor: Colors.blue,
onPressed: canSubmit ? _submit : null,
),
],
),
),
),
);
}
}

View File

@ -2,24 +2,32 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_check_danger_page.dart';
import 'package:qhd_prevention/pages/home/NFC/nfc_question_fecebook.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/services/nfc_service.dart';
import 'package:qhd_prevention/tools/tools.dart';
class HomeNfcDetailPage extends StatefulWidget {
const HomeNfcDetailPage({super.key, required this.info});
final Map<String, dynamic> info;
final Map info;
@override
State<HomeNfcDetailPage> createState() => _HomeNfcDetailPageState();
}
class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
List<ProgressItem> items = [];
List<dynamic> items = [];
int currentPage = 1;
final int pageSize = 10;
late var _total = 0;
late var _totalResult = 0;
bool isLoading = false; //
bool hasMore = true; //
@ -61,11 +69,15 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
}
try {
// API ApiService.nfcTaskDetailList(pageSize, page)
Map data = {
"PATROL_TASK_ID": widget.info['PATROL_TASK_ID'] ?? '',
"PERIOD_START_TIME": widget.info['PERIOD_START_TIME'] ?? '',
"PERIOD_END_TIME": widget.info['PERIOD_END_TIME'] ?? '',
};
final result = await ApiService.nfcTaskDetailList(
pageSize,
currentPage,
widget.info['PATROL_TASK_ID'] ?? '',
data,
);
if (result == null) {
@ -74,35 +86,20 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
}
printLongString(jsonEncode(result));
if (result['result'] == 'success') {
// data / rows / list
final dynamic rawList = result['varList'];
List<ProgressItem> fetched = [];
if (rawList is List) {
fetched =
rawList.map<ProgressItem>((e) {
if (e is ProgressItem) return e;
if (e is Map<String, dynamic>) return ProgressItem.fromJson(e);
return ProgressItem(
status: '未查',
location: e?.toString() ?? '',
code: '',
checkTime: null,
);
}).toList();
}
setState(() {
Map page = result['page'];
_totalResult = page['totalResult'] as int;
_total = result['checkedCount'] as int;
if (refresh) {
items = fetched;
items = rawList;
} else {
items.addAll(fetched);
items.addAll(rawList);
}
// pageSize
if (fetched.length < pageSize) {
if (rawList.length < pageSize) {
hasMore = false;
} else {
//
currentPage++;
}
});
@ -126,23 +123,74 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
Future<void> _startCheckItem(ProgressItem item, int index) async {
// TODO: NFC
//
final now = DateTime.now();
setState(() {
items[index] = items[index].copyWith(
status: '已查',
checkTime:
'${now.year}-${_two(now.month)}-${_two(now.day)} ${_two(now.hour)}:${_two(now.minute)}',
Future<void> _startCheckItem(Map item, int index) async {
final confirmed = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '请将手机贴近设备标签',
cancelText: '',
confirmText: '我知道了',
barrierDismissible: false,
);
});
if (confirmed) {
LoadingDialogHelper.show(message: '等待设备靠近设备NFC标签');
NfcService.instance.startScanOnceWithCallback(
onResult: (uid, parsedText, rawMsg) async {
_getNFCForUid(uid, parsedText);
},
onError: (err) {
//
// await ApiService.reportCheck(items[index].code, ...);
ToastUtil.showError(context, '$err');
LoadingDialogHelper.hide();
},
timeout: Duration(seconds: 12),
);
}
}
String _two(int v) => v.toString().padLeft(2, '0');
Future<void> _getNFCForUid(String uid, String parsedText) async {
if (uid.isEmpty || parsedText.isEmpty) {
ToastUtil.showError(context, 'NFC设备标签数据为空');
LoadingDialogHelper.hide();
return;
}
Map result = {};
// mapToCompactJson({'PIPELINE_AREA_ID':pd['PIPELINE_AREA_ID'],'EQUIPMENT_PIPELINE_ID':pd['EQUIPMENT_PIPELINE_ID']}),
for (Map item in items) {
if (parsedText.isNotEmpty && item['NFC_CODE'] == uid) {
try{
Map parsedData = jsonDecode(parsedText);
if (parsedData['PIPELINE_AREA_ID'] == item['PIPELINE_AREA_ID'] &&
parsedData['EQUIPMENT_PIPELINE_ID'] == item['EQUIPMENT_PIPELINE_ID']) {
result = item;
}
LoadingDialogHelper.hide();
}catch(e){
LoadingDialogHelper.hide();
ToastUtil.showError(context, 'NFC设备数据错误');
}
}
}
if (result.isEmpty) {
LoadingDialogHelper.hide();
ToastUtil.showError(context, 'NFC设备不在当前任务中');
return;
}
LoadingDialogHelper.hide();
Map data = {...result, ...widget.info, "NFC_CODE": uid, 'MANUAL_CONFIRMATION': '0',
};
await pushPage(
HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
context,
);
_getTaskDetail(refresh: true);
}
@override
void dispose() {
@ -150,7 +198,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
super.dispose();
}
Widget _pendingTopCard(Map<String, dynamic> item) {
Widget _pendingTopCard(Map item) {
return SizedBox(
height: 180,
child: Stack(
@ -232,9 +280,8 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
}
///
Widget _buildInfoGrid(Map<String, dynamic> item) {
return Expanded(
child: Column(
Widget _buildInfoGrid(Map item) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// const SizedBox(height: 8),
@ -264,13 +311,12 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
text: item['OPERATTIME'] ?? '',
),
],
),
);
}
Widget _buildListItem(BuildContext context, int idx) {
final item = items[idx];
final bool unchecked = item.status == '未查';
final bool unchecked = item['INSPECTED_FLAG'] == '0';
return Container(
height: 100,
@ -294,7 +340,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
Positioned(
top: 2,
child: Text(
item.status,
unchecked ? '未查' : '已查',
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
@ -309,11 +355,22 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
//
Expanded(
child: GestureDetector(
onTap: (){
if (!unchecked) {
Map data = {...item, ...widget.info, "NFC_CODE": item['PIPELINE_AREA_ID'], 'MANUAL_CONFIRMATION': '0',
};
pushPage(
HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
context,
);
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.location,
item['PIPELINE_AREA_NAME'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@ -322,38 +379,73 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
overflow: TextOverflow.ellipsis, //
),
const SizedBox(height: 4),
Text('NFC编码${item.code}'),
Text('NFC编码${item['NFC_CODE'] ?? ''}'),
const SizedBox(height: 6),
unchecked
? InkWell(
? Row(
spacing: 10,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: InkWell(
onTap: () => _startCheckItem(item, idx),
child: Container(
height: 35,
// width: 120,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 1),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFFA726), Color(0xFFFF7043)],
colors: [
Color(0xFFFFA726),
Color(0xFFFF7043),
],
),
borderRadius: BorderRadius.circular(5),
),
child: const Text(
'开始检查',
style: TextStyle(color: Colors.white, fontSize: 14),
'NFC检查',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
),
),
CustomButton(
onPressed: () {
pushPage(
NfcQuestionFecebook(
info: item,
taskInfo: widget.info,
),
context,
);
},
text: '手动检查',
height: 35,
textStyle: TextStyle(
color: Colors.white,
fontSize: 15,
),
backgroundColor: Colors.blue,
),
],
)
: Text(
'检查时间:${item.checkTime ?? ''}',
'检查时间:${item['PATROL_TIME'] ?? ''}',
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
],
),
)
),
//
const SizedBox(width: 8),
if (!unchecked)
const Icon(Icons.chevron_right, color: Colors.grey),
],
),
@ -371,6 +463,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
children: [
_pendingTopCard(widget.info),
const SizedBox(height: 60),
Expanded(
child: Container(
decoration: BoxDecoration(
@ -384,6 +477,73 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
),
],
),
// +
child: Column(
children: [
//
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
child: Builder(
builder: (context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 8,
),
child: Row(
children: [
Text(
'已查点位 $_total / $_totalResult ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
if (isLoading)
const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
else
GestureDetector(
onTap:
() => _getTaskDetail(refresh: true),
child: Row(
children: const [
Icon(
Icons.refresh,
size: 16,
color: Colors.grey,
),
SizedBox(width: 4),
Text(
'刷新',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
],
),
);
},
),
),
//
Expanded(
child: RefreshIndicator(
onRefresh: () => _getTaskDetail(refresh: true),
child:
@ -396,36 +556,38 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
)
: items.isEmpty
? ListView(
//
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
physics:
const AlwaysScrollableScrollPhysics(),
children: [
const SizedBox(height: 80),
const SizedBox(height: 40),
Center(
child: Text(isLoading ? '加载中...' : '暂无数据'),
child: Text(
isLoading ? '加载中...' : '暂无数据',
),
),
],
)
: ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: items.length + 1, // /
physics:
const AlwaysScrollableScrollPhysics(),
itemCount:
items.length + 1, // /
itemBuilder: (ctx, idx) {
if (idx < items.length) {
return _buildListItem(ctx, idx);
} else {
// /
if (hasMore) {
//
if (!isLoading) {
//
// _getTaskDetail(); // scroll listener
}
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
),
child: Center(
child: CircularProgressIndicator(),
child:
CircularProgressIndicator(),
),
);
} else {
@ -441,6 +603,9 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
),
),
),
],
),
),
),
],
),
@ -449,96 +614,3 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
);
}
}
///
class ProgressItem {
final String status; //
final String location; //
final String code; //
final String? checkTime; //
ProgressItem({
required this.status,
required this.location,
required this.code,
this.checkTime,
});
ProgressItem copyWith({
String? status,
String? location,
String? code,
String? checkTime,
}) {
return ProgressItem(
status: status ?? this.status,
location: location ?? this.location,
code: code ?? this.code,
checkTime: checkTime ?? this.checkTime,
);
}
///
factory ProgressItem.fromJson(Map<String, dynamic> json) {
String status =
(json['status'] ??
json['STATUS'] ??
(json['checked'] == true ? '已查' : null) ??
(json['is_checked'] == 1 ? '已查' : null) ??
json['state'] ??
json['check_status'])
?.toString() ??
'';
if (status.isEmpty) {
// API 0/1
final s = json['status'] ?? json['STATUS'] ?? json['check_status'];
if (s is num) {
status = (s == 0) ? '未查' : '已查';
} else if (s is String && (s == '0' || s == '1')) {
status = (s == '0') ? '未查' : '已查';
}
}
if (status.isEmpty) status = '未查';
final location =
(json['location'] ??
json['LOCATION'] ??
json['point_name'] ??
json['address'] ??
json['point'] ??
'')
.toString();
final code =
(json['code'] ??
json['CODE'] ??
json['nfcCode'] ??
json['nfc_code'] ??
json['NFC_CODE'] ??
json['id'] ??
'')
.toString();
final checkTime =
(json['checkTime'] ??
json['CHECK_TIME'] ??
json['checked_at'] ??
json['check_time'] ??
json['inspect_time'] ??
null)
?.toString();
return ProgressItem(
status: status,
location: location,
code: code,
checkTime: checkTime,
);
}
Map<String, dynamic> toJson() => {
'status': status,
'location': location,
'code': code,
'checkTime': checkTime,
};
}

View File

@ -0,0 +1,583 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.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/bottom_picker_two.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/date_picker_dialog.dart';
import 'package:qhd_prevention/customWidget/department_person_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart';
import 'package:qhd_prevention/customWidget/department_picker_two.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
import '../../../customWidget/photo_picker_row.dart';
import '../../../http/ApiService.dart';
class NfcCheckDangerDetail extends StatefulWidget {
const NfcCheckDangerDetail({super.key, required this.info});
final Map<String, dynamic> info;
@override
State<NfcCheckDangerDetail> createState() => _NfcCheckDangerDetailState();
}
class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
late Map<String, dynamic> pd = {};
//
late List<dynamic> _hazardLeveLlist = [];
late List<dynamic> _hiddenTypeList = [];
late bool _isDanger = false; //true 1 false 2
//
Map<String, dynamic> _personCache = {};
//
late List<String> imgList = [];
late List<String> _videos = [];
//
late List<String> zgImgList = [];
@override
void initState() {
// TODO: implement initState
super.initState();
pd = widget.info;
if (pd.isNotEmpty) {
imgList = pd['imgList'] ?? [];
_videos = pd['videoList'] ?? [];
zgImgList = pd['gzImageList'] ?? [];
}
_getHazardLevel();
}
Future<void> _getHazardLevel() async {
final resultLevel = await ApiService.getHiddenLevelsListTwo();
List<dynamic> levelList = resultLevel['list'] as List;
String parentId = '';
for (var item in levelList) {
if ((item['BIANMA'] as String).contains(
SessionService.instance.loginUser?["PROVINCE"] ?? '',
)) {
parentId = item['DICTIONARIES_ID'];
break;
}
}
final result = await ApiService.getHiddenTypeList(parentId);
final nodes = result['zTreeNodes'] as String;
SessionService.instance.departmentHiddenTypeJsonStr = nodes;
setState(() {
_hiddenTypeList = json.decode(nodes) as List;
});
try {
final result = await ApiService.getHazardLevel();
if (result['result'] == 'success') {
final List<dynamic> newList = result['list'] ?? [];
setState(() {
_hazardLeveLlist = newList;
});
}
} catch (e) {
print('Error fetching data: $e');
}
}
Future<void> _pickHazardType() async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => DepartmentPickerHiddenType(
onSelected: (result) {
try {
final Map m = Map.from(result);
final ids = List<String>.from(m['id'] ?? []);
final names = List<String>.from(m['name'] ?? []);
setState(() {
pd['HIDDENTYPE'] = ids;
pd['HIDDENTYPE_NAME'] = names.join('/');
});
FocusHelper.clearFocus(context);
} catch (_) {}
},
),
);
}
Future<void> chooseDangerLevel() async {
FocusScope.of(context).unfocus();
WidgetsBinding.instance.addPostFrameCallback((_) async {
Map _hazardLeve = {};
String choice = await BottomPickerTwo.show<String>(
context,
items: _hazardLeveLlist,
itemBuilder: (item) => Text(item["NAME"], textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
for (int i = 0; i < _hazardLeveLlist.length; i++) {
if (choice == _hazardLeveLlist[i]["NAME"]) {
_hazardLeve = _hazardLeveLlist[i];
}
}
setState(() {
pd['HIDDENLEVELNAME'] = _hazardLeve["NAME"];
pd['HIDDENLEVEL'] = _hazardLeve["BIANMA"];
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "隐患登记"),
body: Column(
children: [
//
_pageDetail(),
],
),
);
}
///
void chooseUnitHandle(String typeStr) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => DepartmentPicker(
onSelected: (id, name) async {
setState(() {
pd['RECTIFICATIONDEPT'] = id;
pd['RECTIFICATIONDEPTNAME'] = name;
});
FocusHelper.clearFocus(context);
_getPersonListForUnitId(typeStr);
},
),
).then((_) {});
}
Future<void> _getPersonListForUnitId(String typeStr) async {
String unitId = pd['RECTIFICATIONDEPT'] ?? '';
//
final result = await ApiService.getListTreePersonList(unitId);
setState(() {
_personCache[typeStr] = List<Map<String, dynamic>>.from(
result['userList'] as List,
);
});
FocusHelper.clearFocus(context);
}
///
void choosePersonHandle(String typeStr) async {
final personList = _personCache[typeStr];
if (!FormUtils.hasValue(_personCache, typeStr)) {
ToastUtil.showNormal(context, '请先选择单位');
return;
}
DepartmentPersonPicker.show(
context,
personsData: personList!,
onSelectedWithIndex: (userId, name, index) {
setState(() {
pd['RECTIFICATIONOR'] = userId;
pd['RECTIFICATIONORNAME'] = name;
});
FocusHelper.clearFocus(context);
},
).then((_) {});
}
Widget _buildSectionContainer({required Widget child}) {
return Container(
margin: const EdgeInsets.only(top: 10),
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: child,
);
}
Widget _pageDetail() {
return Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 20),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ItemListWidget.itemContainer(
horizontal: 5,
Column(
children: [
RepairedPhotoSection(
title: "隐患照片",
maxCount: 4,
initialMediaPaths: imgList,
mediaType: MediaType.image,
isShowAI: true,
onMediaAdded: (localPath) {
imgList.add(localPath);
},
onMediaRemoved: (localPath) {
imgList.remove(localPath);
},
onChanged: (v) {},
onAiIdentify: () {
// AI
if (imgList.isEmpty) {
ToastUtil.showNormal(context, "请先上传一张图片");
return;
}
if (imgList.length > 1) {
ToastUtil.showNormal(context, "识别暂时只能上传一张图片");
return;
}
_identifyImg(imgList[0]);
},
),
RepairedPhotoSection(
title: "隐患视频",
maxCount: 1,
mediaType: MediaType.video,
onChanged: (v) {},
onMediaRemoved: (localPath) {
_videos = [localPath];
},
onMediaAdded: (localPath) {
_videos = [];
},
onAiIdentify: () {
// AI
},
),
],
),
),
ItemListWidget.multiLineTitleTextField(
label: '隐患描述',
isEditable: true,
isRequired: false,
text: pd['HIDDENDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['HIDDENDESCR'] = v;
});
},
),
const Divider(),
ItemListWidget.multiLineTitleTextField(
label: '隐患部位',
isEditable: true,
isRequired: false,
// controller: TextEditingController(text: pd['HIDDENPART'] ?? ''),
text: pd['HIDDENPART'] ?? '',
hintText: '请对隐患部位进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['HIDDENPART'] = v;
});
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患级别',
isRequired: false,
isEditable: true,
text: pd['HIDDENLEVELNAME'] ?? '',
onTap: chooseDangerLevel,
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患类型',
isEditable: true,
isRequired: false,
text: pd['HIDDENTYPE_NAME'] ?? '',
onTap: _pickHazardType,
),
const Divider(),
ListItemFactory.createYesNoSection(
title: "是否立即整改",
horizontalPadding: 0,
verticalPadding: 0,
yesLabel: "",
noLabel: "",
groupValue: _isDanger,
onChanged: (val) {
setState(() {
_isDanger = val;
});
},
),
const Divider(),
if (_isDanger)
Column(
children: [
ItemListWidget.multiLineTitleTextField(
label: '整改描述',
isEditable: true,
isRequired: false,
text: pd['RECTIFYDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['RECTIFYDESCR'] = v;
});
},
),
const Divider(),
RepairedPhotoSection(
horizontalPadding: 12,
title: "整改后图片",
maxCount: 4,
initialMediaPaths: zgImgList,
mediaType: MediaType.image,
isShowAI: false,
onMediaAdded: (localPath) {
zgImgList.add(localPath);
},
onMediaRemoved: (localPath) {
zgImgList.remove(localPath);
},
onChanged: (v) {},
onAiIdentify: () {},
),
],
),
if (!_isDanger)
Column(
children: [
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任部门',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONDEPTNAME'] ?? '',
onTap: () {
chooseUnitHandle('key');
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任人',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONORNAME'] ?? '',
onTap: () {
choosePersonHandle('key');
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改期限',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONDEADLINE'] ?? '',
onTap: () {
showDialog(
context: context,
builder:
(_) => HDatePickerDialog(
initialDate: DateTime.now(),
onCancel: () => Navigator.of(context).pop(),
onConfirm: (selected) {
Navigator.of(context).pop();
setState(() {
pd['RECTIFICATIONDEADLINE'] = DateFormat(
'yyyy-MM-dd',
).format(selected);
});
},
),
);
},
),
const Divider(),
],
),
SizedBox(height: 30),
CustomButton(
onPressed: () {
_riskListCheckAppAdd();
},
text: "确定",
backgroundColor: Colors.blue,
),
SizedBox(height: 30),
],
),
),
),
);
}
Future<void> _riskListCheckAppAdd() async {
if (imgList.isEmpty) {
ToastUtil.showNormal(context, '请上传隐患图片');
return;
}
final textRules = <Map<String, dynamic>>[
{'value': pd['HIDDENDESCR'] ?? '', 'message': '请填隐患描述'},
{'value': pd['HIDDENPART'] ?? '', 'message': '请填隐患部位'},
{'value': pd['HIDDENLEVEL'] ?? '', 'message': '请选择隐患级别'},
{'value': pd['HIDDENTYPE_NAME'] ?? '', 'message': '请选择隐患类型'},
];
for (Map rule in textRules) {
String value = rule['value'];
if (value.isEmpty) {
ToastUtil.showNormal(context, rule['message']);
return;
}
}
if (_isDanger) {
if (!FormUtils.hasValue(pd, 'RECTIFYDESCR')) {
ToastUtil.showNormal(context, '请填整改描述');
return;
}
if (zgImgList.isEmpty) {
ToastUtil.showNormal(context, '请上传整改后图片');
return;
}
} else {
if (!FormUtils.hasValue(pd, 'RECTIFICATIONDEPT')) {
ToastUtil.showNormal(context, '请选择整改部门');
return;
}
if (!FormUtils.hasValue(pd, 'RECTIFICATIONOR')) {
ToastUtil.showNormal(context, '请选择整改人');
return;
}
if (!FormUtils.hasValue(pd, 'RECTIFICATIONDEADLINE')) {
ToastUtil.showNormal(context, '请选择整改期限');
return;
}
}
List HIDDENTYPE = pd['HIDDENTYPE'] ?? [];
pd['RECTIFICATIONTYPE'] = _isDanger ? '1' : '2';
pd['CREATOR'] = SessionService.instance.loginUserId;
pd['SOURCE'] = '6';
pd['HIDDENTYPE1'] = HIDDENTYPE.length > 0 ? HIDDENTYPE[0] : "";
pd['HIDDENTYPE2'] = HIDDENTYPE.length > 1 ? HIDDENTYPE[1] : "";
pd['HIDDENTYPE3'] = HIDDENTYPE.length > 2 ? HIDDENTYPE[2] : "";
pd['imgList'] = imgList;
pd['videoList'] = _videos;
pd['gzImageList'] = zgImgList;
Navigator.pop(context, pd);
}
Future<void> _identifyImg(String imagePath) async {
try {
LoadingDialogHelper.show();
final raw = await ApiService.identifyImg(imagePath);
if (raw['result'] == 'success') {
final dynamic parsedRes = raw;
final aiHiddens = parsedRes['aiHiddens'];
String hiddenDescr = '';
String rectificationSuggestions = '';
String legalBasis = '';
if (aiHiddens is List) {
for (var item in aiHiddens) {
dynamic obj = item;
if (item is String) {
try {
obj = json.decode(item);
} catch (e) {
//
continue;
}
}
if (obj is Map) {
hiddenDescr += (obj['hiddenDescr']?.toString() ?? '') + ';';
rectificationSuggestions +=
(obj['rectificationSuggestions']?.toString() ?? '') + ';';
legalBasis += (obj['legalBasis']?.toString() ?? '') + ';';
}
}
}
// pd Map
setState(() {
pd['HIDDENDESCR'] = hiddenDescr;
pd['LEGALBASIS'] = legalBasis;
pd['RECTIFYDESCR'] = rectificationSuggestions;
});
LoadingDialogHelper.hide();
} else {
ToastUtil.showNormal(context, "识别失败");
LoadingDialogHelper.hide();
// _showMessage('反馈提交失败');
// return "";
}
} catch (e) {
// Toast
print('加载首页数据失败:$e');
// return "";
LoadingDialogHelper.hide();
}
}
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
//
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
//
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.',
);
}
//
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
}
}

View File

@ -0,0 +1,241 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qhd_prevention/customWidget/photo_picker_row.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_check_danger_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
// feedback_type.dart
enum NfcFeedbackType {
readError('读取失败', 0),
nfcBld('标签损坏', 1),
nfcLose('标签丢失', 2);
final String typeName;
final int type;
const NfcFeedbackType(this.typeName, this.type);
}
class NfcQuestionFecebook extends StatefulWidget {
const NfcQuestionFecebook({super.key, required this.info,required this.taskInfo});
final Map taskInfo;
final Map info;
@override
State<NfcQuestionFecebook> createState() => _NfcQuestionFecebookState();
}
class _NfcQuestionFecebookState extends State<NfcQuestionFecebook> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _descriptionController = TextEditingController();
final FocusNode _descriptionFocus = FocusNode(); // <-
//
NfcFeedbackType? _selectedType = NfcFeedbackType.readError;
//
List<String> _images = [];
@override
void initState() {
// TODO: implement initState
super.initState();
_getFacebookDetail();
}
Future<void> _getFacebookDetail() async {
final result = await ApiService.getNfcFeedBackDetail({});
try{
if (result['result'] == 'success') {
}
}catch(e) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "NFC异常上报"),
body: Container(
color: Colors.white,
child: Form(
key: _formKey,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
const Text(
'详细问题',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextFormField(
controller: _descriptionController,
focusNode: _descriptionFocus,
autofocus: false,
maxLines: 5,
decoration: const InputDecoration(
hintText: '请补充详细问题...',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请补充详细问题';
}
return null;
},
),
const SizedBox(height: 24),
//
const Text(
'反馈类型',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 16,
children:
NfcFeedbackType.values.map((type) {
return ChoiceChip(
label: Text(type.typeName),
selected: _selectedType == type,
onSelected: (selected) {
setState(() {
if (selected) {
_selectedType = type;
}
});
},
);
}).toList(),
),
//
const SizedBox(height: 16),
RepairedPhotoSection(
horizontalPadding: 0,
title: "请提供相关问题照片",
maxCount: 4,
mediaType: MediaType.image,
isCamera: true,
onChanged: (files) {
// files
_images.clear();
for (int i = 0; i < files.length; i++) {
_images.add(files[i].path);
}
},
onAiIdentify: () {},
),
const SizedBox(height: 40),
//
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submitFeedback,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'下一步',
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
),
],
),
),
),
),
);
}
//
Future<void> _submitFeedback() async {
final text = _descriptionController.text.trim();
if (text.isEmpty) {
_showMessage('请填写问题');
return;
}
if (_images.isEmpty) {
_showMessage('请上传图片');
return;
}
Map data = {
...widget.info,
...widget.taskInfo,
'EXCEPTION_TYPE': _selectedType?.type,
'MANUAL_CONFIRMATION': '1',
// 'PHOTO_URL': imagePaths,
'DESCRIPTION': text,
};
pushPage(HomeNfcCheckDangerPage(info: data, facebookImages: _images, isNfcError: true,), context);
// String imagePaths = "";
// for (int i = 0; i < _images.length; i++) {
// String imagePath = await _reloadFeedBack(_images[i]);
//
// if (0 == i) {
// imagePaths = imagePath;
// } else {
// imagePaths = "$imagePaths,$imagePath";
// }
// }
//
// _setFeedBack(text, imagePaths);
}
@override
void dispose() {
_descriptionController.dispose();
super.dispose();
}
Future<void> _setFeedBack(String text, String imagePaths) async {
try {
Map data = {
"PATROL_TASK_ID":widget.taskInfo['PATROL_TASK_ID']?? '',
"PIPELINE_AREA_ID" :widget.taskInfo['PIPELINE_AREA_ID']?? '',
"EQUIPMENT_PIPELINE_ID" :widget.info['EQUIPMENT_PIPELINE_ID']?? '',
"PATROL_RECORD_ID" :widget.info['EQUIPMENT_PIPELINE_ID'] ?? '',
"PATROL_RECORD_DETAIL_ID" :widget.info['PATROL_RECORD_DETAIL_ID']?? '',
"NFC_CODE" :widget.info['PIPELINE_AREA_ID'],
'EXCEPTION_TYPE': _selectedType?.type,
'PHOTO_URL': imagePaths,
'DESCRIPTION': text,
};
final raw = await ApiService.nfcFeedBack(data);
if (raw['result'] == 'success') {
_showMessage('提交成功');
} else {
_showMessage('提交失败');
}
} catch (e) {
// Toast
print('加载首页数据失败:$e');
}
}
void _showMessage(String msg) {
ToastUtil.showNormal(context, msg);
}
}

View File

@ -120,8 +120,9 @@ class ItemListWidget {
Expanded(
child:
isEditable
? TextField(
? TextFormField(
autofocus: false,
initialValue: text,
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
@ -131,9 +132,10 @@ class ItemListWidget {
textAlignVertical: TextAlignVertical.top,
style: TextStyle(fontSize: fontSize),
decoration: InputDecoration(
hintText: hintText,
// TextField
//contentPadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
)

View File

@ -24,6 +24,7 @@ import 'package:nfc_manager_ndef/nfc_manager_ndef.dart';
/// iOS Xcode NFC Android NFC
class NfcService {
NfcService._internal();
static final NfcService instance = NfcService._internal();
/// NFC
@ -31,6 +32,7 @@ class NfcService {
/// 广便 UI
final StreamController<String> _logController = StreamController.broadcast();
Stream<String> get logs => _logController.stream;
/// NFC
@ -50,7 +52,10 @@ class NfcService {
/// bytes -> "AA:BB:CC"
String _bytesToHex(Uint8List bytes) {
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':').toUpperCase();
return bytes
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(':')
.toUpperCase();
}
/// Map/List (List<int> / Uint8List)
@ -101,12 +106,14 @@ class NfcService {
if (Platform.isAndroid) {
try {
final nfcA = NfcAAndroid.from(tag);
if (nfcA != null && nfcA.tag.id != null) return _bytesToHex(Uint8List.fromList(nfcA.tag.id));
if (nfcA != null && nfcA.tag.id != null)
return _bytesToHex(Uint8List.fromList(nfcA.tag.id));
} catch (_) {}
} else if (Platform.isIOS) {
try {
final mifare = MiFareIos.from(tag);
if (mifare != null && mifare.identifier != null) return _bytesToHex(Uint8List.fromList(mifare.identifier));
if (mifare != null && mifare.identifier != null)
return _bytesToHex(Uint8List.fromList(mifare.identifier));
} catch (_) {}
}
} catch (e) {
@ -131,7 +138,8 @@ class NfcService {
String _formatMessageToString(dynamic msg) {
if (msg == null) return '<empty>';
try {
final records = (msg is Map && msg['records'] != null)
final records =
(msg is Map && msg['records'] != null)
? List<dynamic>.from(msg['records'])
: (msg is dynamic ? (msg.records as List<dynamic>?) : null);
if (records == null || records.isEmpty) return '<empty>';
@ -144,12 +152,16 @@ class NfcService {
Uint8List? p;
if (r is Map && r['payload'] != null) {
final ptmp = r['payload'];
if (ptmp is Uint8List) p = ptmp;
else if (ptmp is List<int>) p = Uint8List.fromList(ptmp);
if (ptmp is Uint8List)
p = ptmp;
else if (ptmp is List<int>)
p = Uint8List.fromList(ptmp);
} else {
final pr = (r as dynamic).payload;
if (pr is Uint8List) p = pr;
else if (pr is List<int>) p = Uint8List.fromList(pr);
if (pr is Uint8List)
p = pr;
else if (pr is List<int>)
p = Uint8List.fromList(pr);
}
if (p != null) {
final txt = parseTextFromPayload(p);
@ -171,7 +183,8 @@ class NfcService {
/// onError(error) -
/// timeout -
Future<void> startScanOnceWithCallback({
required void Function(String uid, String parsedText, dynamic rawMessage) onResult,
required void Function(String uid, String parsedText, dynamic rawMessage)
onResult,
void Function(Object error)? onError,
Duration? timeout,
}) async {
@ -215,8 +228,12 @@ class NfcService {
if ((rawMsg.records as List).isNotEmpty) {
final first = rawMsg.records.first;
final payload = first.payload;
if (payload is Uint8List) parsedText = parseTextFromPayload(payload);
else if (payload is List<int>) parsedText = parseTextFromPayload(Uint8List.fromList(payload));
if (payload is Uint8List)
parsedText = parseTextFromPayload(payload);
else if (payload is List<int>)
parsedText = parseTextFromPayload(
Uint8List.fromList(payload),
);
}
}
} else {
@ -226,14 +243,24 @@ class NfcService {
// 退 tag map cachedMessage / records
try {
if (tag is Map) {
rawMsg = tag['cachedMessage'] ?? tag['ndef']?['cachedMessage'] ?? tag['message'];
rawMsg =
tag['cachedMessage'] ??
tag['ndef']?['cachedMessage'] ??
tag['message'];
if (rawMsg != null) {
final recs = (rawMsg['records'] ?? (rawMsg as dynamic).records) as dynamic;
final recs =
(rawMsg['records'] ?? (rawMsg as dynamic).records)
as dynamic;
if (recs != null && recs is List && recs.isNotEmpty) {
final r = recs.first;
final p = (r is Map) ? r['payload'] : (r as dynamic).payload;
if (p is Uint8List) parsedText = parseTextFromPayload(p);
else if (p is List<int>) parsedText = parseTextFromPayload(Uint8List.fromList(p));
final p =
(r is Map) ? r['payload'] : (r as dynamic).payload;
if (p is Uint8List)
parsedText = parseTextFromPayload(p);
else if (p is List<int>)
parsedText = parseTextFromPayload(
Uint8List.fromList(p),
);
}
}
}
@ -242,7 +269,9 @@ class NfcService {
//
onResult(uid, parsedText, rawMsg);
_logController.add('UID: $uid\nmessage: ${_formatMessageToString(rawMsg)}');
_logController.add(
'UID: $uid\nmessage: ${_formatMessageToString(rawMsg)}',
);
await stopSession();
} catch (e) {
@ -255,7 +284,6 @@ class NfcService {
},
//
pollingOptions: {NfcPollingOption.iso14443},
);
} catch (e) {
scanning.value = false;
@ -265,7 +293,9 @@ class NfcService {
}
/// Future 'UID\n文本'
Future<String> readOnceText({Duration timeout = const Duration(seconds: 10)}) async {
Future<String> readOnceText({
Duration timeout = const Duration(seconds: 10),
}) async {
final completer = Completer<String>();
await startScanOnceWithCallback(
onResult: (uid, parsedText, rawMsg) {
@ -301,8 +331,14 @@ class NfcService {
/// - onComplete: (ok, err)
///
/// true onComplete
Future<bool> writeText(String text, {Duration? timeout, void Function(bool ok, Object? err)? onComplete}) async {
Future<bool> writeText(
String text, {
Duration? timeout,
void Function(bool ok, Object? err)? onComplete,
}) async {
debugPrint('writeText called - scanning=${scanning.value}');
final available = await isAvailable();
debugPrint('writeText: isAvailable=$available');
if (!available) {
onComplete?.call(false, 'NFC not available');
return false;
@ -312,6 +348,12 @@ class NfcService {
return false;
}
try {
await NfcManager.instance.stopSession();
} catch (e) {
debugPrint('stopSession ignore: $e');
}
scanning.value = true;
Timer? timer;
if (timeout != null) {
@ -324,15 +366,40 @@ class NfcService {
bool success = false;
try {
await NfcManager.instance.startSession(onDiscovered: (dynamic tag) async {
// pollingOptions
final polling =
Platform.isIOS
? {NfcPollingOption.iso14443} // iOS 使
: {
NfcPollingOption.iso14443,
NfcPollingOption.iso15693,
NfcPollingOption.iso18092,
};
await NfcManager.instance.startSession(
pollingOptions: polling,
onDiscovered: (dynamic tag) async {
timer?.cancel();
try {
final ndef = Ndef.from(tag);
if (ndef == null) {
onComplete?.call(false, 'Tag 不支持 NDEF');
onComplete?.call(false, 'Tag not NDEF');
await stopSession();
scanning.value = false;
return;
}
//
if (Platform.isIOS) {
final miFare = MiFareIos.from(tag);
if (miFare == null) {
onComplete?.call(false, 'Unsupported tag type on iOS');
await stopSession();
scanning.value = false;
return;
}
}
final payload = _buildTextPayload(text, lang: 'en');
final record = NdefRecord(
typeNameFormat: TypeNameFormat.wellKnown,
@ -343,7 +410,6 @@ class NfcService {
final message = NdefMessage(records: [record]);
await ndef.write(message: message);
// UID ( nfca.identifier id)
String? uid;
if (Platform.isAndroid) {
@ -361,26 +427,24 @@ class NfcService {
}
} catch (_) {}
}
success = true;
onComplete?.call(true, uid); // UID
_logController.add('NFC write success, UID=$uid');
} catch (e) {
debugPrint('NFC write error: $e');
} catch (e, st) {
debugPrint('iOS NFC Write Error: $e');
debugPrint('Stack trace: $st');
onComplete?.call(false, e);
} finally {
await stopSession();
timer?.cancel();
scanning.value = false;
}
}, pollingOptions: {NfcPollingOption.iso14443});
} catch (e) {
scanning.value = false;
},
);
} catch (e, st) {
debugPrint('startSession exception: $e\n$st');
timer?.cancel();
scanning.value = false;
onComplete?.call(false, e);
}
return success;
}

View File

@ -1,12 +1,19 @@
import 'dart:math';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter/services.dart';
int getRandomWithNum(int min, int max) {
if (max < min) {
//
final tmp = min;
min = max;
max = tmp;
}
final random = Random();
return random.nextInt(max) + min; //
return random.nextInt(max - min + 1) + min; // [min, max]
}
double screenWidth(BuildContext context) {
@ -332,27 +339,48 @@ void presentPage(BuildContext context, Widget page) {
MaterialPageRoute(fullscreenDialog: true, builder: (_) => page),
);
}
class LoadingDialogHelper {
//
static void show({String? message}) {
static Timer? _timer;
/// 10
static void show({String? message, Duration timeout = const Duration(seconds: 10)}) {
//
_timer?.cancel();
if (message != null) {
EasyLoading.show(status: message);
} else {
EasyLoading.show();
}
//
_timer = Timer(timeout, () {
// dismiss访 isShow
try {
EasyLoading.dismiss();
} catch (e) {
debugPrint('EasyLoading.dismiss error: $e');
}
_timer?.cancel();
_timer = null;
});
}
//
///
static void hide() {
if (EasyLoading.isShow) {
//
_timer?.cancel();
_timer = null;
try {
EasyLoading.dismiss();
} catch (e) {
debugPrint('EasyLoading.dismiss error: $e');
}
}
}
/// HH:MM:SS
String secondsCount(dynamic seconds) {
// double