第一版

main
hs 2025-08-19 11:06:16 +08:00
parent 037a00967f
commit 421d71f7c9
20 changed files with 1654 additions and 1131 deletions

View File

@ -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

View File

@ -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.logconsole.errorwindow.onerror FlutterBridge
(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 日志'),
),
],
),
),
),
),
),
],
);
}
}

View File

@ -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,
);
},
);

View File

@ -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')}';
}

View File

@ -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(

View File

@ -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) {

View File

@ -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,15 +124,13 @@ 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(),
// ...
},
);
}

View File

@ -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": "",
};
//
// 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) {
result = await ApiService.getInspectRecordsDetailYin(widget.id);
} 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 {
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) {
result = await ApiService.getInspectRecordsDetailTwoYin(widget.id);
} 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 {
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]),
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)
],
)),
]),
// 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:
if (isnormalStr == '0') {
status = "合格";
color = Colors.blue;
if (item["IMGCOUNT"] > 0) {
onTap = () => _goToImgs(item["RECORDITEM_ID"]);
}
break;
case 1:
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"]);
break;
case 2:
onTap = () => _goToDetail(item["HIDDEN_ID"]?.toString() ?? '');
} else if (isnormalStr == '2') {
status = "不涉及";
color = Colors.blue;
break;
default:
} 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]),
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"]),
),
]),
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(item["HIDDEN_ID"]),
onTap: () => _goToDetail(mi["HIDDEN_ID"]?.toString() ?? ''),
child: Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Text(
"查看",
style: TextStyle(color: Colors.blue),
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: [
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};
if (hiddenId.isEmpty) return;
final 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")),
// ),
// ),
// );
}
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);
}
}

View File

@ -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,6 +190,7 @@ class DannerRepairState extends State<DannerRepair> {
),
),
Divider(),
ItemListWidget.itemContainer(
RepairedPhotoSection(
title: "整改后照片",
maxCount: 4,
@ -204,6 +206,7 @@ class DannerRepairState extends State<DannerRepair> {
},
),
),
Divider(),
Row(

View File

@ -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']) {
@ -197,6 +195,10 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ItemListWidget.itemContainer(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoItem('隐患描述', pd['HIDDENDESCR'] ?? ''),
@ -250,7 +252,6 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
},
),
//
if (videoList.isNotEmpty) ...[
const SizedBox(height: 16),
@ -277,6 +278,9 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
),
),
],
],
)
),
SizedBox(height: 10,),
//

View File

@ -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,14 +21,20 @@ 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;
///
@ -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;

View File

@ -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();
}

View File

@ -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 = [];

View File

