第一版
parent
037a00967f
commit
421d71f7c9
|
@ -4,7 +4,7 @@ PODS:
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_app_badger (1.3.0):
|
- flutter_new_badger (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
@ -36,6 +36,8 @@ PODS:
|
||||||
- video_player_avfoundation (0.0.1):
|
- video_player_avfoundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- wakelock_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- webview_flutter_wkwebview (0.0.1):
|
- webview_flutter_wkwebview (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
@ -44,7 +46,7 @@ DEPENDENCIES:
|
||||||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`)
|
- flutter_new_badger (from `.symlinks/plugins/flutter_new_badger/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
@ -57,6 +59,7 @@ DEPENDENCIES:
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
@ -66,8 +69,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_app_badger:
|
flutter_new_badger:
|
||||||
:path: ".symlinks/plugins/flutter_app_badger/ios"
|
:path: ".symlinks/plugins/flutter_new_badger/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
geolocator_apple:
|
geolocator_apple:
|
||||||
|
@ -92,6 +95,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||||
|
wakelock_plus:
|
||||||
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||||
|
|
||||||
|
@ -99,7 +104,7 @@ SPEC CHECKSUMS:
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_app_badger: 16b371e989d04cd265df85be2c3851b49cb68d18
|
flutter_new_badger: 133aaf93e9a5542bf905c8483d8b83c5ef4946ea
|
||||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
|
@ -112,6 +117,7 @@ SPEC CHECKSUMS:
|
||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||||
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
|
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
||||||
|
|
|
@ -0,0 +1,342 @@
|
||||||
|
// baidu_map_webview_debug.dart
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
/// 更健壮的 BaiduMapWebView(带 JS 错误回传、ready 检测、超时/重试)
|
||||||
|
class BaiduMapWebView extends StatefulWidget {
|
||||||
|
final String ak; // 请确保这是 web (JS API) 可用的 AK
|
||||||
|
final double latitude;
|
||||||
|
final double longitude;
|
||||||
|
final int zoom; // 对应 uniapp 的 scale
|
||||||
|
final List<Map<String, dynamic>> covers;
|
||||||
|
final ValueChanged<Map<String, dynamic>>? onMarkerTap;
|
||||||
|
final Duration readyTimeout;
|
||||||
|
|
||||||
|
const BaiduMapWebView({
|
||||||
|
Key? key,
|
||||||
|
required this.ak,
|
||||||
|
required this.latitude,
|
||||||
|
required this.longitude,
|
||||||
|
this.zoom = 13,
|
||||||
|
this.covers = const [],
|
||||||
|
this.onMarkerTap,
|
||||||
|
this.readyTimeout = const Duration(seconds: 8),
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BaiduMapWebView> createState() => _BaiduMapWebViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BaiduMapWebViewState extends State<BaiduMapWebView> {
|
||||||
|
late final WebViewController _controller;
|
||||||
|
bool _pageLoaded = false;
|
||||||
|
bool _mapReady = false;
|
||||||
|
String? _lastJsMessage;
|
||||||
|
Timer? _readyTimer;
|
||||||
|
bool _showError = false;
|
||||||
|
String _errorText = '';
|
||||||
|
|
||||||
|
String _html(String ak) {
|
||||||
|
// HTML 模板:创建 map,定义 setCenter / setMarkersFromFlutter,转发 console 与 onerror
|
||||||
|
return '''
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<style>html,body,#map{height:100%;margin:0;padding:0;background:#f0f0f0}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=$ak"></script>
|
||||||
|
<script>
|
||||||
|
// 把 console.log、console.error、window.onerror 转发给 Flutter(Bridge)
|
||||||
|
(function(){
|
||||||
|
function send(kind, msg) {
|
||||||
|
try { Bridge.postMessage(JSON.stringify({__bridge: true, kind: kind, msg: String(msg)})); } catch(e) {}
|
||||||
|
}
|
||||||
|
var _log = console.log;
|
||||||
|
console.log = function(){
|
||||||
|
try{ send('log', Array.prototype.slice.call(arguments).join(' ')); }catch(e){}
|
||||||
|
_log && _log.apply(console, arguments);
|
||||||
|
};
|
||||||
|
var _err = console.error;
|
||||||
|
console.error = function(){
|
||||||
|
try{ send('error', Array.prototype.slice.call(arguments).join(' ')); }catch(e){}
|
||||||
|
_err && _err.apply(console, arguments);
|
||||||
|
};
|
||||||
|
window.onerror = function(msg, url, line, col, err) {
|
||||||
|
try{ send('onerror', msg + ' at ' + url + ':' + line + ':' + col + ' -> ' + (err && err.stack? err.stack:'') ); }catch(e){}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 初始化地图
|
||||||
|
var map;
|
||||||
|
try {
|
||||||
|
map = new BMap.Map("map");
|
||||||
|
map.enableScrollWheelZoom(true);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Map init error', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCenter(lat, lng, zoom) {
|
||||||
|
try {
|
||||||
|
if(!map) { console.error('map undefined in setCenter'); return; }
|
||||||
|
var p = new BMap.Point(lng, lat);
|
||||||
|
map.centerAndZoom(p, zoom || map.getZoom());
|
||||||
|
} catch(e) {
|
||||||
|
console.error('setCenter error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var markers = [];
|
||||||
|
function clearMarkers(){
|
||||||
|
try {
|
||||||
|
for(var i=0;i<markers.length;i++){
|
||||||
|
map.removeOverlay(markers[i]);
|
||||||
|
}
|
||||||
|
markers = [];
|
||||||
|
} catch(e) { console.error('clearMarkers error', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMarkersFromFlutter(coversJson){
|
||||||
|
try {
|
||||||
|
if(!map) { console.error('map undefined in setMarkersFromFlutter'); return; }
|
||||||
|
var arr = JSON.parse(coversJson || '[]');
|
||||||
|
clearMarkers();
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
var it = arr[i];
|
||||||
|
if(!it || it.longitude==null || it.latitude==null) continue;
|
||||||
|
var pt = new BMap.Point(parseFloat(it.longitude), parseFloat(it.latitude));
|
||||||
|
var marker;
|
||||||
|
if (it.icon && it.icon.length > 0) {
|
||||||
|
// 图片过大可能失败,谨慎使用 base64 大图
|
||||||
|
var myIcon = new BMap.Icon(it.icon, new BMap.Size(36,36));
|
||||||
|
marker = new BMap.Marker(pt, {icon: myIcon});
|
||||||
|
} else {
|
||||||
|
marker = new BMap.Marker(pt);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
marker._meta = it.data || it;
|
||||||
|
} catch(e) { marker._meta = {}; }
|
||||||
|
(function(m){
|
||||||
|
m.addEventListener('click', function(){
|
||||||
|
try { Bridge.postMessage(JSON.stringify(m._meta)); } catch(e) {}
|
||||||
|
});
|
||||||
|
})(marker);
|
||||||
|
markers.push(marker);
|
||||||
|
map.addOverlay(marker);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('setMarkersFromFlutter parse error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地图初始化完成后发送 ready
|
||||||
|
function notifyReady(){
|
||||||
|
try{ Bridge.postMessage(JSON.stringify({__mapReady:true})); }catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待 map 实例, 然后 center 并通知 ready
|
||||||
|
(function waitMap(){
|
||||||
|
try {
|
||||||
|
if(map && typeof map.centerAndZoom === 'function') {
|
||||||
|
// initial center (Flutter will call setCenter after load, but still notify)
|
||||||
|
notifyReady();
|
||||||
|
} else {
|
||||||
|
setTimeout(waitMap, 300);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error('waitMap error', e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 导出 API
|
||||||
|
window.setMarkersFromFlutter = setMarkersFromFlutter;
|
||||||
|
window.setCenter = setCenter;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _escapeForJs(String s) => s.replaceAll(r'\', r'\\').replaceAll("'", r"\\'").replaceAll('\n', r' ');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_controller = WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..addJavaScriptChannel('Bridge', onMessageReceived: _onJsMessage)
|
||||||
|
..setNavigationDelegate(NavigationDelegate(
|
||||||
|
onPageFinished: (url) {
|
||||||
|
_pageLoaded = true;
|
||||||
|
_startReadyTimer();
|
||||||
|
// inject initial center & markers AFTER slight delay to allow JS to define functions
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||||
|
await _controller.runJavaScript('setCenter(${widget.latitude}, ${widget.longitude}, ${widget.zoom});');
|
||||||
|
final coversJson = jsonEncode(widget.covers);
|
||||||
|
await _controller.runJavaScript("setMarkersFromFlutter('${_escapeForJs(coversJson)}');");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onWebResourceError: (err) {
|
||||||
|
_reportError('WebResourceError: ${err.description}');
|
||||||
|
},
|
||||||
|
))
|
||||||
|
..loadHtmlString(_html(widget.ak));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startReadyTimer() {
|
||||||
|
_readyTimer?.cancel();
|
||||||
|
_readyTimer = Timer(widget.readyTimeout, () {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!_mapReady) {
|
||||||
|
setState(() {
|
||||||
|
_showError = true;
|
||||||
|
_errorText = '地图初始化超时,可能 AK 无效或网络受限。请检查 AK(需为百度 JS API Key)与网络。';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onJsMessage(JavaScriptMessage msg) {
|
||||||
|
_lastJsMessage = msg.message;
|
||||||
|
// 解析 message
|
||||||
|
try {
|
||||||
|
final m = jsonDecode(msg.message);
|
||||||
|
if (m is Map && m.containsKey('__mapReady') && m['__mapReady'] == true) {
|
||||||
|
// JS 报告地图已 ready
|
||||||
|
_readyTimer?.cancel();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_mapReady = true;
|
||||||
|
_showError = false;
|
||||||
|
_errorText = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// not json meta, maybe marker data or console log
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle console / error wrapper shape: {__bridge:true, kind:..., msg:...}
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(msg.message);
|
||||||
|
if (decoded is Map && decoded['__bridge'] == true) {
|
||||||
|
final kind = decoded['kind'];
|
||||||
|
final mm = decoded['msg'];
|
||||||
|
if (kind == 'error' || kind == 'onerror') {
|
||||||
|
_reportError('JS error: $mm');
|
||||||
|
} else {
|
||||||
|
// console log: keep last message (debug only)
|
||||||
|
debugPrint('JS log: $mm');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// otherwise treat as marker click payload (likely non-meta JSON)
|
||||||
|
try {
|
||||||
|
final payload = jsonDecode(msg.message) as Map<String, dynamic>;
|
||||||
|
if (widget.onMarkerTap != null) widget.onMarkerTap!(payload);
|
||||||
|
} catch (e) {
|
||||||
|
// not JSON? ignore
|
||||||
|
debugPrint('Unrecognized JS message: ${msg.message}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reportError(String text) {
|
||||||
|
debugPrint(text);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_showError = true;
|
||||||
|
_errorText = text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant BaiduMapWebView oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (_pageLoaded) {
|
||||||
|
if (oldWidget.latitude != widget.latitude ||
|
||||||
|
oldWidget.longitude != widget.longitude ||
|
||||||
|
oldWidget.zoom != widget.zoom) {
|
||||||
|
_controller.runJavaScript('setCenter(${widget.latitude}, ${widget.longitude}, ${widget.zoom});');
|
||||||
|
}
|
||||||
|
if (!listEquals(oldWidget.covers, widget.covers)) {
|
||||||
|
final coversJson = jsonEncode(widget.covers);
|
||||||
|
_controller.runJavaScript("setMarkersFromFlutter('${_escapeForJs(coversJson)}');");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_readyTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
WebViewWidget(controller: _controller),
|
||||||
|
if (!_mapReady && !_showError)
|
||||||
|
const Center(child: CircularProgressIndicator()),
|
||||||
|
if (_showError)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white70,
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('地图加载失败', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(_errorText, textAlign: TextAlign.center),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_showError = false;
|
||||||
|
_mapReady = false;
|
||||||
|
});
|
||||||
|
// 重新加载页面
|
||||||
|
_controller.loadHtmlString(_html(widget.ak));
|
||||||
|
},
|
||||||
|
child: const Text('重试'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 把最后一条 js message 展示出来,便于你贴错误给我
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('JS 消息'),
|
||||||
|
content: SingleChildScrollView(child: Text(_lastJsMessage ?? '无')),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(context), child: const Text('关闭')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('查看 JS 日志'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,8 @@ class CustomAlertDialog extends StatefulWidget {
|
||||||
String cancelText = '取消',
|
String cancelText = '取消',
|
||||||
String confirmText = '确定',
|
String confirmText = '确定',
|
||||||
bool barrierDismissible = true,
|
bool barrierDismissible = true,
|
||||||
|
final VoidCallback? onConfirm,
|
||||||
|
|
||||||
}) async {
|
}) async {
|
||||||
final result = await showDialog<bool>(
|
final result = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -52,6 +54,7 @@ class CustomAlertDialog extends StatefulWidget {
|
||||||
cancelText: cancelText,
|
cancelText: cancelText,
|
||||||
confirmText: confirmText,
|
confirmText: confirmText,
|
||||||
mode: DialogMode.text,
|
mode: DialogMode.text,
|
||||||
|
onConfirm: onConfirm,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -65,6 +68,8 @@ class CustomAlertDialog extends StatefulWidget {
|
||||||
String content = '',
|
String content = '',
|
||||||
String confirmText = '确定',
|
String confirmText = '确定',
|
||||||
bool barrierDismissible = true,
|
bool barrierDismissible = true,
|
||||||
|
final VoidCallback? onConfirm,
|
||||||
|
|
||||||
}) async {
|
}) async {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -76,6 +81,8 @@ class CustomAlertDialog extends StatefulWidget {
|
||||||
cancelText: '', // 隐藏取消按钮使其成为单按钮
|
cancelText: '', // 隐藏取消按钮使其成为单按钮
|
||||||
confirmText: confirmText,
|
confirmText: confirmText,
|
||||||
mode: DialogMode.text,
|
mode: DialogMode.text,
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,118 +1,49 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
|
|
||||||
///弹窗组件:VideoPlayerPopup
|
/// 弹窗组件:Chewie 版 VideoPlayerPopup
|
||||||
class VideoPlayerPopup extends StatefulWidget {
|
class VideoPlayerPopup extends StatefulWidget {
|
||||||
/// 视频地址
|
|
||||||
final String videoUrl;
|
final String videoUrl;
|
||||||
const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key);
|
const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VideoPlayerPopup> createState() => _VideoPlayerPopupState();
|
State<VideoPlayerPopup> createState() => _VideoPlayerPopupState();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
||||||
late VideoPlayerController _controller;
|
late VideoPlayerController _videoController;
|
||||||
bool _showControls = true;
|
ChewieController? _chewieController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = VideoPlayerController.networkUrl(
|
_videoController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl))
|
||||||
Uri.parse(widget.videoUrl),
|
..initialize().then((_) {
|
||||||
)..initialize().then((_) {
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
_controller.play();
|
|
||||||
});
|
|
||||||
// 自动隐藏控件
|
|
||||||
_controller.addListener(() {
|
|
||||||
if (_controller.value.isPlaying && _showControls) {
|
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
|
||||||
if (_controller.value.isPlaying && mounted) {
|
|
||||||
setState(() => _showControls = false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_chewieController = ChewieController(
|
||||||
|
videoPlayerController: _videoController,
|
||||||
|
autoPlay: true,
|
||||||
|
looping: false,
|
||||||
|
showOptions:false,
|
||||||
|
allowFullScreen: true,
|
||||||
|
allowPlaybackSpeedChanging: true,
|
||||||
|
allowMuting: true,
|
||||||
|
showControlsOnInitialize: true,
|
||||||
|
materialProgressColors: ChewieProgressColors(playedColor: Colors.blue,backgroundColor: Colors.white, handleColor:Colors.blue,bufferedColor:Colors.red),
|
||||||
|
aspectRatio: _videoController.value.aspectRatio,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_chewieController?.dispose();
|
||||||
|
_videoController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildControls() {
|
|
||||||
final pos = _controller.value.position;
|
|
||||||
final dur = _controller.value.duration;
|
|
||||||
String fmt(Duration d) =>
|
|
||||||
'${d.inMinutes.remainder(60).toString().padLeft(2, '0')}:'
|
|
||||||
'${d.inSeconds.remainder(60).toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
return Positioned.fill(
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: _showControls ? 1 : 0,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () => setState(() => _showControls = !_showControls),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black45,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
// 进度条
|
|
||||||
Slider(
|
|
||||||
value: pos.inMilliseconds.toDouble().clamp(0, dur.inMilliseconds.toDouble()),
|
|
||||||
max: dur.inMilliseconds.toDouble(),
|
|
||||||
onChanged: (v) {
|
|
||||||
_controller.seekTo(Duration(milliseconds: v.toInt()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// 时间 + 控制按钮
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_controller.value.isPlaying
|
|
||||||
? _controller.pause()
|
|
||||||
: _controller.play();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${fmt(pos)} / ${fmt(dur)}',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.fullscreen, color: Colors.white),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
|
||||||
builder: (_) => FullScreenVideoPage(
|
|
||||||
controller: _controller)));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
|
@ -121,20 +52,18 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
maxHeight: 500,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.black,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 视频内容
|
// 视频播放器
|
||||||
if (_controller.value.isInitialized)
|
if (_chewieController != null &&
|
||||||
AspectRatio(
|
_videoController.value.isInitialized)
|
||||||
aspectRatio: _controller.value.aspectRatio,
|
Chewie(controller: _chewieController!)
|
||||||
child: VideoPlayer(_controller),
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
|
|
||||||
|
@ -143,13 +72,10 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
||||||
top: 4,
|
top: 4,
|
||||||
right: 4,
|
right: 4,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.close, color: Colors.black54),
|
icon: const Icon(Icons.close, color: Colors.white),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 播放控制
|
|
||||||
if (_controller.value.isInitialized) _buildControls(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -157,109 +83,3 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 全屏横屏播放页面:FullScreenVideoPage
|
|
||||||
class FullScreenVideoPage extends StatefulWidget {
|
|
||||||
final VideoPlayerController controller;
|
|
||||||
const FullScreenVideoPage({Key? key, required this.controller}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<FullScreenVideoPage> createState() => _FullScreenVideoPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FullScreenVideoPageState extends State<FullScreenVideoPage> {
|
|
||||||
VideoPlayerController get _controller => widget.controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// 进入横屏、沉浸式
|
|
||||||
SystemChrome.setPreferredOrientations([
|
|
||||||
DeviceOrientation.landscapeLeft,
|
|
||||||
DeviceOrientation.landscapeRight,
|
|
||||||
]);
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
// 恢复竖屏
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
|
||||||
SystemChrome.setPreferredOrientations([
|
|
||||||
DeviceOrientation.portraitUp,
|
|
||||||
DeviceOrientation.portraitDown,
|
|
||||||
]);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: Center(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// 全屏视频
|
|
||||||
if (_controller.value.isInitialized)
|
|
||||||
SizedBox.expand(child: VideoPlayer(_controller))
|
|
||||||
else
|
|
||||||
const Center(child: CircularProgressIndicator()),
|
|
||||||
|
|
||||||
// 简单控制:点击画面切换播放/暂停
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_controller.value.isPlaying
|
|
||||||
? _controller.pause()
|
|
||||||
: _controller.play();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
// 返回按钮
|
|
||||||
Positioned(
|
|
||||||
top: 20,
|
|
||||||
left: 20,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white, size: 28),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// 时间 & 进度条(简单版)
|
|
||||||
if (_controller.value.isInitialized)
|
|
||||||
Positioned(
|
|
||||||
bottom: 20,
|
|
||||||
left: 20,
|
|
||||||
right: 20,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${_format(_controller.value.position)} / ${_format(_controller.value.duration)}',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: VideoProgressIndicator(
|
|
||||||
_controller,
|
|
||||||
allowScrubbing: true,
|
|
||||||
colors: VideoProgressColors(
|
|
||||||
playedColor: Colors.red,
|
|
||||||
bufferedColor: Colors.white54,
|
|
||||||
backgroundColor: Colors.white30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _format(Duration d) =>
|
|
||||||
'${d.inMinutes.remainder(60).toString().padLeft(2, '0')}:'
|
|
||||||
'${d.inSeconds.remainder(60).toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||||
|
@ -49,31 +48,12 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
? widget.initialMediaPaths!.take(widget.maxCount).toList()
|
? widget.initialMediaPaths!.take(widget.maxCount).toList()
|
||||||
: [];
|
: [];
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// 改动点:不要把网络地址换成 File(''),保持 File(path)(path 可能是本地也可能是 http)
|
|
||||||
widget.onChanged(
|
widget.onChanged(
|
||||||
_mediaPaths.map((p) => File(p)).toList(),
|
_mediaPaths.map((p) => p.startsWith('http') ? File('') : File(p)).toList(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant MediaPickerRow oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
// 当父组件传入的 initialMediaPaths 变化时,更新内部 _mediaPaths 并触发 onChanged
|
|
||||||
if (!listEquals(oldWidget.initialMediaPaths, widget.initialMediaPaths)) {
|
|
||||||
_mediaPaths = widget.initialMediaPaths != null
|
|
||||||
? widget.initialMediaPaths!.take(widget.maxCount).toList()
|
|
||||||
: [];
|
|
||||||
setState(() {}); // 触发 rebuild
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
// 改动点:同上,保持 File(path)
|
|
||||||
widget.onChanged(
|
|
||||||
_mediaPaths.map((p) => File(p)).toList(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showPickerOptions() async {
|
Future<void> _showPickerOptions() async {
|
||||||
if (!widget.isEdit) return; // 不可编辑时直接返回
|
if (!widget.isEdit) return; // 不可编辑时直接返回
|
||||||
|
|
||||||
|
@ -140,7 +120,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
final path = picked.path;
|
final path = picked.path;
|
||||||
setState(() => _mediaPaths.add(path));
|
setState(() => _mediaPaths.add(path));
|
||||||
// 这里本来就是 File(p)
|
|
||||||
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
||||||
widget.onMediaAdded?.call(path);
|
widget.onMediaAdded?.call(path);
|
||||||
}
|
}
|
||||||
|
@ -329,19 +308,6 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant RepairedPhotoSection oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
// 父组件传入 initialMediaPaths 变更时,同步内部 _mediaPaths 并触发 onChanged
|
|
||||||
if (!listEquals(oldWidget.initialMediaPaths, widget.initialMediaPaths)) {
|
|
||||||
_mediaPaths = widget.initialMediaPaths?.take(widget.maxCount).toList() ?? [];
|
|
||||||
setState(() {});
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -194,6 +194,20 @@ U6Hzm1ninpWeE+awIDAQAB
|
||||||
data: {},
|
data: {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// 检查记录详情地图信息
|
||||||
|
static Future<Map<String, dynamic>> getRecordMapInfo(String CHECKRECORD_ID) {
|
||||||
|
return HttpManager().request(
|
||||||
|
basePath,
|
||||||
|
'/app/customCheckRecord/goMap',
|
||||||
|
method: Method.post,
|
||||||
|
data: {
|
||||||
|
'USER_ID': SessionService.instance.loginUserId,
|
||||||
|
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||||
|
'CHECKRECORD_ID': CHECKRECORD_ID
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 月隐患 1 月隐患,2年隐患
|
/// 月隐患 1 月隐患,2年隐患
|
||||||
static Future<Map<String, dynamic>> getDanger(int type) {
|
static Future<Map<String, dynamic>> getDanger(int type) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qhd_prevention/pages/badge_manager.dart';
|
import 'package:qhd_prevention/pages/badge_manager.dart';
|
||||||
|
import 'package:qhd_prevention/tools/auth_service.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import './pages/login_page.dart';
|
import './pages/login_page.dart';
|
||||||
import './pages/main_tab.dart';
|
import './pages/main_tab.dart';
|
||||||
|
@ -27,65 +28,69 @@ class GlobalMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// 确保Flutter引擎初始化完成
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// 初始化 EasyLoading
|
// 初始化 EasyLoading
|
||||||
// 配置 EasyLoading 全局实例
|
|
||||||
EasyLoading.instance
|
EasyLoading.instance
|
||||||
..displayDuration = const Duration(seconds: 20)
|
..displayDuration = const Duration(seconds: 20)
|
||||||
..indicatorType = EasyLoadingIndicatorType.ring // 使用环形指示器
|
..indicatorType = EasyLoadingIndicatorType.ring
|
||||||
..loadingStyle = EasyLoadingStyle.custom // 必须使用自定义样式
|
..loadingStyle = EasyLoadingStyle.custom
|
||||||
..indicatorSize = 36.0 // 接近系统默认大小
|
..indicatorSize = 36.0
|
||||||
..radius = 0 // 无圆角
|
..radius = 0
|
||||||
..progressColor = Colors.blue // 进度条颜色为系统蓝色
|
..progressColor = Colors.blue
|
||||||
..backgroundColor = Colors.grey.shade300
|
..backgroundColor = Colors.grey.shade300
|
||||||
..indicatorColor = Colors.blue // 指示器颜色为系统蓝色
|
..indicatorColor = Colors.blue
|
||||||
..textColor = Colors.black // 隐藏文字
|
..textColor = Colors.black
|
||||||
..userInteractions = false // 允许用户交互(系统指示器不阻止交互)
|
..userInteractions = false
|
||||||
..dismissOnTap = false;
|
..dismissOnTap = false;
|
||||||
|
|
||||||
await initializeDateFormatting('zh_CN', null);
|
await initializeDateFormatting('zh_CN', null);
|
||||||
// 获取共享首选项实例
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
|
|
||||||
// 获取登录状态
|
// 初始化HTTP管理器未授权回调
|
||||||
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
|
|
||||||
|
|
||||||
// 初始化HTTP管理器
|
|
||||||
HttpManager.onUnauthorized = () async {
|
HttpManager.onUnauthorized = () async {
|
||||||
// 清除登录状态
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setBool('isLoggedIn', false);
|
await prefs.setBool('isLoggedIn', false);
|
||||||
await prefs.remove('token'); // 如果有token的话
|
await prefs.remove('token');
|
||||||
|
|
||||||
// 导航到登录页
|
|
||||||
navigatorKey.currentState?.pushNamedAndRemoveUntil(
|
navigatorKey.currentState?.pushNamedAndRemoveUntil(
|
||||||
'/login',
|
'/login',
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 等一帧让页面构建完成
|
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
GlobalMessage.showError('会话已过期,请重新登录');
|
GlobalMessage.showError('会话已过期,请重新登录');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 自动登录逻辑
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final savedLogin = prefs.getBool('isLoggedIn') ?? false;
|
||||||
|
bool isLoggedIn = false;
|
||||||
|
|
||||||
|
if (savedLogin) {
|
||||||
|
// 如果本地标记已登录,进一步验证 token 是否有效
|
||||||
|
try {
|
||||||
|
isLoggedIn = await AuthService.isLoggedIn();
|
||||||
|
// 这里建议 AuthService.isLoggedIn() 内部实际请求一次用户信息接口
|
||||||
|
// 如果失败,就返回 false
|
||||||
|
} catch (e) {
|
||||||
|
isLoggedIn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(MyApp(isLoggedIn: isLoggedIn));
|
runApp(MyApp(isLoggedIn: isLoggedIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
final bool isLoggedIn;
|
final bool isLoggedIn;
|
||||||
|
|
||||||
MyApp({super.key, required this.isLoggedIn});
|
const MyApp({super.key, required this.isLoggedIn});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: '',
|
title: '',
|
||||||
navigatorKey: navigatorKey, // 设置全局导航键
|
navigatorKey: navigatorKey,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
// 修复:正确集成 EasyLoading 和 GestureDetector
|
|
||||||
return EasyLoading.init(
|
return EasyLoading.init(
|
||||||
builder: (context, widget) {
|
builder: (context, widget) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
@ -101,13 +106,12 @@ class MyApp extends StatelessWidget {
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
dividerTheme: const DividerThemeData(
|
dividerTheme: const DividerThemeData(
|
||||||
color: Colors.black12,
|
color: Colors.black12,
|
||||||
thickness: .5, // 线高
|
thickness: .5,
|
||||||
indent: 0, // 左缩进
|
indent: 0,
|
||||||
endIndent: 0, // 右缩进
|
endIndent: 0,
|
||||||
),
|
),
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
// 统一设置页面背景颜色
|
scaffoldBackgroundColor: const Color(0xFFF1F1F1),
|
||||||
scaffoldBackgroundColor: const Color(0xFFF1F1F1), // 浅灰色背景
|
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
|
@ -120,15 +124,13 @@ class MyApp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||||
color: Colors.blue, // 统一颜色
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 根据登录状态决定初始页面
|
|
||||||
home: isLoggedIn ? const MainPage() : const LoginPage(),
|
home: isLoggedIn ? const MainPage() : const LoginPage(),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
routes: {
|
routes: {
|
||||||
'/login': (_) => const LoginPage(),
|
'/login': (_) => const LoginPage(),
|
||||||
// ... 其他路由
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/BaiDuMap/BaiduMapWebView.dart';
|
||||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
@ -12,29 +16,21 @@ import 'package:qhd_prevention/pages/app/hidden_record_detail_page.dart';
|
||||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
import 'package:qhd_prevention/tools/tools.dart';
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CheckRecordDetailPage extends StatefulWidget {
|
class CheckRecordDetailPage extends StatefulWidget {
|
||||||
const CheckRecordDetailPage(this.id, this.type, {super.key});
|
const CheckRecordDetailPage(this.id, this.type, {super.key});
|
||||||
|
|
||||||
final int type;
|
final int type;
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CheckRecordDetailPageState createState() => _CheckRecordDetailPageState();
|
_CheckRecordDetailPageState createState() => _CheckRecordDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
// 模拟数据
|
// map markers
|
||||||
// List<Map<String, dynamic>> varList = [
|
List<Map<String, dynamic>> covers = [];
|
||||||
// {"RISKPOINT_ID": "1", "CHECK_CONTENT": "安全设备检查", "ISNORMAL": "0", "IMGCOUNT": 2, "RECORDITEM_ID": "1001"},
|
double centerLat = 39.8883;
|
||||||
// {"RISKPOINT_ID": "2", "CHECK_CONTENT": "消防设施检查", "ISNORMAL": "1", "HIDDEN_ID": "2001"},
|
double centerLng = 119.519;
|
||||||
// {"RISKPOINT_ID": "3", "CHECK_CONTENT": "电气线路检查", "ISNORMAL": "2"},
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// List<Map<String, dynamic>> otherHiddenList = [
|
|
||||||
// {"HIDDEN_ID": "3001", "HIDDENDESCR": "应急通道堵塞"},
|
|
||||||
// {"HIDDEN_ID": "3002", "HIDDENDESCR": "安全标识缺失"},
|
|
||||||
// ];
|
|
||||||
|
|
||||||
Map<String, dynamic> pd = {
|
Map<String, dynamic> pd = {
|
||||||
"LIST_NAME": "",
|
"LIST_NAME": "",
|
||||||
|
@ -49,102 +45,201 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
"END_DATE": "",
|
"END_DATE": "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 地图相关
|
|
||||||
// final LatLng _center = const LatLng(39.8883, 119.519);
|
|
||||||
// final Set<Marker> _markers = {};
|
|
||||||
double _scale = 13.0;
|
|
||||||
|
|
||||||
int _currentImageIndex = 0;
|
|
||||||
bool _showImageViewer = false;
|
|
||||||
|
|
||||||
|
|
||||||
// dynamic pd;
|
|
||||||
List<String> imageList = [];
|
List<String> imageList = [];
|
||||||
List<dynamic> varList = [];
|
List<dynamic> varList = [];
|
||||||
List<dynamic> hiddenList = [];
|
List<dynamic> hiddenList = [];
|
||||||
|
|
||||||
|
bool _loadingDetail = true;
|
||||||
|
bool _loadingDetailTwo = true;
|
||||||
|
bool _loadingMap = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// 添加地图标记
|
// 启动时并行请求,UI 使用 setState 更新
|
||||||
// _markers.add(Marker(
|
_fetchAll();
|
||||||
// markerId: MarkerId("checkpoint"),
|
}
|
||||||
// position: _center,
|
|
||||||
// icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
|
|
||||||
// ));
|
|
||||||
|
|
||||||
|
Future<void> _fetchAll() async {
|
||||||
|
// 并行发请求,界面加载更快
|
||||||
|
await Future.wait([
|
||||||
|
_getInspectRecordsDetail(),
|
||||||
|
_getInspectRecordsDetailTwo(),
|
||||||
|
_getMapData(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
_getInspectRecordsDetail();
|
Future<void> _getMapData() async {
|
||||||
_getInspectRecordsDetailTwo();
|
setState(() => _loadingMap = true);
|
||||||
|
try {
|
||||||
|
final resData = await ApiService.getRecordMapInfo(widget.id);
|
||||||
|
if (resData == null) {
|
||||||
|
// 接口返回空的处理
|
||||||
|
setState(() => _loadingMap = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final built = buildCoversFromRes(resData);
|
||||||
|
double lat = centerLat;
|
||||||
|
double lng = centerLng;
|
||||||
|
|
||||||
|
if (resData['cinfo'] != null) {
|
||||||
|
final cinfo = resData['cinfo'];
|
||||||
|
final latStr = (cinfo['LATITUDE'] ?? '').toString();
|
||||||
|
final lngStr = (cinfo['LONGITUDE'] ?? '').toString();
|
||||||
|
final parsedLat = double.tryParse(latStr);
|
||||||
|
final parsedLng = double.tryParse(lngStr);
|
||||||
|
if (parsedLat != null && parsedLng != null) {
|
||||||
|
lat = parsedLat;
|
||||||
|
lng = parsedLng;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
covers = built;
|
||||||
|
centerLat = lat;
|
||||||
|
centerLng = lng;
|
||||||
|
_loadingMap = false;
|
||||||
|
});
|
||||||
|
} catch (e, st) {
|
||||||
|
debugPrint('getMapData error: $e\n$st');
|
||||||
|
setState(() => _loadingMap = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> buildCoversFromRes(Map<String, dynamic> res) {
|
||||||
|
final list = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
|
final cinfo = res['cinfo'];
|
||||||
|
if (cinfo != null &&
|
||||||
|
cinfo['LONGITUDE'] != null &&
|
||||||
|
cinfo['LATITUDE'] != null &&
|
||||||
|
cinfo['LONGITUDE'].toString().isNotEmpty &&
|
||||||
|
cinfo['LATITUDE'].toString().isNotEmpty) {
|
||||||
|
final lat = double.tryParse(cinfo['LATITUDE'].toString());
|
||||||
|
final lng = double.tryParse(cinfo['LONGITUDE'].toString());
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
list.add({
|
||||||
|
'latitude': lat,
|
||||||
|
'longitude': lng,
|
||||||
|
'icon': '', // 可替换为网络图片或 data uri
|
||||||
|
'data': {'type': 'cinfo'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final check = res['checkrecord'];
|
||||||
|
if (check != null &&
|
||||||
|
check['LONGITUDE'] != null &&
|
||||||
|
check['LATITUDE'] != null &&
|
||||||
|
check['LONGITUDE'].toString().isNotEmpty &&
|
||||||
|
check['LATITUDE'].toString().isNotEmpty) {
|
||||||
|
final lat = double.tryParse(check['LATITUDE'].toString());
|
||||||
|
final lng = double.tryParse(check['LONGITUDE'].toString());
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
list.add({
|
||||||
|
'latitude': lat,
|
||||||
|
'longitude': lng,
|
||||||
|
'icon': '',
|
||||||
|
'data': {'type': 'checkrecord'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final vlist = (res['varList'] as List?) ?? [];
|
||||||
|
for (final item in vlist) {
|
||||||
|
try {
|
||||||
|
if (item != null &&
|
||||||
|
item['LONGITUDE'] != null &&
|
||||||
|
item['LATITUDE'] != null &&
|
||||||
|
item['LONGITUDE'].toString().isNotEmpty &&
|
||||||
|
item['LATITUDE'].toString().isNotEmpty) {
|
||||||
|
final lat = double.tryParse(item['LATITUDE'].toString());
|
||||||
|
final lng = double.tryParse(item['LONGITUDE'].toString());
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
list.add({
|
||||||
|
'latitude': lat,
|
||||||
|
'longitude': lng,
|
||||||
|
'icon': '',
|
||||||
|
'data': {'type': 'varList', 'item': item}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// 忽略单条解析错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getInspectRecordsDetail() async {
|
Future<void> _getInspectRecordsDetail() async {
|
||||||
|
setState(() => _loadingDetail = true);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final Map<String, dynamic> result;
|
final Map<String, dynamic> result;
|
||||||
if (widget.type == 1) {
|
if (widget.type == 1) {
|
||||||
result = await ApiService.getInspectRecordsDetailYin(widget.id);
|
result = await ApiService.getInspectRecordsDetailYin(widget.id);
|
||||||
} else {
|
} else {
|
||||||
result = await ApiService.getInspectRecordsDetail(widget.id);
|
result = await ApiService.getInspectRecordsDetail(widget.id);
|
||||||
}
|
}
|
||||||
// final result = await ApiService.getInspectRecordsDetail(widget.id);
|
|
||||||
if (result['result'] == 'success') {
|
if (result['result'] == 'success') {
|
||||||
final List<dynamic> qianmingList = result['qianming'] ?? [];
|
final List<dynamic> qianmingList = result['qianming'] ?? [];
|
||||||
|
// 清空旧数据,防止重复
|
||||||
|
final List<String> newImages = [];
|
||||||
|
for (final item in qianmingList) {
|
||||||
|
final fp = item?["FILEPATH"]?.toString();
|
||||||
|
if (fp != null && fp.isNotEmpty) newImages.add(fp);
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
pd= result['pd'];
|
pd = (result['pd'] as Map<String, dynamic>?) ?? pd;
|
||||||
varList = result['varList'] ?? [];
|
varList = (result['varList'] as List<dynamic>?) ?? [];
|
||||||
for(int i=0;i<qianmingList.length;i++){
|
imageList = newImages;
|
||||||
imageList.add(qianmingList[i]["FILEPATH"] as String);
|
_loadingDetail = false;
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
setState(() => _loadingDetail = false);
|
||||||
ToastUtil.showNormal(context, "加载数据失败");
|
ToastUtil.showNormal(context, "加载数据失败");
|
||||||
// _showMessage('加载数据失败');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
// 出错时可以 Toast 或者在页面上显示错误状态
|
debugPrint('getInspectRecordsDetail error: $e\n$st');
|
||||||
print('加载数据失败:$e');
|
setState(() => _loadingDetail = false);
|
||||||
|
ToastUtil.showNormal(context, "加载数据失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getInspectRecordsDetailTwo() async {
|
Future<void> _getInspectRecordsDetailTwo() async {
|
||||||
|
setState(() => _loadingDetailTwo = true);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final Map<String, dynamic> result;
|
final Map<String, dynamic> result;
|
||||||
if (widget.type == 1) {
|
if (widget.type == 1) {
|
||||||
result = await ApiService.getInspectRecordsDetailTwoYin(widget.id);
|
result = await ApiService.getInspectRecordsDetailTwoYin(widget.id);
|
||||||
} else {
|
} else {
|
||||||
result = await ApiService.getInspectRecordsDetailTwo(widget.id);
|
result = await ApiService.getInspectRecordsDetailTwo(widget.id);
|
||||||
}
|
}
|
||||||
// final result = await ApiService.getInspectRecordsDetailTwo(widget.id);
|
|
||||||
if (result['result'] == 'success') {
|
if (result['result'] == 'success') {
|
||||||
final List<dynamic> newList = result['hiddenList'] ?? [];
|
final List<dynamic> newList = (result['hiddenList'] as List?) ?? [];
|
||||||
setState(() {
|
setState(() {
|
||||||
hiddenList.addAll(newList);
|
hiddenList = newList;
|
||||||
|
_loadingDetailTwo = false;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
setState(() => _loadingDetailTwo = false);
|
||||||
ToastUtil.showNormal(context, "加载数据失败");
|
ToastUtil.showNormal(context, "加载数据失败");
|
||||||
// _showMessage('加载数据失败');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
// 出错时可以 Toast 或者在页面上显示错误状态
|
debugPrint('getInspectRecordsDetailTwo error: $e\n$st');
|
||||||
print('加载数据失败:$e');
|
setState(() => _loadingDetailTwo = false);
|
||||||
|
ToastUtil.showNormal(context, "加载数据失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UI 构建
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: MyAppbar(title: "检查记录详情"),
|
appBar: MyAppbar(title: "检查记录详情"),
|
||||||
// _buildAppBar(),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -153,7 +248,7 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
_buildCheckContentTable(),
|
_buildCheckContentTable(),
|
||||||
_buildSectionTitle("其他隐患信息"),
|
_buildSectionTitle("其他隐患信息"),
|
||||||
_buildOtherHiddenTable(),
|
_buildOtherHiddenTable(),
|
||||||
// _buildMapSection(),
|
_buildMapSection(), // 注意这里地图高度已固定
|
||||||
_buildSectionTitle("清单信息"),
|
_buildSectionTitle("清单信息"),
|
||||||
_buildInfoList(),
|
_buildInfoList(),
|
||||||
_buildSignPhotos(),
|
_buildSignPhotos(),
|
||||||
|
@ -163,36 +258,13 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppBar _buildAppBar() {
|
|
||||||
// return AppBar(
|
|
||||||
// leading: IconButton(
|
|
||||||
// icon: Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
// onPressed: () => Navigator.pop(context),
|
|
||||||
// ),
|
|
||||||
// title: Text("检查记录详情", style: TextStyle(color: Colors.white)),
|
|
||||||
// backgroundColor: Colors.blue,
|
|
||||||
// centerTitle: true,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Widget _buildSectionTitle(String title) {
|
Widget _buildSectionTitle(String title) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(width: 4, height: 16, color: Colors.blue, margin: EdgeInsets.only(right: 10)),
|
||||||
width: 4,
|
Text(title, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
height: 16,
|
|
||||||
color: Colors.blue,
|
|
||||||
margin: EdgeInsets.only(right: 10),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -201,74 +273,64 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
Widget _buildCheckContentTable() {
|
Widget _buildCheckContentTable() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 15),
|
margin: EdgeInsets.symmetric(horizontal: 15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||||
border: Border.all(color: Colors.grey[300]!),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Table(
|
child: Table(
|
||||||
border: TableBorder.all(color: Colors.grey[300]!),
|
border: TableBorder.all(color: Colors.grey[300]!),
|
||||||
columnWidths: const {
|
columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(1)},
|
||||||
0: FlexColumnWidth(3),
|
|
||||||
1: FlexColumnWidth(1),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
TableRow(
|
|
||||||
decoration: BoxDecoration(color: Colors.grey[100]),
|
|
||||||
children: [
|
children: [
|
||||||
|
TableRow(decoration: BoxDecoration(color: Colors.grey[100]), children: [
|
||||||
_buildTableHeaderCell("检查内容"),
|
_buildTableHeaderCell("检查内容"),
|
||||||
_buildTableHeaderCell("状态"),
|
_buildTableHeaderCell("状态"),
|
||||||
],
|
]),
|
||||||
),
|
// 若 varList 为空,显示空行或提示
|
||||||
...varList.map((item) => TableRow(
|
if (varList.isEmpty)
|
||||||
children: [
|
TableRow(children: [
|
||||||
Padding(
|
Padding(padding: EdgeInsets.all(12), child: Text("暂无检查内容")),
|
||||||
padding: EdgeInsets.all(8),
|
SizedBox(),
|
||||||
child: Text(item["CHECK_CONTENT"]),
|
])
|
||||||
),
|
else
|
||||||
_buildStatusCell(item)
|
...varList.map((item) {
|
||||||
],
|
final mi = (item is Map) ? item as Map<String, dynamic> : Map<String, dynamic>.from(item);
|
||||||
)),
|
return TableRow(children: [
|
||||||
|
Padding(padding: EdgeInsets.all(8), child: Text(mi["CHECK_CONTENT"]?.toString() ?? "")),
|
||||||
|
_buildStatusCell(mi),
|
||||||
|
]);
|
||||||
|
}).toList(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatusCell(Map<String, dynamic> item) {
|
Widget _buildStatusCell(Map<String, dynamic> item) {
|
||||||
|
// 兼容字符串/数字类型
|
||||||
|
final isnormalStr = item["ISNORMAL"]?.toString() ?? '';
|
||||||
|
final imgCount = int.tryParse(item["IMGCOUNT"]?.toString() ?? '') ?? 0;
|
||||||
|
|
||||||
String status;
|
String status;
|
||||||
Color color = Colors.black;
|
Color color = Colors.black;
|
||||||
VoidCallback? onTap;
|
VoidCallback? onTap;
|
||||||
|
|
||||||
switch (item["ISNORMAL"]) {
|
if (isnormalStr == '0') {
|
||||||
case 0:
|
|
||||||
status = "合格";
|
status = "合格";
|
||||||
color = Colors.blue;
|
color = imgCount > 0 ? Colors.blue : Colors.black;
|
||||||
if (item["IMGCOUNT"] > 0) {
|
if (imgCount > 0) onTap = () => _goToImgs(item["RECORDITEM_ID"]?.toString() ?? '');
|
||||||
onTap = () => _goToImgs(item["RECORDITEM_ID"]);
|
} else if (isnormalStr == '1') {
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
status = "不合格";
|
status = "不合格";
|
||||||
color = Colors.blue;
|
color = Colors.blue;
|
||||||
onTap = () => _goToDetail(item["HIDDEN_ID"]);
|
onTap = () => _goToDetail(item["HIDDEN_ID"]?.toString() ?? '');
|
||||||
break;
|
} else if (isnormalStr == '2') {
|
||||||
case 2:
|
|
||||||
status = "不涉及";
|
status = "不涉及";
|
||||||
color = Colors.blue;
|
color = Colors.blue;
|
||||||
break;
|
} else {
|
||||||
default:
|
|
||||||
status = "存在未整改隐患";
|
status = "存在未整改隐患";
|
||||||
|
color = Colors.black;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
child: Center(
|
child: Center(child: Text(status, style: TextStyle(color: color))),
|
||||||
child: Text(
|
|
||||||
status,
|
|
||||||
style: TextStyle(color: color),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -276,94 +338,87 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
Widget _buildOtherHiddenTable() {
|
Widget _buildOtherHiddenTable() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||||
border: Border.all(color: Colors.grey[300]!),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Table(
|
child: Table(
|
||||||
border: TableBorder.all(color: Colors.grey[300]!),
|
border: TableBorder.all(color: Colors.grey[300]!),
|
||||||
columnWidths: const {
|
columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(1)},
|
||||||
0: FlexColumnWidth(3),
|
|
||||||
1: FlexColumnWidth(1),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
TableRow(
|
|
||||||
decoration: BoxDecoration(color: Colors.grey[100]),
|
|
||||||
children: [
|
children: [
|
||||||
|
TableRow(decoration: BoxDecoration(color: Colors.grey[100]), children: [
|
||||||
_buildTableHeaderCell("隐患描述"),
|
_buildTableHeaderCell("隐患描述"),
|
||||||
_buildTableHeaderCell("操作"),
|
_buildTableHeaderCell("操作"),
|
||||||
],
|
]),
|
||||||
),
|
if (hiddenList.isEmpty)
|
||||||
...hiddenList.map((item) => TableRow(
|
TableRow(children: [Padding(padding: EdgeInsets.all(12), child: Text("暂无数据")), SizedBox()])
|
||||||
children: [
|
else
|
||||||
Padding(
|
...hiddenList.map((item) {
|
||||||
padding: EdgeInsets.all(8),
|
final mi = (item is Map) ? item as Map<String, dynamic> : Map<String, dynamic>.from(item);
|
||||||
child: Text(item["HIDDENDESCR"]),
|
return TableRow(children: [
|
||||||
),
|
Padding(padding: EdgeInsets.all(8), child: Text(mi["HIDDENDESCR"]?.toString() ?? "")),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => _goToDetail(item["HIDDEN_ID"]),
|
onTap: () => _goToDetail(mi["HIDDEN_ID"]?.toString() ?? ''),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
child: Center(
|
child: Center(child: Text("查看", style: TextStyle(color: Colors.blue))),
|
||||||
child: Text(
|
|
||||||
"查看",
|
|
||||||
style: TextStyle(color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
]);
|
||||||
),
|
}).toList(),
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTableHeaderCell(String text) {
|
Widget _buildTableHeaderCell(String text) {
|
||||||
|
return Padding(padding: EdgeInsets.all(8), child: Center(child: Text(text, style: TextStyle(fontWeight: FontWeight.bold))));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMapSection() {
|
||||||
|
// 给 webview 一个固定高度(与 uniapp 的 300px 对齐)
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||||
child: Center(
|
child: Container(
|
||||||
child: Text(
|
height: 300,
|
||||||
text,
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.grey[300]!)),
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
child: _loadingMap
|
||||||
|
? Center(child: CircularProgressIndicator())
|
||||||
|
: BaiduMapWebView(
|
||||||
|
ak: Platform.isIOS ? 'g3lZyqt0KkFnZGUsjIVO7U6lTCfpjSCt' : '43G1sKuHV6oRTrdR9VTIGPF9soej7V5a',
|
||||||
|
latitude: centerLat,
|
||||||
|
longitude: centerLng,
|
||||||
|
zoom: 13,
|
||||||
|
covers: covers,
|
||||||
|
onMarkerTap: (data) {
|
||||||
|
debugPrint('marker click: $data');
|
||||||
|
// 如果 data 中包含 item 或 id,可用于跳转
|
||||||
|
final type = data['type'] ?? '';
|
||||||
|
if (type == 'varList' && data['item'] != null) {
|
||||||
|
// 示例:打开对应检查项详情
|
||||||
|
final item = data['item'];
|
||||||
|
if (item is Map && item['HIDDEN_ID'] != null) {
|
||||||
|
_goToDetail(item['HIDDEN_ID'].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget _buildMapSection() {
|
|
||||||
// return Container(
|
|
||||||
// height: 300,
|
|
||||||
// margin: EdgeInsets.symmetric(vertical: 10),
|
|
||||||
// child: GoogleMap(
|
|
||||||
// initialCameraPosition: CameraPosition(
|
|
||||||
// target: _center,
|
|
||||||
// zoom: _scale,
|
|
||||||
// ),
|
|
||||||
// markers: _markers,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Widget _buildInfoList() {
|
Widget _buildInfoList() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 15),
|
margin: EdgeInsets.symmetric(horizontal: 15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||||
border: Border.all(color: Colors.grey[300]!),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow("清单名称", pd["LIST_NAME"]?? ''),
|
_buildInfoRow("清单名称", pd["LIST_NAME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("排查清单类型", pd["SCREENTYPENAME"]?? ''),
|
_buildInfoRow("排查清单类型", pd["SCREENTYPENAME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("检查人", pd["USERS"]?? ''),
|
_buildInfoRow("检查人", pd["USERS"]?.toString() ?? ''),
|
||||||
_buildInfoRow("检查时间", pd["CHECK_TIME"]?? ''),
|
_buildInfoRow("检查时间", pd["CHECK_TIME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("所属部门", pd["DEPARTMENT_NAME"]?? ''),
|
_buildInfoRow("所属部门", pd["DEPARTMENT_NAME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("所属岗位", pd["POST_NAME"]?? ''),
|
_buildInfoRow("所属岗位", pd["POST_NAME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("排查周期", pd["PERIODNAME"]?? ''),
|
_buildInfoRow("排查周期", pd["PERIODNAME"]?.toString() ?? ''),
|
||||||
_buildInfoRow("清单类型", pd["TYPENAME"]?? ''),
|
_buildInfoRow("清单类型", pd["TYPENAME"]?.toString() ?? ''),
|
||||||
if (pd["TYPENAME"] == "临时")
|
if (pd["TYPENAME"]?.toString() == "临时") _buildInfoRow("排查日期", "${pd["START_DATE"] ?? ''} - ${pd["END_DATE"] ?? ''}"),
|
||||||
_buildInfoRow("排查日期", "${pd["START_DATE"]} - ${pd["END_DATE"]}"),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -372,16 +427,11 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
Widget _buildInfoRow(String title, String value) {
|
Widget _buildInfoRow(String title, String value) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 15),
|
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey[300]!))),
|
||||||
border: Border(bottom: BorderSide(color: Colors.grey[300]!)),
|
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(title, style: TextStyle(fontSize: 14)),
|
Text(title, style: TextStyle(fontSize: 14)),
|
||||||
Text(value, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
Text(value, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
||||||
],
|
]),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,44 +442,20 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||||
text: "签字照片",
|
text: "签字照片",
|
||||||
imageUrls: imageList,
|
imageUrls: imageList,
|
||||||
onImageTapped: (index) {
|
onImageTapped: (index) {
|
||||||
presentOpaque(
|
presentOpaque(SingleImageViewer(imageUrl: ApiService.baseImgPath + imageList[index]), context);
|
||||||
SingleImageViewer(imageUrl:ApiService.baseImgPath + imageList[index]),
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToDetail(String hiddenId) {
|
void _goToDetail(String hiddenId) {
|
||||||
dynamic item={"HIDDEN_RISKSTANDARD":"2","HIDDEN_ID":hiddenId};
|
if (hiddenId.isEmpty) return;
|
||||||
|
final dynamic item = {"HIDDEN_RISKSTANDARD": "2", "HIDDEN_ID": hiddenId};
|
||||||
pushPage(HiddenRecordDetailPage(DangerType.detailsHiddenInvestigationRecord, item), context);
|
pushPage(HiddenRecordDetailPage(DangerType.detailsHiddenInvestigationRecord, item), context);
|
||||||
// Navigator.push(
|
|
||||||
// context,
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => Scaffold(
|
|
||||||
// appBar: AppBar(title: Text("隐患详情")),
|
|
||||||
// body: Center(child: Text("隐患ID: $hiddenId")),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToImgs(String recordItemId) {
|
void _goToImgs(String recordItemId) {
|
||||||
pushPage(DetailImagesPage(recordItemId), context);
|
if (recordItemId == null) return;
|
||||||
// Navigator.push(
|
pushPage(DetailImagesPage(recordItemId.toString()), context);
|
||||||
// context,
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => Scaffold(
|
|
||||||
// appBar: AppBar(title: Text("图片详情")),
|
|
||||||
// body: Center(child: Text("记录项ID: $recordItemId")),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import 'package:qhd_prevention/customWidget/department_person_picker.dart';
|
||||||
import 'package:qhd_prevention/customWidget/department_picker.dart';
|
import 'package:qhd_prevention/customWidget/department_picker.dart';
|
||||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
import 'package:qhd_prevention/http/ApiService.dart';
|
import 'package:qhd_prevention/http/ApiService.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
import '../../customWidget/ItemWidgetFactory.dart';
|
import '../../customWidget/ItemWidgetFactory.dart';
|
||||||
import '../../customWidget/custom_button.dart';
|
import '../../customWidget/custom_button.dart';
|
||||||
import '../../customWidget/date_picker_dialog.dart';
|
import '../../customWidget/date_picker_dialog.dart';
|
||||||
|
@ -189,6 +190,7 @@ class DannerRepairState extends State<DannerRepair> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
ItemListWidget.itemContainer(
|
||||||
RepairedPhotoSection(
|
RepairedPhotoSection(
|
||||||
title: "整改后照片",
|
title: "整改后照片",
|
||||||
maxCount: 4,
|
maxCount: 4,
|
||||||
|
@ -204,6 +206,7 @@ class DannerRepairState extends State<DannerRepair> {
|
||||||
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
Divider(),
|
Divider(),
|
||||||
Row(
|
Row(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:qhd_prevention/customWidget/big_video_viewer.dart';
|
import 'package:qhd_prevention/customWidget/big_video_viewer.dart';
|
||||||
|
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
|
||||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
@ -94,12 +95,9 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
||||||
|
|
||||||
// 处理图片和视频
|
// 处理图片和视频
|
||||||
for (var img in data['hImgs']) {
|
for (var img in data['hImgs']) {
|
||||||
if (img['FILEPATH'].toString().endsWith('.mp4')) {
|
|
||||||
videoList.add(img);
|
|
||||||
} else {
|
|
||||||
files.add(img["FILEPATH"]);
|
files.add(img["FILEPATH"]);
|
||||||
}
|
}
|
||||||
}
|
videoList = data['hiddenVideo'] ?? [];
|
||||||
|
|
||||||
// List<dynamic> filesZheng = data['rImgs'] ?? [];
|
// List<dynamic> filesZheng = data['rImgs'] ?? [];
|
||||||
for (var img in data['rImgs']) {
|
for (var img in data['rImgs']) {
|
||||||
|
@ -197,6 +195,10 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ItemListWidget.itemContainer(
|
||||||
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoItem('隐患描述', pd['HIDDENDESCR'] ?? ''),
|
_buildInfoItem('隐患描述', pd['HIDDENDESCR'] ?? ''),
|
||||||
|
@ -250,7 +252,6 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
// 隐患视频
|
// 隐患视频
|
||||||
if (videoList.isNotEmpty) ...[
|
if (videoList.isNotEmpty) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
@ -277,6 +278,9 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
SizedBox(height: 10,),
|
SizedBox(height: 10,),
|
||||||
// 整改信息部分
|
// 整改信息部分
|
||||||
|
|
|
@ -6,7 +6,9 @@ import 'package:qhd_prevention/tools/tools.dart';
|
||||||
/// 单例角标管理,多接口分模块更新及全局原生角标同步
|
/// 单例角标管理,多接口分模块更新及全局原生角标同步
|
||||||
class BadgeManager extends ChangeNotifier {
|
class BadgeManager extends ChangeNotifier {
|
||||||
BadgeManager._internal();
|
BadgeManager._internal();
|
||||||
|
|
||||||
static final BadgeManager _instance = BadgeManager._internal();
|
static final BadgeManager _instance = BadgeManager._internal();
|
||||||
|
|
||||||
factory BadgeManager() => _instance;
|
factory BadgeManager() => _instance;
|
||||||
|
|
||||||
// 各模块未读
|
// 各模块未读
|
||||||
|
@ -19,14 +21,20 @@ class BadgeManager extends ChangeNotifier {
|
||||||
|
|
||||||
/// 总未读角标数
|
/// 总未读角标数
|
||||||
int get count => _appCount + _notifCount + _envInspectCount + _eightWorkCount;
|
int get count => _appCount + _notifCount + _envInspectCount + _eightWorkCount;
|
||||||
|
|
||||||
/// MainPage 整体“应用”角标
|
/// MainPage 整体“应用”角标
|
||||||
int get appCount => _appCount;
|
int get appCount => _appCount;
|
||||||
|
|
||||||
/// MainPage “待验收”子项
|
/// MainPage “待验收”子项
|
||||||
int get appDysCount => _appDysCount;
|
int get appDysCount => _appDysCount;
|
||||||
|
|
||||||
/// MainPage “待整改”子项
|
/// MainPage “待整改”子项
|
||||||
int get appDzgCount => _appDzgCount;
|
int get appDzgCount => _appDzgCount;
|
||||||
|
|
||||||
int get notifCount => _notifCount;
|
int get notifCount => _notifCount;
|
||||||
|
|
||||||
int get envInspectCount => _envInspectCount;
|
int get envInspectCount => _envInspectCount;
|
||||||
|
|
||||||
int get eightWorkCount => _eightWorkCount;
|
int get eightWorkCount => _eightWorkCount;
|
||||||
|
|
||||||
/// 登录后或有用户上下文时,初始化所有接口汇总数,并同步原生角标
|
/// 登录后或有用户上下文时,初始化所有接口汇总数,并同步原生角标
|
||||||
|
@ -53,14 +61,14 @@ class BadgeManager extends ChangeNotifier {
|
||||||
// 安全巡检部分
|
// 安全巡检部分
|
||||||
final checkJson = results[2];
|
final checkJson = results[2];
|
||||||
_envInspectCount =
|
_envInspectCount =
|
||||||
(checkJson['confirmCount']?['confirmCount'] ?? 0)
|
checkJson['repulseCount']?['repulseCount'] ?? 0;
|
||||||
(checkJson['repulseCount']?['repulseCount'] ?? 0)
|
|
||||||
(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0) as int;
|
|
||||||
|
|
||||||
// 八项作业部分
|
// 八项作业部分
|
||||||
final redPointJson = results[3];
|
final redPointJson = results[3];
|
||||||
_eightWorkCount = 0;
|
_eightWorkCount = 0;
|
||||||
(redPointJson['count'] as Map<String, dynamic>? ?? {}).values.forEach((v) {
|
(redPointJson['count'] as Map<String, dynamic>? ?? {}).values.forEach((
|
||||||
|
v,
|
||||||
|
) {
|
||||||
_eightWorkCount += (v ?? 0) as int;
|
_eightWorkCount += (v ?? 0) as int;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,11 +107,13 @@ class BadgeManager extends ChangeNotifier {
|
||||||
/// 更新 HomePage 安全巡检角标
|
/// 更新 HomePage 安全巡检角标
|
||||||
void updateEnvInspectCount() async {
|
void updateEnvInspectCount() async {
|
||||||
try {
|
try {
|
||||||
final checkJson = await ApiService.getSafetyEnvironmentalInspectionCount();
|
final checkJson =
|
||||||
|
await ApiService.getSafetyEnvironmentalInspectionCount();
|
||||||
_envInspectCount =
|
_envInspectCount =
|
||||||
(checkJson['confirmCount']?['confirmCount'] ?? 0)
|
(checkJson['confirmCount']?['confirmCount'] ?? 0)(
|
||||||
(checkJson['repulseCount']?['repulseCount'] ?? 0)
|
checkJson['repulseCount']?['repulseCount'] ?? 0,
|
||||||
(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0) as int;
|
)(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0)
|
||||||
|
as int;
|
||||||
_onModuleChanged();
|
_onModuleChanged();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('updateEnvInspectCount error: $e');
|
debugPrint('updateEnvInspectCount error: $e');
|
||||||
|
@ -115,7 +125,9 @@ class BadgeManager extends ChangeNotifier {
|
||||||
try {
|
try {
|
||||||
final redPointJson = await ApiService.getRedPoint();
|
final redPointJson = await ApiService.getRedPoint();
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
(redPointJson['count'] as Map<String, dynamic>? ?? {}).values.forEach((v) {
|
(redPointJson['count'] as Map<String, dynamic>? ?? {}).values.forEach((
|
||||||
|
v,
|
||||||
|
) {
|
||||||
sum += (v ?? 0) as int;
|
sum += (v ?? 0) as int;
|
||||||
});
|
});
|
||||||
_eightWorkCount = sum;
|
_eightWorkCount = sum;
|
||||||
|
|
|
@ -161,7 +161,7 @@ class _SafeCheckDrawerPageState extends State<SafeCheckDrawerPage> {
|
||||||
|
|
||||||
List<String> _getSelectedVideos() {
|
List<String> _getSelectedVideos() {
|
||||||
return (hiddenForm['hiddenVideos'] as List<Map<String, dynamic>>)
|
return (hiddenForm['hiddenVideos'] as List<Map<String, dynamic>>)
|
||||||
.map((e) => '${e['FILEPATH']}')
|
.map((e) => '${ApiService.baseImgPath}${e['FILEPATH']}')
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,6 @@ class _HomeDangerPageState extends State<HomeDangerPage>
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
int _selectedTab = 0;
|
int _selectedTab = 0;
|
||||||
|
|
||||||
// // 模拟数据
|
|
||||||
// final List<NotificationItem> _notifications = List.generate(10, (i) {
|
|
||||||
// bool read = i % 3 == 0;
|
|
||||||
// String title = '测试数据标题标题 ${i + 1}';
|
|
||||||
// String time = '2025-06-${10 + i} 12:3${i}';
|
|
||||||
// return NotificationItem(title, time);
|
|
||||||
// });
|
|
||||||
|
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
String appBarTitle="";
|
String appBarTitle="";
|
||||||
|
@ -41,9 +33,6 @@ class _HomeDangerPageState extends State<HomeDangerPage>
|
||||||
String searchName="";
|
String searchName="";
|
||||||
bool showBottomTags=true;
|
bool showBottomTags=true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<dynamic> listOne = [];
|
List<dynamic> listOne = [];
|
||||||
List<dynamic> listTwo = [];
|
List<dynamic> listTwo = [];
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
|
import 'package:qhd_prevention/tools/auth_service.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||||
import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
|
import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
|
||||||
import 'package:qhd_prevention/pages/badge_manager.dart';
|
import 'package:qhd_prevention/pages/badge_manager.dart';
|
||||||
import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
|
import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart';
|
import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart';
|
||||||
import 'package:qhd_prevention/pages/home/home_danger_page.dart';
|
import 'package:qhd_prevention/pages/home/home_danger_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/low_page.dart';
|
|
||||||
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
|
import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/study/study_garden_page.dart';
|
import 'package:qhd_prevention/pages/home/study/study_garden_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_list_page.dart';
|
import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/userInfo_page.dart';
|
import 'package:qhd_prevention/pages/home/userInfo_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/work/danger_page.dart';
|
|
||||||
import 'package:qhd_prevention/pages/app/danger_wait_list_page.dart';
|
import 'package:qhd_prevention/pages/app/danger_wait_list_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/work/laws_regulations_page.dart';
|
import 'package:qhd_prevention/pages/home/work/laws_regulations_page.dart';
|
||||||
import 'package:qhd_prevention/pages/home/workSet_page.dart';
|
import 'package:qhd_prevention/pages/home/workSet_page.dart';
|
||||||
|
@ -34,6 +37,10 @@ class HomePage extends StatefulWidget {
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
int _eight_work_count = 0;
|
int _eight_work_count = 0;
|
||||||
int _safetyEnvironmentalInspection = 0;
|
int _safetyEnvironmentalInspection = 0;
|
||||||
|
|
||||||
|
// 缓存 key
|
||||||
|
static const String _hiddenCacheKey = 'hidden_roll_cache';
|
||||||
|
|
||||||
String _workKey(int idx) {
|
String _workKey(int idx) {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -50,35 +57,107 @@ class _HomePageState extends State<HomePage> {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按钮信息
|
/// 按钮信息
|
||||||
List<Map<String, dynamic>> buttonInfos = [
|
List<Map<String, dynamic>> buttonInfos = [
|
||||||
{"icon": "assets/icon-apps/home-base.png", "title": "人员信息", "unreadCount": 0},
|
{
|
||||||
{"icon": "assets/icon-apps/home-rili.png", "title": "工作安排", "unreadCount": 0},
|
"icon": "assets/icon-apps/home-base.png",
|
||||||
{"icon": "assets/icon-apps/home-risk.png", "title": "风险布控", "unreadCount": 0},
|
"title": "人员信息",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/home-rili.png",
|
||||||
|
"title": "工作安排",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/home-risk.png",
|
||||||
|
"title": "风险布控",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
{"icon": "assets/icon-apps/home-fl.png", "title": "法律法规", "unreadCount": 0},
|
{"icon": "assets/icon-apps/home-fl.png", "title": "法律法规", "unreadCount": 0},
|
||||||
{"icon": "assets/icon-apps/home-gw.png", "title": "特殊作业", "unreadCount": 0},
|
{"icon": "assets/icon-apps/home-gw.png", "title": "特殊作业", "unreadCount": 0},
|
||||||
{"icon": "assets/icon-apps/home-zdgcgl.jpg", "title": "重点工程管理", "unreadCount": 0},
|
{
|
||||||
{"icon": "assets/icon-apps/home-cns.png", "title": "安全承诺", "unreadCount": 0},
|
"icon": "assets/icon-apps/home-zdgcgl.jpg",
|
||||||
{"icon": "assets/icon-apps/home-study.png", "title": "学习园地", "unreadCount": 0},
|
"title": "重点工程管理",
|
||||||
{"icon": "assets/icon-apps/home-base.png", "title": "安全检查", "unreadCount": 0},
|
"unreadCount": 0,
|
||||||
{"icon": "assets/icon-apps/home-speEquip.jpg", "title": "设备巡检", "unreadCount": 0},
|
},
|
||||||
{"icon": "assets/icon-apps/safetymeeting.png", "title": "安全例会", "unreadCount": 0},
|
{
|
||||||
{"icon": "assets/icon-apps/home-xj.png", "title": "燃气巡检", "unreadCount": 0},
|
"icon": "assets/icon-apps/home-cns.png",
|
||||||
|
"title": "安全承诺",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/home-study.png",
|
||||||
|
"title": "学习园地",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/home-base.png",
|
||||||
|
"title": "安全检查",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/home-speEquip.jpg",
|
||||||
|
"title": "设备巡检",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/safetymeeting.png",
|
||||||
|
"title": "安全例会",
|
||||||
|
"unreadCount": 0,
|
||||||
|
},
|
||||||
|
// {"icon": "assets/icon-apps/home-xj.png", "title": "燃气巡检", "unreadCount": 0},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 我的工作
|
/// 我的工作
|
||||||
List<Map<String, dynamic>> workInfos = [
|
List<Map<String, dynamic>> workInfos = [
|
||||||
{"icon": "assets/icon-apps/jobico1.png", "index": 1, "detail": "待排查", "num": '0'},
|
{
|
||||||
{"icon": "assets/icon-apps/jobico2.png", "index": 2, "detail": "待整改", "num": '0'},
|
"icon": "assets/icon-apps/jobico1.png",
|
||||||
{"icon": "assets/icon-apps/jobico3.png", "index": 3, "detail": "已超期", "num": '0'},
|
"index": 1,
|
||||||
{"icon": "assets/icon-apps/jobico4.png", "index": 4, "detail": "待验收", "num": '0'},
|
"detail": "待排查",
|
||||||
{"icon": "assets/icon-apps/jobico5.png", "index": 5, "detail": "已验收", "num": '0'},
|
"num": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/jobico2.png",
|
||||||
|
"index": 2,
|
||||||
|
"detail": "待整改",
|
||||||
|
"num": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/jobico3.png",
|
||||||
|
"index": 3,
|
||||||
|
"detail": "已超期",
|
||||||
|
"num": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/jobico4.png",
|
||||||
|
"index": 4,
|
||||||
|
"detail": "待验收",
|
||||||
|
"num": '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "assets/icon-apps/jobico5.png",
|
||||||
|
"index": 5,
|
||||||
|
"detail": "已验收",
|
||||||
|
"num": '0',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 排查数据
|
/// 排查数据
|
||||||
List<Map<String, dynamic>> pcData = [
|
List<Map<String, dynamic>> pcData = [
|
||||||
{"index": 1, "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'}},
|
{
|
||||||
{"index": 2, "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'}},
|
"index": 1,
|
||||||
{"index": 3, "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'}},
|
"detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 隐患播报列表及状态
|
/// 隐患播报列表及状态
|
||||||
|
@ -94,31 +173,109 @@ class _HomePageState extends State<HomePage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// 使用初始化加载:先恢复缓存(若存在则直接显示),然后再发起网络请求(成功则覆盖缓存)
|
||||||
|
_initialLoad();
|
||||||
|
BadgeManager().initAllModules();
|
||||||
|
}
|
||||||
|
|
||||||
_onRefresh();
|
/// 首次加载:先恢复缓存(如果有),然后在后台去刷新(只有当无缓存时才显示 loading)
|
||||||
|
Future<void> _initialLoad() async {
|
||||||
|
final result = await AuthService.checkUpdate();
|
||||||
|
if (FormUtils.hasValue(result, 'pd')) {
|
||||||
|
// 有更新 提示更新
|
||||||
|
Map pd = result['pd'];
|
||||||
|
CustomAlertDialog.showAlert(
|
||||||
|
context,
|
||||||
|
title: '更新通知',
|
||||||
|
content: pd['UPLOAD_CONTENT'] ?? '',
|
||||||
|
onConfirm: () {
|
||||||
|
ToastUtil.showNormal(context, '更新去吧!');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _loadHiddenCache();
|
||||||
|
// 拉取其他数据 + 隐患列表(当 hiddenList 为空时显示 loading,否则不显示)
|
||||||
|
await _fetchData();
|
||||||
|
await _fetchHiddenList(showLoading: hiddenList.isEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onRefresh() async {
|
Future<void> _onRefresh() async {
|
||||||
await BadgeManager().initAllModules();
|
await BadgeManager().initAllModules();
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_fetchData(),
|
_fetchData(),
|
||||||
|
// 下拉刷新时不展示 loading(因为用户主动触发),但仍会更新缓存(成功时)
|
||||||
_fetchHiddenList(showLoading: false),
|
_fetchHiddenList(showLoading: false),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 从 SharedPreferences 读取缓存并设置 hiddenList(如果存在)
|
||||||
Future<void> _fetchHiddenList({bool showLoading = true}) async {
|
Future<void> _loadHiddenCache() async {
|
||||||
// 保留已有列表,仅首次显示loading
|
|
||||||
if (showLoading && hiddenList.isEmpty) setState(() {});
|
|
||||||
try {
|
try {
|
||||||
final res = await ApiService.getHiddenRoll().timeout(Duration(seconds: 10));
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final jsonStr = prefs.getString(_hiddenCacheKey);
|
||||||
|
if (jsonStr != null && jsonStr.isNotEmpty) {
|
||||||
|
final parsed = jsonDecode(jsonStr) as List<dynamic>;
|
||||||
|
final list =
|
||||||
|
parsed.map((e) => Map<String, dynamic>.from(e as Map)).toList();
|
||||||
|
setState(() {
|
||||||
|
hiddenList = list;
|
||||||
|
_initialLoadingHidden = false; // 有缓存则不显示首次loading
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 无缓存:保持 _initialLoadingHidden = true(只在需要显示 loading 时 setState)
|
||||||
|
setState(() {
|
||||||
|
_initialLoadingHidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('加载 hidden cache 失败: $e');
|
||||||
|
// 出错时认为无缓存
|
||||||
|
setState(() {
|
||||||
|
_initialLoadingHidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 保存 hiddenList 到 SharedPreferences(调用时确保传入最新 list)
|
||||||
|
Future<void> _saveHiddenCache(List<Map<String, dynamic>> list) async {
|
||||||
|
try {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final jsonStr = jsonEncode(list);
|
||||||
|
await prefs.setString(_hiddenCacheKey, jsonStr);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('保存 hidden cache 失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取隐患播报列表(带缓存/加载控制逻辑)
|
||||||
|
Future<void> _fetchHiddenList({bool showLoading = true}) async {
|
||||||
|
// 仅在需要展示 loading 且当前没有数据时 setState 显示 loading indicator
|
||||||
|
if (showLoading && hiddenList.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_initialLoadingHidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final res = await ApiService.getHiddenRoll().timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
);
|
||||||
final list = (res['hiddenList'] as List).cast<Map<String, dynamic>>();
|
final list = (res['hiddenList'] as List).cast<Map<String, dynamic>>();
|
||||||
|
|
||||||
|
// 请求成功:覆盖 UI 与缓存(始终覆盖)
|
||||||
setState(() {
|
setState(() {
|
||||||
hiddenList = list;
|
hiddenList = list;
|
||||||
_initialLoadingHidden = false;
|
_initialLoadingHidden = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 保存缓存(覆盖)
|
||||||
|
await _saveHiddenCache(list);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 超时或失败且已有数据,则不处理
|
debugPrint('fetchHiddenList error: $e');
|
||||||
|
|
||||||
|
// 请求失败:如果已有缓存(hiddenList 非空),就保留缓存(什么也不做)
|
||||||
|
// 如果没有缓存(首次且失败),需要把 loading 关掉(避免一直转圈)
|
||||||
if (hiddenList.isEmpty) {
|
if (hiddenList.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_initialLoadingHidden = false;
|
_initialLoadingHidden = false;
|
||||||
|
@ -150,7 +307,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
//更新页面状态
|
//更新页面状态
|
||||||
setState(() {
|
setState(() {
|
||||||
// ———— 更新 workInfos ————
|
// ———— 更新 workInfos ————
|
||||||
workInfos = workInfos.map((info) {
|
workInfos =
|
||||||
|
workInfos.map((info) {
|
||||||
final idx = info['index'] as int;
|
final idx = info['index'] as int;
|
||||||
final key = _workKey(idx);
|
final key = _workKey(idx);
|
||||||
final num = (hidCount[key] ?? 0).toString();
|
final num = (hidCount[key] ?? 0).toString();
|
||||||
|
@ -162,7 +320,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
_eight_work_count = BadgeManager().eightWorkCount;
|
_eight_work_count = BadgeManager().eightWorkCount;
|
||||||
|
|
||||||
// ———— 更新 buttonInfos 中两个角标位置 ————
|
// ———— 更新 buttonInfos 中两个角标位置 ————
|
||||||
buttonInfos = buttonInfos.map((info) {
|
buttonInfos =
|
||||||
|
buttonInfos.map((info) {
|
||||||
switch (info['title'] as String) {
|
switch (info['title'] as String) {
|
||||||
case '特殊作业':
|
case '特殊作业':
|
||||||
info['unreadCount'] = _eight_work_count;
|
info['unreadCount'] = _eight_work_count;
|
||||||
|
@ -178,11 +337,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
// ———— 更新排查数据 pcData ————
|
// ———— 更新排查数据 pcData ————
|
||||||
pcData = [
|
pcData =
|
||||||
myData,
|
[myData, deptData, superDeptData].asMap().entries.map((entry) {
|
||||||
deptData,
|
|
||||||
superDeptData,
|
|
||||||
].asMap().entries.map((entry) {
|
|
||||||
final idx = entry.key;
|
final idx = entry.key;
|
||||||
final pdMap = (entry.value['pd'] as Map<String, dynamic>?) ?? {};
|
final pdMap = (entry.value['pd'] as Map<String, dynamic>?) ?? {};
|
||||||
return {
|
return {
|
||||||
|
@ -200,7 +356,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -223,19 +378,18 @@ class _HomePageState extends State<HomePage> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListItemFactory.createBuildSimpleSection("隐患播报"),
|
ListItemFactory.createBuildSimpleSection("隐患播报"),
|
||||||
_initialLoadingHidden && hiddenList.isEmpty
|
hiddenList.isEmpty
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: 150,
|
height: 150,
|
||||||
child: Center(child: CircularProgressIndicator()),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
)
|
)
|
||||||
: HiddenRollWidget(hiddenList: hiddenList),
|
: HiddenRollWidget(hiddenList: hiddenList),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
_buildPCDataSection(),
|
_buildPCDataSection(),
|
||||||
|
@ -266,7 +420,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
runSpacing: spacing,
|
runSpacing: spacing,
|
||||||
children: workInfos.map((info) {
|
children:
|
||||||
|
workInfos.map((info) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: itemWidth,
|
width: itemWidth,
|
||||||
child: _buildGridItem(
|
child: _buildGridItem(
|
||||||
|
@ -303,7 +458,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _itemWidth(BuildContext context) => (MediaQuery.of(context).size.width - 20) / 4;
|
double _itemWidth(BuildContext context) =>
|
||||||
|
(MediaQuery.of(context).size.width - 20) / 4;
|
||||||
|
|
||||||
Widget _buildIconSection(BuildContext context) {
|
Widget _buildIconSection(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
@ -314,7 +470,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
),
|
),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
runSpacing: 16,
|
runSpacing: 16,
|
||||||
children: buttonInfos.asMap().entries.map((entry) {
|
children:
|
||||||
|
buttonInfos.asMap().entries.map((entry) {
|
||||||
final idx = entry.key;
|
final idx = entry.key;
|
||||||
final info = entry.value;
|
final info = entry.value;
|
||||||
return _buildIconButton(
|
return _buildIconButton(
|
||||||
|
@ -402,16 +559,29 @@ class _HomePageState extends State<HomePage> {
|
||||||
right: 20,
|
right: 20,
|
||||||
top: -5,
|
top: -5,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
padding: const EdgeInsets.symmetric(
|
||||||
decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(10)),
|
horizontal: 4,
|
||||||
constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 16,
|
||||||
|
minHeight: 16,
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
unreadCount > 99 ? '99+' : '$unreadCount',
|
unreadCount > 99 ? '99+' : '$unreadCount',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 11, height: 1),
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 11,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -437,9 +607,19 @@ class _HomePageState extends State<HomePage> {
|
||||||
for (var i = 0; i < 3; i++)
|
for (var i = 0; i < 3; i++)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Text(values[i], style: TextStyle(color: colors[i], fontSize: 18, fontWeight: FontWeight.bold)),
|
Text(
|
||||||
|
values[i],
|
||||||
|
style: TextStyle(
|
||||||
|
color: colors[i],
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(labels[i], style: TextStyle(color: Colors.black38, fontSize: 15)),
|
Text(
|
||||||
|
labels[i],
|
||||||
|
style: TextStyle(color: Colors.black38, fontSize: 15),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -449,8 +629,14 @@ class _HomePageState extends State<HomePage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(tagLabels[idx][0], style: TextStyle(color: Colors.white, fontSize: 12)),
|
Text(
|
||||||
Text(tagLabels[idx][1], style: TextStyle(color: Colors.white, fontSize: 12)),
|
tagLabels[idx][0],
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
tagLabels[idx][1],
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -463,7 +649,13 @@ class _HomePageState extends State<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGridItem(BuildContext context, {required String icon, required String title, required String subtitle, required int index}) {
|
Widget _buildGridItem(
|
||||||
|
BuildContext context, {
|
||||||
|
required String icon,
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required int index,
|
||||||
|
}) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (index == 1) {
|
if (index == 1) {
|
||||||
|
@ -480,7 +672,10 @@ class _HomePageState extends State<HomePage> {
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(color: const Color(0xfbf9f9ff), borderRadius: BorderRadius.circular(6)),
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xfbf9f9ff),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(icon, width: 35, height: 35),
|
Image.asset(icon, width: 35, height: 35),
|
||||||
|
@ -489,9 +684,22 @@ class _HomePageState extends State<HomePage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis),
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(subtitle, style: const TextStyle(fontSize: 14, color: Colors.black), maxLines: 1, overflow: TextOverflow.ellipsis),
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: const TextStyle(fontSize: 14, color: Colors.black),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:qhd_prevention/tools/StorageService.dart';
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:qhd_prevention/tools/auth_service.dart';
|
||||||
import '../http/ApiService.dart';
|
|
||||||
import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey;
|
|
||||||
import '../http/HttpManager.dart';
|
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
import 'main_tab.dart';
|
import 'main_tab.dart';
|
||||||
|
|
||||||
|
@ -45,14 +37,44 @@ class LoginPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
final TextEditingController _phoneController = TextEditingController(text: '13293211008');
|
final TextEditingController _phoneController = TextEditingController(
|
||||||
final TextEditingController _passwordController = TextEditingController(text: 'Zsaq@123456');
|
text: '13293211008',
|
||||||
|
);
|
||||||
|
final TextEditingController _passwordController = TextEditingController(
|
||||||
|
text: 'Zsaq@123456',
|
||||||
|
);
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
String _errorMessage = '';
|
String _errorMessage = '';
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
bool _agreed = false;
|
bool _agreed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
_checkUpdata();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -105,8 +127,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
hintText: "请输入手机号...",
|
hintText: "请输入手机号...",
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty)
|
if (value == null || value.isEmpty) return '请输入手机号';
|
||||||
return '请输入手机号';
|
|
||||||
// if (!RegExp(r'^1[3-9]\d{9}\$').hasMatch(value))
|
// if (!RegExp(r'^1[3-9]\d{9}\$').hasMatch(value))
|
||||||
// return '请输入有效的手机号';
|
// return '请输入有效的手机号';
|
||||||
return null;
|
return null;
|
||||||
|
@ -130,14 +151,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed:
|
||||||
() =>
|
() => setState(
|
||||||
setState(
|
|
||||||
() => _obscurePassword = !_obscurePassword,
|
() => _obscurePassword = !_obscurePassword,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty)
|
if (value == null || value.isEmpty) return '请输入密码';
|
||||||
return '请输入密码';
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -169,8 +188,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child:
|
child: const Text(
|
||||||
const Text(
|
|
||||||
'登录',
|
'登录',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
@ -294,26 +312,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleLogin() async {
|
Future<void> _handleLogin() async {
|
||||||
// 表单校验
|
|
||||||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
if (!(_formKey.currentState?.validate() ?? false)) return;
|
||||||
|
|
||||||
final userName = _phoneController.text.trim();
|
final userName = _phoneController.text.trim();
|
||||||
final userPwd = _passwordController.text;
|
final userPwd = _passwordController.text;
|
||||||
|
|
||||||
// RSA 加密:encrypt 包的用法
|
|
||||||
final parser = encrypt.RSAKeyParser();
|
|
||||||
final pub = parser.parse(ApiService.publicKey) as RSAPublicKey;
|
|
||||||
final encrypter = encrypt.Encrypter(encrypt.RSA(publicKey: pub));
|
|
||||||
final plain = 'zcloudchina$userName,zy,$userPwd';
|
|
||||||
|
|
||||||
String keydataVal;
|
|
||||||
try {
|
|
||||||
keydataVal = encrypter.encrypt(plain).base64;
|
|
||||||
} catch (e) {
|
|
||||||
Fluttertoast.showToast(msg: '加密失败:$e', toastLength: Toast.LENGTH_LONG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -322,68 +325,21 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final data = await ApiService.loginCheck(keydataVal);
|
final success = await AuthService.login(userName, userPwd);
|
||||||
final result = data['result'] as String? ?? '';
|
|
||||||
|
|
||||||
if (result == 'success') {
|
|
||||||
// 存储用户信息
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.setString('USER', json.encode(data));
|
|
||||||
await prefs.setStringList('remember', [userName, userPwd]);
|
|
||||||
|
|
||||||
SessionService.instance
|
|
||||||
..setLoginUserId(data['USER_ID'] as String)
|
|
||||||
..setCorpinfoId(data['CORPINFO_ID'] as String)
|
|
||||||
..setDeptId(data['DEPARTMENT_ID'] as String)
|
|
||||||
..setDeptLevel(data['DEPARTMENT_LEVEL'] as String)
|
|
||||||
..setIsRest(data['ISREST'] as String)
|
|
||||||
..setUsername(data['NAME'] as String)
|
|
||||||
..setLoginUser(data); // 这里 data 保存整个用户 JSON
|
|
||||||
|
|
||||||
final weak = data['WEAK_PASSWORD'] == '1';
|
|
||||||
final longTerm = data['LONG_TERM_PASSWORD_NOT_CHANGED'] == '1';
|
|
||||||
|
|
||||||
Navigator.of(context).pop(); // 关loading
|
Navigator.of(context).pop(); // 关loading
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
|
|
||||||
if (weak) {
|
if (success) {
|
||||||
// uni.redirectTo({
|
|
||||||
// url: '/pages/login/forget/forget-reset?canBack=1'
|
|
||||||
// });
|
|
||||||
} else if (longTerm) {
|
|
||||||
// uni.redirectTo({
|
|
||||||
// url: '/pages/login/forget/forget-reset?canBack=2'
|
|
||||||
// });
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
// 理论上不会走这里,非 'success' 会抛 ApiException
|
|
||||||
}
|
|
||||||
|
|
||||||
} on ApiException catch (e) {
|
|
||||||
// 业务错误:
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
|
|
||||||
String tip = e.message;
|
|
||||||
|
|
||||||
Fluttertoast.showToast(msg: tip, toastLength: Toast.LENGTH_LONG);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 网络或其它未预期错误
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(msg: '登录失败: $e');
|
||||||
msg: '服务器正在升级,请稍后再试。\n${e.toString()}',
|
|
||||||
toastLength: Toast.LENGTH_LONG,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -90,20 +90,20 @@ class _MainPageState extends State<MainPage> {
|
||||||
? null
|
? null
|
||||||
: MyAppbar(
|
: MyAppbar(
|
||||||
title:
|
title:
|
||||||
_currentIndex == 0 ? "泰盛安全首页" : _titles[_currentIndex],
|
_currentIndex == 0 ? "智守安全首页" : _titles[_currentIndex],
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
isBack: false,
|
isBack: false,
|
||||||
actions: [
|
actions: [
|
||||||
if (_currentIndex == 0) ...[
|
if (_currentIndex == 0) ...[
|
||||||
IconButton(
|
// IconButton(
|
||||||
onPressed: () => Navigator.push(
|
// onPressed: () => Navigator.push(
|
||||||
context,
|
// context,
|
||||||
MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
builder: (_) => NfcTestPage()),
|
// builder: (_) => NfcTestPage()),
|
||||||
),
|
// ),
|
||||||
icon: Image.asset("assets/images/ai_img.png",
|
// icon: Image.asset("assets/images/ai_img.png",
|
||||||
width: 20, height: 20),
|
// width: 20, height: 20),
|
||||||
),
|
// ),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Navigator.push(
|
onPressed: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -1,21 +1,47 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||||
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||||
import 'package:qhd_prevention/pages/home/study/face_ecognition_page.dart';
|
import 'package:qhd_prevention/pages/home/study/face_ecognition_page.dart';
|
||||||
import 'package:qhd_prevention/pages/login_page.dart';
|
import 'package:qhd_prevention/pages/login_page.dart';
|
||||||
import 'package:qhd_prevention/pages/mine/mine_first_sign_page.dart';
|
import 'package:qhd_prevention/pages/mine/mine_first_sign_page.dart';
|
||||||
import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
|
import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
|
||||||
import 'package:qhd_prevention/pages/mine/mine_sign_page.dart';
|
|
||||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||||
|
import 'package:qhd_prevention/tools/auth_service.dart';
|
||||||
import 'package:qhd_prevention/tools/h_colors.dart';
|
import 'package:qhd_prevention/tools/h_colors.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
class MineSetPage extends StatelessWidget {
|
class MineSetPage extends StatefulWidget {
|
||||||
const MineSetPage({super.key});
|
const MineSetPage({super.key});
|
||||||
|
|
||||||
Future<void> _clearUserSession() async {
|
@override
|
||||||
final prefs = await SharedPreferences.getInstance();
|
State<MineSetPage> createState() => _MineSetPageState();
|
||||||
await prefs.remove('isLoggedIn'); // 清除登录状态
|
}
|
||||||
|
|
||||||
|
class _MineSetPageState extends State<MineSetPage> {
|
||||||
|
|
||||||
|
/// 检查更新
|
||||||
|
Future<void> _checkUpdate() async {
|
||||||
|
LoadingDialogHelper.show();
|
||||||
|
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, '更新去吧!');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
ToastUtil.showNormal(context, '已经是最新版本!');
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,68 +54,63 @@ class MineSetPage extends StatelessWidget {
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: _setItemWidget("修改密码"),
|
child: _setItemWidget("修改密码"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pushPage(MineSetPwdPage(), context);
|
pushPage(const MineSetPwdPage(), context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Divider(height: 1, color: Colors.black12),
|
const Divider(height: 1, color: Colors.black12),
|
||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: _setItemWidget("更新人脸信息"),
|
child: _setItemWidget("更新人脸信息"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pushPage(FaceRecognitionPage(studentId: '', mode: FaceMode.manual,), context);
|
pushPage(
|
||||||
|
const FaceRecognitionPage(studentId: '', mode: FaceMode.manual),
|
||||||
|
context,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const Divider(height: 1, color: Colors.black12),
|
||||||
|
|
||||||
Divider(height: 1, color: Colors.black12),
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: _setItemWidget("更新签字信息"),
|
child: _setItemWidget("更新签字信息"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pushPage(FirstSignPage(), context);
|
pushPage(const FirstSignPage(), context);
|
||||||
// pushPage(MineSignPage(), context);
|
// pushPage(MineSignPage(), context);
|
||||||
}
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1, color: Colors.black12),
|
||||||
|
|
||||||
|
GestureDetector(
|
||||||
|
child: _setItemWidget("检查更新"),
|
||||||
|
onTap: () {
|
||||||
|
_checkUpdate();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
Divider(height: 1, color: Colors.black12),
|
const SizedBox(height: 15),
|
||||||
GestureDetector(child: _setItemWidget("检查更新"), onTap: () {}),
|
|
||||||
|
|
||||||
SizedBox(height: 15),
|
|
||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: 15),
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Center(child: Text("退出当前账户", style: TextStyle(fontSize: 16),)),
|
child: const Center(
|
||||||
|
child: Text("退出当前账户", style: TextStyle(fontSize: 16)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// 显示确认对话框
|
CustomAlertDialog.showConfirm(
|
||||||
bool? confirm = await showDialog(
|
context,
|
||||||
context: context,
|
title: '确认退出',
|
||||||
builder: (context) => AlertDialog(
|
content: '确定要退出当前账号吗',
|
||||||
title: Text("确认退出"),
|
onConfirm: () async {
|
||||||
content: Text("确定要退出当前账号吗?"),
|
await AuthService.logout(); // ✅ 等待登出完成
|
||||||
actions: [
|
if (!mounted) return;
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, false),
|
|
||||||
child: Text("取消"),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, true),
|
|
||||||
child: Text("确定", style: TextStyle(color: Colors.red)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirm == true) {
|
|
||||||
// 清除用户登录状态
|
|
||||||
await _clearUserSession();
|
|
||||||
|
|
||||||
// 跳转到登录页并清除所有历史路由
|
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => LoginPage()),
|
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||||
(Route<dynamic> route) => false, // 移除所有历史路由
|
(Route<dynamic> route) => false,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -102,12 +123,12 @@ class MineSetPage extends StatelessWidget {
|
||||||
height: 55,
|
height: 55,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(text, style: TextStyle(fontSize: 16)),
|
Text(text, style: const TextStyle(fontSize: 16)),
|
||||||
Icon(Icons.chevron_right),
|
const Icon(Icons.chevron_right),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey;
|
||||||
|
import 'package:qhd_prevention/tools/tools.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
|
import '../http/ApiService.dart';
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
static const _keyUser = 'USER';
|
||||||
|
static const _keyRemember = 'remember';
|
||||||
|
static const _keyIsLoggedIn = 'isLoggedIn';
|
||||||
|
|
||||||
|
/// 登录
|
||||||
|
static Future<bool> login(String username, String password) async {
|
||||||
|
final parser = encrypt.RSAKeyParser();
|
||||||
|
final pub = parser.parse(ApiService.publicKey) as RSAPublicKey;
|
||||||
|
final encrypter = encrypt.Encrypter(encrypt.RSA(publicKey: pub));
|
||||||
|
|
||||||
|
final plain = 'zcloudchina$username,zy,$password';
|
||||||
|
|
||||||
|
String encrypted;
|
||||||
|
try {
|
||||||
|
encrypted = encrypter.encrypt(plain).base64;
|
||||||
|
} catch (e) {
|
||||||
|
Fluttertoast.showToast(msg: '加密失败:$e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = await ApiService.loginCheck(encrypted);
|
||||||
|
final result = data['result'] as String? ?? '';
|
||||||
|
if (result != 'success') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(_keyUser, json.encode(data));
|
||||||
|
await prefs.setStringList(_keyRemember, [username, password]);
|
||||||
|
await prefs.setBool(_keyIsLoggedIn, true);
|
||||||
|
|
||||||
|
SessionService.instance
|
||||||
|
..setLoginUserId(data['USER_ID'] as String)
|
||||||
|
..setCorpinfoId(data['CORPINFO_ID'] as String)
|
||||||
|
..setDeptId(data['DEPARTMENT_ID'] as String)
|
||||||
|
..setDeptLevel(data['DEPARTMENT_LEVEL'] as String)
|
||||||
|
..setIsRest(data['ISREST'] as String)
|
||||||
|
..setUsername(data['NAME'] as String)
|
||||||
|
..setLoginUser(data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/// 检查更新
|
||||||
|
static Future<Map<String, dynamic>> checkUpdate() async {
|
||||||
|
final data = await ApiService.getUpdateInfo();
|
||||||
|
final result = data['result'] as String? ?? '';
|
||||||
|
if (result == 'success') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// 验证是否已登录(通过重新登录)
|
||||||
|
static Future<bool> isLoggedIn() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final isLocalLoggedIn = prefs.getBool(_keyIsLoggedIn) ?? false;
|
||||||
|
if (!isLocalLoggedIn) return false;
|
||||||
|
|
||||||
|
final remember = prefs.getStringList(_keyRemember);
|
||||||
|
if (remember == null || remember.length < 2) return false;
|
||||||
|
|
||||||
|
final username = remember[0];
|
||||||
|
final password = remember[1];
|
||||||
|
|
||||||
|
// 用存储的账号密码重新登录
|
||||||
|
final success = await login(username, password);
|
||||||
|
if (!success) {
|
||||||
|
await logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 获取已保存的用户信息
|
||||||
|
static Future<Map<String, dynamic>?> getUser() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final jsonStr = prefs.getString(_keyUser);
|
||||||
|
if (jsonStr == null) return null;
|
||||||
|
return json.decode(jsonStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登出
|
||||||
|
static Future<void> logout() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove(_keyUser);
|
||||||
|
await prefs.remove(_keyRemember);
|
||||||
|
await prefs.setBool(_keyIsLoggedIn, false);
|
||||||
|
}
|
||||||
|
}
|
324
pubspec.lock
324
pubspec.lock
File diff suppressed because it is too large
Load Diff
|
@ -79,6 +79,11 @@ dependencies:
|
||||||
flutter_new_badger: ^1.1.1
|
flutter_new_badger: ^1.1.1
|
||||||
#loading
|
#loading
|
||||||
flutter_easyloading: ^3.0.5
|
flutter_easyloading: ^3.0.5
|
||||||
|
#视频播放器
|
||||||
|
chewie: ^1.12.1
|
||||||
|
#百度地图
|
||||||
|
flutter_baidu_mapapi_base: ^3.9.5
|
||||||
|
flutter_baidu_mapapi_map: ^3.9.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue