第一版
parent
037a00967f
commit
421d71f7c9
|
@ -4,7 +4,7 @@ PODS:
|
|||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_badger (1.3.0):
|
||||
- flutter_new_badger (0.0.1):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
|
@ -36,6 +36,8 @@ PODS:
|
|||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
@ -44,7 +46,7 @@ DEPENDENCIES:
|
|||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- 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`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- 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`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- 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`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
|
@ -66,8 +69,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_app_badger:
|
||||
:path: ".symlinks/plugins/flutter_app_badger/ios"
|
||||
flutter_new_badger:
|
||||
:path: ".symlinks/plugins/flutter_new_badger/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
geolocator_apple:
|
||||
|
@ -92,6 +95,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
|
@ -99,7 +104,7 @@ SPEC CHECKSUMS:
|
|||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_app_badger: 16b371e989d04cd265df85be2c3851b49cb68d18
|
||||
flutter_new_badger: 133aaf93e9a5542bf905c8483d8b83c5ef4946ea
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
|
@ -112,6 +117,7 @@ SPEC CHECKSUMS:
|
|||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
|
||||
|
||||
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 confirmText = '确定',
|
||||
bool barrierDismissible = true,
|
||||
final VoidCallback? onConfirm,
|
||||
|
||||
}) async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
|
@ -52,6 +54,7 @@ class CustomAlertDialog extends StatefulWidget {
|
|||
cancelText: cancelText,
|
||||
confirmText: confirmText,
|
||||
mode: DialogMode.text,
|
||||
onConfirm: onConfirm,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -65,6 +68,8 @@ class CustomAlertDialog extends StatefulWidget {
|
|||
String content = '',
|
||||
String confirmText = '确定',
|
||||
bool barrierDismissible = true,
|
||||
final VoidCallback? onConfirm,
|
||||
|
||||
}) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
|
@ -76,6 +81,8 @@ class CustomAlertDialog extends StatefulWidget {
|
|||
cancelText: '', // 隐藏取消按钮使其成为单按钮
|
||||
confirmText: confirmText,
|
||||
mode: DialogMode.text,
|
||||
onConfirm: onConfirm,
|
||||
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,118 +1,49 @@
|
|||
|
||||
import 'package:flutter/material.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 {
|
||||
/// 视频地址
|
||||
final String videoUrl;
|
||||
const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<VideoPlayerPopup> createState() => _VideoPlayerPopupState();
|
||||
|
||||
}
|
||||
|
||||
class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
||||
late VideoPlayerController _controller;
|
||||
bool _showControls = true;
|
||||
late VideoPlayerController _videoController;
|
||||
ChewieController? _chewieController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = VideoPlayerController.networkUrl(
|
||||
Uri.parse(widget.videoUrl),
|
||||
)..initialize().then((_) {
|
||||
_videoController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl))
|
||||
..initialize().then((_) {
|
||||
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
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_chewieController?.dispose();
|
||||
_videoController.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
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
|
@ -121,20 +52,18 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
|||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
maxHeight: 500,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// 视频内容
|
||||
if (_controller.value.isInitialized)
|
||||
AspectRatio(
|
||||
aspectRatio: _controller.value.aspectRatio,
|
||||
child: VideoPlayer(_controller),
|
||||
)
|
||||
// 视频播放器
|
||||
if (_chewieController != null &&
|
||||
_videoController.value.isInitialized)
|
||||
Chewie(controller: _chewieController!)
|
||||
else
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
|
||||
|
@ -143,13 +72,10 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
|
|||
top: 4,
|
||||
right: 4,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.black54),
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
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 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_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()
|
||||
: [];
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// 改动点:不要把网络地址换成 File(''),保持 File(path)(path 可能是本地也可能是 http)
|
||||
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 {
|
||||
if (!widget.isEdit) return; // 不可编辑时直接返回
|
||||
|
||||
|
@ -140,7 +120,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
if (picked != null) {
|
||||
final path = picked.path;
|
||||
setState(() => _mediaPaths.add(path));
|
||||
// 这里本来就是 File(p)
|
||||
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
@ -406,4 +372,4 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -194,6 +194,20 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
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年隐患
|
||||
static Future<Map<String, dynamic>> getDanger(int type) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.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 './pages/login_page.dart';
|
||||
import './pages/main_tab.dart';
|
||||
|
@ -27,65 +28,69 @@ class GlobalMessage {
|
|||
}
|
||||
|
||||
void main() async {
|
||||
// 确保Flutter引擎初始化完成
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 初始化 EasyLoading
|
||||
// 配置 EasyLoading 全局实例
|
||||
EasyLoading.instance
|
||||
..displayDuration = const Duration(seconds: 20)
|
||||
..indicatorType = EasyLoadingIndicatorType.ring // 使用环形指示器
|
||||
..loadingStyle = EasyLoadingStyle.custom // 必须使用自定义样式
|
||||
..indicatorSize = 36.0 // 接近系统默认大小
|
||||
..radius = 0 // 无圆角
|
||||
..progressColor = Colors.blue // 进度条颜色为系统蓝色
|
||||
..indicatorType = EasyLoadingIndicatorType.ring
|
||||
..loadingStyle = EasyLoadingStyle.custom
|
||||
..indicatorSize = 36.0
|
||||
..radius = 0
|
||||
..progressColor = Colors.blue
|
||||
..backgroundColor = Colors.grey.shade300
|
||||
..indicatorColor = Colors.blue // 指示器颜色为系统蓝色
|
||||
..textColor = Colors.black // 隐藏文字
|
||||
..userInteractions = false // 允许用户交互(系统指示器不阻止交互)
|
||||
..indicatorColor = Colors.blue
|
||||
..textColor = Colors.black
|
||||
..userInteractions = false
|
||||
..dismissOnTap = false;
|
||||
|
||||
await initializeDateFormatting('zh_CN', null);
|
||||
// 获取共享首选项实例
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// 获取登录状态
|
||||
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
|
||||
|
||||
// 初始化HTTP管理器
|
||||
// 初始化HTTP管理器未授权回调
|
||||
HttpManager.onUnauthorized = () async {
|
||||
// 清除登录状态
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isLoggedIn', false);
|
||||
await prefs.remove('token'); // 如果有token的话
|
||||
|
||||
// 导航到登录页
|
||||
await prefs.remove('token');
|
||||
navigatorKey.currentState?.pushNamedAndRemoveUntil(
|
||||
'/login',
|
||||
(route) => false,
|
||||
);
|
||||
|
||||
// 等一帧让页面构建完成
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
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));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
final bool isLoggedIn;
|
||||
|
||||
MyApp({super.key, required this.isLoggedIn});
|
||||
const MyApp({super.key, required this.isLoggedIn});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '',
|
||||
navigatorKey: navigatorKey, // 设置全局导航键
|
||||
navigatorKey: navigatorKey,
|
||||
builder: (context, child) {
|
||||
// 修复:正确集成 EasyLoading 和 GestureDetector
|
||||
return EasyLoading.init(
|
||||
builder: (context, widget) {
|
||||
return GestureDetector(
|
||||
|
@ -101,13 +106,12 @@ class MyApp extends StatelessWidget {
|
|||
theme: ThemeData(
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: Colors.black12,
|
||||
thickness: .5, // 线高
|
||||
indent: 0, // 左缩进
|
||||
endIndent: 0, // 右缩进
|
||||
thickness: .5,
|
||||
indent: 0,
|
||||
endIndent: 0,
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
// 统一设置页面背景颜色
|
||||
scaffoldBackgroundColor: const Color(0xFFF1F1F1), // 浅灰色背景
|
||||
scaffoldBackgroundColor: const Color(0xFFF1F1F1),
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: InputBorder.none,
|
||||
|
@ -120,16 +124,14 @@ class MyApp extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||
color: Colors.blue, // 统一颜色
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
// 根据登录状态决定初始页面
|
||||
home: isLoggedIn ? const MainPage() : const LoginPage(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: {
|
||||
'/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_gallery.dart';
|
||||
import 'package:qhd_prevention/customWidget/BaiDuMap/BaiduMapWebView.dart';
|
||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
|
@ -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/tools/tools.dart';
|
||||
|
||||
|
||||
|
||||
class CheckRecordDetailPage extends StatefulWidget {
|
||||
const CheckRecordDetailPage(this.id, this.type, {super.key});
|
||||
|
||||
final int type;
|
||||
final String id;
|
||||
|
||||
@override
|
||||
_CheckRecordDetailPageState createState() => _CheckRecordDetailPageState();
|
||||
}
|
||||
|
||||
class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
||||
// 模拟数据
|
||||
// List<Map<String, dynamic>> varList = [
|
||||
// {"RISKPOINT_ID": "1", "CHECK_CONTENT": "安全设备检查", "ISNORMAL": "0", "IMGCOUNT": 2, "RECORDITEM_ID": "1001"},
|
||||
// {"RISKPOINT_ID": "2", "CHECK_CONTENT": "消防设施检查", "ISNORMAL": "1", "HIDDEN_ID": "2001"},
|
||||
// {"RISKPOINT_ID": "3", "CHECK_CONTENT": "电气线路检查", "ISNORMAL": "2"},
|
||||
// ];
|
||||
|
||||
// List<Map<String, dynamic>> otherHiddenList = [
|
||||
// {"HIDDEN_ID": "3001", "HIDDENDESCR": "应急通道堵塞"},
|
||||
// {"HIDDEN_ID": "3002", "HIDDENDESCR": "安全标识缺失"},
|
||||
// ];
|
||||
// map markers
|
||||
List<Map<String, dynamic>> covers = [];
|
||||
double centerLat = 39.8883;
|
||||
double centerLng = 119.519;
|
||||
|
||||
Map<String, dynamic> pd = {
|
||||
"LIST_NAME": "",
|
||||
|
@ -49,102 +45,201 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
|||
"END_DATE": "",
|
||||
};
|
||||
|
||||
List<String> imageList = [];
|
||||
List<dynamic> varList = [];
|
||||
List<dynamic> hiddenList = [];
|
||||
|
||||
|
||||
// 地图相关
|
||||
// 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<dynamic> varList =[];
|
||||
List<dynamic> hiddenList =[];
|
||||
bool _loadingDetail = true;
|
||||
bool _loadingDetailTwo = true;
|
||||
bool _loadingMap = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 添加地图标记
|
||||
// _markers.add(Marker(
|
||||
// markerId: MarkerId("checkpoint"),
|
||||
// position: _center,
|
||||
// icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
|
||||
// ));
|
||||
// 启动时并行请求,UI 使用 setState 更新
|
||||
_fetchAll();
|
||||
}
|
||||
|
||||
Future<void> _fetchAll() async {
|
||||
// 并行发请求,界面加载更快
|
||||
await Future.wait([
|
||||
_getInspectRecordsDetail(),
|
||||
_getInspectRecordsDetailTwo(),
|
||||
_getMapData(),
|
||||
]);
|
||||
}
|
||||
|
||||
_getInspectRecordsDetail();
|
||||
_getInspectRecordsDetailTwo();
|
||||
Future<void> _getMapData() async {
|
||||
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 {
|
||||
setState(() => _loadingDetail = true);
|
||||
try {
|
||||
|
||||
final Map<String, dynamic> result;
|
||||
if(widget.type==1){
|
||||
if (widget.type == 1) {
|
||||
result = await ApiService.getInspectRecordsDetailYin(widget.id);
|
||||
}else{
|
||||
} else {
|
||||
result = await ApiService.getInspectRecordsDetail(widget.id);
|
||||
}
|
||||
// final result = await ApiService.getInspectRecordsDetail(widget.id);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
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(() {
|
||||
pd= result['pd'];
|
||||
varList = result['varList'] ?? [];
|
||||
for(int i=0;i<qianmingList.length;i++){
|
||||
imageList.add(qianmingList[i]["FILEPATH"] as String);
|
||||
}
|
||||
|
||||
pd = (result['pd'] as Map<String, dynamic>?) ?? pd;
|
||||
varList = (result['varList'] as List<dynamic>?) ?? [];
|
||||
imageList = newImages;
|
||||
_loadingDetail = false;
|
||||
});
|
||||
|
||||
}else{
|
||||
} else {
|
||||
setState(() => _loadingDetail = false);
|
||||
ToastUtil.showNormal(context, "加载数据失败");
|
||||
// _showMessage('加载数据失败');
|
||||
}
|
||||
} catch (e) {
|
||||
// 出错时可以 Toast 或者在页面上显示错误状态
|
||||
print('加载数据失败:$e');
|
||||
} catch (e, st) {
|
||||
debugPrint('getInspectRecordsDetail error: $e\n$st');
|
||||
setState(() => _loadingDetail = false);
|
||||
ToastUtil.showNormal(context, "加载数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getInspectRecordsDetailTwo() async {
|
||||
setState(() => _loadingDetailTwo = true);
|
||||
try {
|
||||
|
||||
final Map<String, dynamic> result;
|
||||
if(widget.type==1){
|
||||
if (widget.type == 1) {
|
||||
result = await ApiService.getInspectRecordsDetailTwoYin(widget.id);
|
||||
}else{
|
||||
} else {
|
||||
result = await ApiService.getInspectRecordsDetailTwo(widget.id);
|
||||
}
|
||||
// final result = await ApiService.getInspectRecordsDetailTwo(widget.id);
|
||||
|
||||
if (result['result'] == 'success') {
|
||||
final List<dynamic> newList = result['hiddenList'] ?? [];
|
||||
final List<dynamic> newList = (result['hiddenList'] as List?) ?? [];
|
||||
setState(() {
|
||||
hiddenList.addAll(newList);
|
||||
|
||||
|
||||
hiddenList = newList;
|
||||
_loadingDetailTwo = false;
|
||||
});
|
||||
|
||||
}else{
|
||||
} else {
|
||||
setState(() => _loadingDetailTwo = false);
|
||||
ToastUtil.showNormal(context, "加载数据失败");
|
||||
// _showMessage('加载数据失败');
|
||||
}
|
||||
} catch (e) {
|
||||
// 出错时可以 Toast 或者在页面上显示错误状态
|
||||
print('加载数据失败:$e');
|
||||
} catch (e, st) {
|
||||
debugPrint('getInspectRecordsDetailTwo error: $e\n$st');
|
||||
setState(() => _loadingDetailTwo = false);
|
||||
ToastUtil.showNormal(context, "加载数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UI 构建
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: MyAppbar(title: "检查记录详情"),
|
||||
// _buildAppBar(),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -153,7 +248,7 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
|||
_buildCheckContentTable(),
|
||||
_buildSectionTitle("其他隐患信息"),
|
||||
_buildOtherHiddenTable(),
|
||||
// _buildMapSection(),
|
||||
_buildMapSection(), // 注意这里地图高度已固定
|
||||
_buildSectionTitle("清单信息"),
|
||||
_buildInfoList(),
|
||||
_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) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 16,
|
||||
color: Colors.blue,
|
||||
margin: EdgeInsets.only(right: 10),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Container(width: 4, 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() {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 15),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||
child: Table(
|
||||
border: TableBorder.all(color: Colors.grey[300]!),
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(3),
|
||||
1: FlexColumnWidth(1),
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: BoxDecoration(color: Colors.grey[100]),
|
||||
children: [
|
||||
_buildTableHeaderCell("检查内容"),
|
||||
_buildTableHeaderCell("状态"),
|
||||
],
|
||||
),
|
||||
...varList.map((item) => TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(item["CHECK_CONTENT"]),
|
||||
),
|
||||
_buildStatusCell(item)
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
border: TableBorder.all(color: Colors.grey[300]!),
|
||||
columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(1)},
|
||||
children: [
|
||||
TableRow(decoration: BoxDecoration(color: Colors.grey[100]), children: [
|
||||
_buildTableHeaderCell("检查内容"),
|
||||
_buildTableHeaderCell("状态"),
|
||||
]),
|
||||
// 若 varList 为空,显示空行或提示
|
||||
if (varList.isEmpty)
|
||||
TableRow(children: [
|
||||
Padding(padding: EdgeInsets.all(12), child: Text("暂无检查内容")),
|
||||
SizedBox(),
|
||||
])
|
||||
else
|
||||
...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) {
|
||||
// 兼容字符串/数字类型
|
||||
final isnormalStr = item["ISNORMAL"]?.toString() ?? '';
|
||||
final imgCount = int.tryParse(item["IMGCOUNT"]?.toString() ?? '') ?? 0;
|
||||
|
||||
String status;
|
||||
Color color = Colors.black;
|
||||
VoidCallback? onTap;
|
||||
|
||||
switch (item["ISNORMAL"]) {
|
||||
case 0:
|
||||
status = "合格";
|
||||
color = Colors.blue;
|
||||
if (item["IMGCOUNT"] > 0) {
|
||||
onTap = () => _goToImgs(item["RECORDITEM_ID"]);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
status = "不合格";
|
||||
color = Colors.blue;
|
||||
onTap = () => _goToDetail(item["HIDDEN_ID"]);
|
||||
break;
|
||||
case 2:
|
||||
status = "不涉及";
|
||||
color = Colors.blue;
|
||||
break;
|
||||
default:
|
||||
status = "存在未整改隐患";
|
||||
if (isnormalStr == '0') {
|
||||
status = "合格";
|
||||
color = imgCount > 0 ? Colors.blue : Colors.black;
|
||||
if (imgCount > 0) onTap = () => _goToImgs(item["RECORDITEM_ID"]?.toString() ?? '');
|
||||
} else if (isnormalStr == '1') {
|
||||
status = "不合格";
|
||||
color = Colors.blue;
|
||||
onTap = () => _goToDetail(item["HIDDEN_ID"]?.toString() ?? '');
|
||||
} else if (isnormalStr == '2') {
|
||||
status = "不涉及";
|
||||
color = Colors.blue;
|
||||
} else {
|
||||
status = "存在未整改隐患";
|
||||
color = Colors.black;
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
status,
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
child: Center(child: Text(status, style: TextStyle(color: color))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -276,94 +338,87 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
|||
Widget _buildOtherHiddenTable() {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||
child: Table(
|
||||
border: TableBorder.all(color: Colors.grey[300]!),
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(3),
|
||||
1: FlexColumnWidth(1),
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: BoxDecoration(color: Colors.grey[100]),
|
||||
children: [
|
||||
_buildTableHeaderCell("隐患描述"),
|
||||
_buildTableHeaderCell("操作"),
|
||||
],
|
||||
),
|
||||
...hiddenList.map((item) => TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(item["HIDDENDESCR"]),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => _goToDetail(item["HIDDEN_ID"]),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"查看",
|
||||
style: TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
border: TableBorder.all(color: Colors.grey[300]!),
|
||||
columnWidths: const {0: FlexColumnWidth(3), 1: FlexColumnWidth(1)},
|
||||
children: [
|
||||
TableRow(decoration: BoxDecoration(color: Colors.grey[100]), children: [
|
||||
_buildTableHeaderCell("隐患描述"),
|
||||
_buildTableHeaderCell("操作"),
|
||||
]),
|
||||
if (hiddenList.isEmpty)
|
||||
TableRow(children: [Padding(padding: EdgeInsets.all(12), child: Text("暂无数据")), SizedBox()])
|
||||
else
|
||||
...hiddenList.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["HIDDENDESCR"]?.toString() ?? "")),
|
||||
GestureDetector(
|
||||
onTap: () => _goToDetail(mi["HIDDEN_ID"]?.toString() ?? ''),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Center(child: Text("查看", style: TextStyle(color: Colors.blue))),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
child: Container(
|
||||
height: 300,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.grey[300]!)),
|
||||
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() {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 15),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4)),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoRow("清单名称", pd["LIST_NAME"]?? ''),
|
||||
_buildInfoRow("排查清单类型", pd["SCREENTYPENAME"]?? ''),
|
||||
_buildInfoRow("检查人", pd["USERS"]?? ''),
|
||||
_buildInfoRow("检查时间", pd["CHECK_TIME"]?? ''),
|
||||
_buildInfoRow("所属部门", pd["DEPARTMENT_NAME"]?? ''),
|
||||
_buildInfoRow("所属岗位", pd["POST_NAME"]?? ''),
|
||||
_buildInfoRow("排查周期", pd["PERIODNAME"]?? ''),
|
||||
_buildInfoRow("清单类型", pd["TYPENAME"]?? ''),
|
||||
if (pd["TYPENAME"] == "临时")
|
||||
_buildInfoRow("排查日期", "${pd["START_DATE"]} - ${pd["END_DATE"]}"),
|
||||
_buildInfoRow("清单名称", pd["LIST_NAME"]?.toString() ?? ''),
|
||||
_buildInfoRow("排查清单类型", pd["SCREENTYPENAME"]?.toString() ?? ''),
|
||||
_buildInfoRow("检查人", pd["USERS"]?.toString() ?? ''),
|
||||
_buildInfoRow("检查时间", pd["CHECK_TIME"]?.toString() ?? ''),
|
||||
_buildInfoRow("所属部门", pd["DEPARTMENT_NAME"]?.toString() ?? ''),
|
||||
_buildInfoRow("所属岗位", pd["POST_NAME"]?.toString() ?? ''),
|
||||
_buildInfoRow("排查周期", pd["PERIODNAME"]?.toString() ?? ''),
|
||||
_buildInfoRow("清单类型", pd["TYPENAME"]?.toString() ?? ''),
|
||||
if (pd["TYPENAME"]?.toString() == "临时") _buildInfoRow("排查日期", "${pd["START_DATE"] ?? ''} - ${pd["END_DATE"] ?? ''}"),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -372,16 +427,11 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
|||
Widget _buildInfoRow(String title, String value) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 15),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.grey[300]!)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 14)),
|
||||
Text(value, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
||||
],
|
||||
),
|
||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey[300]!))),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Text(title, style: TextStyle(fontSize: 14)),
|
||||
Text(value, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -392,44 +442,20 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
|
|||
text: "签字照片",
|
||||
imageUrls: imageList,
|
||||
onImageTapped: (index) {
|
||||
presentOpaque(
|
||||
SingleImageViewer(imageUrl:ApiService.baseImgPath + imageList[index]),
|
||||
context,
|
||||
);
|
||||
presentOpaque(SingleImageViewer(imageUrl: ApiService.baseImgPath + imageList[index]), context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _goToDetail(String hiddenId) {
|
||||
dynamic item={"HIDDEN_RISKSTANDARD":"2","HIDDEN_ID":hiddenId};
|
||||
pushPage(HiddenRecordDetailPage(DangerType.detailsHiddenInvestigationRecord,item), context);
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => Scaffold(
|
||||
// appBar: AppBar(title: Text("隐患详情")),
|
||||
// body: Center(child: Text("隐患ID: $hiddenId")),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
if (hiddenId.isEmpty) return;
|
||||
final dynamic item = {"HIDDEN_RISKSTANDARD": "2", "HIDDEN_ID": hiddenId};
|
||||
pushPage(HiddenRecordDetailPage(DangerType.detailsHiddenInvestigationRecord, item), context);
|
||||
}
|
||||
|
||||
void _goToImgs(String recordItemId) {
|
||||
pushPage(DetailImagesPage(recordItemId), context);
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => Scaffold(
|
||||
// appBar: AppBar(title: Text("图片详情")),
|
||||
// body: Center(child: Text("记录项ID: $recordItemId")),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
|
||||
if (recordItemId == null) return;
|
||||
pushPage(DetailImagesPage(recordItemId.toString()), context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/toast_util.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/custom_button.dart';
|
||||
import '../../customWidget/date_picker_dialog.dart';
|
||||
|
@ -189,20 +190,22 @@ class DannerRepairState extends State<DannerRepair> {
|
|||
),
|
||||
),
|
||||
Divider(),
|
||||
RepairedPhotoSection(
|
||||
title: "整改后照片",
|
||||
maxCount: 4,
|
||||
mediaType: MediaType.image,
|
||||
onChanged: (files) {
|
||||
// 上传 files 到服务器
|
||||
gaiHouImages.clear();
|
||||
for(int i=0;i<files.length;i++){
|
||||
gaiHouImages.add(files[i].path);
|
||||
}
|
||||
},
|
||||
onAiIdentify: () {
|
||||
ItemListWidget.itemContainer(
|
||||
RepairedPhotoSection(
|
||||
title: "整改后照片",
|
||||
maxCount: 4,
|
||||
mediaType: MediaType.image,
|
||||
onChanged: (files) {
|
||||
// 上传 files 到服务器
|
||||
gaiHouImages.clear();
|
||||
for(int i=0;i<files.length;i++){
|
||||
gaiHouImages.add(files[i].path);
|
||||
}
|
||||
},
|
||||
onAiIdentify: () {
|
||||
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
Divider(),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
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 'dart:convert';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
@ -94,12 +95,9 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
|||
|
||||
// 处理图片和视频
|
||||
for (var img in data['hImgs']) {
|
||||
if (img['FILEPATH'].toString().endsWith('.mp4')) {
|
||||
videoList.add(img);
|
||||
} else {
|
||||
files.add(img["FILEPATH"]);
|
||||
}
|
||||
}
|
||||
videoList = data['hiddenVideo'] ?? [];
|
||||
|
||||
// List<dynamic> filesZheng = data['rImgs'] ?? [];
|
||||
for (var img in data['rImgs']) {
|
||||
|
@ -199,85 +197,91 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem('隐患描述', pd['HIDDENDESCR'] ?? ''),
|
||||
ItemListWidget.itemContainer(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem('隐患描述', pd['HIDDENDESCR'] ?? ''),
|
||||
|
||||
Divider(height: 1),
|
||||
// 隐患来源
|
||||
_buildInfoItem('隐患来源', _getSourceText(pd['SOURCE'])),
|
||||
Divider(height: 1),
|
||||
// 条件渲染部分
|
||||
if (pd['SOURCE'] == '2') ...[
|
||||
_buildInfoItem('风险点(单元)', pd['RISK_UNIT'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('辨识部位', pd['IDENTIFICATION'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('存在风险', pd['RISK_DESCR'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('风险分级', pd['LEVEL'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('检查内容', pd['CHECK_CONTENT'] ?? ''),
|
||||
Divider(height: 1),
|
||||
],
|
||||
Divider(height: 1),
|
||||
// 隐患来源
|
||||
_buildInfoItem('隐患来源', _getSourceText(pd['SOURCE'])),
|
||||
Divider(height: 1),
|
||||
// 条件渲染部分
|
||||
if (pd['SOURCE'] == '2') ...[
|
||||
_buildInfoItem('风险点(单元)', pd['RISK_UNIT'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('辨识部位', pd['IDENTIFICATION'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('存在风险', pd['RISK_DESCR'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('风险分级', pd['LEVEL'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('检查内容', pd['CHECK_CONTENT'] ?? ''),
|
||||
Divider(height: 1),
|
||||
],
|
||||
|
||||
_buildInfoItem('隐患部位', pd['HIDDENPART'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('发现人', pd['CREATORNAME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('发现时间', pd['CREATTIME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('隐患部位', pd['HIDDENPART'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('发现人', pd['CREATORNAME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('发现时间', pd['CREATTIME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
|
||||
if (pd['HIDDEN_CATEGORY']?.isNotEmpty == true)
|
||||
_buildInfoItem('隐患类别', pd['HIDDEN_CATEGORY_NAME'] ?? ''),
|
||||
if (pd['HIDDEN_CATEGORY']?.isNotEmpty == true)
|
||||
_buildInfoItem('隐患类别', pd['HIDDEN_CATEGORY_NAME'] ?? ''),
|
||||
|
||||
_buildInfoItem('隐患类型', pd['HIDDENTYPE_NAME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('整改类型', _getRectificationType(pd['RECTIFICATIONTYPE'])),
|
||||
_buildInfoItem('隐患类型', pd['HIDDENTYPE_NAME'] ?? ''),
|
||||
Divider(height: 1),
|
||||
_buildInfoItem('整改类型', _getRectificationType(pd['RECTIFICATIONTYPE'])),
|
||||
|
||||
if (pd['RECTIFICATIONTYPE'] == '2')
|
||||
_buildInfoItem('整改期限', pd['RECTIFICATIONDEADLINE'] ?? ''),
|
||||
Divider(height: 1),
|
||||
// 隐患照片
|
||||
// const Text('隐患照片', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
// _buildImageGrid(files, onTap: (index) => _showImageGallery(files, index)),
|
||||
ListItemFactory.createTextImageItem(
|
||||
text: "隐患照片",
|
||||
imageUrls: files,
|
||||
onImageTapped: (index) {
|
||||
presentOpaque(
|
||||
SingleImageViewer(imageUrl:ApiService.baseImgPath + files[index]),
|
||||
context,
|
||||
);
|
||||
},
|
||||
if (pd['RECTIFICATIONTYPE'] == '2')
|
||||
_buildInfoItem('整改期限', pd['RECTIFICATIONDEADLINE'] ?? ''),
|
||||
Divider(height: 1),
|
||||
// 隐患照片
|
||||
// const Text('隐患照片', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
// _buildImageGrid(files, onTap: (index) => _showImageGallery(files, index)),
|
||||
ListItemFactory.createTextImageItem(
|
||||
text: "隐患照片",
|
||||
imageUrls: files,
|
||||
onImageTapped: (index) {
|
||||
presentOpaque(
|
||||
SingleImageViewer(imageUrl:ApiService.baseImgPath + files[index]),
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// 隐患视频
|
||||
if (videoList.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text('隐患视频', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black54,
|
||||
builder: (_) => VideoPlayerPopup(videoUrl: ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
);
|
||||
// present(
|
||||
// BigVideoViewer(videoUrl:ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
// context,
|
||||
// );
|
||||
},
|
||||
// => _playVideo(ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
child: Image.asset(
|
||||
'assets/image/videostart.png', // 替换为你的视频占位图
|
||||
color: Colors.blue,
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
// 隐患视频
|
||||
if (videoList.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text('隐患视频', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black54,
|
||||
builder: (_) => VideoPlayerPopup(videoUrl: ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
);
|
||||
// present(
|
||||
// BigVideoViewer(videoUrl:ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
// context,
|
||||
// );
|
||||
},
|
||||
// => _playVideo(ApiService.baseImgPath + videoList[0]['FILEPATH']),
|
||||
child: Image.asset(
|
||||
'assets/image/videostart.png', // 替换为你的视频占位图
|
||||
color: Colors.blue,
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
SizedBox(height: 10,),
|
||||
// 整改信息部分
|
||||
// if (pd['STATE'] != null && int.parse(pd['STATE']) >= 2 && int.parse(pd['STATE']) <= 4) ...[
|
||||
|
|
|
@ -6,7 +6,9 @@ import 'package:qhd_prevention/tools/tools.dart';
|
|||
/// 单例角标管理,多接口分模块更新及全局原生角标同步
|
||||
class BadgeManager extends ChangeNotifier {
|
||||
BadgeManager._internal();
|
||||
|
||||
static final BadgeManager _instance = BadgeManager._internal();
|
||||
|
||||
factory BadgeManager() => _instance;
|
||||
|
||||
// 各模块未读
|
||||
|
@ -19,24 +21,30 @@ class BadgeManager extends ChangeNotifier {
|
|||
|
||||
/// 总未读角标数
|
||||
int get count => _appCount + _notifCount + _envInspectCount + _eightWorkCount;
|
||||
|
||||
/// MainPage 整体“应用”角标
|
||||
int get appCount => _appCount;
|
||||
|
||||
/// MainPage “待验收”子项
|
||||
int get appDysCount => _appDysCount;
|
||||
|
||||
/// MainPage “待整改”子项
|
||||
int get appDzgCount => _appDzgCount;
|
||||
|
||||
int get notifCount => _notifCount;
|
||||
|
||||
int get envInspectCount => _envInspectCount;
|
||||
|
||||
int get eightWorkCount => _eightWorkCount;
|
||||
|
||||
/// 登录后或有用户上下文时,初始化所有接口汇总数,并同步原生角标
|
||||
Future<void> initAllModules() async {
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
ApiService.getWork(), // 应用中心
|
||||
ApiService.getNotifRedPoint(), // 通知公告
|
||||
ApiService.getWork(), // 应用中心
|
||||
ApiService.getNotifRedPoint(), // 通知公告
|
||||
ApiService.getSafetyEnvironmentalInspectionCount(), // 安全巡检
|
||||
ApiService.getRedPoint(), // 八项作业
|
||||
ApiService.getRedPoint(), // 八项作业
|
||||
]);
|
||||
|
||||
// 应用中心部分
|
||||
|
@ -53,14 +61,14 @@ class BadgeManager extends ChangeNotifier {
|
|||
// 安全巡检部分
|
||||
final checkJson = results[2];
|
||||
_envInspectCount =
|
||||
(checkJson['confirmCount']?['confirmCount'] ?? 0)
|
||||
(checkJson['repulseCount']?['repulseCount'] ?? 0)
|
||||
(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0) as int;
|
||||
checkJson['repulseCount']?['repulseCount'] ?? 0;
|
||||
|
||||
// 八项作业部分
|
||||
final redPointJson = results[3];
|
||||
_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;
|
||||
});
|
||||
|
||||
|
@ -99,11 +107,13 @@ class BadgeManager extends ChangeNotifier {
|
|||
/// 更新 HomePage 安全巡检角标
|
||||
void updateEnvInspectCount() async {
|
||||
try {
|
||||
final checkJson = await ApiService.getSafetyEnvironmentalInspectionCount();
|
||||
final checkJson =
|
||||
await ApiService.getSafetyEnvironmentalInspectionCount();
|
||||
_envInspectCount =
|
||||
(checkJson['confirmCount']?['confirmCount'] ?? 0)
|
||||
(checkJson['repulseCount']?['repulseCount'] ?? 0)
|
||||
(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0) as int;
|
||||
(checkJson['confirmCount']?['confirmCount'] ?? 0)(
|
||||
checkJson['repulseCount']?['repulseCount'] ?? 0,
|
||||
)(checkJson['repulseAndCheckCount']?['repulseAndCheckCount'] ?? 0)
|
||||
as int;
|
||||
_onModuleChanged();
|
||||
} catch (e) {
|
||||
debugPrint('updateEnvInspectCount error: $e');
|
||||
|
@ -115,7 +125,9 @@ class BadgeManager extends ChangeNotifier {
|
|||
try {
|
||||
final redPointJson = await ApiService.getRedPoint();
|
||||
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;
|
||||
});
|
||||
_eightWorkCount = sum;
|
||||
|
|
|
@ -161,7 +161,7 @@ class _SafeCheckDrawerPageState extends State<SafeCheckDrawerPage> {
|
|||
|
||||
List<String> _getSelectedVideos() {
|
||||
return (hiddenForm['hiddenVideos'] as List<Map<String, dynamic>>)
|
||||
.map((e) => '${e['FILEPATH']}')
|
||||
.map((e) => '${ApiService.baseImgPath}${e['FILEPATH']}')
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,14 +25,6 @@ class _HomeDangerPageState extends State<HomeDangerPage>
|
|||
late TabController _tabController;
|
||||
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();
|
||||
|
||||
String appBarTitle="";
|
||||
|
@ -41,9 +33,6 @@ class _HomeDangerPageState extends State<HomeDangerPage>
|
|||
String searchName="";
|
||||
bool showBottomTags=true;
|
||||
|
||||
|
||||
|
||||
|
||||
List<dynamic> listOne = [];
|
||||
List<dynamic> listTwo = [];
|
||||
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||
import 'package:qhd_prevention/customWidget/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/pages/KeyProjects/keyProjects_tab_list.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/SafeCheck/safeCheck_tab_list.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/study/study_garden_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/work/danger_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/workSet_page.dart';
|
||||
|
@ -34,51 +37,127 @@ class HomePage extends StatefulWidget {
|
|||
class _HomePageState extends State<HomePage> {
|
||||
int _eight_work_count = 0;
|
||||
int _safetyEnvironmentalInspection = 0;
|
||||
|
||||
// 缓存 key
|
||||
static const String _hiddenCacheKey = 'hidden_roll_cache';
|
||||
|
||||
String _workKey(int idx) {
|
||||
switch (idx) {
|
||||
case 1:
|
||||
return 'dpc'; // 待排查
|
||||
return 'dpc'; // 待排查
|
||||
case 2:
|
||||
return 'dzg'; // 待整改
|
||||
return 'dzg'; // 待整改
|
||||
case 3:
|
||||
return 'ycq'; // 已超期
|
||||
return 'ycq'; // 已超期
|
||||
case 4:
|
||||
return 'dys'; // 待验收
|
||||
return 'dys'; // 待验收
|
||||
case 5:
|
||||
return 'yys'; // 已验收
|
||||
return 'yys'; // 已验收
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 按钮信息
|
||||
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-risk.png", "title": "风险布控", "unreadCount": 0},
|
||||
{
|
||||
"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-risk.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-zdgcgl.jpg", "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},
|
||||
{
|
||||
"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-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 = [
|
||||
{"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/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'},
|
||||
{
|
||||
"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/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 = [
|
||||
{"index": 1, "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'}},
|
||||
{
|
||||
"index": 1,
|
||||
"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
|
||||
void 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 {
|
||||
await BadgeManager().initAllModules();
|
||||
await Future.wait([
|
||||
_fetchData(),
|
||||
// 下拉刷新时不展示 loading(因为用户主动触发),但仍会更新缓存(成功时)
|
||||
_fetchHiddenList(showLoading: false),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _fetchHiddenList({bool showLoading = true}) async {
|
||||
// 保留已有列表,仅首次显示loading
|
||||
if (showLoading && hiddenList.isEmpty) setState(() {});
|
||||
/// 从 SharedPreferences 读取缓存并设置 hiddenList(如果存在)
|
||||
Future<void> _loadHiddenCache() async {
|
||||
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>>();
|
||||
|
||||
// 请求成功:覆盖 UI 与缓存(始终覆盖)
|
||||
setState(() {
|
||||
hiddenList = list;
|
||||
_initialLoadingHidden = false;
|
||||
});
|
||||
|
||||
// 保存缓存(覆盖)
|
||||
await _saveHiddenCache(list);
|
||||
} catch (e) {
|
||||
// 超时或失败且已有数据,则不处理
|
||||
debugPrint('fetchHiddenList error: $e');
|
||||
|
||||
// 请求失败:如果已有缓存(hiddenList 非空),就保留缓存(什么也不做)
|
||||
// 如果没有缓存(首次且失败),需要把 loading 关掉(避免一直转圈)
|
||||
if (hiddenList.isEmpty) {
|
||||
setState(() {
|
||||
_initialLoadingHidden = false;
|
||||
|
@ -143,64 +300,62 @@ class _HomePageState extends State<HomePage> {
|
|||
ApiService.getDeptData(),
|
||||
ApiService.getSuperviseDeptData(),
|
||||
]);
|
||||
final myData = results[0];
|
||||
final deptData = results[1];
|
||||
final superDeptData= results[2];
|
||||
final myData = results[0];
|
||||
final deptData = results[1];
|
||||
final superDeptData = results[2];
|
||||
|
||||
//更新页面状态
|
||||
setState(() {
|
||||
// ———— 更新 workInfos ————
|
||||
workInfos = workInfos.map((info) {
|
||||
final idx = info['index'] as int;
|
||||
final key = _workKey(idx);
|
||||
final num = (hidCount[key] ?? 0).toString();
|
||||
return {...info, 'num': num};
|
||||
}).toList();
|
||||
workInfos =
|
||||
workInfos.map((info) {
|
||||
final idx = info['index'] as int;
|
||||
final key = _workKey(idx);
|
||||
final num = (hidCount[key] ?? 0).toString();
|
||||
return {...info, 'num': num};
|
||||
}).toList();
|
||||
|
||||
// ———— 从 BadgeManager 拿最新的本页角标 ————
|
||||
_safetyEnvironmentalInspection = BadgeManager().envInspectCount;
|
||||
_eight_work_count = BadgeManager().eightWorkCount;
|
||||
_eight_work_count = BadgeManager().eightWorkCount;
|
||||
|
||||
// ———— 更新 buttonInfos 中两个角标位置 ————
|
||||
buttonInfos = buttonInfos.map((info) {
|
||||
switch (info['title'] as String) {
|
||||
case '特殊作业':
|
||||
info['unreadCount'] = _eight_work_count;
|
||||
break;
|
||||
case '安全检查':
|
||||
info['unreadCount'] = _safetyEnvironmentalInspection;
|
||||
break;
|
||||
default:
|
||||
// 其它保持原样
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
}).toList();
|
||||
buttonInfos =
|
||||
buttonInfos.map((info) {
|
||||
switch (info['title'] as String) {
|
||||
case '特殊作业':
|
||||
info['unreadCount'] = _eight_work_count;
|
||||
break;
|
||||
case '安全检查':
|
||||
info['unreadCount'] = _safetyEnvironmentalInspection;
|
||||
break;
|
||||
default:
|
||||
// 其它保持原样
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
}).toList();
|
||||
|
||||
// ———— 更新排查数据 pcData ————
|
||||
pcData = [
|
||||
myData,
|
||||
deptData,
|
||||
superDeptData,
|
||||
].asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final pdMap = (entry.value['pd'] as Map<String, dynamic>? ) ?? {};
|
||||
return {
|
||||
'index': idx + 1,
|
||||
'detail': {
|
||||
'jiancha': pdMap['check_count']?.toString() ?? '0',
|
||||
'yinhuan': pdMap['hidden_count']?.toString() ?? '0',
|
||||
'yanshou': pdMap['rectify_count']?.toString() ?? '0',
|
||||
},
|
||||
};
|
||||
}).toList();
|
||||
pcData =
|
||||
[myData, deptData, superDeptData].asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final pdMap = (entry.value['pd'] as Map<String, dynamic>?) ?? {};
|
||||
return {
|
||||
'index': idx + 1,
|
||||
'detail': {
|
||||
'jiancha': pdMap['check_count']?.toString() ?? '0',
|
||||
'yinhuan': pdMap['hidden_count']?.toString() ?? '0',
|
||||
'yanshou': pdMap['rectify_count']?.toString() ?? '0',
|
||||
},
|
||||
};
|
||||
}).toList();
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('加载首页数据失败:$e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -223,19 +378,18 @@ class _HomePageState extends State<HomePage> {
|
|||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.white,
|
||||
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ListItemFactory.createBuildSimpleSection("隐患播报"),
|
||||
_initialLoadingHidden && hiddenList.isEmpty
|
||||
hiddenList.isEmpty
|
||||
? SizedBox(
|
||||
height: 150,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
height: 150,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
: HiddenRollWidget(hiddenList: hiddenList),
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildPCDataSection(),
|
||||
|
@ -266,18 +420,19 @@ class _HomePageState extends State<HomePage> {
|
|||
return Wrap(
|
||||
spacing: spacing,
|
||||
runSpacing: spacing,
|
||||
children: workInfos.map((info) {
|
||||
return SizedBox(
|
||||
width: itemWidth,
|
||||
child: _buildGridItem(
|
||||
context,
|
||||
icon: info["icon"]!,
|
||||
title: info["num"]!,
|
||||
subtitle: info["detail"]!,
|
||||
index: info["index"]!,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
children:
|
||||
workInfos.map((info) {
|
||||
return SizedBox(
|
||||
width: itemWidth,
|
||||
child: _buildGridItem(
|
||||
context,
|
||||
icon: info["icon"]!,
|
||||
title: info["num"]!,
|
||||
subtitle: info["detail"]!,
|
||||
index: info["index"]!,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -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) {
|
||||
return Container(
|
||||
|
@ -314,58 +470,59 @@ class _HomePageState extends State<HomePage> {
|
|||
),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: buttonInfos.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final info = entry.value;
|
||||
return _buildIconButton(
|
||||
context: context,
|
||||
iconPath: info["icon"] as String,
|
||||
label: info["title"] as String,
|
||||
unreadCount: info["unreadCount"] as int,
|
||||
onPressed: () async {
|
||||
switch (idx) {
|
||||
case 0:// 人员信息
|
||||
pushPage(UserinfoPage(), context);
|
||||
break;
|
||||
case 1:// 法律法规
|
||||
pushPage(WorkSetPage(), context);
|
||||
break;
|
||||
case 2:// 风险布控
|
||||
pushPage(RiskControlPage(), context);
|
||||
break;
|
||||
case 3:// 法律法规
|
||||
pushPage(LawsRegulationsPage(), context);
|
||||
break;
|
||||
case 4:// 八项作业
|
||||
await pushPage(WorkTabListPage(), context);
|
||||
break;
|
||||
case 5: // 重点工程管理
|
||||
await pushPage(KeyprojectsTabList(), context);
|
||||
break;
|
||||
case 6://安全承诺
|
||||
pushPage(SafetyCommitmentPage(), context);
|
||||
break;
|
||||
case 7:
|
||||
pushPage(StudyGardenPage(), context);
|
||||
break;
|
||||
case 9:
|
||||
pushPage(EquipmentInspectionListPage(), context);
|
||||
break;
|
||||
case 8: // 安全检查
|
||||
await pushPage(SafecheckTabList(), context);
|
||||
break;
|
||||
case 10:
|
||||
pushPage(SafetyMeetingListPage(), context);
|
||||
break;
|
||||
case 11:
|
||||
pushPage(HomeNfcListPage(), context);
|
||||
break;
|
||||
}
|
||||
children:
|
||||
buttonInfos.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final info = entry.value;
|
||||
return _buildIconButton(
|
||||
context: context,
|
||||
iconPath: info["icon"] as String,
|
||||
label: info["title"] as String,
|
||||
unreadCount: info["unreadCount"] as int,
|
||||
onPressed: () async {
|
||||
switch (idx) {
|
||||
case 0: // 人员信息
|
||||
pushPage(UserinfoPage(), context);
|
||||
break;
|
||||
case 1: // 法律法规
|
||||
pushPage(WorkSetPage(), context);
|
||||
break;
|
||||
case 2: // 风险布控
|
||||
pushPage(RiskControlPage(), context);
|
||||
break;
|
||||
case 3: // 法律法规
|
||||
pushPage(LawsRegulationsPage(), context);
|
||||
break;
|
||||
case 4: // 八项作业
|
||||
await pushPage(WorkTabListPage(), context);
|
||||
break;
|
||||
case 5: // 重点工程管理
|
||||
await pushPage(KeyprojectsTabList(), context);
|
||||
break;
|
||||
case 6: //安全承诺
|
||||
pushPage(SafetyCommitmentPage(), context);
|
||||
break;
|
||||
case 7:
|
||||
pushPage(StudyGardenPage(), context);
|
||||
break;
|
||||
case 9:
|
||||
pushPage(EquipmentInspectionListPage(), context);
|
||||
break;
|
||||
case 8: // 安全检查
|
||||
await pushPage(SafecheckTabList(), context);
|
||||
break;
|
||||
case 10:
|
||||
pushPage(SafetyMeetingListPage(), context);
|
||||
break;
|
||||
case 11:
|
||||
pushPage(HomeNfcListPage(), context);
|
||||
break;
|
||||
}
|
||||
|
||||
_onRefresh();
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
_onRefresh();
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -402,16 +559,29 @@ class _HomePageState extends State<HomePage> {
|
|||
right: 20,
|
||||
top: -5,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(10)),
|
||||
constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 16,
|
||||
minHeight: 16,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
child: Text(
|
||||
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,
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -437,9 +607,19 @@ class _HomePageState extends State<HomePage> {
|
|||
for (var i = 0; i < 3; i++)
|
||||
Column(
|
||||
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),
|
||||
Text(labels[i], style: TextStyle(color: Colors.black38, fontSize: 15)),
|
||||
Text(
|
||||
labels[i],
|
||||
style: TextStyle(color: Colors.black38, fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
|
@ -449,8 +629,14 @@ class _HomePageState extends State<HomePage> {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(tagLabels[idx][0], style: TextStyle(color: Colors.white, fontSize: 12)),
|
||||
Text(tagLabels[idx][1], style: TextStyle(color: Colors.white, fontSize: 12)),
|
||||
Text(
|
||||
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(
|
||||
onTap: () {
|
||||
if (index == 1) {
|
||||
|
@ -480,7 +672,10 @@ class _HomePageState extends State<HomePage> {
|
|||
},
|
||||
child: Container(
|
||||
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(
|
||||
children: [
|
||||
Image.asset(icon, width: 35, height: 35),
|
||||
|
@ -489,9 +684,22 @@ class _HomePageState extends State<HomePage> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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),
|
||||
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 'dart:convert';
|
||||
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:qhd_prevention/tools/StorageService.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../http/ApiService.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey;
|
||||
import '../http/HttpManager.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
import 'package:qhd_prevention/tools/auth_service.dart';
|
||||
import '../tools/tools.dart';
|
||||
import 'main_tab.dart';
|
||||
|
||||
|
@ -45,14 +37,44 @@ class LoginPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final TextEditingController _phoneController = TextEditingController(text: '13293211008');
|
||||
final TextEditingController _passwordController = TextEditingController(text: 'Zsaq@123456');
|
||||
final TextEditingController _phoneController = TextEditingController(
|
||||
text: '13293211008',
|
||||
);
|
||||
final TextEditingController _passwordController = TextEditingController(
|
||||
text: 'Zsaq@123456',
|
||||
);
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
String _errorMessage = '';
|
||||
bool _isLoading = false;
|
||||
bool _obscurePassword = true;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -105,8 +127,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
hintText: "请输入手机号...",
|
||||
keyboardType: TextInputType.phone,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty)
|
||||
return '请输入手机号';
|
||||
if (value == null || value.isEmpty) return '请输入手机号';
|
||||
// if (!RegExp(r'^1[3-9]\d{9}\$').hasMatch(value))
|
||||
// return '请输入有效的手机号';
|
||||
return null;
|
||||
|
@ -130,14 +151,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||
color: Colors.white,
|
||||
),
|
||||
onPressed:
|
||||
() =>
|
||||
setState(
|
||||
() => _obscurePassword = !_obscurePassword,
|
||||
() => setState(
|
||||
() => _obscurePassword = !_obscurePassword,
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty)
|
||||
return '请输入密码';
|
||||
if (value == null || value.isEmpty) return '请输入密码';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
@ -162,15 +181,14 @@ class _LoginPageState extends State<LoginPage> {
|
|||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
(!_isLoading && _agreed) ? _handleLogin : null,
|
||||
(!_isLoading && _agreed) ? _handleLogin : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child:
|
||||
const Text(
|
||||
child: const Text(
|
||||
'登录',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
|
@ -210,10 +228,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
color: Color(0xFF0D1D8C),
|
||||
),
|
||||
recognizer:
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开用户协议
|
||||
},
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开用户协议
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: '和',
|
||||
|
@ -227,10 +245,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
color: Color(0xFF0D1D8C),
|
||||
),
|
||||
recognizer:
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开隐私政策
|
||||
},
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开隐私政策
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -294,26 +312,11 @@ class _LoginPageState extends State<LoginPage> {
|
|||
}
|
||||
|
||||
Future<void> _handleLogin() async {
|
||||
// 表单校验
|
||||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
||||
|
||||
final userName = _phoneController.text.trim();
|
||||
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);
|
||||
showDialog(
|
||||
context: context,
|
||||
|
@ -322,68 +325,21 @@ class _LoginPageState extends State<LoginPage> {
|
|||
);
|
||||
|
||||
try {
|
||||
final data = await ApiService.loginCheck(keydataVal);
|
||||
final result = data['result'] as String? ?? '';
|
||||
final success = await AuthService.login(userName, userPwd);
|
||||
|
||||
if (result == 'success') {
|
||||
// 存储用户信息
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('USER', json.encode(data));
|
||||
await prefs.setStringList('remember', [userName, userPwd]);
|
||||
Navigator.of(context).pop(); // 关loading
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
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
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (weak) {
|
||||
// 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(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 理论上不会走这里,非 'success' 会抛 ApiException
|
||||
if (success) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
);
|
||||
}
|
||||
|
||||
} 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) {
|
||||
// 网络或其它未预期错误
|
||||
Navigator.of(context).pop();
|
||||
setState(() => _isLoading = false);
|
||||
Fluttertoast.showToast(
|
||||
msg: '服务器正在升级,请稍后再试。\n${e.toString()}',
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
);
|
||||
Fluttertoast.showToast(msg: '登录失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,20 +90,20 @@ class _MainPageState extends State<MainPage> {
|
|||
? null
|
||||
: MyAppbar(
|
||||
title:
|
||||
_currentIndex == 0 ? "泰盛安全首页" : _titles[_currentIndex],
|
||||
_currentIndex == 0 ? "智守安全首页" : _titles[_currentIndex],
|
||||
backgroundColor: Colors.blue,
|
||||
isBack: false,
|
||||
actions: [
|
||||
if (_currentIndex == 0) ...[
|
||||
IconButton(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => NfcTestPage()),
|
||||
),
|
||||
icon: Image.asset("assets/images/ai_img.png",
|
||||
width: 20, height: 20),
|
||||
),
|
||||
// IconButton(
|
||||
// onPressed: () => Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (_) => NfcTestPage()),
|
||||
// ),
|
||||
// icon: Image.asset("assets/images/ai_img.png",
|
||||
// width: 20, height: 20),
|
||||
// ),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
|
|
|
@ -1,21 +1,47 @@
|
|||
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/login_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_sign_page.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:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class MineSetPage extends StatelessWidget {
|
||||
class MineSetPage extends StatefulWidget {
|
||||
const MineSetPage({super.key});
|
||||
|
||||
Future<void> _clearUserSession() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('isLoggedIn'); // 清除登录状态
|
||||
@override
|
||||
State<MineSetPage> createState() => _MineSetPageState();
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -28,70 +54,65 @@ class MineSetPage extends StatelessWidget {
|
|||
GestureDetector(
|
||||
child: _setItemWidget("修改密码"),
|
||||
onTap: () {
|
||||
pushPage(MineSetPwdPage(), context);
|
||||
pushPage(const MineSetPwdPage(), context);
|
||||
},
|
||||
),
|
||||
Divider(height: 1, color: Colors.black12),
|
||||
const Divider(height: 1, color: Colors.black12),
|
||||
|
||||
GestureDetector(
|
||||
child: _setItemWidget("更新人脸信息"),
|
||||
onTap: () {
|
||||
pushPage(FaceRecognitionPage(studentId: '', mode: FaceMode.manual,), context);
|
||||
pushPage(
|
||||
const FaceRecognitionPage(studentId: '', mode: FaceMode.manual),
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1, color: Colors.black12),
|
||||
|
||||
GestureDetector(
|
||||
child: _setItemWidget("更新签字信息"),
|
||||
onTap: () {
|
||||
pushPage(const FirstSignPage(), 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: () {
|
||||
pushPage(FirstSignPage(), context);
|
||||
// pushPage(MineSignPage(), context);
|
||||
}
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
color: Colors.white,
|
||||
child: const Center(
|
||||
child: Text("退出当前账户", style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
title: '确认退出',
|
||||
content: '确定要退出当前账号吗',
|
||||
onConfirm: () async {
|
||||
await AuthService.logout(); // ✅ 等待登出完成
|
||||
if (!mounted) return;
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Divider(height: 1, color: Colors.black12),
|
||||
GestureDetector(child: _setItemWidget("检查更新"), onTap: () {}),
|
||||
|
||||
SizedBox(height: 15),
|
||||
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 15),
|
||||
color: Colors.white,
|
||||
child: Center(child: Text("退出当前账户", style: TextStyle(fontSize: 16),)),
|
||||
),
|
||||
onTap: () async {
|
||||
// 显示确认对话框
|
||||
bool? confirm = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("确认退出"),
|
||||
content: Text("确定要退出当前账号吗?"),
|
||||
actions: [
|
||||
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(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => LoginPage()),
|
||||
(Route<dynamic> route) => false, // 移除所有历史路由
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -102,12 +123,12 @@ class MineSetPage extends StatelessWidget {
|
|||
height: 55,
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(15),
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(text, style: TextStyle(fontSize: 16)),
|
||||
Icon(Icons.chevron_right),
|
||||
Text(text, style: const TextStyle(fontSize: 16)),
|
||||
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
|
||||
#loading
|
||||
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:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue