From 172ac497b6e1e02b718697f1d14a9fcdd520a867 Mon Sep 17 00:00:00 2001 From: hs <873121290@qq.com> Date: Mon, 15 Sep 2025 15:54:03 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=82=E3=80=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/AppDelegate.swift | 151 +++--- ios/Runner/Info.plist | 182 +++---- lib/customWidget/photo_picker_row.dart | 464 +++++++++++------- lib/customWidget/remote_file_page.dart | 3 +- lib/http/ApiService.dart | 5 +- lib/http/HttpManager.dart | 2 +- lib/pages/home/NFC/home_nfc_list_page.dart | 5 +- .../safeCheck_assignment_detail_page.dart | 1 + .../home/SafeCheck/SafeCheckFormView.dart | 2 + .../Start/safeCheck_start_detail.dart | 1 + .../Start/safeCheck_start_list_page.dart | 4 +- lib/pages/home/home_page.dart | 4 - lib/pages/home/low_page.dart | 2 +- lib/pages/home/risk/riskControl_page.dart | 2 +- lib/pages/home/risk/risk_detail_page.dart | 2 +- .../home/study/face_ecognition_page.dart | 370 +++++++++++--- .../study/strengthen_video_study_page.dart | 186 ++++--- lib/pages/home/study/study_detail_page.dart | 283 +++++++---- lib/pages/home/study/study_my_task_page.dart | 449 +++++++++-------- lib/pages/home/study/study_score_page.dart | 11 +- lib/pages/home/study/take_exam_page.dart | 29 +- .../home/study/video_study_detail_page.dart | 11 +- lib/pages/home/tap/item_list_widget.dart | 72 ++- .../special_wrok/MeasuresListWidget.dart | 114 +++-- .../special_wrok/dangerous_options_page.dart | 27 +- .../dh_work/HotWorkDetailFormWidget.dart | 21 +- .../aqjd_work_detail/hotwork_aqjd_detail.dart | 24 +- .../dh_work_detai/hotwork_apply_detail.dart | 6 +- .../dh_work/hotwork_list_page.dart | 13 +- .../hotwork_set_safe_detail.dart | 22 +- .../ysgd_work_detail/hotwork_ysgd_detail.dart | 32 +- .../dl_work/CutroadDetailFormWidget.dart | 16 +- .../aqjd_work_detail/cutroad_aqjd_detail.dart | 24 +- .../dl_work/cutroad_list_page.dart | 14 +- .../dl_work_detai/cutroad_apply_detail.dart | 1 + .../cutroad_set_safe_detail.dart | 23 +- .../ysgd_work_detail/cutroad_ysgd_detail.dart | 32 +- .../zyr_work_detail/cutroad_zyr_detail.dart | 24 +- .../dt_work/BreakgroundDetailFormWidget.dart | 37 +- .../breakground_aqjd_detail.dart | 24 +- .../dt_work/breakground_list_page.dart | 15 +- .../breakground_apply_detail.dart | 1 + .../breakground_set_safe_detail.dart | 23 +- .../breakground_ysgd_detail.dart | 32 +- .../dz_work/HoistworkDetailFormWidget.dart | 107 ++-- .../dz_work/SpecialWorkFormBaseWork.dart | 6 +- .../hoistwork_aqjd_detail.dart | 24 +- .../dz_work_detai/hoistwork_apply_detail.dart | 5 +- .../dz_work/hoistwork_list_page.dart | 14 +- .../hoistwork_set_safe_detail.dart | 23 +- .../hoistwork_ysgd_detail.dart | 32 +- .../gc_work/HighWorkDetailFormWidget.dart | 68 ++- .../highwork_aqjd_detail.dart | 24 +- .../gc_work_detai/highwork_apply_detail.dart | 31 +- .../gc_work/highwork_list_page.dart | 13 +- .../highwork_set_safe_detail.dart | 23 +- .../highwork_ysgd_detail.dart | 34 +- .../ElectricityDetailFormWidget.dart | 17 +- .../lsyd_work/SpecialWorkFormBaseWork.dart | 40 +- .../electricity_aqjd_detail.dart | 24 +- .../lsyd_work/electricity_list_page.dart | 14 +- .../electricity_apply_detail.dart | 1 + .../electricity_set_safe_detail.dart | 23 +- .../electricity_ysgd_detail.dart | 32 +- .../mbcd_work/BlindboardDetailFormWidget.dart | 21 +- .../blindboard_safe_func_sure.dart | 14 + .../blindboard_aqjd_detail.dart | 24 +- .../mbcd_work/blindboard_list_page.dart | 14 +- .../blindboard_cjry_detail.dart | 24 +- .../blindboard_apply_detail.dart | 1 + .../blindboard_set_safe_detail.dart | 23 +- .../blindboard_ysgd_detail.dart | 32 +- .../sxkj_work/SpaceWorkDetailFormWidget.dart | 27 +- .../sxkj_work/SpecialWorkFormBaseWork.dart | 20 +- .../spacework_aqjd_detail.dart | 24 +- .../spacework_apply_detail.dart | 1 + .../sxkj_work/spacework_list_page.dart | 14 +- .../spacework_set_safe_detail.dart | 23 +- .../spacework_ysgd_detail.dart | 32 +- lib/pages/home/userInfo_page.dart | 2 +- lib/pages/home/work/custom_driver_drawer.dart | 2 +- .../home/work/laws_regulations_page.dart | 2 +- .../home/work/laws_regulations_two_page.dart | 2 +- lib/pages/home/workSet_page.dart | 2 +- lib/pages/login_page.dart | 35 +- lib/pages/mine/mine_feedback_page.dart | 2 +- lib/pages/mine/mine_set_pwd_page.dart | 2 +- lib/services/auth_service.dart | 30 +- lib/tools/tools.dart | 77 ++- pubspec.lock | 14 +- pubspec.yaml | 12 +- 91 files changed, 2343 insertions(+), 1454 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index fb52aaf..c2476be 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,77 +1,94 @@ - import UIKit - import Flutter +import UIKit +import Flutter +import AVFoundation - @main - @objc class AppDelegate: FlutterAppDelegate { - // 动态方向掩码(默认竖屏) - static var orientationMask: UIInterfaceOrientationMask = .portrait +@main +@objc class AppDelegate: FlutterAppDelegate { + // 动态方向掩码(默认竖屏) + static var orientationMask: UIInterfaceOrientationMask = .portrait - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { - let controller = window?.rootViewController as! FlutterViewController - let channel = FlutterMethodChannel(name: "app.orientation", + let controller = window?.rootViewController as! FlutterViewController + + // 1) orientation channel (你已有) + let orientationChannel = FlutterMethodChannel(name: "app.orientation", binaryMessenger: controller.binaryMessenger) + orientationChannel.setMethodCallHandler { [weak self] call, result in + guard let self = self else { return } - channel.setMethodCallHandler { [weak self] call, result in - guard let self = self else { return } - - if call.method == "setOrientation" { - guard let arg = call.arguments as? String else { - result(FlutterError(code: "BAD_ARGS", message: "need 'landscape' | 'portrait'", details: nil)) - return - } - - // 先更新允许的方向掩码 - if arg == "landscape" { - AppDelegate.orientationMask = .landscape - } else if arg == "portrait" { - AppDelegate.orientationMask = .portrait - } else { - result(FlutterError(code: "BAD_ARGS", message: "unknown arg", details: nil)) - return - } - - - // 再请求实际旋转 - if #available(iOS 16.0, *) { - // 通知顶层 VC:其 supportedInterfaceOrientations 需要刷新 - self.window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations() - - if let scene = self.window?.windowScene { - let orientations: UIInterfaceOrientationMask = - (arg == "landscape") ? .landscape : .portrait - do { - try scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations)) - } catch { - result(FlutterError(code: "GEOMETRY_UPDATE_FAILED", - message: error.localizedDescription, details: nil)) - return - } - } - } else { - let target: UIInterfaceOrientation = - (arg == "landscape") ? .landscapeLeft : .portrait - UIDevice.current.setValue(target.rawValue, forKey: "orientation") - UIViewController.attemptRotationToDeviceOrientation() - } - - result(true) - } else { - result(FlutterMethodNotImplemented) - } + if call.method == "setOrientation" { + guard let arg = call.arguments as? String else { + result(FlutterError(code: "BAD_ARGS", message: "need 'landscape' | 'portrait'", details: nil)) + return } - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + // 先更新允许的方向掩码 + if arg == "landscape" { + AppDelegate.orientationMask = .landscape + } else if arg == "portrait" { + AppDelegate.orientationMask = .portrait + } else { + result(FlutterError(code: "BAD_ARGS", message: "unknown arg", details: nil)) + return + } - // 关键:把当前的掩码提供给系统 - override func application(_ application: UIApplication, - supportedInterfaceOrientationsFor window: UIWindow?) - -> UIInterfaceOrientationMask { - return AppDelegate.orientationMask + // 再请求实际旋转 + if #available(iOS 16.0, *) { + // 通知顶层 VC:其 supportedInterfaceOrientations 需要刷新 + self.window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations() + + if let scene = self.window?.windowScene { + let orientations: UIInterfaceOrientationMask = + (arg == "landscape") ? .landscape : .portrait + do { + try scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations)) + } catch { + result(FlutterError(code: "GEOMETRY_UPDATE_FAILED", + message: error.localizedDescription, details: nil)) + return + } + } + } else { + let target: UIInterfaceOrientation = + (arg == "landscape") ? .landscapeLeft : .portrait + UIDevice.current.setValue(target.rawValue, forKey: "orientation") + UIViewController.attemptRotationToDeviceOrientation() + } + + result(true) + } else { + result(FlutterMethodNotImplemented) } } + + // 2) permissions channel (新增): 用于 iOS 原生请求相机访问权限 + let permissionChannel = FlutterMethodChannel(name: "qhd_prevention/permissions", + binaryMessenger: controller.binaryMessenger) + permissionChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in + if call.method == "requestCameraAccess" { + // AVCaptureDevice.requestAccess 会在首次调用时触发系统权限弹窗 + AVCaptureDevice.requestAccess(for: .video) { granted in + DispatchQueue.main.async { + result(granted) + } + } + } else { + result(FlutterMethodNotImplemented) + } + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // 关键:把当前的掩码提供给系统 + override func application(_ application: UIApplication, + supportedInterfaceOrientationsFor window: UIWindow?) + -> UIInterfaceOrientationMask { + return AppDelegate.orientationMask + } +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 29fd9ff..97cc4a1 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,98 +1,98 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDisplayName - 智守安全 - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - 智守安全 - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - LSApplicationQueriesSchemes - - baidumap - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - NFCReaderUsageDescription - 需要NFC权限来读取和写入标签 - NSAppTransportSecurity - NSAllowsArbitraryLoads + CADisableMinimumFrameDurationOnPhone + CFBundleDisplayName + 智守安全 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + 智守安全 + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + LSApplicationQueriesSchemes + + baidumap + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NFCReaderUsageDescription + 需要NFC权限来读取和写入标签 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSBluetoothAlwaysUsageDescription + app需要蓝牙权限连接设备 + NSCameraUsageDescription + app需要相机权限来扫描二维码 + NSContactsUsageDescription + app需要通讯录权限添加好友 + NSHealthShareUsageDescription + app需要读取健康数据 + NSHealthUpdateUsageDescription + app需要写入健康数据 + NSLocalNetworkUsageDescription + app需要发现本地网络设备 + NSLocationAlwaysAndWhenInUseUsageDescription + app需要后台定位以实现持续跟踪 + NSLocationAlwaysUsageDescription + 需要位置权限以提供定位服务 + NSLocationWhenInUseUsageDescription + 需要位置权限以提供定位服务 + NSMicrophoneUsageDescription + app需要麦克风权限进行语音通话 + NSMotionUsageDescription + app需要访问运动数据统计步数 + NSPhotoLibraryAddUsageDescription + app需要保存图片到相册 + NSPhotoLibraryUsageDescription + app需要访问相册以上传图片 + NSUserNotificationsUsageDescription + app需要发送通知提醒重要信息 + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + com.apple.developer.nfc.readersession.formats + + NDEF + TAG + - NSBluetoothAlwaysUsageDescription - app需要蓝牙权限连接设备 - NSCameraUsageDescription - app需要相机权限来扫描二维码 - NSContactsUsageDescription - app需要通讯录权限添加好友 - NSHealthShareUsageDescription - app需要读取健康数据 - NSHealthUpdateUsageDescription - app需要写入健康数据 - NSLocalNetworkUsageDescription - app需要发现本地网络设备 - NSLocationAlwaysAndWhenInUseUsageDescription - app需要后台定位以实现持续跟踪 - NSLocationAlwaysUsageDescription - 需要位置权限以提供定位服务 - NSLocationWhenInUseUsageDescription - 需要位置权限以提供定位服务 - NSMicrophoneUsageDescription - app需要麦克风权限进行语音通话 - NSMotionUsageDescription - app需要访问运动数据统计步数 - NSPhotoLibraryAddUsageDescription - app需要保存图片到相册 - NSPhotoLibraryUsageDescription - app需要访问相册以上传图片 - NSUserNotificationsUsageDescription - app需要发送通知提醒重要信息 - UIApplicationSupportsIndirectInputEvents - - UIBackgroundModes - - remote-notification - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - com.apple.developer.nfc.readersession.formats - - NDEF - TAG - - diff --git a/lib/customWidget/photo_picker_row.dart b/lib/customWidget/photo_picker_row.dart index c3c20f8..d4ae8a1 100644 --- a/lib/customWidget/photo_picker_row.dart +++ b/lib/customWidget/photo_picker_row.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; @@ -15,11 +17,73 @@ import 'package:photo_manager/photo_manager.dart'; import 'package:path/path.dart' as p; import 'ItemWidgetFactory.dart'; +const String kAcceptVideoSectionKey = 'accept_video'; + +/// ---------------------- 全局 MediaBus (轻量事件总线) ---------------------- +class MediaEvent { + final String key; + final MediaEventType type; + final List? paths; + + MediaEvent._(this.key, this.type, [this.paths]); + + factory MediaEvent.clear(String key) => MediaEvent._(key, MediaEventType.clear); + + factory MediaEvent.set(String key, List paths) => + MediaEvent._(key, MediaEventType.set, List.from(paths)); +} + +enum MediaEventType { clear, set } + +class MediaBus { + MediaBus._internal(); + static final MediaBus _instance = MediaBus._internal(); + factory MediaBus() => _instance; + + final StreamController _ctrl = StreamController.broadcast(); + + Stream get stream => _ctrl.stream; + + void emit(MediaEvent ev) { + if (!_ctrl.isClosed) _ctrl.add(ev); + } + + Future dispose() async { + await _ctrl.close(); + } +} +/// ---------------------- /MediaBus ---------------------- + + /// 媒体选择类型 enum MediaType { image, video } -/// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示 -/// 新增 isEdit 属性控制编辑状态 +/// ---------- 辅助函数(文件顶部复用) ---------- +bool _isNetworkPath(String? p) { + if (p == null) return false; + final s = p.trim().toLowerCase(); + return s.startsWith('http://') || s.startsWith('https://'); +} + +/// 把路径列表转换为存在的本地 File 列表(过滤掉网络路径与空路径与不存在的本地文件) +List _localFilesFromPaths(List? paths) { + if (paths == null) return []; + return paths + .map((e) => (e ?? '').toString().trim()) + .where((s) => s.isNotEmpty && !_isNetworkPath(s)) + .where((s) => File(s).existsSync()) // 只回调真实存在的本地文件 + .map((s) => File(s)) + .toList(); +} + +/// 规范化路径列表:trim + 过滤空字符串 +List _normalizePaths(List? src) { + if (src == null) return []; + return src.map((e) => (e ?? '').toString().trim()).where((s) => s.isNotEmpty).toList(); +} + +/// ---------- MediaPickerRow ---------- +/// ---------- MediaPickerRow ---------- class MediaPickerRow extends StatefulWidget { final int maxCount; final MediaType mediaType; @@ -27,9 +91,11 @@ class MediaPickerRow extends StatefulWidget { final ValueChanged> onChanged; final ValueChanged? onMediaAdded; final ValueChanged? onMediaRemoved; - final ValueChanged? onMediaTapped; // 新增:媒体点击回调 - final bool isEdit; // 新增:控制编辑状态 - final bool isCamera; // 新增:只能拍照 + final ValueChanged? onMediaTapped; + final bool isEdit; + final bool isCamera; + /// 默认 false —— 仅在 initState 时读取 initialMediaPaths + final bool followInitialUpdates; const MediaPickerRow({ Key? key, @@ -39,9 +105,10 @@ class MediaPickerRow extends StatefulWidget { required this.onChanged, this.onMediaAdded, this.onMediaRemoved, - this.onMediaTapped, // 新增 - this.isEdit = true, // 默认可编辑 + this.onMediaTapped, + this.isEdit = true, this.isCamera = false, + this.followInitialUpdates = false, // 默认 false }) : super(key: key); @override @@ -51,25 +118,41 @@ class MediaPickerRow extends StatefulWidget { class _MediaPickerGridState extends State { final ImagePicker _picker = ImagePicker(); late List _mediaPaths; - bool _isProcessing = false; // 转码或处理时显示 loading + bool _isProcessing = false; @override void initState() { super.initState(); - _mediaPaths = - widget.initialMediaPaths != null - ? widget.initialMediaPaths!.take(widget.maxCount).toList() - : []; - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onChanged( - _mediaPaths - .map((p) => p.startsWith('http') ? File('') : File(p)) - .toList(), - ); - }); + // 初始化内部路径(保留网络路径与本地路径) + _mediaPaths = _normalizePaths(widget.initialMediaPaths).take(widget.maxCount).toList(); + + // 仅在存在本地真实文件时才把 File 列表回调给外部(避免父组件用这个回调覆盖只有网络路径的数据) + final initialLocalFiles = _localFilesFromPaths(_mediaPaths); + if (initialLocalFiles.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + widget.onChanged(initialLocalFiles); + }); + } + } + + @override + void didUpdateWidget(covariant MediaPickerRow oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.followInitialUpdates) { + final oldList = _normalizePaths(oldWidget.initialMediaPaths); + final newList = _normalizePaths(widget.initialMediaPaths); + + if (!listEquals(oldList, newList)) { + _mediaPaths = newList.take(widget.maxCount).toList(); + if (mounted) setState(() {}); + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + }); + } + } } - // 公共:当得到本地媒体路径时(可能是 mov/avi 等),需要在这里统一处理(转码、入队、回调) Future _handlePickedPath(String path) async { if (!mounted) return; if (path.isEmpty) return; @@ -77,7 +160,6 @@ class _MediaPickerGridState extends State { try { String finalPath = path; - // 如果是视频并且不是 mp4,则调用 video_compress 转码 if (widget.mediaType == MediaType.video) { final ext = p.extension(path).toLowerCase(); if (ext != '.mp4') { @@ -106,10 +188,10 @@ class _MediaPickerGridState extends State { } } - // 添加到列表 if (_mediaPaths.length < widget.maxCount) { setState(() => _mediaPaths.add(finalPath)); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + // 回调仅包含本地真实存在的文件(网络路径不会出现在此回调中) + widget.onChanged(_localFilesFromPaths(_mediaPaths)); widget.onMediaAdded?.call(finalPath); } } catch (e) { @@ -118,54 +200,41 @@ class _MediaPickerGridState extends State { } Future _showPickerOptions() async { - if (!widget.isEdit) return; // 不可编辑时直接返回 + if (!widget.isEdit) return; showModalBottomSheet( context: context, backgroundColor: Colors.white, - builder: - (_) => SafeArea( - child: Wrap( - children: [ - ListTile( - titleAlignment: ListTileTitleAlignment.center, - leading: Icon( - widget.mediaType == MediaType.image - ? Icons.camera_alt - : Icons.videocam, - ), - title: Text( - widget.mediaType == MediaType.image ? '拍照' : '拍摄视频', - ), - onTap: () { - Navigator.of(context).pop(); - _pickCamera(); - }, - ), - ListTile( - titleAlignment: ListTileTitleAlignment.center, - leading: Icon( - widget.mediaType == MediaType.image - ? Icons.photo_library - : Icons.video_library, - ), - title: Text( - widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频', - ), - onTap: () { - Navigator.of(context).pop(); - _pickGallery(); - }, - ), - ListTile( - titleAlignment: ListTileTitleAlignment.center, - leading: const Icon(Icons.close), - title: const Text('取消'), - onTap: () => Navigator.of(context).pop(), - ), - ], + builder: (_) => SafeArea( + child: Wrap( + children: [ + ListTile( + titleAlignment: ListTileTitleAlignment.center, + leading: Icon(widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam), + title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'), + onTap: () { + Navigator.of(context).pop(); + _pickCamera(); + }, ), - ), + ListTile( + titleAlignment: ListTileTitleAlignment.center, + leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library), + title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'), + onTap: () { + Navigator.of(context).pop(); + _pickGallery(); + }, + ), + ListTile( + titleAlignment: ListTileTitleAlignment.center, + leading: const Icon(Icons.close), + title: const Text('取消'), + onTap: () => Navigator.of(context).pop(), + ), + ], + ), + ), ); } @@ -179,7 +248,7 @@ class _MediaPickerGridState extends State { if (picked != null) { final path = picked.path; setState(() => _mediaPaths.add(path)); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); widget.onMediaAdded?.call(path); } } else { @@ -193,31 +262,26 @@ class _MediaPickerGridState extends State { } } - // 修改 _pickGallery 方法 Future _pickGallery() async { if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return; try { - // iOS: 使用 PhotoManager.requestPermissionExtend() 唤起系统权限弹窗(支持 limited) if (Platform.isIOS) { final permission = await PhotoManager.requestPermissionExtend(); debugPrint('iOS photo permission state: $permission'); - if (permission != PermissionState.authorized && - permission != PermissionState.limited) { + if (permission != PermissionState.authorized && permission != PermissionState.limited) { if (mounted) { ToastUtil.showNormal(context, '请到设置中开启相册访问权限'); } return; } - // 授权或 limited:继续打开 AssetPicker final remaining = widget.maxCount - _mediaPaths.length; final List? assets = await AssetPicker.pickAssets( context, pickerConfig: AssetPickerConfig( - requestType: - widget.mediaType == MediaType.image ? RequestType.image : RequestType.video, + requestType: widget.mediaType == MediaType.image ? RequestType.image : RequestType.video, maxAssets: remaining, gridCount: 4, ), @@ -239,11 +303,9 @@ class _MediaPickerGridState extends State { } } if (mounted) setState(() {}); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); } - } else { - // Android: 更可靠地请求权限(适配不同 Android 版本) final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; final int sdkInt = androidInfo.version.sdkInt ?? 0; @@ -251,28 +313,21 @@ class _MediaPickerGridState extends State { PermissionStatus permissionStatus = PermissionStatus.denied; if (sdkInt >= 33) { - // Android 13+: 使用更细粒度的媒体权限 - // 如果应用只选图片,request photos;只选视频则 request videos;若两者都可能同时需要,可请求两个。 if (widget.mediaType == MediaType.image) { - permissionStatus = await Permission.photos.request(); // maps to READ_MEDIA_IMAGES on Android + permissionStatus = await Permission.photos.request(); } else if (widget.mediaType == MediaType.video) { - permissionStatus = await Permission.videos.request(); // maps to READ_MEDIA_VIDEO + permissionStatus = await Permission.videos.request(); } else { - // 两者皆可(同时请求会合并成单个系统对话) final statuses = await [Permission.photos, Permission.videos].request(); permissionStatus = statuses[Permission.photos] ?? statuses[Permission.videos] ?? PermissionStatus.denied; } } else if (sdkInt >= 30) { - // Android 11/12: 通常仍然使用 READ_EXTERNAL_STORAGE;若需要“全部文件访问”,要 request manageExternalStorage(慎用) permissionStatus = await Permission.storage.request(); } else { - // Android 10 及以下 permissionStatus = await Permission.storage.request(); } - // 处理结果 if (permissionStatus.isGranted) { - // 有权限:继续打开 AssetPicker final remaining = widget.maxCount - _mediaPaths.length; final List? assets = await AssetPicker.pickAssets( context, @@ -299,10 +354,9 @@ class _MediaPickerGridState extends State { } } if (mounted) setState(() {}); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); } } else if (permissionStatus.isPermanentlyDenied) { - // 用户点了“不再询问”或系统拒绝弹窗,跳转设置页并提示 if (mounted) { ToastUtil.showNormal(context, '请到设置中开启相册访问权限'); } @@ -318,25 +372,19 @@ class _MediaPickerGridState extends State { } catch (e, st) { debugPrint('相册选择失败: $e\n$st'); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('相册选择失败')), - ); + ToastUtil.showNormal(context, '相册选择失败'); } } } - // 修改拍照方法,使用 permission_handler 请求相机权限 Future _cameraAction() async { if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return; - // 请求相机权限 final PermissionStatus status = await Permission.camera.request(); if (status != PermissionStatus.granted) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('相机权限被拒绝'))); + ToastUtil.showNormal(context, '相机权限被拒绝'); } return; } @@ -346,11 +394,10 @@ class _MediaPickerGridState extends State { if (picked != null) { final path = picked.path; setState(() => _mediaPaths.add(path)); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); widget.onMediaAdded?.call(path); } } else { - // video from camera XFile? picked = await _picker.pickVideo(source: ImageSource.camera); if (picked != null) { await _handlePickedPath(picked.path); @@ -358,11 +405,8 @@ class _MediaPickerGridState extends State { } } catch (e) { debugPrint('拍摄失败: $e'); - // 友好提示 if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('拍摄失败,请检查相机权限或设备状态'))); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('拍摄失败,请检查相机权限或设备状态'))); } } } @@ -371,16 +415,22 @@ class _MediaPickerGridState extends State { final ok = await CustomAlertDialog.showConfirm( context, title: '温馨提示', - content: - widget.mediaType == MediaType.image ? '确定要删除这张图片吗?' : '确定要删除这个视频吗?', + content: widget.mediaType == MediaType.image ? '确定要删除这张图片吗?' : '确定要删除这个视频吗?', cancelText: '取消', ); - if (ok) { - final removed = _mediaPaths[index]; - setState(() => _mediaPaths.removeAt(index)); - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); - widget.onMediaRemoved?.call(removed); - } + if (!ok) return; + + final removed = _mediaPaths[index]; + final wasNetwork = _isNetworkPath(removed); + + setState(() => _mediaPaths.removeAt(index)); + + // 始终通知 onMediaRemoved(用于父端业务逻辑) + widget.onMediaRemoved?.call(removed); + + // 只有当本地文件集合发生变化时才触发 onChanged(避免因为删除网络路径导致父端把列表置空) + final localFiles = _localFilesFromPaths(_mediaPaths); + widget.onChanged(localFiles); } @override @@ -401,72 +451,71 @@ class _MediaPickerGridState extends State { ), itemCount: itemCount, itemBuilder: (context, index) { - // 显示媒体项 if (index < _mediaPaths.length) { - final path = _mediaPaths[index]; - final isNetwork = path.startsWith('http'); + final raw = (_mediaPaths[index] ?? '').toString().trim(); + final isNetwork = _isNetworkPath(raw); return GestureDetector( - onTap: () => widget.onMediaTapped?.call(path), + onTap: () => widget.onMediaTapped?.call(raw), child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(5), child: SizedBox.expand( - // 让内容强制填满格子 - child: - widget.mediaType == MediaType.image - ? (isNetwork - ? Image.network( - path, - fit: BoxFit.cover, // 铺满格子并裁剪 - ) - : Image.file( - File(path), - fit: BoxFit.cover, // 铺满格子并裁剪 - )) - : Container( - color: Colors.black12, - child: const Center( - child: Icon( - Icons.videocam, - color: Colors.white70, - ), - ), - ), + child: raw.isEmpty + ? Container( + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image, size: 28, color: Colors.grey)), + ) + : (widget.mediaType == MediaType.image + ? (isNetwork + ? Image.network( + raw, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ), + ) + : (File(raw).existsSync() + ? Image.file( + File(raw), + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ), + ) + : Container( + color: Colors.grey.shade200, + child: const Center(child: Icon(Icons.broken_image)), + ))) + : Container( + color: Colors.black12, + child: const Center( + child: Icon(Icons.videocam, color: Colors.white70), + ), + )), ), ), - - // 只在可编辑状态下显示删除按钮 if (widget.isEdit) Positioned( top: -15, right: -15, child: IconButton( - icon: const Icon( - Icons.cancel, - size: 20, - color: Colors.red, - ), + icon: const Icon(Icons.cancel, size: 20, color: Colors.red), onPressed: () => _removeMedia(index), ), ), ], ), ); - } - // 显示添加按钮 - else if (showAddButton) { + } else if (showAddButton) { return GestureDetector( onTap: widget.isCamera ? _cameraAction : _showPickerOptions, child: Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.black12), - borderRadius: BorderRadius.circular(5), - ), - child: const Center( - child: Icon(Icons.camera_alt, color: Colors.black26), - ), + decoration: BoxDecoration(border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(5)), + child: const Center(child: Icon(Icons.camera_alt, color: Colors.black26)), ), ); } else { @@ -474,8 +523,6 @@ class _MediaPickerGridState extends State { } }, ), - - // 转码/处理 loading 遮罩 if (_isProcessing) Positioned.fill( child: Container( @@ -488,8 +535,8 @@ class _MediaPickerGridState extends State { } } -/// 照片上传区域组件,使用纵向四列Grid展示 -/// 新增 isEdit 属性控制编辑状态 + +/// ---------- RepairedPhotoSection ---------- class RepairedPhotoSection extends StatefulWidget { final int maxCount; final MediaType mediaType; @@ -498,14 +545,17 @@ class RepairedPhotoSection extends StatefulWidget { final ValueChanged> onChanged; final ValueChanged? onMediaAdded; final ValueChanged? onMediaRemoved; - final ValueChanged? onMediaTapped; // 新增:媒体点击回调 + final ValueChanged? onMediaTapped; final VoidCallback onAiIdentify; final bool isShowAI; final double horizontalPadding; final bool isRequired; final bool isShowNum; - final bool isEdit; // 新增:控制编辑状态 - final bool isCamera; // 新增:只能拍照 + final bool isEdit; + final bool isCamera; + final String sectionKey; + final bool followInitialUpdates; + const RepairedPhotoSection({ Key? key, @@ -519,11 +569,13 @@ class RepairedPhotoSection extends StatefulWidget { this.horizontalPadding = 5, this.onMediaAdded, this.onMediaRemoved, - this.onMediaTapped, // 新增 + this.onMediaTapped, this.isRequired = false, this.isShowNum = true, - this.isEdit = true, // 默认可编辑 + this.isEdit = true, this.isCamera = false, + this.followInitialUpdates = false, // 默认 false + this.sectionKey = kAcceptVideoSectionKey, }) : super(key: key); @override @@ -532,17 +584,81 @@ class RepairedPhotoSection extends StatefulWidget { class _RepairedPhotoSectionState extends State { late List _mediaPaths; + StreamSubscription? _sub; @override void initState() { super.initState(); - _mediaPaths = - widget.initialMediaPaths?.take(widget.maxCount).toList() ?? []; - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); + _mediaPaths = _normalizePaths(widget.initialMediaPaths).take(widget.maxCount).toList(); + + // 订阅 MediaBus(如果需要) + _sub = MediaBus().stream.listen((ev) { + if (ev.key != widget.sectionKey) return; + + if (ev.type == MediaEventType.clear) { + if (_mediaPaths.isNotEmpty) { + setState(() { + _mediaPaths = []; + }); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + } + } else if (ev.type == MediaEventType.set && ev.paths != null) { + final newList = _normalizePaths(ev.paths).take(widget.maxCount).toList(); + if (!listEquals(newList, _mediaPaths)) { + setState(() { + _mediaPaths = newList; + }); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + } + } }); } + @override + void didUpdateWidget(covariant RepairedPhotoSection oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.followInitialUpdates) { + final oldList = _normalizePaths(oldWidget.initialMediaPaths); + final newList = _normalizePaths(widget.initialMediaPaths); + + if (!listEquals(oldList, newList)) { + setState(() { + _mediaPaths = newList.take(widget.maxCount).toList(); + }); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + } + } + if (oldWidget.sectionKey != widget.sectionKey) { + _sub?.cancel(); + _sub = MediaBus().stream.listen((ev) { + if (ev.key != widget.sectionKey) return; + + if (ev.type == MediaEventType.clear) { + if (_mediaPaths.isNotEmpty) { + setState(() { + _mediaPaths = []; + }); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + } + } else if (ev.type == MediaEventType.set && ev.paths != null) { + final newList = _normalizePaths(ev.paths).take(widget.maxCount).toList(); + if (!listEquals(newList, _mediaPaths)) { + setState(() { + _mediaPaths = newList; + }); + widget.onChanged(_localFilesFromPaths(_mediaPaths)); + } + } + }); + } + } + + @override + void dispose() { + _sub?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Container( @@ -555,10 +671,7 @@ class _RepairedPhotoSectionState extends State { padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), child: ListItemFactory.createRowSpaceBetweenItem( leftText: widget.title, - rightText: - widget.isShowNum - ? '${_mediaPaths.length}/${widget.maxCount}' - : '', + rightText: widget.isShowNum ? '${_mediaPaths.length}/${widget.maxCount}' : '', isRequired: widget.isRequired, ), ), @@ -582,24 +695,21 @@ class _RepairedPhotoSectionState extends State { onMediaTapped: (filePath) { if (widget.mediaType == MediaType.image) { presentOpaque(SingleImageViewer(imageUrl: filePath), context); - }else{ + } else { showDialog( context: context, barrierColor: Colors.black54, - builder: (_) => VideoPlayerPopup(videoUrl:filePath), + builder: (_) => VideoPlayerPopup(videoUrl: filePath), ); } }, - // 传递点击回调 - isEdit: widget.isEdit, // 传递编辑状态 + isEdit: widget.isEdit, ), ), const SizedBox(height: 8), - if (widget.isShowAI && widget.isEdit) // 只在可编辑状态下显示AI按钮 + if (widget.isShowAI && widget.isEdit) Padding( - padding: EdgeInsets.symmetric( - horizontal: widget.horizontalPadding, - ), + padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), child: GestureDetector( onTap: widget.onAiIdentify, child: Container( diff --git a/lib/customWidget/remote_file_page.dart b/lib/customWidget/remote_file_page.dart index 3cc8803..dd637b0 100644 --- a/lib/customWidget/remote_file_page.dart +++ b/lib/customWidget/remote_file_page.dart @@ -39,6 +39,7 @@ class _RemoteFilePageState extends State { _secondsRemaining = widget.countdownSeconds; _startCountdown(); _downloadAndLoad(); + LoadingDialogHelper.hide(); } Future _downloadAndLoad() async { @@ -135,7 +136,7 @@ class _RemoteFilePageState extends State { onPressed: isButtonEnabled ? () { // TODO: 完成回调 - Navigator.pop(context); + Navigator.pop(context, true); } : null, ), diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index 840b726..4a08048 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -22,8 +22,8 @@ class ApiService { // static const String baseFacePath = "https://qaaqwh.qhdsafety.com/whb_stu_face"; static const String baseFacePath = "http://192.168.20.240:8500/whb_stu_face/"; /// 登录及其他管理后台接口 - // static const String basePath = "http://192.168.20.240:8500/integrated_whb";//测试服务器 // 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.0.25:28199";//王轩服务器 static const String basePath = "http://192.168.0.45:28199";//长久服务器 @@ -958,7 +958,8 @@ U6Hzm1ninpWeE+awIDAQAB static Future> getWorkAreaList() { return HttpManager().request( basePath, - '/app/electronicFence/listTree', + // '/app/electronicFence/listTree', + '/app/electronicFence/simple-list', method: Method.post, data: {"CORPINFO_ID": SessionService.instance.corpinfoId}, ); diff --git a/lib/http/HttpManager.dart b/lib/http/HttpManager.dart index 9d8c1fb..b9725a6 100644 --- a/lib/http/HttpManager.dart +++ b/lib/http/HttpManager.dart @@ -155,7 +155,6 @@ extension HttpManagerUpload on HttpManager { contentType: 'multipart/form-data', ), ); - final json = resp.data is Map ? resp.data as Map : {}; @@ -166,6 +165,7 @@ extension HttpManagerUpload on HttpManager { if (e.error is ApiException) { throw e.error as ApiException; } + return {'network_error': e.message ?? e.toString()}; throw ApiException('network_error', e.message ?? e.toString()); } } diff --git a/lib/pages/home/NFC/home_nfc_list_page.dart b/lib/pages/home/NFC/home_nfc_list_page.dart index 5bba899..503a2fd 100644 --- a/lib/pages/home/NFC/home_nfc_list_page.dart +++ b/lib/pages/home/NFC/home_nfc_list_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.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_add_page.dart'; import 'package:qhd_prevention/pages/home/NFC/home_nfc_detail_page.dart'; @@ -241,9 +242,7 @@ class _HomeNfcListPageState extends State } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取巡检记录失败:$e'))); + ToastUtil.showNormal(context, '获取巡检记录失败:$e'); } } finally { if (mounted) { diff --git a/lib/pages/home/SafeCheck/DangeCheck/safeCheck_assignment_detail_page.dart b/lib/pages/home/SafeCheck/DangeCheck/safeCheck_assignment_detail_page.dart index ee8fa1c..cef5f77 100644 --- a/lib/pages/home/SafeCheck/DangeCheck/safeCheck_assignment_detail_page.dart +++ b/lib/pages/home/SafeCheck/DangeCheck/safeCheck_assignment_detail_page.dart @@ -281,6 +281,7 @@ class _SafecheckAssignmentDetailPageState isRequired: false, isClean: false, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() {}); }, text: form['RECTIFICATIONDEPTNAME'] ?? '请选择', diff --git a/lib/pages/home/SafeCheck/SafeCheckFormView.dart b/lib/pages/home/SafeCheck/SafeCheckFormView.dart index 0355d2d..fb6ea35 100644 --- a/lib/pages/home/SafeCheck/SafeCheckFormView.dart +++ b/lib/pages/home/SafeCheck/SafeCheckFormView.dart @@ -6,6 +6,7 @@ import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/dotted_border_box.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/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart'; import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart'; @@ -59,6 +60,7 @@ class _SafeCheckFormViewState extends State { label: '${index + 1}.检查人员单位:', isEditable: false, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() {}); }, text: item['INSPECTION_DEPARTMENT_NAME'] ?? '请选择', diff --git a/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart b/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart index 305a713..a42a123 100644 --- a/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart +++ b/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart @@ -700,6 +700,7 @@ class _SafecheckStartDetailState extends State { label: '${index + 1}.检查人员单位:', isEditable: index == 0 ? false : _isEdit, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() {}); }, text: item['INSPECTION_DEPARTMENT_NAME'] ?? '请选择', diff --git a/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart b/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart index f531ac4..0d0113a 100644 --- a/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart +++ b/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart @@ -138,9 +138,7 @@ class _SafecheckStartListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + ToastUtil.showNormal(context, '获取流程图失败: $e'); } } diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 311171d..7a34e03 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -334,10 +334,6 @@ class HomePageState extends State { final data = await ApiService.getWork(); final hidCount = data['hidCount'] as Map? ?? {}; - // // 告知BadgeManager去更新“安全巡检”和“八项作业”角标 - // BadgeManager().updateEnvInspectCount(); - // BadgeManager().updateEightWorkCount(); - // 拉取其他数据 final results = await Future.wait([ ApiService.getUserData(), diff --git a/lib/pages/home/low_page.dart b/lib/pages/home/low_page.dart index b76bbfa..6f810cd 100644 --- a/lib/pages/home/low_page.dart +++ b/lib/pages/home/low_page.dart @@ -109,6 +109,6 @@ class _LowPagePageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/risk/riskControl_page.dart b/lib/pages/home/risk/riskControl_page.dart index 5e9ae9f..5983e28 100644 --- a/lib/pages/home/risk/riskControl_page.dart +++ b/lib/pages/home/risk/riskControl_page.dart @@ -334,7 +334,7 @@ class _RiskControlPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/risk/risk_detail_page.dart b/lib/pages/home/risk/risk_detail_page.dart index 623f062..5860157 100644 --- a/lib/pages/home/risk/risk_detail_page.dart +++ b/lib/pages/home/risk/risk_detail_page.dart @@ -412,6 +412,6 @@ class _RiskDetailPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/study/face_ecognition_page.dart b/lib/pages/home/study/face_ecognition_page.dart index 49af5ee..d992097 100644 --- a/lib/pages/home/study/face_ecognition_page.dart +++ b/lib/pages/home/study/face_ecognition_page.dart @@ -1,12 +1,20 @@ import 'dart:async'; +import 'dart:io'; +import 'package:flutter/services.dart'; + import 'package:camera/camera.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:permission_handler/permission_handler.dart' as ph; +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/my_appbar.dart'; import 'package:qhd_prevention/tools/tools.dart'; -import '../../../http/ApiService.dart'; + +// 在类最上面(State 内)加入一个 channel 常量 +const MethodChannel _platformChan = MethodChannel('qhd_prevention/permissions'); /// 人脸识别模式 enum FaceMode { auto, manual } @@ -25,92 +33,290 @@ class FaceRecognitionPage extends StatefulWidget { _FaceRecognitionPageState createState() => _FaceRecognitionPageState(); } -class _FaceRecognitionPageState extends State { +class _FaceRecognitionPageState extends State + with WidgetsBindingObserver { CameraController? _cameraController; Timer? _timer; int _attempts = 0; static const int _maxAttempts = 8; static const Duration _interval = Duration(seconds: 2); - late String _errMsg = ''; + String _errMsg = ''; + bool get _isManualMode => widget.mode == FaceMode.manual; + bool _isInitializing = false; + bool _isTaking = false; + @override void initState() { super.initState(); - _initCamera(); + WidgetsBinding.instance.addObserver(this); + // 延迟到首帧渲染后再请求权限并初始化相机,避免 iOS 在还未准备好时错过系统弹窗 + WidgetsBinding.instance.addPostFrameCallback((_) { + _initCameraWithPermission(); + }); } @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _timer?.cancel(); - _cameraController?.dispose(); + try { + _cameraController?.dispose(); + } catch (_) {} super.dispose(); } - Future _initCamera() async { - final cams = await availableCameras(); - final front = cams.firstWhere((c) => c.lensDirection == CameraLensDirection.front); - _cameraController = CameraController(front, ResolutionPreset.medium, enableAudio: false); - await _cameraController!.initialize(); - if (!mounted) return; - setState(() {}); - - if (!_isManualMode) { - _timer = Timer.periodic(_interval, (_) => _captureAndUpload()); + // 生命周期:后台/前台切换时处理相机释放/恢复 + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (_cameraController == null || !_cameraController!.value.isInitialized) { + return; } - } - - /// 显示加载弹窗 - void _showLoading() { - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const Center(child: CircularProgressIndicator()), - ); - } - - /// 隐藏弹窗 - void _hideLoading() { - if (Navigator.canPop(context)) Navigator.pop(context); - } -/// 定时上传 - Future _captureAndUpload() async { - if (!_isManualMode) { - if (_attempts >= _maxAttempts) return _onTimeout(); - _attempts++; + if (state == AppLifecycleState.inactive) { + // 可选:释放相机,节省资源 try { - final pic = await _cameraController!.takePicture(); - final res = await ApiService.getUserFace(pic.path, widget.studentId); - if (res['result'] == 'success') { - _onSuccess(); - }else{ - setState(() { - _errMsg = res['msg'] ?? ''; - }); - } - } catch (_) { - + _cameraController?.dispose(); + } catch (_) {} + _cameraController = null; + } else if (state == AppLifecycleState.resumed) { + // 恢复时重新初始化相机(并会再次请求权限) + if (!_isInitializing) { + _initCameraWithPermission(); } } } -/// 手动上传验证 - Future _captureAndReload() async { - _showLoading(); + + /// 请求 camera 权限(iOS:优先走原生 AVCaptureDevice.requestAccess;Android:使用 permission_handler) + Future _requestCameraPermission() async { try { - final pic = await _cameraController!.takePicture(); + // iOS:使用原生 API 强制唤起系统授权弹窗 + if (Platform.isIOS) { + try { + final dynamic res = await _platformChan.invokeMethod('requestCameraAccess'); + // 原生返回 true/false + if (res == true) { + debugPrint('[FaceRecognition] iOS native camera access granted'); + return true; + } else { + debugPrint('[FaceRecognition] iOS native camera access denied'); + // 如果被拒绝(非永久还是永久都返回 false),再根据 permission_handler 的状态做后续提示/引导 + final status = await ph.Permission.camera.status; + if (status.isPermanentlyDenied) { + await _showPermissionDialog(permanent: true); + } else { + await _showPermissionDialog(permanent: false); + } + return false; + } + } on PlatformException catch (e) { + debugPrint('[FaceRecognition] platform channel error: $e — fallback to permission_handler'); + // 如果 platform channel 异常,回退到 permission_handler + } + } + + // 非 iOS 或 platform 调用失败时走 permission_handler(保证 Android 正常) + final result = await ph.Permission.camera.request(); + debugPrint('[FaceRecognition] permission_handler camera result: $result'); + + if (result.isGranted) return true; + if (result.isPermanentlyDenied) { + await _showPermissionDialog(permanent: true); + return false; + } + if (result.isDenied) { + await _showPermissionDialog(permanent: false); + return false; + } + if (result.isRestricted) { + if (mounted) ToastUtil.showNormal(context, '相机权限受限,无法使用本功能'); + return false; + } + if (result.isLimited) { + if (mounted) ToastUtil.showNormal(context, '相机权限受限(limited)'); + return false; + } + return false; + } catch (e) { + debugPrint('[FaceRecognition] permission request error: $e'); + if (mounted) ToastUtil.showNormal(context, '请求相机权限时出错'); + return false; + } + } + /// 显示权限被拒/永久拒绝对话框(区分 permanent) + Future _showPermissionDialog({required bool permanent}) async { + if (!mounted) return; + await CustomAlertDialog.showConfirm( + context, + title: '需要相机权限', + content: + permanent + ? '检测到相机权限已被永久拒绝,请到系统设置中打开相机权限以继续使用人脸识别功能。' + : '相机权限被拒绝,是否重试或前往设置打开权限?', + cancelText: '取消', + onConfirm: () async { + try { + final retry = await ph.Permission.camera.request(); + debugPrint('[FaceRecognition] retry request result: $retry'); + if (retry.isGranted) { + if (mounted) await _initCamera(); + } else if (retry.isPermanentlyDenied) { + try { + await ph.openAppSettings(); + } catch (e) { + debugPrint('[FaceRecognition] openAppSettings error: $e'); + if (mounted) ToastUtil.showNormal(context, '无法打开设置,请手动前往系统设置授权'); + } + } else { + if (mounted) ToastUtil.showNormal(context, '相机权限未授予'); + } + } catch (e) { + debugPrint('[FaceRecognition] retry request error: $e'); + } + }, + ); + } + + /// 初始化:先请求权限,再打开相机 + Future _initCameraWithPermission() async { + if (!mounted) return; + final ok = await _requestCameraPermission(); + if (!ok) return; + await _initCamera(); + } + + Future _initCamera() async { + if (_isInitializing) return; + _isInitializing = true; + + try { + final cams = await availableCameras(); + + CameraDescription? front; + try { + front = cams.firstWhere( + (c) => c.lensDirection == CameraLensDirection.front, + ); + } catch (_) { + if (cams.isNotEmpty) front = cams.first; + } + + if (front == null) { + if (!mounted) return; + ToastUtil.showError(context, '未检测到可用摄像头'); + _isInitializing = false; + return; + } + + _cameraController = CameraController( + front, + ResolutionPreset.medium, + enableAudio: false, + imageFormatGroup: ImageFormatGroup.yuv420, + ); + + await _cameraController!.initialize(); + + // 尽量关闭闪光 + try { + await _cameraController!.setFlashMode(FlashMode.off); + } catch (_) {} + + if (!mounted) return; + setState(() {}); + + // 启动自动拍照定时器(若为自动模式) + _timer?.cancel(); + _attempts = 0; + if (!_isManualMode) { + _timer = Timer.periodic(_interval, (_) => _captureAndUpload()); + } + } catch (e, st) { + debugPrint('[FaceRecognition] init camera error: $e\n$st'); + if (mounted) { + ToastUtil.showError(context, '初始化摄像头失败'); + } + } finally { + _isInitializing = false; + } + } + + /// 自动模式:定时拍照并上传 + Future _captureAndUpload() async { + if (_isManualMode) return; + if (_cameraController == null || !_cameraController!.value.isInitialized) + return; + if (_attempts >= _maxAttempts) return _onTimeout(); + + if (_isTaking) return; + _isTaking = true; + _attempts++; + + try { + try { + await _cameraController!.setFlashMode(FlashMode.off); + } catch (_) {} + + final XFile pic = await _cameraController!.takePicture(); + final res = await ApiService.getUserFace(pic.path, widget.studentId); + + if (res['result'] == 'success') { + _onSuccess(); + } else { + if (!mounted) return; + setState(() { + _errMsg = (res['msg'] ?? '').toString(); + }); + } + } catch (e, st) { + debugPrint('[FaceRecognition] capture error: $e\n$st'); + // 忽略单次异常,等待下一次尝试 + } finally { + _isTaking = false; + } + } + + /// 手动拍照并上传 + Future _captureAndReload() async { + if (_cameraController == null || !_cameraController!.value.isInitialized) + return; + if (_isTaking) return; + _isTaking = true; + _showLoading(); + + try { + // 再次确认 camera 权限(以防用户运行时撤销) + final status = await ph.Permission.camera.status; + if (!status.isGranted) { + final ok = await _requestCameraPermission(); + if (!ok) { + _hideLoading(); + _isTaking = false; + return; + } + } + + try { + await _cameraController!.setFlashMode(FlashMode.off); + } catch (_) {} + + final XFile pic = await _cameraController!.takePicture(); final res = await ApiService.reloadMyFace(pic.path, widget.studentId); + _hideLoading(); + if (res['result'] == 'success') { _onSuccess(); } else { ToastUtil.showError(context, '验证失败,请重试'); - } - } catch (_) { + } catch (e, st) { + debugPrint('[FaceRecognition] manual capture error: $e\n$st'); _hideLoading(); - _showToast('发生错误,请重试'); - ToastUtil.showError(context, '发生错误,请重试'); + ToastUtil.showError(context, '拍照失败,请重试'); + } finally { + _isTaking = false; } } @@ -118,17 +324,37 @@ class _FaceRecognitionPageState extends State { _timer?.cancel(); if (widget.mode == FaceMode.manual) { ToastUtil.showSuccess(context, '已更新人脸信息'); - Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true)); + Future.delayed(const Duration(milliseconds: 800), () { + if (mounted) Navigator.of(context).pop(true); + }); return; } - Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true)); + Future.delayed(const Duration(milliseconds: 800), () { + if (mounted) Navigator.of(context).pop(true); + }); } void _onTimeout() { _timer?.cancel(); ToastUtil.showError(context, '人脸超时,请重新识别!'); - Future.delayed(const Duration(seconds: 3), () => Navigator.of(context).pop(false)); + Future.delayed(const Duration(seconds: 3), () { + if (mounted) Navigator.of(context).pop(false); + }); + } + + void _showLoading() { + if (!mounted) return; + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const Center(child: CircularProgressIndicator()), + ); + } + + void _hideLoading() { + if (!mounted) return; + if (Navigator.canPop(context)) Navigator.pop(context); } void _showToast(String msg) { @@ -143,13 +369,14 @@ class _FaceRecognitionPageState extends State { body: Center(child: CircularProgressIndicator()), ); } + final previewSize = _cameraController!.value.previewSize!; final previewAspect = previewSize.height / previewSize.width; final radius = (screenWidth(context) - 100) / 2; return Scaffold( backgroundColor: Colors.white, - appBar: MyAppbar(title: '人脸识别'), + appBar: const MyAppbar(title: '人脸识别'), body: Stack( children: [ Positioned.fill(child: Container(color: Colors.white)), @@ -182,19 +409,27 @@ class _FaceRecognitionPageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( + const Text( '请将人脸置于圆圈内', - style: const TextStyle(fontSize: 16, color: Colors.black87), - textAlign: TextAlign.center, - ), - Text( - _errMsg, - style: const TextStyle(fontSize: 14, color: Colors.red), + style: TextStyle(fontSize: 16, color: Colors.black87), textAlign: TextAlign.center, ), + if (_errMsg.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 6.0), + child: Text( + _errMsg, + style: const TextStyle(fontSize: 14, color: Colors.red), + textAlign: TextAlign.center, + ), + ), const SizedBox(height: 20), if (_isManualMode) - CustomButton(text: '拍照/上传', backgroundColor: Colors.blue,onPressed: _captureAndReload,) + CustomButton( + text: '拍照/上传', + backgroundColor: Colors.blue, + onPressed: _captureAndReload, + ), ], ), ), @@ -207,6 +442,7 @@ class _FaceRecognitionPageState extends State { class _WhiteMaskPainter extends CustomPainter { final double radius; + _WhiteMaskPainter({required this.radius}); @override diff --git a/lib/pages/home/study/strengthen_video_study_page.dart b/lib/pages/home/study/strengthen_video_study_page.dart index 6ec9b45..f0394ed 100644 --- a/lib/pages/home/study/strengthen_video_study_page.dart +++ b/lib/pages/home/study/strengthen_video_study_page.dart @@ -34,6 +34,7 @@ class _StrengthenStudyPageState extends State { String _videoCoverUrl = ''; List _videoList = []; Map _info = {}; + bool _loading = true; @override void initState() { @@ -43,60 +44,87 @@ class _StrengthenStudyPageState extends State { @override void dispose() { - _videoController?.removeListener(_controllerListener); + try { + _videoController?.removeListener(_controllerListener); + } catch (_) {} _videoController?.dispose(); super.dispose(); } Future _fetchData() async { - final res = await ApiService.getListStrengthenVideo({ - 'CLASS_ID': widget.classId, - 'STUDENT_ID': widget.studentId, - 'TYPE': 'APP', - }); - if (res['result'] == 'success') { - setState(() { - _info = res['relation']; - _videoList = res['videoList']; + try { + final res = await ApiService.getListStrengthenVideo({ + 'CLASS_ID': widget.classId, + 'STUDENT_ID': widget.studentId, + 'TYPE': 'APP', }); - final first = _processData(_videoList); - _loadPlayInfo(first, loadPdf: false); + if (res['result'] == 'success') { + setState(() { + _info = res['relation'] ?? {}; + _videoList = res['videoList'] ?? []; + _loading = false; + }); + + // 选一个首个可播放项并加载(不强制加载 PDF) + final first = _processData(_videoList); + if (first.isNotEmpty) { + await _loadPlayInfo(first, loadPdf: false); + } + } else { + setState(() => _loading = false); + ToastUtil.showNormal(context, res['msg'] ?? '数据加载失败'); + } + } catch (e, st) { + debugPrint('StrengthenStudyPage._fetchData error: $e\n$st'); + setState(() => _loading = false); + ToastUtil.showNormal(context, '加载失败'); } } Map _processData(List data) { for (var item in data) { - if (item['nodes'] != null) { + if (item == null) continue; + if (item['nodes'] != null && item['nodes'] is List) { final node = (item['nodes'] as List) .cast>() .firstWhere((n) => n['IS_VIDEO'] == 0, orElse: () => {}); if (node.isNotEmpty) return node; } else if (item['IS_VIDEO'] == 0) { - return item; + return Map.from(item); } } return {}; } Future _loadPlayInfo( - Map row, { - required bool loadPdf, - }) async { + Map row, { + required bool loadPdf, + }) async { + if (row.isEmpty) return; + final id = row['VIDEOCOURSEWARE_ID']; final isVideo = row['IS_VIDEO']; if (isVideo == 0) { - final res = await ApiService.fnGetVideoPlayInfo(id); - if (res['result'] == 'success') { - setState(() => _videoCoverUrl = res['videoBase']?['coverURL'] ?? ''); + try { + final res = await ApiService.fnGetVideoPlayInfo(id); + if (res['result'] == 'success') { + setState(() => _videoCoverUrl = res['videoBase']?['coverURL'] ?? ''); - _initVideo( - res['videoList']?[0]['playURL'] ?? '', - _videoCoverUrl, - ); - } else { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(res['msg'] ?? '播放信息获取失败'))); + final playUrl = res['videoList'] != null && res['videoList'].isNotEmpty + ? (res['videoList'][0]['playURL'] ?? '') + : ''; + + if (playUrl != null && playUrl.toString().isNotEmpty) { + _initVideo(playUrl.toString(), _videoCoverUrl); + } else { + ToastUtil.showNormal(context, '未找到播放地址'); + } + } else { + ToastUtil.showNormal(context, res['msg'] ?? '播放信息获取失败'); + } + } catch (e, st) { + debugPrint('StrengthenStudyPage._loadPlayInfo error: $e\n$st'); + ToastUtil.showNormal(context, '获取播放信息失败'); } } else if (loadPdf && row['VIDEOFILES'] != null) { _videoController?.pause(); @@ -110,19 +138,41 @@ class _StrengthenStudyPageState extends State { } } - void _initVideo(String url, String cover) { - _videoController?.removeListener(_controllerListener); - _videoController?.dispose(); - _videoController = VideoPlayerController.networkUrl(Uri.parse(url)) - ..initialize().then((_) { - _videoController! - ..play() - ..addListener(_controllerListener); - }); + Future _initVideo(String url, String cover) async { + // 移除旧监听并释放旧 controller + try { + _videoController?.removeListener(_controllerListener); + } catch (_) {} + try { + await _videoController?.dispose(); + } catch (_) {} + + if (url.isEmpty) return; + + _videoController = VideoPlayerController.networkUrl(Uri.parse(url)); + + try { + await _videoController!.initialize(); + if (!mounted) return; + // 在初始化完成后立即刷新 UI,这样 VideoPlayerWidget 可以正确拿到 controller + setState(() {}); + // 添加 listener 并播放 + _videoController! + ..addListener(_controllerListener) + ..setLooping(false) + ..play(); + } catch (e, st) { + debugPrint('StrengthenStudyPage._initVideo initialize/play error: $e\n$st'); + if (mounted) { + ToastUtil.showNormal(context, '视频播放初始化失败'); + } + } } void _controllerListener() { - if (mounted) setState(() {}); + if (!mounted) return; + // 只在需要刷新 UI 时调用 setState + setState(() {}); } String _formatDuration(dynamic secs) { @@ -153,8 +203,7 @@ class _StrengthenStudyPageState extends State { 'STRENGTHEN_PAPER_QUESTION_ID': _info['STRENGTHEN_STAGEEXAMPAPER_INPUT_ID'], ...data }, examType: type), context); - - }else{ + } else { ToastUtil.showError(context, '请求错误'); } } @@ -163,33 +212,38 @@ class _StrengthenStudyPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: MyAppbar(title: '加强学习课件'), - body: Column( + body: _loading + ? const Center(child: CircularProgressIndicator()) + : Column( children: [ ListItemFactory.createBuildSimpleSection('加强学习课件'), - if(_videoList.isEmpty) - Stack(children: [ - Container( - width: double.infinity, - height: 200, + + // --- 顶部播放器区域:如果有初始化好的 controller 则显示播放器,否则显示占位/封面 --- + SizedBox( + width: double.infinity, + height: 250, + child: Container( color: Colors.white, - child: Center( - child:Text("暂未播放", - ), - ), - ), - if(_videoList.isNotEmpty) - SizedBox( - width: double.infinity, - height: 250, - child: VideoPlayerWidget( + child: _videoController != null && _videoController!.value.isInitialized + ? VideoPlayerWidget( allowSeek: true, controller: _videoController, - coverUrl:'', - aspectRatio: _videoController?.value.aspectRatio ?? 16 / 9, + coverUrl: _videoCoverUrl, + aspectRatio: _videoController!.value.aspectRatio, + ) + : Center( + child: _videoCoverUrl.isNotEmpty + ? Image.network( + ApiService.baseImgPath + _videoCoverUrl, + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => const Text('暂无封面'), + ) + : const Text('暂未播放'), ), ), - ],), + ), + const SizedBox(height: 8), Expanded( child: ListView.builder( @@ -197,6 +251,8 @@ class _StrengthenStudyPageState extends State { itemCount: _videoList.length, itemBuilder: (_, i) { final item = _videoList[i]; + // item 可能是章节或节点结构,兼容原有显示 + final name = item['COURSEWARENAME'] ?? item['NAME'] ?? ''; return GestureDetector( onTap: () => _loadPlayInfo(item, loadPdf: true), child: Container( @@ -212,18 +268,15 @@ class _StrengthenStudyPageState extends State { Row( children: [ Image.asset( - item['IS_VIDEO'] == 0 - ? 'assets/study/play.png' - : 'assets/study/copy-one.png', + item['IS_VIDEO'] == 0 ? 'assets/study/play.png' : 'assets/study/copy-one.png', width: 20, height: 20, ), const SizedBox(width: 10), - Text(item['COURSEWARENAME'] ?? ''), + Text(name), ], ), - if (item['IS_VIDEO'] == 0) - Text(_formatDuration(item['VIDEOTIME'])), + if (item['IS_VIDEO'] == 0) Text(_formatDuration(item['VIDEOTIME'])), ], ), ), @@ -231,6 +284,7 @@ class _StrengthenStudyPageState extends State { }, ), ), + Padding( padding: const EdgeInsets.all(20), child: CustomButton( @@ -241,11 +295,9 @@ class _StrengthenStudyPageState extends State { _startExam(TakeExamType.strengththen); }, ), - ), ], ), ); } - } diff --git a/lib/pages/home/study/study_detail_page.dart b/lib/pages/home/study/study_detail_page.dart index 73c08b4..00d134f 100644 --- a/lib/pages/home/study/study_detail_page.dart +++ b/lib/pages/home/study/study_detail_page.dart @@ -1,9 +1,9 @@ - import 'dart:async'; import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:qhd_prevention/customWidget/remote_file_page.dart'; import 'package:qhd_prevention/pages/home/study/take_exam_page.dart'; import 'package:qhd_prevention/pages/home/study/study_practise_page.dart'; @@ -11,6 +11,7 @@ import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/services/auth_service.dart'; import 'package:qhd_prevention/tools/tools.dart'; import 'package:video_player/video_player.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -78,9 +79,7 @@ class _StudyDetailPageState extends State void dispose() { _tabController.dispose(); WakelockPlus.disable(); - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - ]); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // 让未完成的提交完成(非阻塞) if (_ongoingSubmit != null) { _ongoingSubmit!.whenComplete(() {}); @@ -102,12 +101,14 @@ class _StudyDetailPageState extends State final ok = await CustomAlertDialog.showConfirm( context, title: '温馨提示', - content: - '重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。', + content: '重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。', cancelText: '取消', confirmText: '同意并继续', ); - if (ok) {} + if (!ok) { + Navigator.of(context).pop(); + return; + } } Future _loadData() async { @@ -154,21 +155,37 @@ class _StudyDetailPageState extends State required dynamic resourceTime, required dynamic videoTime, }) { + // 播放次数大于 0 视为 100% final seen = int.tryParse('$playCount') ?? 0; if (seen > 0) return '100%'; + final resT = double.tryParse('$resourceTime') ?? 0.0; final vidT = double.tryParse('$videoTime') ?? 0.0; - if (vidT == 0) return '0%'; - final pct = ((resT / vidT) * 100).clamp(0.0, 100.0).toStringAsFixed(0); - return '$pct%'; + + if (vidT <= 0) return '0%'; + + double pct = (resT / vidT) * 100.0; + // 限制在合理范围 + if (pct <= 0.0) return '0%'; + if (pct >= 100.0) return '100%'; + + // 非零但非常小的值,例如 0.004%,显示为 <0.01% + if (pct > 0.0 && pct < 0.01) return '<0.01%'; + + // 保留最多 2 位小数,然后去掉多余的零和小数点 + String s = pct.toStringAsFixed(2); + s = s.replaceFirst(RegExp(r'\.?0+$'), ''); // 去掉尾随的 .00 或 .0 + return '$s%'; } + + Future _onVideoTap( - Map data, - bool hasNodes, - int fi, - int ni, - ) async { + Map data, + bool hasNodes, + int fi, + int ni, + ) async { if (_isLoadingVideo) { debugPrint('_onVideoTap ignored because a video is loading'); return; @@ -189,7 +206,11 @@ class _StudyDetailPageState extends State 'FIRST_INDEX': _currentFirstIndex, 'NODE_INDEX': _currentNodeIndex, }; - await _submitPlayTime(snapshot: prevSnapshot, end: false, seconds: _lastReported.inSeconds); + await _submitPlayTime( + snapshot: prevSnapshot, + end: false, + seconds: _lastReported.inSeconds, + ); } _lastReported = Duration.zero; @@ -212,32 +233,39 @@ class _StudyDetailPageState extends State try { await _videoController?.pause(); } catch (_) {} - await pushPage( + final success = await pushPage( RemoteFilePage( fileUrl: ApiService.baseImgPath + data['VIDEOFILES'], countdownSeconds: 30, ), context, ); - // 对文档直接上报完整时长(用 data 做快照) - final docSnapshot = { - 'VIDEOCOURSEWARE_ID': data['VIDEOCOURSEWARE_ID'] ?? '', - 'CURRICULUM_ID': data['CURRICULUM_ID'] ?? '', - 'CHAPTER_ID': data['CHAPTER_ID'] ?? '', - 'VIDEOTIME': data['VIDEOTIME'] ?? 0, - 'IS_NODE': hasNodes, - 'FIRST_INDEX': fi, - 'NODE_INDEX': ni, - }; - await _submitPlayTime(snapshot: docSnapshot, end: true, seconds: int.parse('${data['VIDEOTIME'] ?? '0'}')); + if (success) { + // 对文档直接上报完整时长(用 data 做快照) + final docSnapshot = { + 'VIDEOCOURSEWARE_ID': data['VIDEOCOURSEWARE_ID'] ?? '', + 'CURRICULUM_ID': data['CURRICULUM_ID'] ?? '', + 'CHAPTER_ID': data['CHAPTER_ID'] ?? '', + 'VIDEOTIME': data['VIDEOTIME'] ?? 0, + 'IS_NODE': hasNodes, + 'FIRST_INDEX': fi, + 'NODE_INDEX': ni, + }; + await _submitPlayTime( + snapshot: docSnapshot, + end: true, + seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'), + ); + } + } else { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('课件文件资源已失效,请联系管理员'))); + ToastUtil.showNormal(context, '课件文件资源已失效,请联系管理员'); } } else { + LoadingDialogHelper.show(); // 视频 await _getVideoPlayInfo(data['VIDEOCOURSEWARE_ID']); + LoadingDialogHelper.hide(); _startFaceTimer(); } }); @@ -260,6 +288,7 @@ class _StudyDetailPageState extends State await _restoreDefaultOrientations(); if (passed == true) { + LoadingDialogHelper.show(); await ApiService.fnSetUserFaceTime(_faceTime); await onPass(); } else { @@ -308,14 +337,14 @@ class _StudyDetailPageState extends State final raw = prog['pd']?['RESOURCETIME']; final seen = - (() { - if (raw == null) return 0; - // 如果本身就是数字 - if (raw is num) return raw.toInt(); - // 否则转成字符串再 parse - final s = raw.toString(); - return (double.tryParse(s) ?? 0.0).toInt(); - })(); + (() { + if (raw == null) return 0; + // 如果本身就是数字 + if (raw is num) return raw.toInt(); + // 否则转成字符串再 parse + final s = raw.toString(); + return (double.tryParse(s) ?? 0.0).toInt(); + })(); // 先销毁旧 controller(安全顺序:removeListener -> pause -> dispose -> null) if (_videoController != null) { @@ -391,12 +420,16 @@ class _StudyDetailPageState extends State // 串行上报:通过 _ongoingSubmit 链式调用来保证顺序 _ongoingSubmit = (_ongoingSubmit ?? Future.value()) .then((_) { - return _submitPlayTime(snapshot: snapshot, end: false, seconds: pos.inSeconds); - }) + return _submitPlayTime( + snapshot: snapshot, + end: false, + seconds: pos.inSeconds, + ); + }) .whenComplete(() { - // 完成后清理引用 - _ongoingSubmit = null; - }); + // 完成后清理引用 + _ongoingSubmit = null; + }); } // 接近结束的检测,容差 800ms,触发一次最终结束上报 @@ -419,13 +452,17 @@ class _StudyDetailPageState extends State _ongoingSubmit = (_ongoingSubmit ?? Future.value()) .then((_) async { - await _submitPlayTime(snapshot: snapshot, end: true, seconds: finalSeconds); - }) + await _submitPlayTime( + snapshot: snapshot, + end: true, + seconds: finalSeconds, + ); + }) .whenComplete(() { - _ongoingSubmit = null; - // 最终上报完成后尝试退出顶层 route(退出全屏) - _exitTopRouteIfPresent(); - }); + _ongoingSubmit = null; + // 最终上报完成后尝试退出顶层 route(退出全屏) + _exitTopRouteIfPresent(); + }); ApiService.fnClearUserFaceTime(); _faceTimer?.cancel(); @@ -439,7 +476,9 @@ class _StudyDetailPageState extends State required int seconds, }) async { // snapshot 中应该包含 VIDEOCOURSEWARE_ID, CURRICULUM_ID, CHAPTER_ID, VIDEOTIME, ... - if (snapshot['VIDEOCOURSEWARE_ID'] == null || snapshot['VIDEOCOURSEWARE_ID'] == '') return; + if (snapshot['VIDEOCOURSEWARE_ID'] == null || + snapshot['VIDEOCOURSEWARE_ID'] == '') + return; // 如果已经上报结束并且当前不是结束上报,则跳过非结束上报 if (_endReported && !end) return; @@ -467,20 +506,20 @@ class _StudyDetailPageState extends State // 解析后端返回的 RESOURCETIME(可能是数字或字符串) final resTraw = pd['RESOURCETIME']; final resT = - (resTraw is num) - ? resTraw.toDouble() - : double.tryParse('$resTraw') ?? seconds.toDouble(); + (resTraw is num) + ? resTraw.toDouble() + : double.tryParse('$resTraw') ?? seconds.toDouble(); final videoTimeRaw = snapshot['VIDEOTIME']; final videoTime = - (videoTimeRaw is String) - ? double.tryParse(videoTimeRaw) ?? 1.0 - : (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0); + (videoTimeRaw is String) + ? double.tryParse(videoTimeRaw) ?? 1.0 + : (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0); final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0; final pctDouble = - comp ? 100.0 : ((resT / (videoTime > 0 ? videoTime : 1.0)) * 100.0); + comp ? 100.0 : ((resT / (videoTime > 0 ? videoTime : 1.0)) * 100.0); final pct = (pctDouble.clamp(0.00, 100.00) * 100).round() / 100; final str = '${pct}%'; @@ -562,7 +601,7 @@ class _StudyDetailPageState extends State 'POST_ID': pd['POST_ID'] ?? '', 'STUDENT_ID': widget.studentId, 'STRENGTHEN_PAPER_QUESTION_ID': - paper['STAGEEXAMPAPERINPUT_ID'] ?? '', + paper['STAGEEXAMPAPERINPUT_ID'] ?? '', ...data, }, examType: TakeExamType.video_study, @@ -655,7 +694,9 @@ class _StudyDetailPageState extends State try { final currentPos = _videoController!.value.position; _lastReported = currentPos; - debugPrint('Face auth passed — resetting _lastReported to $currentPos'); + debugPrint( + 'Face auth passed — resetting _lastReported to $currentPos', + ); } catch (_) { _lastReported = Duration.zero; } @@ -665,7 +706,9 @@ class _StudyDetailPageState extends State _videoController!.addListener(_onTimeUpdate); } else { // 如果 controller 为空或未初始化,仅尝试更新 UI - debugPrint('Face auth passed but _videoController is null or not initialized'); + debugPrint( + 'Face auth passed but _videoController is null or not initialized', + ); } setState(() {}); } catch (e, st) { @@ -695,9 +738,10 @@ class _StudyDetailPageState extends State return VideoPlayerWidget( allowSeek: false, controller: _videoController, - coverUrl: _videoCoverUrl.isNotEmpty - ? ApiService.baseImgPath + _videoCoverUrl - : ApiService.baseImgPath + (_info?['COVERPATH'] ?? ''), + coverUrl: + _videoCoverUrl.isNotEmpty + ? ApiService.baseImgPath + _videoCoverUrl + : ApiService.baseImgPath + (_info?['COVERPATH'] ?? ''), aspectRatio: _videoController?.value.aspectRatio ?? 16 / 9, ); } else { @@ -773,56 +817,87 @@ class _StudyDetailPageState extends State itemBuilder: (ctx, idx) { final item = _videoList[idx] as Map; final nodes = item['nodes'] as List?; + + // 如果是带有子节点的章节,先渲染章节头,再渲染该章节的所有子项 if (nodes != null && nodes.isNotEmpty) { - // 章节标题 + 直接展开的子项列表(不折叠) return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ...nodes.asMap().entries.map( - (e) => _buildVideoItem( - item, - e.value as Map, - true, - idx, - e.key, + // 章节头 + Container( + width: double.infinity, + color: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, ), + child: Row( + children: [ + Icon( + item['IS_VIDEO'] == 0 + ? Icons.video_collection_rounded + : Icons.file_copy_rounded, + color: Colors.grey, + size: 20, + ), + const SizedBox(width: 5), + Text( + (item['NAME'] ?? '').toString(), + style: const TextStyle( + fontSize: 15, + ), + ), + ], + ), + // Text( + // (item['NAME'] ?? '').toString(), + // style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + // ), ), - const Divider(height: 1), // 每章节后一个分割线 + + // 子项列表 + ...nodes.asMap().entries.map((e) { + final node = e.value as Map; + return _buildVideoItem(item, node, true, idx, e.key); + }).toList(), + + const Divider(height: 1), ], ); } - // 没有子节点,直接显示该条目 + + // 没有子节点,把 item 本身当成单条记录显示 return _buildVideoItem(item, item, false, idx, 0); }, ); } Widget _buildVideoItem( - Map item, - Map m, - bool hasNodes, - int fi, - int ni, - ) { + Map item, + Map m, + bool hasNodes, + int fi, + int ni, + ) { return Container( color: Colors.white, - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), child: Column( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon(Icons.file_copy_rounded, color: Colors.grey, size: 20), - const SizedBox(width: 8), - Expanded( - child: Text( - item['NAME'] ?? '', - style: const TextStyle(fontSize: 14), - ), - ), - ], - ), - const SizedBox(height: 10), + // Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Icon(m['IS_VIDEO'] == 0 ? Icons.video_collection_rounded : Icons.file_copy_rounded, color: Colors.grey, size: 20), + // const SizedBox(width: 8), + // Expanded( + // child: Text( + // item['NAME'] ?? '', + // style: const TextStyle(fontSize: 14), + // ), + // ), + // ], + // ), + // const SizedBox(height: 10), GestureDetector( onTap: () => _onVideoTap(m, hasNodes, fi, ni), child: Container( @@ -856,15 +931,19 @@ class _StudyDetailPageState extends State const Icon(Icons.play_circle, color: Colors.blue), ], if (m['IS_VIDEO'] != 0) - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [Text('-'),const SizedBox(width: 50,)],), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('-'), const SizedBox(width: 50)], + ), SizedBox(width: 20), CustomButton( - onPressed: () => pushPage( - StudyPractisePage( - videoCoursewareId: m['VIDEOCOURSEWARE_ID'], - ), - context, - ), + onPressed: + () => pushPage( + StudyPractisePage( + videoCoursewareId: m['VIDEOCOURSEWARE_ID'], + ), + context, + ), text: "课后练习", backgroundColor: Colors.blue, height: 30, diff --git a/lib/pages/home/study/study_my_task_page.dart b/lib/pages/home/study/study_my_task_page.dart index e4091fb..8612a73 100644 --- a/lib/pages/home/study/study_my_task_page.dart +++ b/lib/pages/home/study/study_my_task_page.dart @@ -22,20 +22,27 @@ class StudyMyTaskPage extends StatefulWidget { State createState() => _StudyMyTaskPageState(); } -class _StudyMyTaskPageState extends State with RouteAware { +class _StudyMyTaskPageState extends State + with RouteAware, AutomaticKeepAliveClientMixin { int _page = 1; final int _showCount = 10; bool _isLoading = false; bool _hasMore = true; int _totalPage = 1; List _list = []; + final ScrollController _scrollController = ScrollController(); Timer? _timer; + @override + bool get wantKeepAlive => true; + @override void initState() { super.initState(); + _scrollController.addListener(_scrollListener); WidgetsBinding.instance.addPostFrameCallback((_) => _getStudyList()); } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -50,14 +57,18 @@ class _StudyMyTaskPageState extends State with RouteAware { void didPopNext() { if (!_isLoading) _getStudyList(); } + @override void didPush() { // 可选:首次 initState 已经调用过一次 _getStudyList, if (!_isLoading) _getStudyList(); } + @override void dispose() { _timer?.cancel(); + _scrollController.removeListener(_scrollListener); + _scrollController.dispose(); // 退订 routeObserver(防止内存泄漏) try { routeObserver.unsubscribe(this); @@ -65,28 +76,13 @@ class _StudyMyTaskPageState extends State with RouteAware { super.dispose(); } - /// 每秒减少 remainingSeconds,用于倒计时显示 - void _startCountdownTimer() { - if (_timer != null && _timer!.isActive) return; // 避免重复开启 - - _timer = Timer.periodic(Duration(seconds: 1), (_) { - setState(() { - for (var i = 0; i < _list.length; i++) { - final endTimeStr = _list[i]["END_TIME"] as String; - - try { - // 直接用原始字符串解析 - final endTime = DateTime.parse(endTimeStr); - final now = DateTime.now(); - final seconds = endTime.difference(now).inSeconds; - - _list[i]['remainingSeconds'] = seconds > 0 ? seconds : 0; - } catch (e) { - _list[i]['remainingSeconds'] = 0; - } - } - }); - }); + void _scrollListener() { + if (_scrollController.position.pixels > + _scrollController.position.maxScrollExtent - 100 && + _hasMore && + !_isLoading) { + _getStudyList(loadMore: true); + } } Future _getStudyList({bool loadMore = false}) async { @@ -100,20 +96,6 @@ class _StudyMyTaskPageState extends State with RouteAware { final List varList = res['varList'] ?? []; _totalPage = res['totalPage'] ?? 1; - // 计算初始倒计时 - final now = DateTime.now(); - for (var item in varList) { - final endStr = - (item['END_TIME'] as String?)?.replaceAll('-', '/') ?? ''; - try { - final end = DateTime.parse(endStr); - final diff = end.difference(now).inSeconds; - item['remainingSeconds'] = diff > 0 ? diff : 0; - } catch (_) { - item['remainingSeconds'] = 0; - } - } - setState(() { if (loadMore) { _list.addAll(varList); @@ -122,8 +104,6 @@ class _StudyMyTaskPageState extends State with RouteAware { } _hasMore = _page < _totalPage; if (_hasMore) _page++; - _startCountdownTimer(); - }); } } catch (e) { @@ -134,6 +114,219 @@ class _StudyMyTaskPageState extends State with RouteAware { } } + void _onTapSign(Map item) async { + await NativeOrientation.setLandscape(); + + final String? imagePath = await Navigator.push( + context, + MaterialPageRoute(builder: (_) => MineSignPage()), + ); + await NativeOrientation.setPortrait(); + + // 用户有签名并取到了 imagePath + if (imagePath != null && imagePath.isNotEmpty) { + // 上传 + await _uploadSignAndNavigate(item, imagePath); + } + } + + Future _getImageBase64WithHeader(String imagePath) async { + final File file = File(imagePath); + final List bytes = await file.readAsBytes(); + final String base64Data = base64Encode(bytes); + + // 根据文件扩展名确定MIME类型 + final extension = imagePath.split('.').last.toLowerCase(); + + String header; + switch (extension) { + case 'jpg': + case 'jpeg': + header = 'data:image/jpeg;base64,'; + break; + case 'png': + header = 'data:image/png;base64,'; + break; + case 'gif': + header = 'data:image/gif;base64,'; + break; + case 'webp': + header = 'data:image/webp;base64,'; + break; + case 'svg': + header = 'data:image/svg+xml;base64,'; + break; + default: + // 对于未知类型,使用通用的二进制数据头 + header = 'data:application/octet-stream;base64,'; + } + + return header + base64Data; + } + + Future _uploadSignAndNavigate(Map item, String imagePath) async { + try { + final String signBase64 = await _getImageBase64WithHeader(imagePath); + final result = await ApiService.signUpdate( + signBase64, + item['CLASS_ID'], + item['STAGESTUDENTRELATION_ID'], + ); + + if (result['result'] == 'success') { + pushPage(StudyClassListPage(item), context); + } + } catch (e) { + // 捕获并提示错误 + ToastUtil.showNormal(context, '上传出错:$e'); + } + } + + /// 开始考试 + Future _startExam(Map resData, TakeExamType type) async { + setState(() { + _isLoading = true; + }); + final arguments = { + 'STAGEEXAMPAPERINPUT_ID': resData['STAGEEXAMPAPERINPUT_ID'] ?? '', + 'STAGEEXAMPAPER_ID': resData['STAGEEXAMPAPERINPUT_ID'] ?? '', + 'CLASS_ID': resData['CLASS_ID'] ?? '', + 'POST_ID': resData['POST_ID'] ?? '', + 'STUDENT_ID': resData['STUDENT_ID'] ?? '', + 'NUMBEROFEXAMS': resData['NUMBEROFEXAMS'] ?? '', + }; + print('--_startExam data---$arguments'); + + final data = await ApiService.getStartExam(arguments); + setState(() { + _isLoading = false; + }); + if (data['result'] == 'success') { + pushPage( + TakeExamPage( + examInfo: { + 'CLASS_ID': resData['CLASS_ID'] ?? '', + 'POST_ID': resData['POST_ID'] ?? '', + 'STUDENT_ID': resData['STUDENT_ID'] ?? '', + 'STRENGTHEN_PAPER_QUESTION_ID': + resData['STAGEEXAMPAPERINPUT_ID'] ?? '', + ...data + }, + examType: TakeExamType.video_study), + context); + } else { + ToastUtil.showError(context, '请求错误'); + } + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + appBar: MyAppbar(title: '我的学习'), + body: SafeArea( + child: _list.isEmpty + ? NoDataWidget.show() + : ListView.builder( + controller: _scrollController, + itemCount: _list.length + (_hasMore ? 1 : 0), + itemBuilder: (context, index) { + if (index == _list.length) { + return _hasMore + ? Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + )) + : SizedBox.shrink(); + } + return _StudyTaskItem( + item: _list[index], + onTapSign: _onTapSign, + startExam: _startExam, + ); + }, + ), + ), + ); + } +} + +class _StudyTaskItem extends StatefulWidget { + final Map item; + final Function(Map) onTapSign; + final Function(Map, TakeExamType) startExam; + + const _StudyTaskItem({ + required this.item, + required this.onTapSign, + required this.startExam, + }); + + @override + State<_StudyTaskItem> createState() => _StudyTaskItemState(); +} + +class _StudyTaskItemState extends State<_StudyTaskItem> { + late int _remainingSeconds; + Timer? _countdownTimer; + + @override + void initState() { + super.initState(); + // 初始化倒计时 + _updateRemainingSeconds(); + // 只在需要时启动定时器 + if (_remainingSeconds > 0) { + _startCountdownTimer(); + } + } + + @override + void didUpdateWidget(_StudyTaskItem oldWidget) { + super.didUpdateWidget(oldWidget); + // 如果item更新了,重新计算倒计时 + if (oldWidget.item['END_TIME'] != widget.item['END_TIME']) { + _updateRemainingSeconds(); + // 如果之前有定时器,取消它 + _countdownTimer?.cancel(); + // 如果需要,重新启动定时器 + if (_remainingSeconds > 0) { + _startCountdownTimer(); + } + } + } + + @override + void dispose() { + _countdownTimer?.cancel(); + super.dispose(); + } + + void _updateRemainingSeconds() { + final endTimeStr = widget.item["END_TIME"] as String; + try { + final endTime = DateTime.parse(endTimeStr); + final now = DateTime.now(); + _remainingSeconds = endTime.difference(now).inSeconds; + if (_remainingSeconds < 0) _remainingSeconds = 0; + } catch (e) { + _remainingSeconds = 0; + } + } + + void _startCountdownTimer() { + _countdownTimer = Timer.periodic(Duration(seconds: 1), (timer) { + if (_remainingSeconds <= 0) { + timer.cancel(); + return; + } + setState(() { + _remainingSeconds--; + }); + }); + } + String formatSeconds(int seconds) { if (seconds <= 0) return '00天 00:00:00'; final d = Duration(seconds: seconds); @@ -175,82 +368,15 @@ class _StudyMyTaskPageState extends State with RouteAware { } } - void _onTapSign(Map item) async { - await NativeOrientation.setLandscape(); - - final String? imagePath = await Navigator.push( - context, - MaterialPageRoute(builder: (_) => MineSignPage()), - ); - await NativeOrientation.setPortrait(); - - // 用户有签名并取到了 imagePath - if (imagePath != null && imagePath.isNotEmpty) { - // 上传 - await _uploadSignAndNavigate(item, imagePath); - } - } - Future _getImageBase64WithHeader(String imagePath) async { - final File file = File(imagePath); - final List bytes = await file.readAsBytes(); - final String base64Data = base64Encode(bytes); - - // 根据文件扩展名确定MIME类型 - final extension = imagePath.split('.').last.toLowerCase(); - - String header; - switch (extension) { - case 'jpg': - case 'jpeg': - header = 'data:image/jpeg;base64,'; - break; - case 'png': - header = 'data:image/png;base64,'; - break; - case 'gif': - header = 'data:image/gif;base64,'; - break; - case 'webp': - header = 'data:image/webp;base64,'; - break; - case 'svg': - header = 'data:image/svg+xml;base64,'; - break; - default: - // 对于未知类型,使用通用的二进制数据头 - header = 'data:application/octet-stream;base64,'; - } - - return header + base64Data; - } - Future _uploadSignAndNavigate(Map item, String imagePath) async { - try { - final String signBase64 = await _getImageBase64WithHeader(imagePath); - final result = await ApiService.signUpdate( - signBase64, - item['CLASS_ID'], - item['STAGESTUDENTRELATION_ID'], - ); - - if (result['result'] == 'success') { - pushPage(StudyClassListPage(item), context); - } - } catch (e) { - // 捕获并提示错误 - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('上传出错:$e'))); - } - } - - Widget _buildItem(Map item) { + @override + Widget build(BuildContext context) { + final item = widget.item; final now = DateTime.now(); final start = DateTime.tryParse(item['START_TIME'] ?? ''); final end = DateTime.tryParse(item['END_TIME'] ?? ''); - final nowOk = - start != null && end != null - ? now.isAfter(start) && now.isBefore(end) - : false; + final nowOk = start != null && end != null + ? now.isAfter(start) && now.isBefore(end) + : false; final int studyState = int.tryParse(item['STUDYSTATE'] ?? '') ?? 0; final int stageExamState = int.tryParse(item['STAGEEXAMSTATE'] ?? '') ?? 0; @@ -259,9 +385,7 @@ class _StudyMyTaskPageState extends State with RouteAware { final int numberOfExams = int.tryParse('${item['NUMBEROFEXAMS']}') ?? 0; final int ksCount = int.tryParse('${item['ksCount']}') ?? 0; final int examinationFlag = - item['EXAMINATION'] is int - ? item['EXAMINATION'] as int - : int.tryParse('${item['EXAMINATION']}') ?? 0; + item['EXAMINATION'] is int ? item['EXAMINATION'] as int : int.tryParse('${item['EXAMINATION']}') ?? 0; final String isStrengthen = item['ISSTRENGTHEN'] ?? '0'; return Container( @@ -286,7 +410,9 @@ class _StudyMyTaskPageState extends State with RouteAware { style: const TextStyle(fontWeight: FontWeight.bold), ), ), - const SizedBox(width: 10,), + const SizedBox( + width: 10, + ), Text( _stateText(item['STUDYSTATE']), style: TextStyle(color: _stateColor(item['STUDYSTATE'])), @@ -311,7 +437,7 @@ class _StudyMyTaskPageState extends State with RouteAware { const Icon(Icons.access_time, size: 18, color: Colors.grey), const SizedBox(width: 4), Text( - formatSeconds(item['remainingSeconds'] ?? 0), + formatSeconds(_remainingSeconds), style: const TextStyle(fontSize: 12), ), ], @@ -330,15 +456,14 @@ class _StudyMyTaskPageState extends State with RouteAware { padding: EdgeInsets.symmetric(horizontal: 18), borderRadius: 18, backgroundColor: Colors.blue, - onPressed: - () => pushPage( - StrengthenStudyPage( - classId: item['CLASS_ID'] ?? '', - postId: item['POST_ID'] ?? '', - studentId: item['STUDENT_ID'] ?? '', - ), - context, - ), + onPressed: () => pushPage( + StrengthenStudyPage( + classId: item['CLASS_ID'] ?? '', + postId: item['POST_ID'] ?? '', + studentId: item['STUDENT_ID'] ?? '', + ), + context, + ), ), // 立即学习 @@ -352,7 +477,7 @@ class _StudyMyTaskPageState extends State with RouteAware { backgroundColor: Colors.blue, onPressed: () { if (studyState == 0) { - _onTapSign(item); + widget.onTapSign(item); } else { pushPage(StudyClassListPage(item), context); } @@ -369,12 +494,11 @@ class _StudyMyTaskPageState extends State with RouteAware { height: 36, text: "立即考试", margin: const EdgeInsets.symmetric(horizontal: 2), - padding: EdgeInsets.symmetric(horizontal: 15), borderRadius: 18, backgroundColor: Colors.green, - onPressed: - () => _startExam(item, TakeExamType.video_study), + onPressed: () => + widget.startExam(item, TakeExamType.video_study), ), // 考试详情 if (stageExamState == 3) @@ -382,7 +506,6 @@ class _StudyMyTaskPageState extends State with RouteAware { height: 36, text: "考试详情", margin: const EdgeInsets.symmetric(horizontal: 2), - padding: EdgeInsets.symmetric(horizontal: 15), borderRadius: 18, backgroundColor: Colors.green, @@ -391,6 +514,7 @@ class _StudyMyTaskPageState extends State with RouteAware { VideoStudyDetailPage( studentId: item['STUDENT_ID'] ?? '', classId: item['CLASS_ID'] ?? '', + state: 1, ), context, ); @@ -404,65 +528,4 @@ class _StudyMyTaskPageState extends State with RouteAware { ), ); } - /// 开始考试 - Future _startExam(Map resData, TakeExamType type) async { - setState(() { - _isLoading = true; - }); - final arguments = { - 'STAGEEXAMPAPERINPUT_ID': resData['STAGEEXAMPAPERINPUT_ID'] ?? '', - 'STAGEEXAMPAPER_ID': resData['STAGEEXAMPAPER_ID'] ?? '', - 'CLASS_ID': resData['CLASS_ID'] ?? '', - 'POST_ID': resData['POST_ID'] ?? '', - 'STUDENT_ID': resData['STUDENT_ID'] ?? '', - 'NUMBEROFEXAMS': resData['NUMBEROFEXAMS'] ?? '', - }; - print('--_startExam data---$arguments'); - - final data = await ApiService.getStartExam(arguments); - setState(() { - _isLoading = false; - }); - if (data['result'] == 'success') { - pushPage(TakeExamPage(examInfo: { - 'CLASS_ID': resData['CLASS_ID'] ?? '', - 'POST_ID': resData['POST_ID'] ?? '', - 'STUDENT_ID': resData['STUDENT_ID'] ?? '', - 'STRENGTHEN_PAPER_QUESTION_ID': resData['STAGEEXAMPAPERINPUT_ID'] ?? '', - ...data - }, examType: TakeExamType.video_study), context); - - }else{ - ToastUtil.showError(context, '请求错误'); - } - } - - - bool _onScroll(ScrollNotification n) { - if (n.metrics.pixels > n.metrics.maxScrollExtent - 100 && - _hasMore && - !_isLoading) { - _getStudyList(loadMore: true); - } - return false; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: MyAppbar(title: '我的学习'), - body: SafeArea( - child: NotificationListener( - onNotification: _onScroll, - child: - _list.isEmpty - ? NoDataWidget.show() - : ListView.builder( - itemCount: _list.length, - itemBuilder: (_, i) => _buildItem(_list[i]), - ), - ), - ), - ); - } -} +} \ No newline at end of file diff --git a/lib/pages/home/study/study_score_page.dart b/lib/pages/home/study/study_score_page.dart index adf37cc..6296312 100644 --- a/lib/pages/home/study/study_score_page.dart +++ b/lib/pages/home/study/study_score_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:qhd_prevention/pages/home/study/video_study_detail_page.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/tools/h_colors.dart'; import '../../../http/ApiService.dart'; @@ -126,7 +127,15 @@ class _StudyScorePageState extends State { onTap: () { final state = item['STAGEEXAMSTATE']; if (state == '2' || state == '3') { - Navigator.pushNamed(context, '/exam_details', arguments: item); + // Navigator.pushNamed(context, '/exam_details', arguments: item); + pushPage( + VideoStudyDetailPage( + studentId: item['STUDENT_ID'] ?? '', + classId: item['CLASS_ID'] ?? '', + state: 2, + ), + context, + ); } }, child: Padding( diff --git a/lib/pages/home/study/take_exam_page.dart b/lib/pages/home/study/take_exam_page.dart index 7abcb09..4dcc55c 100644 --- a/lib/pages/home/study/take_exam_page.dart +++ b/lib/pages/home/study/take_exam_page.dart @@ -8,6 +8,8 @@ import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import 'dart:convert'; +import 'package:qhd_prevention/tools/tools.dart'; + class Question { final Map rawData; final String questionDry; @@ -83,20 +85,36 @@ class _TakeExamPageState extends State { .toList(); final numberOfExams = widget.examInfo['NUMBEROFEXAMS']; - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async{ if (numberOfExams is int) { if (numberOfExams > 0) { } else if (numberOfExams == -9999) { _showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!'); } else { - _showTip('您无考试次数!'); + + final ok = await CustomAlertDialog.showConfirm( + context, + title: '温馨提示', + content: '您无考试次数!', + ); + if (ok) { + Navigator.pop(context); + + }; } } else if (numberOfExams is String) { if (numberOfExams == '-9999') { _showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!'); } } else { - _showTip('您无考试次数!'); + final ok = await CustomAlertDialog.showConfirm( + context, + title: '温馨提示', + content: '您无考试次数!', + ); + if (ok) { + Navigator.pop(context); + }; } }); @@ -184,6 +202,7 @@ class _TakeExamPageState extends State { } Future _submit() async { + LoadingDialogHelper.show(message: '正在提交'); for (var q in questions) { if (q.questionType == '2') q.checked = q.checked.replaceAll(',', ''); q.rawData['checked'] = q.checked; @@ -200,10 +219,11 @@ class _TakeExamPageState extends State { 'options': jsonEncode(questions.map((q) => q.rawData).toList()), }; final res = await ApiService.submitExam(data); + LoadingDialogHelper.hide(); if (res['result'] == 'success') { final score = res['examScore'] ?? '0'; final passed = res['examResult'] != '0'; - final ok = await CustomAlertDialog.showConfirm( + await CustomAlertDialog.showConfirm( context, title: '温馨提示', content: @@ -214,7 +234,6 @@ class _TakeExamPageState extends State { confirmText: '确定', ); Navigator.of(context).pop(); - } } diff --git a/lib/pages/home/study/video_study_detail_page.dart b/lib/pages/home/study/video_study_detail_page.dart index 9f3e3be..0ba4a71 100644 --- a/lib/pages/home/study/video_study_detail_page.dart +++ b/lib/pages/home/study/video_study_detail_page.dart @@ -8,8 +8,9 @@ import '../../../tools/tools.dart'; class VideoStudyDetailPage extends StatefulWidget { final String studentId; final String classId; + final int state; - const VideoStudyDetailPage({Key? key, required this.studentId, required this.classId}) : super(key: key); + const VideoStudyDetailPage({Key? key, required this.studentId, required this.classId, required this.state}) : super(key: key); @override _VideoStudyDetailPageState createState() => _VideoStudyDetailPageState(); @@ -103,8 +104,8 @@ class _VideoStudyDetailPageState extends State { List keys = q.questionType == '3' ? ['A', 'B'] : ['A', 'B', 'C', 'D']; return Column( children: keys.map((key) { - bool isChecked = q.answer.split(',').contains(key); - bool isCorrect = q.answerRight.split(',').contains(key); + bool isChecked = q.answer.contains(key); + bool isCorrect = q.answerRight.contains(key); bool right = q.answered && isCorrect && isChecked; bool err = q.answered && !isCorrect && isChecked; bool warn = q.answered && isCorrect && !isChecked; @@ -161,7 +162,7 @@ class _VideoStudyDetailPageState extends State { Widget build(BuildContext context) { final q = questions.isNotEmpty ? questions[current] : null; return Scaffold( - appBar: MyAppbar(title: '课程练习详情'), + appBar: MyAppbar(title: widget.state == 1 ? '课程练习详情' : "考试详情"), body: loading ? Center(child: CircularProgressIndicator()) : questions.isEmpty @@ -215,7 +216,7 @@ class _VideoStudyDetailPageState extends State { SizedBox(height: 16), _buildOptions(q), Divider(), - Text('我的答案: ${_renderAnswerText(q)}'), + Text('我的答案: ${q.answer}'), Text('正确答案: ${q.answerRight}'), Text('权威解读: ${q.descr}'), ], diff --git a/lib/pages/home/tap/item_list_widget.dart b/lib/pages/home/tap/item_list_widget.dart index 3d14ce7..302fe0f 100644 --- a/lib/pages/home/tap/item_list_widget.dart +++ b/lib/pages/home/tap/item_list_widget.dart @@ -655,6 +655,39 @@ class ItemListWidget { ), ); } + /// 单行布局: + /// 标题 + 按钮(挨着) + static Widget OneRowStartButtonTitle({ + required String label, // 标题 + String buttonText = '分析详情', // 按钮文字 + String text = '', // 标题 + required VoidCallback onTap, // 点击回调 + double fontSize = 14, // 字体大小 + Color btnColor = Colors.green, + }) { + return Container( + padding: const EdgeInsets.symmetric(vertical: vertical_inset, horizontal: horizontal_inset), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + ), + CustomButton( + text: buttonText, + height: 30, + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), + backgroundColor: btnColor, + onPressed: onTap, + ), + ], + ), + ); + } /// 单行布局: /// 标题 + 网络图片 @@ -957,7 +990,44 @@ class ItemListWidget { ); } - + /// 两行垂直布局: + /// 标题 + /// 第二行:多行文本展示 + static Widget twoRowTitleText({ + required String label, // 第一行标题 + required String text, // 显示内容或提示 + double fontSize = 14, // 字体大小 + bool isRequired = true, + }) { + return Container( + padding: const EdgeInsets.symmetric(vertical: vertical_inset, horizontal: horizontal_inset), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 第一行:标题 + Text( + label, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Text( + text, + maxLines: 5, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: fontSize, + color: detailtextColor, + ), + ), + ], + ), + ); + } static Widget itemContainer(Widget child, {double horizontal = horizontal_inset, double vertical = vertical_inset}) { return Container( diff --git a/lib/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart b/lib/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart index 3a7abd9..625a3ca 100644 --- a/lib/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart @@ -236,7 +236,7 @@ class MeasuresListWidget extends StatelessWidget { imageUrl, width: 40, height: 40, - fit: BoxFit.cover, + fit: BoxFit.fill, // 防止图片加载失败导致布局异常 errorBuilder: (_, __, ___) => Container( @@ -1017,7 +1017,7 @@ class _SelectionPopupState extends State { borderRadius: BorderRadius.circular(5), // 圆角半径 ), backgroundColor: Colors.white, - insetPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 40), + insetPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 40), child: SizedBox( width: double.infinity, height: screenHeight(context), @@ -1144,55 +1144,67 @@ class _SelectionPopupState extends State { ), // 列表多选 Expanded( - child: ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 0), - itemCount: list.length, - itemBuilder: (c, i) { - final item = list[i]; - final value = selectValue; - final key = - widget.type == 'assignments' - ? item['CHECK_NO'] as String? ?? '' - : item['NAME'] as String? ?? ''; - final checked = value.contains(key); - return CheckboxListTile( - controlAffinity: ListTileControlAffinity.leading, - // <-- 把图标放左边 - contentPadding: EdgeInsets.zero, - // 可选:移除左右默认 padding,使图标贴近左侧 - activeColor: Colors.blue, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(key, style: TextStyle(fontSize: 13)), - if (widget.type == 'assignments') ...[ - Text( - '作业内容: ${item['WORK_CONTENT'] ?? ''}', - style: TextStyle(fontSize: 13), - ), - Text( - '作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}', - style: TextStyle(fontSize: 13), - ), - Text( - '作业申请时间: ${item['CREATTIME'] ?? ''}', - style: TextStyle(fontSize: 13), - ), - ], + child: + ListTileTheme( + dense: true, + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 0), + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + itemCount: list.length, + itemBuilder: (c, i) { + final item = list[i]; + final value = selectValue; + final key = + widget.type == 'assignments' + ? item['CHECK_NO'] as String? ?? '' + : item['NAME'] as String? ?? ''; + final checked = value.contains(key); + return CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 0), + activeColor: Colors.blue, + dense: true, // 更紧凑 + visualDensity: const VisualDensity(vertical: -4), // 调整垂直密度:-1..-4 + title: Column( + mainAxisSize: MainAxisSize.min, // 避免 Column 占用多余高度 + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + key, + style: const TextStyle(fontSize: 13, height: 1.0), + ), + if (widget.type == 'assignments') ...[ + const SizedBox(height: 4), // 子文本间距可更小 + Text( + '作业内容: ${item['WORK_CONTENT'] ?? ''}', + style: const TextStyle(fontSize: 13, height: 1.0), + ), + Text( + '作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}', + style: const TextStyle(fontSize: 13, height: 1.0), + ), + Text( + '作业申请时间: ${item['CREATTIME'] ?? ''}', + style: const TextStyle(fontSize: 13, height: 1.0), + ), ], - ), - value: checked, - onChanged: (v) { - setState(() { - if (v == true) - selectValue.add(key); - else - selectValue.remove(key); - }); - }, - ); - }, - ), + ], + ), + value: checked, + onChanged: (v) { + setState(() { + if (v == true) + selectValue.add(key); + else + selectValue.remove(key); + }); + }, + ) + ; + }, + ), + ) + ), // 确定/关闭 Padding( @@ -1608,7 +1620,7 @@ class ConfirmWithSignWidget extends StatelessWidget { ), // 描述内容(当 descrText 有意义才显示) - if (descrText.isNotEmpty && descrText != '无') + if (descrText.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), child: Text( diff --git a/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart b/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart index 3974392..aac3fce 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dangerous_options_page.dart @@ -96,10 +96,26 @@ class _DangerousOptionsPageState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -333,7 +349,8 @@ class _DangerousOptionsPageState extends State { onChanged: (paths) {}, onMediaAdded: _onImageAdded, onMediaRemoved: (path) { - final item = imgList.firstWhere((e) => e.localPath == path); + print(path); + final item = imgList.firstWhere((e) => path.contains(e.localPath) ); _onImageRemoved(item); }, onAiIdentify: () {}, diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/HotWorkDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/HotWorkDetailFormWidget.dart index 993c32f..2c38beb 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/HotWorkDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/HotWorkDetailFormWidget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import '../../../../../../tools/tools.dart'; import '../../../item_list_widget.dart'; @@ -166,13 +167,13 @@ class _HotWorkDetailFormWidgetState extends State { hintText: '请选择动火人及证书编号', onTap: widget.onChooseHotworkUser, ), - if (FormUtils.hasValue(pd, 'CONFIRM_DEPARTMENT_NAME') && + if (FormUtils.hasValue(pd, 'UNIT_NAME') && !widget.isEditable) ...[ const Divider(), ItemListWidget.singleLineTitleText( label: '作业单位:', isEditable: false, - text: pd['CONFIRM_DEPARTMENT_NAME'] ?? '', + text: pd['UNIT_NAME'] ?? '', ), ], if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') && @@ -209,7 +210,7 @@ class _HotWorkDetailFormWidgetState extends State { }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), ItemListWidget.twoRowButtonTitleText( @@ -239,15 +240,10 @@ class _HotWorkDetailFormWidgetState extends State { if (FormUtils.hasValue(pd, 'WORK_START_DATE') && !widget.isEditable) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '动火作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', + ItemListWidget.twoRowTitleText(label: '动火作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), - ), if (!FormUtils.hasValue(pd, 'WORK_END_DATE')) ...[ const Divider(), @@ -380,6 +376,7 @@ class _HotWorkDetailFormWidgetState extends State { isRequired: pd['WORK_LEVEL'] == '特级', isEditable: widget.isEditable, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; @@ -391,7 +388,7 @@ class _HotWorkDetailFormWidgetState extends State { if (FormUtils.hasValue(pd, 'ANALYZE_TIME') && !widget.isEditable) ...[ const Divider(), - ItemListWidget.OneRowButtonTitleText( + ItemListWidget.OneRowStartButtonTitle( label: '气体分析信息:', text: pd['ANALYZE_USER_NAME'] ?? '', onTap: widget.onAnalyzeTap, diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/aqjd_work_detail/hotwork_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/aqjd_work_detail/hotwork_aqjd_detail.dart index 2305c0b..c4812f3 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/aqjd_work_detail/hotwork_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/aqjd_work_detail/hotwork_aqjd_detail.dart @@ -63,10 +63,26 @@ class _HotworkAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart index f3f176f..68b0b58 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart @@ -203,18 +203,21 @@ class _HotworkApplyDetailState extends State { setState(() { List names = []; List ids = []; - + List units = []; for (String name in res) { for (final item in workUserList) { if (item['NAME'] == name) { names.add(item['UNAME']); ids.add(item['PERSONNELMANAGEMENT_ID']); + units.add(item['UNIT_NAME']); } } } pd['WORK_USER_NAME'] = names.join(','); pd['WORK_USER_ID'] = ids.join(','); pd['WORK_USER'] = res.join(','); + pd['UNIT_NAME'] = units.first; + print('${pd['WORK_USER_NAME']}\n${pd['WORK_USER_ID']}\n${pd['WORK_USER']}'); }); } else { @@ -383,6 +386,7 @@ class _HotworkApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/hotwork_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/hotwork_list_page.dart index 621e4c1..37fdce9 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/hotwork_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/hotwork_list_page.dart @@ -162,9 +162,7 @@ class _HotWorkListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -499,14 +497,11 @@ class _HotWorkListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/szaq_work_detail/hotwork_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/szaq_work_detail/hotwork_set_safe_detail.dart index 874d6d6..2aa4b04 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/szaq_work_detail/hotwork_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/szaq_work_detail/hotwork_set_safe_detail.dart @@ -239,10 +239,6 @@ class _HotworkSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } List> signers = []; String reasonText = ''; @@ -495,23 +491,7 @@ class _HotworkSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work/ysgd_work_detail/hotwork_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work/ysgd_work_detail/hotwork_ysgd_detail.dart index 40343d9..0eba2f1 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work/ysgd_work_detail/hotwork_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work/ysgd_work_detail/hotwork_ysgd_detail.dart @@ -70,10 +70,26 @@ class _HotworkYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -190,11 +206,7 @@ class _HotworkYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -383,7 +395,7 @@ class _HotworkYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/CutroadDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/CutroadDetailFormWidget.dart index cfb666b..d11c7f7 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/CutroadDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/CutroadDetailFormWidget.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.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 '../../../../../../tools/tools.dart'; @@ -190,7 +191,7 @@ class _CutroadDetailFormWidgetState extends State { }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), if (FormUtils.hasValue(signs, 'PROJECT_MANAGER') && @@ -235,14 +236,10 @@ class _CutroadDetailFormWidgetState extends State { ), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '断路作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '段路作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), + ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -348,6 +345,7 @@ class _CutroadDetailFormWidgetState extends State { isClean: widget.isEditable, cleanText: '清除监控', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/aqjd_work_detail/cutroad_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/aqjd_work_detail/cutroad_aqjd_detail.dart index d080787..4c49dc2 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/aqjd_work_detail/cutroad_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/aqjd_work_detail/cutroad_aqjd_detail.dart @@ -62,10 +62,26 @@ class _CutroadAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/cutroad_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/cutroad_list_page.dart index 9b7e02f..fe6aaca 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/cutroad_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/cutroad_list_page.dart @@ -170,9 +170,8 @@ class _CutroadListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -445,14 +444,11 @@ class _CutroadListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart index af141b0..e7d14ab 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/dl_work_detai/cutroad_apply_detail.dart @@ -176,6 +176,7 @@ class _CutroadApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/szaq_work_detail/cutroad_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/szaq_work_detail/cutroad_set_safe_detail.dart index 1c591d5..6d14fec 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/szaq_work_detail/cutroad_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/szaq_work_detail/cutroad_set_safe_detail.dart @@ -297,10 +297,7 @@ class _CutroadSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -530,23 +527,7 @@ class _CutroadSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/ysgd_work_detail/cutroad_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/ysgd_work_detail/cutroad_ysgd_detail.dart index 4dc0dd2..2c73184 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/ysgd_work_detail/cutroad_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/ysgd_work_detail/cutroad_ysgd_detail.dart @@ -69,10 +69,26 @@ class _CutroadYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -189,11 +205,7 @@ class _CutroadYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -374,7 +386,7 @@ class _CutroadYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/dl_work/zyr_work_detail/cutroad_zyr_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dl_work/zyr_work_detail/cutroad_zyr_detail.dart index d1cb8e1..a57ca3f 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dl_work/zyr_work_detail/cutroad_zyr_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dl_work/zyr_work_detail/cutroad_zyr_detail.dart @@ -253,10 +253,26 @@ class _CutroadZyrDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/BreakgroundDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/BreakgroundDetailFormWidget.dart index e691c48..ad578eb 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/BreakgroundDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/BreakgroundDetailFormWidget.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.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/tools/h_colors.dart'; @@ -141,17 +142,17 @@ class _BreakgroundDetailFormWidgetState hintText: '请输入作业内容', text: pd['JOB_CONTENT'] ?? '', ), - if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && - !widget.isEditable) ...[ - const Divider(), - // ItemListWidget.OneRowImageTitle(label: '作业负责人', imgPath: imgPath) - SignRowImageTitle( - label: '监护人:', - signKey: 'GUARDIAN', - signs: widget.signs, - text: pd['GUARDIAN_USER_NAME'] ?? '', - ), - ], + // if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && + // !widget.isEditable) ...[ + // const Divider(), + // // ItemListWidget.OneRowImageTitle(label: '作业负责人', imgPath: imgPath) + // SignRowImageTitle( + // label: '监护人:', + // signKey: 'GUARDIAN', + // signs: widget.signs, + // text: pd['GUARDIAN_USER_NAME'] ?? '', + // ), + // ], if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') && !widget.isEditable) ...[ const Divider(), @@ -187,7 +188,7 @@ class _BreakgroundDetailFormWidgetState }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), @@ -258,14 +259,9 @@ class _BreakgroundDetailFormWidgetState ), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -371,6 +367,7 @@ class _BreakgroundDetailFormWidgetState isClean: widget.isEditable, cleanText: '清除监控', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/aqjd_work_detail/breakground_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/aqjd_work_detail/breakground_aqjd_detail.dart index 6fd84a4..639e112 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/aqjd_work_detail/breakground_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/aqjd_work_detail/breakground_aqjd_detail.dart @@ -62,10 +62,26 @@ class _BreakgroundAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/breakground_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/breakground_list_page.dart index 487fcc7..31d785a 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/breakground_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/breakground_list_page.dart @@ -167,9 +167,8 @@ class _BreakgroundListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -558,18 +557,16 @@ class _BreakgroundListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } + // 创建选项列表 final options = stepList.map((e) => e['STEP_NAME'] as String).toList(); diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart index fd6e8ee..d67c470 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/dt_work_detai/breakground_apply_detail.dart @@ -325,6 +325,7 @@ class _BreakgroundApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/szaq_work_detail/breakground_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/szaq_work_detail/breakground_set_safe_detail.dart index 9349f36..d0aa9c0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/szaq_work_detail/breakground_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/szaq_work_detail/breakground_set_safe_detail.dart @@ -239,10 +239,7 @@ class _BreakgroundSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -506,23 +503,7 @@ class _BreakgroundSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/dt_work/ysgd_work_detail/breakground_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dt_work/ysgd_work_detail/breakground_ysgd_detail.dart index 7395412..4c2bb5d 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dt_work/ysgd_work_detail/breakground_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dt_work/ysgd_work_detail/breakground_ysgd_detail.dart @@ -70,10 +70,26 @@ class _BreakgroundYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -190,11 +206,7 @@ class _BreakgroundYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -377,7 +389,7 @@ class _BreakgroundYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/HoistworkDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/HoistworkDetailFormWidget.dart index b2ba79f..c08975e 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/HoistworkDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/HoistworkDetailFormWidget.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.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/tools/h_colors.dart'; import 'package:intl/intl.dart'; @@ -166,26 +167,26 @@ class _HoistworkDetailFormWidgetState extends State { text: pd['WORK_USER_USER_NAME'] ?? '', ), ], - if (FormUtils.hasValue(pd, 'SISUO_USER_NAME') && - !widget.isEditable) ...[ - const Divider(), - SignRowImageTitle( - label: '司索人:', - signKey: 'SISUO', - signs: widget.signs, - text: pd['SISUO_USER_NAME'] ?? '', - ), - ], - if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && - !widget.isEditable) ...[ - const Divider(), - SignRowImageTitle( - label: '监护人:', - signKey: 'GUARDIAN', - signs: widget.signs, - text: pd['GUARDIAN_USER_NAME'] ?? '', - ), - ], + // if (FormUtils.hasValue(pd, 'SISUO_USER_NAME') && + // !widget.isEditable) ...[ + // const Divider(), + // SignRowImageTitle( + // label: '司索人:', + // signKey: 'SISUO', + // signs: widget.signs, + // text: pd['SISUO_USER_NAME'] ?? '', + // ), + // ], + // if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && + // !widget.isEditable) ...[ + // const Divider(), + // SignRowImageTitle( + // label: '监护人:', + // signKey: 'GUARDIAN', + // signs: widget.signs, + // text: pd['GUARDIAN_USER_NAME'] ?? '', + // ), + // ], if (FormUtils.hasValue(pd, 'PROJECT_MANAGER_USER_NAME') && !widget.isEditable) ...[ const Divider(), @@ -197,25 +198,40 @@ class _HoistworkDetailFormWidgetState extends State { ), ], const Divider(), - ItemListWidget.singleLineTitleText( - label: '吊物质量(吨):', - keyboardType: TextInputType.numberWithOptions( - decimal: true, // 允许小数点 - signed: false, // 允许负号(如需) + if (widget.isEditable) ...[ + ItemListWidget.singleLineTitleText( + label: '吊物质量(吨):', + keyboardType: TextInputType.numberWithOptions( + decimal: true, // 允许小数点 + signed: false, // 允许负号(如需) + ), + isEditable: widget.isEditable, + controller: widget.hightController, + hintText: '请输入吊物质量(吨)', + text: pd['PART_WEIGHT'] ?? '', ), - isEditable: widget.isEditable, - controller: widget.hightController, - hintText: '请输入吊物质量(吨)', - text: pd['PART_WEIGHT'] ?? '', - ), - const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业级别:', - isEditable: false, - onTap: widget.onChooseLevel, - text: _getWorkLevelText(pd['WORK_LEVEL']), - ), - const Divider(), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '作业级别:', + isEditable: false, + onTap: widget.onChooseLevel, + text: _getWorkLevelText(pd['WORK_LEVEL']), + ), + const Divider(), + ]else...[ + ItemListWidget.singleLineTitleText( + label: '吊物质量(t)及作业级别:', + keyboardType: TextInputType.numberWithOptions( + decimal: true, // 允许小数点 + signed: false, // 允许负号(如需) + ), + isEditable: widget.isEditable, + controller: widget.hightController, + text: '${pd['PART_WEIGHT'] ?? ''}t,${_getWorkLevelText(pd['WORK_LEVEL'])}', + ), + const Divider(), + ], + ItemListWidget.twoRowButtonTitleText( label: '关联其他特殊作业及安全作业票编号', @@ -239,7 +255,7 @@ class _HoistworkDetailFormWidgetState extends State { }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), ItemListWidget.twoRowButtonTitleText( @@ -267,14 +283,10 @@ class _HoistworkDetailFormWidgetState extends State { ), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), + ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -379,6 +391,7 @@ class _HoistworkDetailFormWidgetState extends State { isClean: widget.isEditable, cleanText: '清除监控', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/SpecialWorkFormBaseWork.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/SpecialWorkFormBaseWork.dart index 7f9c814..b217666 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/SpecialWorkFormBaseWork.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/SpecialWorkFormBaseWork.dart @@ -312,13 +312,13 @@ class SpecialWorkFormBaseWork extends StatelessWidget { headerTitle: '所在单位意见', roleTitle: '', ), - if (FormUtils.hasValue(signs, 'AUDIT')) + if (FormUtils.hasValue(signs, 'REVIEWER')) ConfirmWithSignWidget( signs: signs, pd: pd, baseImgPath: baseImgPath, - sectionKey: 'AUDIT', - nameKey: 'AUDIT_USER_NAME', + sectionKey: 'REVIEWER', + nameKey: 'REVIEWER_USER_NAME', headerTitle: '审核部门意见', roleTitle: '', ), diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/aqjd_work_detail/hoistwork_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/aqjd_work_detail/hoistwork_aqjd_detail.dart index 3754287..890ffa5 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/aqjd_work_detail/hoistwork_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/aqjd_work_detail/hoistwork_aqjd_detail.dart @@ -62,10 +62,26 @@ class _HoistworkAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart index f659601..946f03a 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/dz_work_detai/hoistwork_apply_detail.dart @@ -325,6 +325,7 @@ class _HoistworkApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; @@ -566,8 +567,10 @@ class _HoistworkApplyDetailState extends State { // 给所有输入框赋值 _contentController.text = pd['WORK_CONTENT'] ?? ''; + _nameController.text = pd['TOOL_NAME'] ?? ''; + _contentController.text = pd['WORK_CONTENT'] ?? ''; _locationController.text = pd['WORK_PLACE'] ?? ''; - _hightController.text = pd['WORK_HIGH'] ?? ''; + _hightController.text = pd['PART_WEIGHT'] ?? ''; _relatedController.text = pd['SPECIAL_WORK'] ?? ''; _riskController.text = pd['RISK_IDENTIFICATION'] ?? ''; }); diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/hoistwork_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/hoistwork_list_page.dart index 39cf1c1..5c1eb16 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/hoistwork_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/hoistwork_list_page.dart @@ -164,9 +164,8 @@ class _HoistworkListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -449,14 +448,11 @@ class _HoistworkListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/szaq_work_detail/hoistwork_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/szaq_work_detail/hoistwork_set_safe_detail.dart index f04c7a5..3e79896 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/szaq_work_detail/hoistwork_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/szaq_work_detail/hoistwork_set_safe_detail.dart @@ -238,10 +238,7 @@ class _HoistworkSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -505,23 +502,7 @@ class _HoistworkSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/dz_work/ysgd_work_detail/hoistwork_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dz_work/ysgd_work_detail/hoistwork_ysgd_detail.dart index 87e8943..ecae435 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dz_work/ysgd_work_detail/hoistwork_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dz_work/ysgd_work_detail/hoistwork_ysgd_detail.dart @@ -70,10 +70,26 @@ class _HoistworkYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -190,11 +206,7 @@ class _HoistworkYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -377,7 +389,7 @@ class _HoistworkYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/HighWorkDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/HighWorkDetailFormWidget.dart index 9234d6a..2f985f1 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/HighWorkDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/HighWorkDetailFormWidget.dart @@ -3,6 +3,7 @@ import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import '../../../../../../tools/tools.dart'; import '../../../item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart'; @@ -162,7 +163,7 @@ class _HighWorkDetailFormWidgetState extends State { ItemListWidget.selectableLineTitleTextRightButton( label: '高处作业级别:', isTip: true, - isEditable: false, + isEditable: widget.isEditable, isRequired: true, onTap: widget.onChooseLevel, onTapTip: () { @@ -191,16 +192,16 @@ class _HighWorkDetailFormWidgetState extends State { text: pd['WORK_USER_USER_NAME'] ?? '', ), ], - if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && - !widget.isEditable) ...[ - const Divider(), - SignRowImageTitle( - label: '监护人:', - signKey: 'GUARDIAN', - signs: widget.signs, - text: pd['GUARDIAN_USER_NAME'] ?? '', - ), - ], + // if (FormUtils.hasValue(pd, 'GUARDIAN_USER_NAME') && + // !widget.isEditable) ...[ + // const Divider(), + // SignRowImageTitle( + // label: '监护人:', + // signKey: 'GUARDIAN', + // signs: widget.signs, + // text: pd['GUARDIAN_USER_NAME'] ?? '', + // ), + // ], if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') && !widget.isEditable) ...[ const Divider(), @@ -235,7 +236,7 @@ class _HighWorkDetailFormWidgetState extends State { }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), ItemListWidget.twoRowButtonTitleText( @@ -264,31 +265,11 @@ class _HighWorkDetailFormWidgetState extends State { ), const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业视频监控:', - isClean: widget.isEditable, - cleanText: '清除监控', - onTapClean: () { - setState(() { - pd['VIDEONAME'] = ''; - pd['VIDEOMANAGER_ID'] = ''; - }); - }, - isRequired: false, - isEditable: widget.isEditable, - onTap: widget.onChooseVideoManager ?? () {}, - text: pd['VIDEONAME'] ?? '', - ), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -386,7 +367,22 @@ class _HighWorkDetailFormWidgetState extends State { hintText: '', text: pd['LATITUDE_LONGITUDE'] ?? (widget.isEditable ? '' : '无'), ), - + ItemListWidget.selectableLineTitleTextRightButton( + label: '作业视频监控:', + isClean: widget.isEditable, + cleanText: '清除监控', + onTapClean: () { + ToastUtil.showNormal(context, '已清除'); + setState(() { + pd['VIDEONAME'] = ''; + pd['VIDEOMANAGER_ID'] = ''; + }); + }, + isRequired: false, + isEditable: widget.isEditable, + onTap: widget.onChooseVideoManager ?? () {}, + text: pd['VIDEONAME'] ?? '', + ), ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/aqjd_work_detail/highwork_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/aqjd_work_detail/highwork_aqjd_detail.dart index ed487f8..ed09711 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/aqjd_work_detail/highwork_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/aqjd_work_detail/highwork_aqjd_detail.dart @@ -62,10 +62,26 @@ class _HighworkAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart index 00df405..b5fc862 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/gc_work_detai/highwork_apply_detail.dart @@ -115,21 +115,21 @@ class _HighworkApplyDetailState extends State { }); _hightController.addListener(() { pd['WORK_HIGH'] = _hightController.text.trim(); - setState(() { - double height = double.parse(pd['WORK_HIGH']); - int level = 0; - if (height >= 2.0 && height <= 5.0) { - level = 1; - }else if (height >= 5.0 && height <= 15.0) { - level = 2; - }else if (height >= 15.0 && height <= 30.0) { - level = 3; - }else{ - level = 4; - } - pd['WORK_LEVEL'] = level; - pd['WORK_LEVEL_NAME'] = levelList[level-1].toString(); - }); + // setState(() { + // double height = double.parse(pd['WORK_HIGH']); + // int level = 0; + // if (height >= 2.0 && height <= 5.0) { + // level = 1; + // }else if (height >= 5.0 && height <= 15.0) { + // level = 2; + // }else if (height >= 15.0 && height <= 30.0) { + // level = 3; + // }else{ + // level = 4; + // } + // pd['WORK_LEVEL'] = level; + // pd['WORK_LEVEL_NAME'] = levelList[level-1].toString(); + // }); }); _relatedController.addListener(() { pd['SPECIAL_WORK'] = _relatedController.text.trim(); @@ -343,6 +343,7 @@ class _HighworkApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/highwork_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/highwork_list_page.dart index 4ff5e9c..b886b9c 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/highwork_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/highwork_list_page.dart @@ -163,9 +163,7 @@ class _HighworkListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + ToastUtil.showNormal(context, '获取流程图失败: $e'); } } @@ -443,14 +441,11 @@ class _HighworkListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/szaq_work_detail/highwork_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/szaq_work_detail/highwork_set_safe_detail.dart index 00a587a..8400046 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/szaq_work_detail/highwork_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/szaq_work_detail/highwork_set_safe_detail.dart @@ -296,10 +296,7 @@ class _HighworkSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -530,23 +527,7 @@ class _HighworkSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/gc_work/ysgd_work_detail/highwork_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/gc_work/ysgd_work_detail/highwork_ysgd_detail.dart index 5dbe896..a76f21c 100644 --- a/lib/pages/home/tap/tabList/special_wrok/gc_work/ysgd_work_detail/highwork_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/gc_work/ysgd_work_detail/highwork_ysgd_detail.dart @@ -70,12 +70,28 @@ class _HighworkYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } - + /// 删除图片处理:调用删除接口并更新列表 Future _onImageRemoved(ImageData item) async { if (item.serverPath != null) { @@ -190,11 +206,7 @@ class _HighworkYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -377,7 +389,7 @@ class _HighworkYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ElectricityDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ElectricityDetailFormWidget.dart index c89d849..c6eefb5 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ElectricityDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ElectricityDetailFormWidget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import '../../../../../../tools/tools.dart'; import '../../../item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart'; @@ -238,7 +239,7 @@ class _ElectricityDetailFormWidgetState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/electricity_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/electricity_list_page.dart index 751fa33..6849393 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/electricity_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/electricity_list_page.dart @@ -164,9 +164,8 @@ class _ElectricityListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -519,14 +518,11 @@ class _ElectricityListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart index 54ef6a6..4b528d3 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/lsyd_work_detai/electricity_apply_detail.dart @@ -313,6 +313,7 @@ class _ElectricityApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/szaq_work_detail/electricity_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/szaq_work_detail/electricity_set_safe_detail.dart index c3d1c7c..ed0c370 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/szaq_work_detail/electricity_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/szaq_work_detail/electricity_set_safe_detail.dart @@ -239,10 +239,7 @@ class _ElectricitySetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -494,23 +491,7 @@ class _ElectricitySetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ysgd_work_detail/electricity_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ysgd_work_detail/electricity_ysgd_detail.dart index 3b8f1da..bc9fa28 100644 --- a/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ysgd_work_detail/electricity_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/lsyd_work/ysgd_work_detail/electricity_ysgd_detail.dart @@ -70,10 +70,26 @@ class _ElectricityYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -190,11 +206,7 @@ class _ElectricityYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -383,7 +395,7 @@ class _ElectricityYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/BlindboardDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/BlindboardDetailFormWidget.dart index 7fb8d9f..be7b8ed 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/BlindboardDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/BlindboardDetailFormWidget.dart @@ -7,6 +7,7 @@ import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import '../../../../../../tools/tools.dart'; import '../../../item_list_widget.dart'; @@ -487,14 +488,10 @@ class _BlindboardDetailFormWidgetState }), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '盲板作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '盲板作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), + ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -546,7 +543,8 @@ class _BlindboardDetailFormWidgetState text: pd['WORK_EXPECTED_END_TIME'] ?? '', ), ], - if (!widget.isEditable && FormUtils.hasValue(pd, 'BOARD_PATH_NO')) + if (!widget.isEditable && FormUtils.hasValue(pd, 'BOARD_PATH_NO')) ...[ + const Divider(), ConfirmWithSignWidget( signs: widget.signs, pd: pd, @@ -558,7 +556,7 @@ class _BlindboardDetailFormWidgetState contentKey: 'BOARD_PATH_NO', roleTitle: '', ), - + ], if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') && !widget.isEditable) ...[ const Divider(), @@ -614,7 +612,7 @@ class _BlindboardDetailFormWidgetState }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), ItemListWidget.twoRowButtonTitleText( @@ -694,6 +692,7 @@ class _BlindboardDetailFormWidgetState isClean: widget.isEditable, cleanText: '清除监控', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqcs_work_detail/blindboard_safe_func_sure.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqcs_work_detail/blindboard_safe_func_sure.dart index 7368160..b775cd0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqcs_work_detail/blindboard_safe_func_sure.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqcs_work_detail/blindboard_safe_func_sure.dart @@ -38,6 +38,7 @@ class _BlindboardSafeFuncSureState extends State { /// 详情 late Map pd = {}; + late Map signs = {}; late List boardList = []; late List> measuresList = []; @@ -53,6 +54,15 @@ class _BlindboardSafeFuncSureState extends State { super.initState(); _getData(); } + Future _getSigns(String homework_id) async { + final data = await ApiService.listSignFinished( + 'blindboard', + homework_id.length > 0 ? homework_id : widget.BLINDBOARD_ID, + ); + setState(() { + signs = data['signs'] ?? {}; + }); + } /// 弹出单位选择 void chooseUnitHandle(MeasureItem item) { @@ -268,6 +278,7 @@ class _BlindboardSafeFuncSureState extends State { setState(() { pd = data['pd']; boardList = data['boardList']; + _getSigns(pd['BLINDBOARD_ID'] ?? ''); _getMeasures(); }); } @@ -367,6 +378,7 @@ class _BlindboardSafeFuncSureState extends State { onSign: (item) { _itemToSign(item, measuresList.indexOf(item)); }, + ), ), ], @@ -445,11 +457,13 @@ class _BlindboardSafeFuncSureState extends State { pd: pd, isEditable: false, onChooseLevel: () {}, + signs: signs, ), SizedBox(height: 20), _setSafeDetailWidget(), SizedBox(height: 20), _bottomButtons(), + ], ), ), diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqjd_work_detail/blindboard_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqjd_work_detail/blindboard_aqjd_detail.dart index 220815a..9c76340 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqjd_work_detail/blindboard_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/aqjd_work_detail/blindboard_aqjd_detail.dart @@ -63,10 +63,26 @@ class _BlindboardAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/blindboard_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/blindboard_list_page.dart index 7a84870..0acb6d9 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/blindboard_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/blindboard_list_page.dart @@ -167,9 +167,8 @@ class _BlindboardListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -420,14 +419,11 @@ class _BlindboardListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart index 9375e57..746d097 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/cjry_work_detail/blindboard_cjry_detail.dart @@ -261,10 +261,26 @@ class _BlindboardCjryDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart index 9ef525a..ed564d5 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/mbcd_work_detai/blindboard_apply_detail.dart @@ -323,6 +323,7 @@ class _BlindboardApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/szaq_work_detail/blindboard_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/szaq_work_detail/blindboard_set_safe_detail.dart index 6ec9819..54e2544 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/szaq_work_detail/blindboard_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/szaq_work_detail/blindboard_set_safe_detail.dart @@ -250,10 +250,7 @@ class _BlindboardSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -508,23 +505,7 @@ class _BlindboardSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/ysgd_work_detail/blindboard_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/ysgd_work_detail/blindboard_ysgd_detail.dart index c62ba42..925b737 100644 --- a/lib/pages/home/tap/tabList/special_wrok/mbcd_work/ysgd_work_detail/blindboard_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/mbcd_work/ysgd_work_detail/blindboard_ysgd_detail.dart @@ -71,10 +71,26 @@ class _BlindboardYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -181,11 +197,7 @@ class _BlindboardYsgdDetailState extends State { return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -370,7 +382,7 @@ class _BlindboardYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpaceWorkDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpaceWorkDetailFormWidget.dart index 58a15bc..f79b055 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpaceWorkDetailFormWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpaceWorkDetailFormWidget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import '../../../../../../tools/tools.dart'; import '../../../item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/MeasuresListWidget.dart'; @@ -116,6 +117,14 @@ class _SpaceWorkDetailFormWidgetState extends State { isEditable: false, text: pd['APPLY_USER_NAME'] ?? '', ), + if (FormUtils.hasValue(pd, 'CREATTIME') && !widget.isEditable) ...[ + const Divider(), + ItemListWidget.singleLineTitleText( + label: '作业申请时间:', + isEditable: false, + text: pd['CREATTIME'] ?? '', + ), + ], const Divider(), if (!widget.isEditSpaceName) ItemListWidget.selectableLineTitleTextRightButton( @@ -125,6 +134,7 @@ class _SpaceWorkDetailFormWidgetState extends State { isClean: widget.isEditable, text: pd['LIMITSPACE_NAME'] ?? '', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['LIMITSPACE_NAME'] = ''; pd['LIMITSPACE_NUMBER'] = ''; @@ -142,6 +152,7 @@ class _SpaceWorkDetailFormWidgetState extends State { cleanText: '选择', text: pd['LIMITSPACE_NAME'] ?? '', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['LIMITSPACE_NAME'] = ''; pd['LIMITSPACE_NUMBER'] = ''; @@ -238,7 +249,7 @@ class _SpaceWorkDetailFormWidgetState extends State { }, hintText: '请输入关联的其他特殊作业及安全作业票编号', controller: widget.relatedController, - text: pd['SPECIAL_WORK'] ?? '', + text: pd['SPECIAL_WORK'] ?? '无', ), const Divider(), ItemListWidget.twoRowButtonTitleText( @@ -267,14 +278,9 @@ class _SpaceWorkDetailFormWidgetState extends State { ), if (FormUtils.hasValue(pd, 'WORK_START_DATE')) ...[ const Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - label: '作业实施时间:', - isEditable: widget.isEditable, - text: - FormUtils.hasValue(pd, 'WORK_END_DATE') - ? '${pd['WORK_START_DATE'] ?? ''}\n-${pd['WORK_END_DATE'] ?? ''}' - : pd['WORK_START_DATE'] ?? '', - ), + ItemListWidget.twoRowTitleText(label: '作业实施时间', text: FormUtils.hasValue(pd, 'WORK_END_DATE') + ? '${pd['WORK_START_DATE'] ?? ''} - ${pd['WORK_END_DATE'] ?? ''}' + : pd['WORK_START_DATE'] ?? ''), ] else ...[ const Divider(), ItemListWidget.selectableLineTitleTextRightButton( @@ -379,6 +385,7 @@ class _SpaceWorkDetailFormWidgetState extends State { isClean: widget.isEditable, cleanText: '清除监控', onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['VIDEONAME'] = ''; pd['VIDEOMANAGER_ID'] = ''; @@ -392,7 +399,7 @@ class _SpaceWorkDetailFormWidgetState extends State { if (FormUtils.hasValue(pd, 'ANALYZE_TIME') && !widget.isEditable) ...[ const Divider(), - ItemListWidget.OneRowButtonTitleText( + ItemListWidget.OneRowStartButtonTitle( label: '气体分析信息:', text: pd['ANALYZE_USER_NAME'] ?? '', onTap: widget.onAnalyzeTap, diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpecialWorkFormBaseWork.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpecialWorkFormBaseWork.dart index 4a95482..225b1d0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpecialWorkFormBaseWork.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/SpecialWorkFormBaseWork.dart @@ -102,16 +102,16 @@ class SpecialWorkFormBaseWork extends StatelessWidget { ], ), ), - if (FormUtils.hasValue(signs, 'GUARDIAN')) - ConfirmWithSignWidget( - signs: signs, - pd: pd, - baseImgPath: baseImgPath, - sectionKey: 'GUARDIAN', - nameKey: 'GUARDIAN_USER_NAME', - headerTitle: '监护人签字', - roleTitle: '监护人', - ), + // if (FormUtils.hasValue(signs, 'GUARDIAN')) + // ConfirmWithSignWidget( + // signs: signs, + // pd: pd, + // baseImgPath: baseImgPath, + // sectionKey: 'GUARDIAN', + // nameKey: 'GUARDIAN_USER_NAME', + // headerTitle: '监护人签字', + // roleTitle: '监护人', + // ), if (FormUtils.hasValue(signs, 'CONFESS')) ConfirmWithSignWidget( signs: signs, diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/aqjd_work_detail/spacework_aqjd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/aqjd_work_detail/spacework_aqjd_detail.dart index aa38c47..71b2fc5 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/aqjd_work_detail/spacework_aqjd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/aqjd_work_detail/spacework_aqjd_detail.dart @@ -64,10 +64,26 @@ class _SpaceworkAqjdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart index 2b5f300..68a6ae0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/space_work_detai/spacework_apply_detail.dart @@ -334,6 +334,7 @@ class _SpaceworkApplyDetailState extends State { isEditable: isEditable, isClean: isClean, onTapClean: () { + ToastUtil.showNormal(context, '已清除'); setState(() { pd['${type.name}_DEPARTMENT_NAME'] = ''; pd['${type.name}_USER_NAME'] = ''; diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/spacework_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/spacework_list_page.dart index d3e8f08..c3d9184 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/spacework_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/spacework_list_page.dart @@ -167,9 +167,8 @@ class _SpaceworkListPageState extends State { }); } catch (e) { print('Error fetching flow data: $e'); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + +ToastUtil.showNormal(context, '获取流程图失败'); } } @@ -535,14 +534,11 @@ class _SpaceworkListPageState extends State { // 显示底部选择器 Future _showStepPicker() async { if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + LoadingDialogHelper.show(); await _fetchSteps(); + LoadingDialogHelper.hide(); if (stepList.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + ToastUtil.showNormal(context, '无法加载数据'); return; } } diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/szaq_work_detail/spacework_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/szaq_work_detail/spacework_set_safe_detail.dart index 6c5e158..a1a4c39 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/szaq_work_detail/spacework_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/szaq_work_detail/spacework_set_safe_detail.dart @@ -239,10 +239,7 @@ class _SpaceworkSetSafeDetailState extends State { /// 作废 -1 通过 1 Future _submit(String status) async { - if (imagePaths.isEmpty) { - ToastUtil.showNormal(context, '请签字'); - return; - } + List> signers = []; String reasonText = ''; @@ -493,23 +490,7 @@ class _SpaceworkSetSafeDetailState extends State { ), ); }), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - text: '新增手写签字', - height: 36, - backgroundColor: Colors.green, - onPressed: () { - _sign(); - }, - ), - ], - ), - SizedBox(height: 10), - if (imagePaths.isNotEmpty) _signListWidget(), + ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/ysgd_work_detail/spacework_ysgd_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/ysgd_work_detail/spacework_ysgd_detail.dart index c752810..32e8f1c 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/ysgd_work_detail/spacework_ysgd_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/ysgd_work_detail/spacework_ysgd_detail.dart @@ -71,10 +71,26 @@ class _SpaceworkYsgdDetailState extends State { LoadingDialogHelper.show(); final res = await ApiService.uploadSaveFile(localPath); LoadingDialogHelper.hide(); - final url = res['FILE_PATH'] as String; - setState(() { - imgList.add(ImageData(localPath: localPath, serverPath: url)); - }); + try { + if (res['result'] == 'success') { + final url = res['FILE_PATH'] as String; + setState(() { + imgList.add(ImageData(localPath: localPath, serverPath: url)); + }); + } else { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } + } catch (_) { + ToastUtil.showError(context, '上传失败,资源过大请重新选择'); + setState(() { + imgList = []; + MediaBus().emit(MediaEvent.clear(kAcceptVideoSectionKey)); + }); + } } /// 删除图片处理:调用删除接口并更新列表 @@ -191,11 +207,7 @@ class _SpaceworkYsgdDetailState extends State { ToastUtil.showNormal(context, '请在作业结束30分钟内完成验收'); return; } - if (imgList.isEmpty) { - ToastUtil.showNormal(context, '请上传验收视频'); - - return; - } + } else { reasonText = await CustomAlertDialog.showInput( context, @@ -387,7 +399,7 @@ class _SpaceworkYsgdDetailState extends State { ), Divider(), RepairedPhotoSection( - isRequired: true, + isRequired: false, title: '验收视频', maxCount: 1, mediaType: MediaType.video, diff --git a/lib/pages/home/userInfo_page.dart b/lib/pages/home/userInfo_page.dart index f64b9f6..0d2539c 100644 --- a/lib/pages/home/userInfo_page.dart +++ b/lib/pages/home/userInfo_page.dart @@ -109,7 +109,7 @@ class _UserinfoPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/work/custom_driver_drawer.dart b/lib/pages/home/work/custom_driver_drawer.dart index 4d72544..515f00d 100644 --- a/lib/pages/home/work/custom_driver_drawer.dart +++ b/lib/pages/home/work/custom_driver_drawer.dart @@ -349,7 +349,7 @@ class _CustomDriverDrawerState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/work/laws_regulations_page.dart b/lib/pages/home/work/laws_regulations_page.dart index d75d0a5..1835809 100644 --- a/lib/pages/home/work/laws_regulations_page.dart +++ b/lib/pages/home/work/laws_regulations_page.dart @@ -238,7 +238,7 @@ class _LawsRegulationsPage extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/work/laws_regulations_two_page.dart b/lib/pages/home/work/laws_regulations_two_page.dart index e5108c9..410719e 100644 --- a/lib/pages/home/work/laws_regulations_two_page.dart +++ b/lib/pages/home/work/laws_regulations_two_page.dart @@ -205,7 +205,7 @@ class _LawsRegulationsTwoPage extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/home/workSet_page.dart b/lib/pages/home/workSet_page.dart index 186872f..0369ddf 100644 --- a/lib/pages/home/workSet_page.dart +++ b/lib/pages/home/workSet_page.dart @@ -466,6 +466,6 @@ class _WorkSetPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index bb97439..dbc9fbd 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -70,22 +70,25 @@ class _LoginPageState extends State { } Future _checkUpdata() async { - final result = await AuthService.checkUpdate(); - if (FormUtils.hasValue(result, 'pd')) { - // 有更新 提示更新 - Map pd = result['pd']; - CustomAlertDialog.showConfirm( - context, - title: '更新通知', - cancelText: '', - confirmText: '我知道了', - content: pd['UPLOAD_CONTENT'] ?? '', - onConfirm: () { - ToastUtil.showNormal(context, '更新去吧!'); - }, - ); - return; - } + try{ + final result = await AuthService.checkUpdate(); + if (FormUtils.hasValue(result, 'pd')) { + // 有更新 提示更新 + Map pd = result['pd']; + CustomAlertDialog.showConfirm( + context, + title: '更新通知', + cancelText: '', + confirmText: '我知道了', + content: pd['UPLOAD_CONTENT'] ?? '', + onConfirm: () { + ToastUtil.showNormal(context, '更新去吧!'); + }, + ); + return; + } + }catch(_) {} + } @override diff --git a/lib/pages/mine/mine_feedback_page.dart b/lib/pages/mine/mine_feedback_page.dart index fb73588..0bb9786 100644 --- a/lib/pages/mine/mine_feedback_page.dart +++ b/lib/pages/mine/mine_feedback_page.dart @@ -400,6 +400,6 @@ class _FeedbackPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } } diff --git a/lib/pages/mine/mine_set_pwd_page.dart b/lib/pages/mine/mine_set_pwd_page.dart index fcdc780..9596c3e 100644 --- a/lib/pages/mine/mine_set_pwd_page.dart +++ b/lib/pages/mine/mine_set_pwd_page.dart @@ -136,7 +136,7 @@ class _MineSetPwdPageState extends State { } void _showMessage(String msg) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ToastUtil.showNormal(context, msg); } Widget _buildPwdField(String hintText, TextEditingController controller) { diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 774d3a1..07f0e71 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:flutter/src/widgets/framework.dart'; import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey; @@ -6,7 +7,7 @@ import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart'; import 'package:qhd_prevention/tools/tools.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluttertoast/fluttertoast.dart'; - +import 'package:permission_handler/permission_handler.dart'; import '../http/ApiService.dart'; class AuthService { @@ -67,7 +68,34 @@ class AuthService { } return {}; } + // 检查并请求相机权限 + static Future checkCameraPermission() async { + final status = await Permission.camera.status; + if (status.isDenied) { + final result = await Permission.camera.request(); + return result.isGranted; + } + return status.isGranted; + } +// 检查并请求照片库权限(iOS)和存储权限(Android) + static Future checkPhotoLibraryPermission() async { + if (Platform.isIOS) { + final status = await Permission.photos.status; + if (status.isDenied) { + final result = await Permission.photos.request(); + return result.isGranted; + } + return status.isGranted; + } else { + final status = await Permission.storage.status; + if (status.isDenied) { + final result = await Permission.storage.request(); + return result.isGranted; + } + return status.isGranted; + } + } /// 验证是否已登录(通过重新登录) static Future> isLoggedIn() async { diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index 5729d4e..88fdcba 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -4,6 +4,9 @@ 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'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; int getRandomWithNum(int min, int max) { if (max < min) { @@ -373,8 +376,8 @@ void presentPage(BuildContext context, Widget page) { class LoadingDialogHelper { static Timer? _timer; - /// 显示加载框(带超时,默认 10 秒) - static void show({String? message, Duration timeout = const Duration(seconds: 10)}) { + /// 显示加载框(带超时,默认 60 秒) + static void show({String? message, Duration timeout = const Duration(seconds: 60)}) { // 先清理上一个计时器,避免重复 _timer?.cancel(); @@ -521,3 +524,73 @@ class NativeOrientation { } } +class CameraPermissionHelper { + static final ImagePicker _picker = ImagePicker(); + + // 检查并请求相机权限(使用 ImagePicker 触发权限请求) + static Future checkAndRequestCameraPermission() async { + if (Platform.isIOS) { + // 对于 iOS,使用 ImagePicker 触发权限请求 + try { + // 尝试获取图片(但立即取消)来触发权限请求 + final XFile? file = await _picker.pickImage( + source: ImageSource.camera, + maxWidth: 1, // 最小尺寸,减少处理时间 + maxHeight: 1, + imageQuality: 1, + ).timeout(const Duration(milliseconds: 100), onTimeout: () { + return null; // 超时返回 null,避免等待用户操作 + }); + + // 无论是否成功获取文件,权限请求已经被触发 + // 现在检查实际的权限状态 + var status = await Permission.camera.status; + return status.isGranted; + } catch (e) { + // 如果出现错误,回退到直接检查权限状态 + var status = await Permission.camera.status; + return status.isGranted; + } + } else { + // Android 使用标准的权限检查方式 + var status = await Permission.camera.status; + if (status.isDenied) { + status = await Permission.camera.request(); + } + return status.isGranted; + } + } + + // 检查并请求相册权限 + static Future checkAndRequestPhotoPermission() async { + if (Platform.isIOS) { + // 对于 iOS,使用 ImagePicker 触发权限请求 + try { + // 尝试获取图片(但立即取消)来触发权限请求 + final XFile? file = await _picker.pickImage( + source: ImageSource.gallery, + maxWidth: 1, + maxHeight: 1, + imageQuality: 1, + ).timeout(const Duration(milliseconds: 100), onTimeout: () { + return null; + }); + + // 检查实际的权限状态 + var status = await Permission.photos.status; + return status.isGranted; + } catch (e) { + var status = await Permission.photos.status; + return status.isGranted; + } + } else { + // Android 使用标准的权限检查方式 + var status = await Permission.storage.status; + if (status.isDenied) { + status = await Permission.storage.request(); + } + return status.isGranted; + } + } +} + diff --git a/pubspec.lock b/pubspec.lock index 8d6d20e..5a518ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: "direct main" description: name: chewie - sha256: "19b93a1e60e4ba640a792208a6543f1c7d5b124d011ce0199e2f18802199d984" + sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.12.1" + version: "1.13.0" cli_util: dependency: transitive description: @@ -1222,10 +1222,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "6.3.18" + version: "6.3.20" url_launcher_ios: dependency: transitive description: @@ -1358,10 +1358,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 + sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.3.2" + version: "1.3.3" wakelock_plus_platform_interface: dependency: transitive description: @@ -1468,4 +1468,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index ff09f2f..aee7d80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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+7 +version: 2.1.2+8 environment: sdk: ^3.7.0 @@ -27,11 +27,11 @@ environment: # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. -flutter_launcher_icons: - android: true - ios: true - image_path: "assets/images/app-logo.png" - min_sdk_android: 21 # (可选) Android 最低支持 +#flutter_launcher_icons: +# android: true +# ios: true +# image_path: "assets/images/app-logo.png" +# min_sdk_android: 21 # (可选) Android 最低支持 flutter_native_splash: background_image: assets/images/bg-login.png