@ -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,6 +37,10 @@ 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:
@ -50,35 +57,107 @@ class _HomePageState extends State<HomePage> {
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;
@ -150,7 +307,8 @@ class _HomePageState extends State<HomePage> {
//
setState(() {
// workInfos
workInfos = workInfos.map((info) {
workInfos =
workInfos.map((info) {
final idx = info['index'] as int;
final key = _workKey(idx);
final num = (hidCount[key] ?? 0).toString();
@ -162,7 +320,8 @@ class _HomePageState extends State<HomePage> {
_eight_work_count = BadgeManager().eightWorkCount;
// buttonInfos
buttonInfos = buttonInfos.map((info) {
buttonInfos =
buttonInfos.map((info) {
switch (info['title'] as String) {
case '特殊作业':
info['unreadCount'] = _eight_work_count;
@ -178,11 +337,8 @@ class _HomePageState extends State<HomePage> {
}).toList();
// pcData
pcData = [
myData,
deptData,
superDeptData,
].asMap().entries.map((entry) {
pcData =
[myData, deptData, superDeptData].asMap().entries.map((entry) {
final idx = entry.key;
final pdMap = (entry.value['pd'] as Map<String, dynamic>?) ?? {};
return {
@ -200,7 +356,6 @@ class _HomePageState extends State<HomePage> {
}
}
@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()),
)
: HiddenRollWidget(hiddenList: hiddenList),
],
)
),
),
const SizedBox(height: 10),
_buildPCDataSection(),
@ -266,7 +420,8 @@ class _HomePageState extends State<HomePage> {
return Wrap(
spacing: spacing,
runSpacing: spacing,
children: workInfos.map((info) {
children:
workInfos.map((info) {
return SizedBox(
width: itemWidth,
child: _buildGridItem(
@ -303,7 +458,8 @@ class _HomePageState extends State<HomePage> {
);
}
double _itemWidth(BuildContext context) => (MediaQuery.of(context).size.width - 20) / 4;
double _itemWidth(BuildContext context) =>
(MediaQuery.of(context).size.width - 20) / 4;
Widget _buildIconSection(BuildContext context) {
return Container(
@ -314,7 +470,8 @@ class _HomePageState extends State<HomePage> {
),
child: Wrap(
runSpacing: 16,
children: buttonInfos.asMap().entries.map((entry) {
children:
buttonInfos.asMap().entries.map((entry) {
final idx = entry.key;
final info = entry.value;
return _buildIconButton(
@ -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(
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,
),
],
),
),

View File

@ -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(
() => setState(
() => _obscurePassword = !_obscurePassword,
),
),
validator: (value) {
if (value == null || value.isEmpty)
return '请输入密码';
if (value == null || value.isEmpty) return '请输入密码';
return null;
},
),
@ -169,8 +188,7 @@ class _LoginPageState extends State<LoginPage> {
borderRadius: BorderRadius.circular(8),
),
),
child:
const Text(
child: const Text(
'登录',
style: TextStyle(
fontSize: 18,
@ -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? ?? '';
if (result == 'success') {
//
final prefs = await SharedPreferences.getInstance();
await prefs.setString('USER', json.encode(data));
await prefs.setStringList('remember', [userName, userPwd]);
SessionService.instance
..setLoginUserId(data['USER_ID'] as String)
..setCorpinfoId(data['CORPINFO_ID'] as String)
..setDeptId(data['DEPARTMENT_ID'] as String)
..setDeptLevel(data['DEPARTMENT_LEVEL'] as String)
..setIsRest(data['ISREST'] as String)
..setUsername(data['NAME'] as String)
..setLoginUser(data); // data JSON
final weak = data['WEAK_PASSWORD'] == '1';
final longTerm = data['LONG_TERM_PASSWORD_NOT_CHANGED'] == '1';
final success = await AuthService.login(userName, userPwd);
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 {
if (success) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
);
}
} else {
// 'success' ApiException
}
} on ApiException catch (e) {
//
Navigator.of(context).pop();
setState(() => _isLoading = false);
String tip = e.message;
Fluttertoast.showToast(msg: tip, toastLength: Toast.LENGTH_LONG);
} catch (e) {
//
Navigator.of(context).pop();
setState(() => _isLoading = false);
Fluttertoast.showToast(
msg: '服务器正在升级,请稍后再试。\n${e.toString()}',
toastLength: Toast.LENGTH_LONG,
);
Fluttertoast.showToast(msg: '登录失败: $e');
}
}
}

View File

@ -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,

View File

@ -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,68 +54,63 @@ 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),
Divider(height: 1, color: Colors.black12),
GestureDetector(
child: _setItemWidget("更新签字信息"),
onTap: () {
pushPage(FirstSignPage(), context);
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),
GestureDetector(child: _setItemWidget("检查更新"), onTap: () {}),
SizedBox(height: 15),
const SizedBox(height: 15),
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(vertical: 15),
padding: const EdgeInsets.symmetric(vertical: 15),
color: Colors.white,
child: Center(child: Text("退出当前账户", style: TextStyle(fontSize: 16),)),
child: const 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();
//
CustomAlertDialog.showConfirm(
context,
title: '确认退出',
content: '确定要退出当前账号吗',
onConfirm: () async {
await AuthService.logout(); //
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false, //
MaterialPageRoute(builder: (context) => const 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),
],
),
),

102
lib/tools/auth_service.dart Normal file
View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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: