。。。
parent
6d737adce3
commit
21387b3cbc
|
@ -15,7 +15,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "com.zhuoyun.qhdprevention.qhd_prevention"
|
||||
namespace = "uni.UNI85F7A17"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "28.1.13356709"
|
||||
|
||||
|
@ -29,7 +29,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.zhuoyun.qhdprevention.qhd_prevention"
|
||||
applicationId = "uni.UNI85F7A17"
|
||||
minSdk = 24
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
<!-- FileProvider 配置 -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.zhuoyun.qhdprevention.qhd_prevention.fileprovider"
|
||||
android:authorities="uni.UNI85F7A17.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
package com.zhuoyun.qhdprevention.qhd_prevention
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.FileProvider
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.File
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val CHANNEL = "app.install"
|
||||
private val REQ_INSTALL_UNKNOWN = 9999
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"installApk" -> {
|
||||
val path = call.argument<String>("path")
|
||||
if (path == null) {
|
||||
result.error("NO_PATH", "no path provided", null)
|
||||
return@setMethodCallHandler
|
||||
}
|
||||
installApk(path, result)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installApk(path: String, result: MethodChannel.Result) {
|
||||
val file = File(path)
|
||||
if (!file.exists()) {
|
||||
result.error("NO_FILE", "file not exist", null)
|
||||
return
|
||||
}
|
||||
|
||||
// Android 8.0+ 需要允许安装未知来源
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!packageManager.canRequestPackageInstalls()) {
|
||||
// 引导用户去设置允许安装未知来源(你可以在 Flutter 侧提示用户)
|
||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:$packageName"))
|
||||
startActivity(intent)
|
||||
result.error("NEED_INSTALL_PERMISSION", "need install permission", null)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val apkUri: Uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
startActivity(intent)
|
||||
result.success(true)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.zhuoyun.qhdprevention.qhd_prevention
|
||||
package uni.UNI85F7A17
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
|
|
|
@ -503,10 +503,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -523,7 +523,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
|
@ -542,7 +542,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
|
@ -559,7 +559,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
|
@ -697,10 +697,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -728,10 +728,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
|
@ -33,6 +33,9 @@ class _RemoteFilePageState extends State<RemoteFilePage> {
|
|||
late PdfControllerPinch _pdfController;
|
||||
int _totalPages = 0;
|
||||
|
||||
// 用于短文档(<=3页)的停留计时器兜底
|
||||
Timer? _pageViewTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -93,12 +96,36 @@ class _RemoteFilePageState extends State<RemoteFilePage> {
|
|||
@override
|
||||
void dispose() {
|
||||
_countdownTimer?.cancel();
|
||||
_pageViewTimer?.cancel();
|
||||
if (!_isLoading) {
|
||||
_pdfController.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 当页面改变时调用(pdfx 的 page 是 1-based)
|
||||
void _onPageChanged(int page) {
|
||||
// 取消可能存在的短页面计时器(重新开始)
|
||||
_pageViewTimer?.cancel();
|
||||
|
||||
// 如果到达最后一页(注意页码从 1 开始)
|
||||
if (_totalPages > 0 && page >= _totalPages) {
|
||||
// 直接标记为已翻到底
|
||||
setState(() => _hasScrolledToBottom = true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于特别短的文档(例如 <=3 页),我们做一个停留计时器:如果用户停留在最后一页一小段时间,也视为已浏览完毕
|
||||
if (_totalPages > 0 && _totalPages <= 3) {
|
||||
// 如果当前页已经是最后一页(page == _totalPages),启动短计时器
|
||||
if (page == _totalPages) {
|
||||
_pageViewTimer = Timer(const Duration(seconds: 1), () {
|
||||
if (mounted) setState(() => _hasScrolledToBottom = true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isButtonEnabled = _timerFinished && _hasScrolledToBottom;
|
||||
|
@ -117,12 +144,14 @@ class _RemoteFilePageState extends State<RemoteFilePage> {
|
|||
onDocumentLoaded: (document) {
|
||||
setState(() {
|
||||
_totalPages = document.pagesCount;
|
||||
// 如果文档只有 1 页,直接视为已看完
|
||||
if (_totalPages <= 1) {
|
||||
_hasScrolledToBottom = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
onPageChanged: (page) {
|
||||
if (page == _totalPages - 1) {
|
||||
setState(() => _hasScrolledToBottom = true);
|
||||
}
|
||||
_onPageChanged(page);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -19,13 +19,13 @@ class ApiService {
|
|||
// static const String projectManagerUrl = 'https://pm.qhdsafety.com/zy-projectManage/';
|
||||
// static const String publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB'
|
||||
/// 人脸识别服务
|
||||
// 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 baseFacePath = "https://qaaqwh.qhdsafety.com/whb_stu_face";
|
||||
// static const String baseFacePath = "http://192.168.20.240:8500/whb_stu_face/";
|
||||
// static const String baseFacePath = "http://192.168.0.25:38199"; //王轩服务器
|
||||
|
||||
/// 登录及其他管理后台接口
|
||||
// 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 = "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.0.25:28199";//王轩服务器
|
||||
// static const String basePath = "http://192.168.0.45:28199";//长久服务器
|
||||
|
||||
|
@ -190,7 +190,7 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
'/app/versionmanager/getVersion',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'FILETYPE':Platform.pathSeparator
|
||||
'FILETYPE':Platform.isIOS ? 'iOS' : 'Android'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import 'pages/mine/mine_set_pwd_page.dart';
|
|||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
// 全局路由
|
||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
|
||||
bool _isLoggingOut = false;
|
||||
// 全局消息控制器
|
||||
class GlobalMessage {
|
||||
static void showError(String message) {
|
||||
|
@ -109,17 +109,32 @@ void main( ) async {
|
|||
|
||||
// 初始化HTTP管理器未授权回调
|
||||
HttpManager.onUnauthorized = () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isLoggedIn', false);
|
||||
await prefs.remove('token');
|
||||
navigatorKey.currentState?.pushNamedAndRemoveUntil(
|
||||
'/login',
|
||||
(route) => false,
|
||||
);
|
||||
final navigatorState = navigatorKey.currentState;
|
||||
if (navigatorState == null) return;
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
GlobalMessage.showError('您的账号已在其他设备登录,已自动下线,请使用单一设备进行学习。');
|
||||
});
|
||||
// 尝试获取当前路由名(注意:ModalRoute 可能为 null)
|
||||
final currentRouteName = ModalRoute.of(navigatorState.context)?.settings.name;
|
||||
if (currentRouteName == '/login') {
|
||||
// 已在登录页,直接返回,避免重复登出流程与提示
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isLoggingOut) return; // 防止并发多次触发
|
||||
_isLoggingOut = true;
|
||||
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isLoggedIn', false);
|
||||
await prefs.remove('token');
|
||||
|
||||
navigatorState.pushNamedAndRemoveUntil('/login', (route) => false);
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
GlobalMessage.showError('您的账号已在其他设备登录,已自动下线,请使用单一设备进行学习。');
|
||||
});
|
||||
} finally {
|
||||
_isLoggingOut = false;
|
||||
}
|
||||
};
|
||||
// 自动登录逻辑
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
|
|
@ -353,9 +353,9 @@ setState(() {
|
|||
|
||||
Widget _mainWidget() {
|
||||
bool isShowCheck = false;
|
||||
if (FormUtils.hasValue(inspectedForm, 'hiddenList')) {
|
||||
List list = inspectedForm['hiddenList'];
|
||||
if (list.isEmpty) {
|
||||
if (FormUtils.hasValue(form, 'hiddenList')) {
|
||||
List list = form['hiddenList'];
|
||||
if (list.isNotEmpty) {
|
||||
isShowCheck = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,32 +190,32 @@ class HomePageState extends State<HomePage> {
|
|||
|
||||
/// 首次加载:先恢复缓存(如果有),然后在后台去刷新(只有当无缓存时才显示 loading)
|
||||
Future<void> _initialLoad() async {
|
||||
/// 清单列表
|
||||
final data = await ApiService.getListData();
|
||||
if (data['result'] == 'success') {
|
||||
final content = data['varList'] ?? [];
|
||||
for (Map item in content) {
|
||||
if (item['checkCount'] == 0) {
|
||||
totalList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final result = await AuthService.checkUpdate();
|
||||
if (FormUtils.hasValue(result, 'pd')) {
|
||||
// 有更新 提示更新
|
||||
Map pd = result['pd'];
|
||||
CustomAlertDialog.showAlert(
|
||||
context,
|
||||
title: '更新通知',
|
||||
content: pd['UPLOAD_CONTENT'] ?? '',
|
||||
onConfirm: () async{
|
||||
final apkUrl = 'http://192.168.1.191:8888/app-release.apk';
|
||||
await showUpdateConfirmDialog(context, apkUrl: apkUrl);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
try{
|
||||
if (FormUtils.hasValue(result, 'pd')) {
|
||||
Map pd = result['pd'];
|
||||
final versionInfo = await getAppVersion();
|
||||
bool isWifi = true;
|
||||
if (versionInfo.versionName != pd['VERSION']) {
|
||||
//有更新 提示更新
|
||||
final ok = await CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
barrierDismissible:false,
|
||||
title: '更新通知',
|
||||
content: isWifi ? '发现新版本,是否更新?为了更好的体验,请更新到最新版本。' : '发现新版本,检查到您当前使用的是移动网络,是否更新?更新时请注意流量消耗。为了更好的体验,请更新到最新版本。',
|
||||
cancelText: pd['ISUPDATE'] == '1' ? '' : '稍后更新',
|
||||
confirmText: '立即更新'
|
||||
);
|
||||
if (ok) {
|
||||
final apkUrl = pd['FILEURL'] ?? '';
|
||||
await showUpdateConfirm(context, apkUrl: apkUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}catch(_){}
|
||||
|
||||
|
||||
final corppromiseData = await ApiService.checkSafeCorppromise();
|
||||
if (corppromiseData['ISSIGN'] == 1) {
|
||||
|
@ -241,7 +241,16 @@ class HomePageState extends State<HomePage> {
|
|||
_fetchData();
|
||||
_fetchHiddenList(showLoading: hiddenList.isEmpty);
|
||||
fetchAndSaveBd09(context);
|
||||
|
||||
/// 清单列表
|
||||
final data = await ApiService.getListData();
|
||||
if (data['result'] == 'success') {
|
||||
final content = data['varList'] ?? [];
|
||||
for (Map item in content) {
|
||||
if (item['checkCount'] == 0) {
|
||||
totalList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
|
@ -333,7 +342,16 @@ class HomePageState extends State<HomePage> {
|
|||
// “我的工作”数据
|
||||
final data = await ApiService.getWork();
|
||||
final hidCount = data['hidCount'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
setState(() {
|
||||
// ———— 更新 workInfos ————
|
||||
workInfos =
|
||||
workInfos.map((info) {
|
||||
final idx = info['index'] as int;
|
||||
final key = _workKey(idx);
|
||||
final num = (hidCount[key] ?? 0).toString();
|
||||
return {...info, 'num': num};
|
||||
}).toList();
|
||||
});
|
||||
// // 告知BadgeManager去更新“安全巡检”和“八项作业”角标
|
||||
// BadgeManager().updateEnvInspectCount();
|
||||
// BadgeManager().updateEightWorkCount();
|
||||
|
@ -350,15 +368,6 @@ class HomePageState extends State<HomePage> {
|
|||
|
||||
//更新页面状态
|
||||
setState(() {
|
||||
// ———— 更新 workInfos ————
|
||||
workInfos =
|
||||
workInfos.map((info) {
|
||||
final idx = info['index'] as int;
|
||||
final key = _workKey(idx);
|
||||
final num = (hidCount[key] ?? 0).toString();
|
||||
return {...info, 'num': num};
|
||||
}).toList();
|
||||
|
||||
// ———— 从 BadgeManager 拿最新的本页角标 ————
|
||||
_safetyEnvironmentalInspection = BadgeManager().envInspectCount;
|
||||
_eight_work_count = BadgeManager().eightWorkCount;
|
||||
|
|
|
@ -80,6 +80,7 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage>
|
|||
// 可选:释放相机,节省资源
|
||||
try {
|
||||
_cameraController?.dispose();
|
||||
Navigator.pop(context);
|
||||
} catch (_) {}
|
||||
_cameraController = null;
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
|
|
|
@ -225,7 +225,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
await _submitPlayTime(
|
||||
snapshot: prevSnapshot,
|
||||
end: false,
|
||||
seconds: _lastReported.inSeconds,
|
||||
seconds: _lastReported.inSeconds.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
'VIDEOCOURSEWARE_ID': data['VIDEOCOURSEWARE_ID'] ?? '',
|
||||
'CURRICULUM_ID': data['CURRICULUM_ID'] ?? '',
|
||||
'CHAPTER_ID': data['CHAPTER_ID'] ?? '',
|
||||
'VIDEOTIME': data['VIDEOTIME'] ?? 0,
|
||||
'VIDEOTIME': data['VIDEOTIME'] ?? '0.0',
|
||||
'IS_NODE': hasNodes,
|
||||
'FIRST_INDEX': fi,
|
||||
'NODE_INDEX': ni,
|
||||
|
@ -265,7 +265,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
await _submitPlayTime(
|
||||
snapshot: docSnapshot,
|
||||
end: true,
|
||||
seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'),
|
||||
seconds: data['VIDEOTIME'] ?? '0.0',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -324,6 +324,17 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
await onPass();
|
||||
} else {
|
||||
ToastUtil.showError(context, '人脸验证未通过,无法继续');
|
||||
if (_videoController != null) {
|
||||
try {
|
||||
_videoController?.removeListener(_onTimeUpdate);
|
||||
} catch (_) {}
|
||||
try {
|
||||
_videoController?.dispose();
|
||||
} catch (_) {}
|
||||
_videoController = null;
|
||||
}
|
||||
_faceTimer?.cancel();
|
||||
setState(() {});
|
||||
}
|
||||
} else {
|
||||
final ok = await CustomAlertDialog.showConfirm(
|
||||
|
@ -458,7 +469,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
return _submitPlayTime(
|
||||
snapshot: snapshot,
|
||||
end: false,
|
||||
seconds: pos.inSeconds,
|
||||
seconds: pos.inSeconds.toString(),
|
||||
);
|
||||
})
|
||||
.whenComplete(() {
|
||||
|
@ -490,7 +501,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
await _submitPlayTime(
|
||||
snapshot: snapshot,
|
||||
end: true,
|
||||
seconds: finalSeconds,
|
||||
seconds: finalSeconds.toString(),
|
||||
);
|
||||
})
|
||||
.whenComplete(() {
|
||||
|
@ -516,7 +527,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
Future<void> _submitPlayTime({
|
||||
required Map<String, dynamic> snapshot,
|
||||
required bool end,
|
||||
required int seconds,
|
||||
required String seconds,
|
||||
}) async {
|
||||
// snapshot 必须包含 VIDEOCOURSEWARE_ID
|
||||
if (snapshot['VIDEOCOURSEWARE_ID'] == null ||
|
||||
|
@ -602,7 +613,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
final resT =
|
||||
(resTraw is num)
|
||||
? resTraw.toDouble()
|
||||
: double.tryParse('$resTraw') ?? seconds.toDouble();
|
||||
: double.tryParse('$resTraw') ?? double.parse(seconds);
|
||||
|
||||
final videoTimeRaw = snapshot['VIDEOTIME'];
|
||||
final videoTime =
|
||||
|
|
|
@ -232,7 +232,7 @@ class _TakeExamPageState extends State<TakeExamPage> {
|
|||
content:
|
||||
passed
|
||||
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
|
||||
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再 厉!',
|
||||
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
|
||||
cancelText: '',
|
||||
confirmText: '确定',
|
||||
);
|
||||
|
|
|
@ -1322,6 +1322,7 @@ class SignItemWidget extends StatelessWidget {
|
|||
this.smallThumbSize = 50.0,
|
||||
this.signImageWidth = 200.0,
|
||||
this.signImageHeight = 100.0,
|
||||
required this.isShowTime,
|
||||
}) : super(key: key);
|
||||
|
||||
/// signs map 中用于查找签名列表的 key
|
||||
|
@ -1348,6 +1349,7 @@ class SignItemWidget extends StatelessWidget {
|
|||
/// 签名图片展示尺寸(每张一行)
|
||||
final double signImageWidth;
|
||||
final double signImageHeight;
|
||||
final bool isShowTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1579,22 +1581,25 @@ class SignItemWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
// 时间文本(始终固定在最右侧并底部对齐)
|
||||
list.add(const SizedBox(height: 10));
|
||||
list.add(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
lastTime,
|
||||
textAlign: TextAlign.right,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 13, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (isShowTime) {
|
||||
// 时间文本(始终固定在最右侧并底部对齐)
|
||||
list.add(const SizedBox(height: 10));
|
||||
list.add(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
lastTime,
|
||||
textAlign: TextAlign.right,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 13, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
@ -1615,6 +1620,7 @@ class ConfirmWithSignWidget extends StatelessWidget {
|
|||
required this.nameKey, // 'CONFIRM_USER_NAME',
|
||||
required this.headerTitle, // '作业负责人意见',
|
||||
required this.roleTitle, // '作业负责人',
|
||||
this.isShowTime = true,
|
||||
}) : super(key: key);
|
||||
|
||||
// 允许外部传入 null(但构建内部会降级为 {},避免空异常)
|
||||
|
@ -1643,6 +1649,8 @@ class ConfirmWithSignWidget extends StatelessWidget {
|
|||
/// 传给 SignItemWidget 的 title(例如 '作业负责人')
|
||||
final String roleTitle;
|
||||
|
||||
final bool isShowTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 容错:如果传入为 null,降级成空 Map,后续访问安全
|
||||
|
@ -1767,6 +1775,7 @@ class ConfirmWithSignWidget extends StatelessWidget {
|
|||
pd: safePd,
|
||||
signs: safeSigns,
|
||||
baseImgPath: baseImgPath,
|
||||
isShowTime:isShowTime,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -26,11 +26,7 @@ class SignImageData {
|
|||
String? filePath;
|
||||
int? key;
|
||||
|
||||
SignImageData({
|
||||
required this.SIGNER_TIME,
|
||||
this.filePath,
|
||||
this.key,
|
||||
});
|
||||
SignImageData({required this.SIGNER_TIME, this.filePath, this.key});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'SIGNER_TIME': SIGNER_TIME,
|
||||
|
@ -47,8 +43,10 @@ class SignImageData {
|
|||
}
|
||||
|
||||
@override
|
||||
String toString() => 'SignImageData(key:$key, filePath:$filePath, SIGNER_TIME:$SIGNER_TIME)';
|
||||
String toString() =>
|
||||
'SignImageData(key:$key, filePath:$filePath, SIGNER_TIME:$SIGNER_TIME)';
|
||||
}
|
||||
|
||||
class DangerousOptionsPage extends StatefulWidget {
|
||||
final int index;
|
||||
final int status;
|
||||
|
@ -134,40 +132,61 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
return;
|
||||
}
|
||||
LoadingDialogHelper.show();
|
||||
List<String> filePaths =
|
||||
signImgList.map((img) => img.filePath ?? '').toList();
|
||||
final result = await ApiService.saveDangerousOptionsFile(filePaths);
|
||||
final List<dynamic> signList = result['FILE_PATH_LIST'];
|
||||
List<Map<String, dynamic>> sineImageList = [];
|
||||
List<SignImageData> filePaths = [];
|
||||
List severImageList = [];
|
||||
|
||||
for (SignImageData data in signImgList) {
|
||||
String path = data.filePath ?? '';
|
||||
if (!path.contains('uploadFiles')) {
|
||||
filePaths.add(data);
|
||||
} else {
|
||||
severImageList.add({
|
||||
'filePath': data.filePath,
|
||||
'SIGNER_TIME': data.SIGNER_TIME,
|
||||
'key': data.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (filePaths.length == 0) {
|
||||
// 如果没有本地未提交,直接返回
|
||||
setState(() => buttonLoading = true);
|
||||
LoadingDialogHelper.hide();
|
||||
Navigator.pop(context, {
|
||||
'imgList':
|
||||
imgList
|
||||
.map((e) => {'local': e.localPath, 'remote': e.serverPath})
|
||||
.toList(),
|
||||
'signImgList': signImgList,
|
||||
'index': index,
|
||||
'status': status,
|
||||
});
|
||||
return;
|
||||
}
|
||||
final result = await ApiService.saveDangerousOptionsFile(filePaths.map((item) => item.filePath).toList());
|
||||
final List<dynamic> signList = result['FILE_PATH_LIST'];
|
||||
for (SignImageData data in filePaths) {
|
||||
for (Map<String, dynamic> img in signList) {
|
||||
String imgName = 'file${data.key}';
|
||||
if (data.filePath!.contains('uploadFiles')) {
|
||||
final idata = {
|
||||
'filePath': data.filePath,
|
||||
'SIGNER_TIME': data.SIGNER_TIME,
|
||||
'key': data.key,
|
||||
};
|
||||
sineImageList.add(idata);
|
||||
}
|
||||
|
||||
if (imgName == img['key']) {
|
||||
final idata = {
|
||||
'filePath': img['filePath'] ?? '',
|
||||
'SIGNER_TIME': data.SIGNER_TIME,
|
||||
'key': data.key,
|
||||
};
|
||||
sineImageList.add(idata);
|
||||
severImageList.add(idata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() => buttonLoading = true);
|
||||
LoadingDialogHelper.hide();
|
||||
Navigator.pop(context, {
|
||||
'imgList':
|
||||
imgList
|
||||
.map((e) => {'local': e.localPath, 'remote': e.serverPath})
|
||||
.toList(),
|
||||
'signImgList': sineImageList,
|
||||
imgList
|
||||
.map((e) => {'local': e.localPath, 'remote': e.serverPath})
|
||||
.toList(),
|
||||
'signImgList': severImageList,
|
||||
'index': index,
|
||||
'status': status,
|
||||
});
|
||||
|
@ -198,56 +217,56 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
Widget _signListWidget() {
|
||||
return Column(
|
||||
children:
|
||||
signImgList.map((imgData) {
|
||||
final idx = signImgList.indexOf(imgData);
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
signImgList.map((imgData) {
|
||||
final idx = signImgList.indexOf(imgData);
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 230,
|
||||
maxHeight: 150,
|
||||
),
|
||||
child:
|
||||
(imgData.filePath ?? '').contains('uploadFiles')
|
||||
? Image.network(
|
||||
'${ApiService.baseImgPath}${imgData.filePath}',
|
||||
)
|
||||
: Image.file(
|
||||
File(imgData.filePath ?? ''),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
onTap:
|
||||
() => presentOpaque(
|
||||
SingleImageViewer(imageUrl: imgData.filePath ?? ''),
|
||||
context,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CustomButton(
|
||||
text: '删除',
|
||||
height: 30,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
backgroundColor: Colors.red,
|
||||
onPressed: () {
|
||||
setState(() => signImgList.removeAt(idx));
|
||||
},
|
||||
GestureDetector(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 230,
|
||||
maxHeight: 150,
|
||||
),
|
||||
child:
|
||||
(imgData.filePath ?? '').contains('uploadFiles')
|
||||
? Image.network(
|
||||
'${ApiService.baseImgPath}${imgData.filePath}',
|
||||
)
|
||||
: Image.file(
|
||||
File(imgData.filePath ?? ''),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
onTap:
|
||||
() => presentOpaque(
|
||||
SingleImageViewer(imageUrl: imgData.filePath ?? ''),
|
||||
context,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
CustomButton(
|
||||
text: '删除',
|
||||
height: 30,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
backgroundColor: Colors.red,
|
||||
onPressed: () {
|
||||
setState(() => signImgList.removeAt(idx));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -343,14 +362,16 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
maxCount: 2,
|
||||
mediaType: MediaType.image,
|
||||
initialMediaPaths:
|
||||
imgList
|
||||
.map((e) => '${ApiService.baseImgPath}${e.serverPath}')
|
||||
.toList(),
|
||||
imgList
|
||||
.map((e) => '${ApiService.baseImgPath}${e.serverPath}')
|
||||
.toList(),
|
||||
onChanged: (paths) {},
|
||||
onMediaAdded: _onImageAdded,
|
||||
onMediaRemoved: (path) {
|
||||
print(path);
|
||||
final item = imgList.firstWhere((e) => path.contains(e.localPath) );
|
||||
final item = imgList.firstWhere(
|
||||
(e) => path.contains(e.localPath),
|
||||
);
|
||||
_onImageRemoved(item);
|
||||
},
|
||||
onAiIdentify: () {},
|
||||
|
@ -381,4 +402,4 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,6 +223,7 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -110,7 +110,8 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
baseImgPath: baseImgPath,
|
||||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人意见',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -121,12 +122,14 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'GUARDIAN'))
|
||||
ConfirmWithSignWidget(
|
||||
signs: signs,
|
||||
pd: pd,
|
||||
isShowTime: false,
|
||||
baseImgPath: baseImgPath,
|
||||
sectionKey: 'GUARDIAN',
|
||||
nameKey: 'GUARDIAN_USER_NAME',
|
||||
|
|
|
@ -387,7 +387,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -212,6 +212,7 @@ class _CutroadDetailFormWidgetState extends State<CutroadDetailFormWidget> {
|
|||
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -106,6 +106,7 @@ class CutroadFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -116,6 +117,7 @@ class CutroadFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'CONFIRM'))
|
||||
|
|
|
@ -378,7 +378,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -210,6 +210,7 @@ class _BreakgroundDetailFormWidgetState
|
|||
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -116,6 +116,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -126,6 +127,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'CONFIRM'))
|
||||
|
|
|
@ -381,7 +381,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -261,6 +261,7 @@ class _HoistworkDetailFormWidgetState extends State<HoistWorkDetailFormWidget> {
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -278,6 +278,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -288,6 +289,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
|
||||
|
|
|
@ -381,7 +381,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -241,6 +241,7 @@ class _HighWorkDetailFormWidgetState extends State<HighWorkDetailFormWidget> {
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -115,6 +115,7 @@ class HighWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -125,6 +126,7 @@ class HighWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'CONFIRM'))
|
||||
|
|
|
@ -436,7 +436,25 @@ class _HighworkApplyDetailState extends State<HighworkApplyDetail> {
|
|||
//FocusHelper.clearFocus(context);
|
||||
});
|
||||
}
|
||||
bool checkWorkTime(Map<String, dynamic> pd, BuildContext context) {
|
||||
// 解析开始和结束时间
|
||||
final start = DateTime.parse(pd['WORK_EXPECTED_START_TIME'] as String);
|
||||
final end = DateTime.parse(pd['WORK_EXPECTED_END_TIME'] as String);
|
||||
|
||||
// 校验:结束时间必须晚于开始时间
|
||||
if (end.isAtSameMomentAs(start) || end.isBefore(start)) {
|
||||
ToastUtil.showNormal(context, '作业开始时间不能晚于或等于结束时间,请重新选择');
|
||||
|
||||
return false;
|
||||
}
|
||||
final diffMs = end.difference(start).inMilliseconds;
|
||||
const max8h = 7 * 24 * 60 * 60 * 1000;
|
||||
if (diffMs >= max8h) {
|
||||
ToastUtil.showNormal(context, '作业开始时间与结束时间应不超过7天,请重新选择');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// 提交 1 提交 0暂存
|
||||
Future<void> _submit(String status) async {
|
||||
// 通用文本字段校验规则
|
||||
|
|
|
@ -59,6 +59,12 @@ class _HighworkJszyDetailState extends State<HighworkJszyDetail> {
|
|||
// 校验:结束时间必须晚于开始时间
|
||||
if (end.isAtSameMomentAs(start) || end.isBefore(start)) {
|
||||
ToastUtil.showNormal(context, '作业开始时间不能晚于或等于结束时间,请重新选择');
|
||||
return false;
|
||||
}
|
||||
final diffMs = end.difference(start).inMilliseconds;
|
||||
const max8h = 7 *24 * 60 * 60 * 1000;
|
||||
if (diffMs >= max8h) {
|
||||
ToastUtil.showNormal(context, '作业开始时间与结束时间应不超过7天,请重新选择');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -68,7 +74,6 @@ class _HighworkJszyDetailState extends State<HighworkJszyDetail> {
|
|||
|
||||
/// 作废 -1 通过 1
|
||||
Future<void> _submit(String status) async {
|
||||
|
||||
String? reasonText = '';
|
||||
if (status == '1') {
|
||||
if (endTime.isEmpty) {
|
||||
|
@ -84,19 +89,19 @@ class _HighworkJszyDetailState extends State<HighworkJszyDetail> {
|
|||
title: '作废原因',
|
||||
hintText: '请输入作废原因',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定'
|
||||
confirmText: '确定',
|
||||
);
|
||||
// 用户取消(或点遮罩、返回键)
|
||||
if (reasonText == null) {
|
||||
// 取消时什么也不做,不提示
|
||||
return;
|
||||
}
|
||||
if (reasonText == null) {
|
||||
// 取消时什么也不做,不提示
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户点击确认但没填内容
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
return;
|
||||
}
|
||||
// 用户点击确认但没填内容
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 存回 measures
|
||||
|
@ -129,7 +134,7 @@ if (reasonText.isEmpty) {
|
|||
if (result['result'] == 'success') {
|
||||
ToastUtil.showSuccess(context, '保存成功');
|
||||
Navigator.of(context).pop(true);
|
||||
}else{
|
||||
} else {
|
||||
ToastUtil.showNormal(context, '操作失败:${result['msg'] ?? '未知错误'}');
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -214,7 +219,7 @@ if (reasonText.isEmpty) {
|
|||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
spacing: 12,
|
||||
children: [
|
||||
// _setSafeDetailWidget(),
|
||||
HighWorkFormBaseWork(
|
||||
|
|
|
@ -381,7 +381,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -184,7 +184,6 @@ class _HomeGasTestPageState extends State<HomeGasTestPage> {
|
|||
return;
|
||||
}
|
||||
if (status == 1) {
|
||||
|
||||
Map itemForm = {
|
||||
'ANALYZE_GAS': _gasController.text,
|
||||
'ANALYZE_RESULT': _resultController.text,
|
||||
|
@ -219,16 +218,16 @@ class _HomeGasTestPageState extends State<HomeGasTestPage> {
|
|||
confirmText: '确定',
|
||||
);
|
||||
// 用户取消(或点遮罩、返回键)
|
||||
if (reasonText == null) {
|
||||
// 取消时什么也不做,不提示
|
||||
return;
|
||||
}
|
||||
if (reasonText == null) {
|
||||
// 取消时什么也不做,不提示
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户点击确认但没填内容
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
return;
|
||||
}
|
||||
// 用户点击确认但没填内容
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
return;
|
||||
}
|
||||
}
|
||||
final confirmed = await CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
|
@ -256,7 +255,7 @@ if (reasonText.isEmpty) {
|
|||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
};
|
||||
|
||||
LoadingDialogHelper.show();
|
||||
try {
|
||||
await ApiService.saveGasTest('hotwork', formData, imagePaths);
|
||||
ToastUtil.showNormal(context, status == 1 ? '保存成功' : '作废成功');
|
||||
|
@ -266,6 +265,7 @@ if (reasonText.isEmpty) {
|
|||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
LoadingDialogHelper.hide();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -244,6 +244,7 @@ class _ElectricityDetailFormWidgetState extends State<ElectricityDetailFormWidge
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -292,7 +292,8 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
baseImgPath: baseImgPath,
|
||||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人意见',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -303,6 +304,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
|
||||
|
|
|
@ -217,6 +217,7 @@ if (reasonText.isEmpty) {
|
|||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
};
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
try {
|
||||
await ApiService.saveGasTest('electricity', formData, imagePaths);
|
||||
|
@ -227,6 +228,7 @@ if (reasonText.isEmpty) {
|
|||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
LoadingDialogHelper.hide();
|
||||
}
|
||||
|
||||
/// 选择气体类型
|
||||
|
|
|
@ -387,7 +387,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -642,6 +642,7 @@ class _BlindboardDetailFormWidgetState
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -299,6 +299,7 @@ class BlindboardFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -309,6 +310,7 @@ class BlindboardFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'CONFIRM'))
|
||||
|
|
|
@ -374,7 +374,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -254,6 +254,7 @@ class _SpaceWorkDetailFormWidgetState extends State<SpaceWorkDetailFormWidget> {
|
|||
const Divider(),
|
||||
ItemListWidget.twoRowButtonTitleText(
|
||||
label: '风险辨识结果',
|
||||
isInput: false,
|
||||
isEditable: widget.isEditable,
|
||||
onTap: () async {
|
||||
await showDialog<String>(
|
||||
|
|
|
@ -120,6 +120,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'CONFESS',
|
||||
nameKey: 'CONFESS_USER_NAME',
|
||||
headerTitle: '安全交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '安全交底人',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'ACCEPT_CONFESS'))
|
||||
|
@ -130,6 +131,7 @@ class SpecialWorkFormBaseWork extends StatelessWidget {
|
|||
sectionKey: 'ACCEPT_CONFESS',
|
||||
nameKey: 'ACCEPT_CONFESS_USER_NAME',
|
||||
headerTitle: '接受交底人',
|
||||
isShowTime: false,
|
||||
roleTitle: '',
|
||||
),
|
||||
if (FormUtils.hasValue(signs, 'CONFIRM'))
|
||||
|
|
|
@ -252,6 +252,7 @@ if (reasonText.isEmpty) {
|
|||
barrierDismissible: false,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
try {
|
||||
await ApiService.saveGasTest('confinedspace', formData, imagePaths);
|
||||
|
@ -262,6 +263,7 @@ if (reasonText.isEmpty) {
|
|||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
LoadingDialogHelper.hide();
|
||||
}
|
||||
|
||||
/// 选择计量单位
|
||||
|
|
|
@ -391,7 +391,7 @@ if (reasonText.isEmpty) {
|
|||
),
|
||||
onTap: () async {
|
||||
DateTime? picked = await BottomDateTimePicker.showDate(
|
||||
allowPast:false,
|
||||
minTimeStr: pd['WORK_END_DATE'],
|
||||
mode: BottomPickerMode.dateTimeWithSeconds,
|
||||
context,
|
||||
);
|
||||
|
|
|
@ -101,19 +101,20 @@ class _LoginPageState extends State<LoginPage> {
|
|||
if (FormUtils.hasValue(result, 'pd')) {
|
||||
Map pd = result['pd'];
|
||||
final versionInfo = await getAppVersion();
|
||||
bool isWifi = await checkNetworkWifi();
|
||||
bool isWifi = true;
|
||||
if (versionInfo.versionName != pd['VERSION']) {
|
||||
//有更新 提示更新
|
||||
final ok = await CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
barrierDismissible:false,
|
||||
title: '更新通知',
|
||||
content: isWifi ? '发现新版本,是否更新?为了更好的体验,请更新到最新版本。' : '发现新版本,检查到您当前使用的是移动网络,是否更新?更新时请注意流量消耗。为了更好的体验,请更新到最新版本。',
|
||||
cancelText: pd['ISUPDATE'] == '1' ? '' : '稍后更新',
|
||||
confirmText: '立即更新'
|
||||
);
|
||||
if (ok) {
|
||||
// await showUpdateConfirmDialog(context, apkUrl: apkUrl);
|
||||
|
||||
final apkUrl = pd['FILEURL'] ?? '';
|
||||
await showUpdateConfirm(context, apkUrl: apkUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
|
|||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/services/auth_service.dart';
|
||||
import 'package:qhd_prevention/tools/h_colors.dart';
|
||||
import 'package:qhd_prevention/tools/update/update_dialogs.dart';
|
||||
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
|
@ -26,19 +27,26 @@ class _MineSetPageState extends State<MineSetPage> {
|
|||
final result = await AuthService.checkUpdate();
|
||||
LoadingDialogHelper.hide();
|
||||
if (FormUtils.hasValue(result, 'pd')) {
|
||||
|
||||
// 有更新 提示更新
|
||||
Map pd = result['pd'];
|
||||
CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
title: '更新通知',
|
||||
cancelText: '',
|
||||
confirmText: '我知道了',
|
||||
content: pd['UPLOAD_CONTENT'] ?? '',
|
||||
onConfirm: () {
|
||||
ToastUtil.showNormal(context, '更新去吧!');
|
||||
}
|
||||
);
|
||||
final versionInfo = await getAppVersion();
|
||||
bool isWifi = true;
|
||||
if (versionInfo.versionName != pd['VERSION']) {
|
||||
//有更新 提示更新
|
||||
final ok = await CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
barrierDismissible:false,
|
||||
title: '更新通知',
|
||||
content: isWifi ? '发现新版本,是否更新?为了更好的体验,请更新到最新版本。' : '发现新版本,检查到您当前使用的是移动网络,是否更新?更新时请注意流量消耗。为了更好的体验,请更新到最新版本。',
|
||||
cancelText: pd['ISUPDATE'] == '1' ? '' : '稍后更新',
|
||||
confirmText: '立即更新'
|
||||
);
|
||||
if (ok) {
|
||||
final apkUrl = pd['FILEURL'] ?? '';
|
||||
await showUpdateConfirm(context, apkUrl: apkUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}else{
|
||||
_loadAppVersion();
|
||||
|
||||
|
|
|
@ -600,6 +600,7 @@ Future<bool> checkNetworkWifi() async {
|
|||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.mobile) {
|
||||
print("当前是移动网络(可能是 2G/3G/4G/5G)");
|
||||
return false;
|
||||
} else if (connectivityResult == ConnectivityResult.wifi) {
|
||||
return true;
|
||||
print("当前是 WiFi");
|
||||
|
|
|
@ -3,42 +3,15 @@ import 'package:flutter/material.dart';
|
|||
import 'package:qhd_prevention/services/update_service.dart';
|
||||
|
||||
/// 显示“发现新版本”确认对话框,点击更新后会弹出下载进度弹窗
|
||||
Future<void> showUpdateConfirmDialog(BuildContext context, {
|
||||
Future<void> showUpdateConfirm(BuildContext context, {
|
||||
required String apkUrl,
|
||||
String title = '发现新版本',
|
||||
String content = '检测到新版本,是否立即更新?',
|
||||
String updateButtonText = '更新',
|
||||
String cancelButtonText = '稍后',
|
||||
|
||||
}) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: Text(cancelButtonText),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: Text(updateButtonText),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (ctx) => DownloadProgressDialog(apkUrl: apkUrl),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
// 跳出下载进度弹窗(阻塞式,直到弹窗被关闭)
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => DownloadProgressDialog(apkUrl: apkUrl),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载进度弹窗(会在 initState 里自动开始下载)
|
||||
|
|
|
@ -16,8 +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+10
|
||||
version: 2.2.0
|
||||
version: 2.1.2+59
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.0
|
||||
|
|
Loading…
Reference in New Issue