488 lines
15 KiB
Dart
488 lines
15 KiB
Dart
// lib/pages/map_webview_page.dart
|
||
import 'dart:async';
|
||
import 'dart:convert';
|
||
import 'dart:math' as math;
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:geolocator/geolocator.dart';
|
||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||
import 'package:webview_flutter/webview_flutter.dart';
|
||
|
||
class MapWebViewPage extends StatefulWidget {
|
||
const MapWebViewPage({this.canEdit = true,this.oldLongitude = '',this.oldLatitude = '', Key? key}) : super(key: key);
|
||
|
||
final bool canEdit;
|
||
final String oldLongitude;
|
||
final String oldLatitude;
|
||
|
||
@override
|
||
State<MapWebViewPage> createState() => _MapWebViewPageState();
|
||
}
|
||
|
||
class _MapWebViewPageState extends State<MapWebViewPage> {
|
||
late final WebViewController _controller;
|
||
bool _loading = true;
|
||
String _mapUrl = '';
|
||
double? _selectedLongitude;
|
||
double? _selectedLatitude;
|
||
bool _locationError = false;
|
||
|
||
// 默认坐标(北京)
|
||
static const double defaultLongitude = 116.397428;
|
||
static const double defaultLatitude = 39.90923;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_initializeWebView();
|
||
_initializeMap();
|
||
}
|
||
|
||
void _initializeWebView() {
|
||
_controller = WebViewController()
|
||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||
..addJavaScriptChannel('FlutterChannel', onMessageReceived: _onMessageReceived)
|
||
..setNavigationDelegate(NavigationDelegate(
|
||
onPageStarted: (url) {
|
||
debugPrint('页面开始加载: $url');
|
||
},
|
||
onPageFinished: (url) {
|
||
debugPrint('页面加载完成: $url');
|
||
setState(() {
|
||
_loading = false;
|
||
});
|
||
_injectFlutterBridge();
|
||
},
|
||
onWebResourceError: (error) {
|
||
debugPrint('Web资源错误: ${error.errorCode} - ${error.description}');
|
||
setState(() {
|
||
_loading = false;
|
||
});
|
||
},
|
||
onNavigationRequest: (request) {
|
||
debugPrint('导航请求: ${request.url}');
|
||
return NavigationDecision.navigate;
|
||
},
|
||
));
|
||
}
|
||
|
||
Future<void> _initializeMap() async {
|
||
try {
|
||
debugPrint('开始获取位置信息...');
|
||
|
||
// 先设置默认URL,避免长时间等待
|
||
final defaultCoord = _gcj02ToBd09(defaultLongitude, defaultLatitude);
|
||
_setMapUrl(defaultCoord[0], defaultCoord[1]);
|
||
|
||
var position ;
|
||
if(widget.canEdit){
|
||
// 异步获取当前位置
|
||
position = await _getCurrentLocationWithTimeout();
|
||
}else{
|
||
try{
|
||
double oldLongitude = double.parse(widget.oldLongitude);
|
||
double oldLatitude = double.parse(widget.oldLatitude);
|
||
position = Position(longitude: oldLongitude, latitude:oldLatitude ,
|
||
timestamp: DateTime.now(), accuracy: 0.0, altitude: 0.0, altitudeAccuracy: 0.0, heading: 0.0,
|
||
headingAccuracy: 0.0, speed: 0.0, speedAccuracy: 0.0);
|
||
}catch (e) {
|
||
// 异步获取当前位置
|
||
position = await _getCurrentLocationWithTimeout();
|
||
}
|
||
}
|
||
|
||
if (position != null) {
|
||
debugPrint('成功获取位置: ${position.longitude}, ${position.latitude}');
|
||
// GCJ02 -> BD09 坐标转换
|
||
final bd09Coord = _gcj02ToBd09(position.longitude, position.latitude);
|
||
_setMapUrl(bd09Coord[0], bd09Coord[1]);
|
||
} else {
|
||
debugPrint('使用默认位置');
|
||
_showToast('使用默认位置,您可以手动选择位置');
|
||
}
|
||
|
||
// 加载地图
|
||
await _controller.loadRequest(Uri.parse(_mapUrl));
|
||
|
||
} catch (e) {
|
||
debugPrint('地图初始化失败: $e');
|
||
// 即使出错也继续加载地图,使用默认位置
|
||
_showToast('位置获取失败,使用默认位置');
|
||
await _controller.loadRequest(Uri.parse(_mapUrl));
|
||
}
|
||
}
|
||
|
||
Future<Position?> _getCurrentLocationWithTimeout() async {
|
||
try {
|
||
// 首先检查定位服务是否开启
|
||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||
if (!serviceEnabled) {
|
||
debugPrint('定位服务未开启');
|
||
_showToast('定位服务未开启,使用默认位置');
|
||
return null;
|
||
}
|
||
|
||
// 检查权限
|
||
LocationPermission permission = await Geolocator.checkPermission();
|
||
debugPrint('当前定位权限: $permission');
|
||
|
||
if (permission == LocationPermission.denied) {
|
||
permission = await Geolocator.requestPermission();
|
||
debugPrint('请求后定位权限: $permission');
|
||
}
|
||
|
||
if (permission == LocationPermission.denied ||
|
||
permission == LocationPermission.deniedForever) {
|
||
debugPrint('定位权限被拒绝');
|
||
_showToast('定位权限被拒绝,使用默认位置');
|
||
return null;
|
||
}
|
||
|
||
// 设置超时
|
||
final position = await Geolocator.getCurrentPosition(
|
||
desiredAccuracy: LocationAccuracy.best,
|
||
).timeout(const Duration(seconds: 10));
|
||
|
||
return position;
|
||
} catch (e) {
|
||
debugPrint('获取位置异常: $e');
|
||
// 尝试获取最后已知位置
|
||
try {
|
||
final lastPosition = await Geolocator.getLastKnownPosition();
|
||
if (lastPosition != null) {
|
||
debugPrint('使用最后已知位置: ${lastPosition.longitude}, ${lastPosition.latitude}');
|
||
return lastPosition;
|
||
}
|
||
} catch (e) {
|
||
debugPrint('获取最后已知位置失败: $e');
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
void _setMapUrl(double longitude, double latitude) {
|
||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||
setState(() {
|
||
_mapUrl = 'https://skqhdg.porthebei.com:9004/map/map.html?'
|
||
'longitude=$longitude&'
|
||
'latitude=$latitude&'
|
||
't=$timestamp';
|
||
});
|
||
debugPrint('地图URL: $_mapUrl');
|
||
}
|
||
|
||
void _injectFlutterBridge() {
|
||
const bridgeScript = '''
|
||
// 重写uni.postMessage以发送到Flutter
|
||
if (typeof uni !== 'undefined') {
|
||
const originalPostMessage = uni.postMessage;
|
||
uni.postMessage = function(data) {
|
||
console.log('uni.postMessage被调用:', data);
|
||
// 发送到Flutter
|
||
if (window.FlutterChannel) {
|
||
try {
|
||
window.FlutterChannel.postMessage(JSON.stringify(data));
|
||
} catch(e) {
|
||
console.log('发送到Flutter失败:', e);
|
||
}
|
||
}
|
||
// 保持原有逻辑
|
||
if (typeof originalPostMessage === 'function') {
|
||
originalPostMessage(data);
|
||
}
|
||
};
|
||
|
||
// 确保plus环境检测返回true
|
||
uni.getEnv = function(callback) {
|
||
if (typeof callback === 'function') {
|
||
callback({ plus: true });
|
||
}
|
||
};
|
||
}
|
||
|
||
// 添加Flutter专用的消息发送方法
|
||
window.sendToFlutter = function(data) {
|
||
console.log('sendToFlutter被调用:', data);
|
||
if (window.FlutterChannel) {
|
||
try {
|
||
window.FlutterChannel.postMessage(JSON.stringify(data));
|
||
} catch(e) {
|
||
console.log('发送到Flutter失败:', e);
|
||
}
|
||
}
|
||
};
|
||
|
||
console.log('Flutter bridge注入完成');
|
||
''';
|
||
|
||
_controller.runJavaScript(bridgeScript).catchError((error) {
|
||
debugPrint('注入Flutter bridge失败: $error');
|
||
});
|
||
}
|
||
|
||
void _onMessageReceived(JavaScriptMessage message) {
|
||
try {
|
||
debugPrint('收到原始消息: ${message.message}');
|
||
final data = jsonDecode(message.message);
|
||
debugPrint('解析后的消息: $data');
|
||
|
||
// 解析坐标数据
|
||
if (data != null) {
|
||
final coords = data['data'];
|
||
setState(() {
|
||
_selectedLongitude = coords['longitue'];
|
||
_selectedLatitude = coords['latitude'];
|
||
});
|
||
|
||
debugPrint('选中坐标: $_selectedLongitude, $_selectedLatitude');
|
||
} else {
|
||
debugPrint('无法从消息中提取坐标');
|
||
}
|
||
} catch (e) {
|
||
debugPrint('解析地图消息失败: $e');
|
||
debugPrint('原始消息内容: ${message.message}');
|
||
}
|
||
}
|
||
|
||
Map<String, double>? _extractCoordinates(dynamic data) {
|
||
try {
|
||
debugPrint('开始提取坐标,数据类型: ${data.runtimeType}');
|
||
|
||
if (data is Map) {
|
||
// 处理不同的数据结构
|
||
dynamic coordsData = data;
|
||
|
||
// 处理嵌套结构
|
||
if (data.containsKey('data') && data['data'] is List && data['data'].isNotEmpty) {
|
||
coordsData = data['data'][0];
|
||
debugPrint('从data数组中提取坐标数据');
|
||
}
|
||
|
||
if (coordsData is Map) {
|
||
debugPrint('坐标数据键: ${coordsData.keys}');
|
||
|
||
// 处理拼写错误 (longitue -> longitude)
|
||
final longitude = _toDouble(coordsData['longitude']) ??
|
||
_toDouble(coordsData['longitue']);
|
||
final latitude = _toDouble(coordsData['latitude']);
|
||
|
||
debugPrint('解析结果 - 经度: $longitude, 纬度: $latitude');
|
||
|
||
if (longitude != null && latitude != null) {
|
||
return {
|
||
'longitude': longitude,
|
||
'latitude': latitude,
|
||
};
|
||
}
|
||
}
|
||
} else if (data is String) {
|
||
// 尝试从字符串中提取坐标
|
||
debugPrint('尝试从字符串中提取坐标');
|
||
final coordPattern = RegExp(r'[-+]?\d+\.\d+');
|
||
final matches = coordPattern.allMatches(data).toList();
|
||
if (matches.length >= 2) {
|
||
final longitude = double.tryParse(matches[0].group(0)!);
|
||
final latitude = double.tryParse(matches[1].group(0)!);
|
||
if (longitude != null && latitude != null) {
|
||
return {
|
||
'longitude': longitude,
|
||
'latitude': latitude,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
debugPrint('提取坐标失败: $e');
|
||
}
|
||
return null;
|
||
}
|
||
|
||
double? _toDouble(dynamic value) {
|
||
if (value == null) return null;
|
||
if (value is double) return value;
|
||
if (value is int) return value.toDouble();
|
||
if (value is String) {
|
||
// 处理可能的字符串格式
|
||
final cleaned = value.replaceAll(RegExp(r'[^\d.-]'), '');
|
||
return double.tryParse(cleaned);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
void _showToast(String message) {
|
||
if (!mounted) return;
|
||
ToastUtil.showNormal(context, message);
|
||
}
|
||
|
||
Future<void> _confirmSelection() async {
|
||
if (_selectedLongitude == null || _selectedLatitude == null) {
|
||
// 如果没有选中位置,尝试从页面获取当前位置
|
||
await _getCurrentLocationFromPage();
|
||
}
|
||
|
||
if (_selectedLongitude != null && _selectedLatitude != null) {
|
||
final result = {
|
||
'longitude': _selectedLongitude!,
|
||
'latitude': _selectedLatitude!,
|
||
};
|
||
|
||
debugPrint('返回坐标结果: $result');
|
||
Navigator.of(context).pop(result);
|
||
} else {
|
||
_showToast('请先在地图上选择位置');
|
||
}
|
||
}
|
||
|
||
Future<void> _getCurrentLocationFromPage() async {
|
||
try {
|
||
debugPrint('尝试从页面获取选中位置');
|
||
const getterScript = '''
|
||
(function(){
|
||
try {
|
||
// 尝试多种方式获取选中位置
|
||
if (typeof getSelectedLocation === 'function') {
|
||
var result = getSelectedLocation();
|
||
if (result) return JSON.stringify(result);
|
||
}
|
||
|
||
if (window.selectedLocation) {
|
||
return JSON.stringify(window.selectedLocation);
|
||
}
|
||
|
||
// 如果没有选中位置,返回地图中心
|
||
if (map && typeof map.getCenter === 'function') {
|
||
var center = map.getCenter();
|
||
return JSON.stringify({
|
||
longitude: center.getLng(),
|
||
latitude: center.getLat()
|
||
});
|
||
}
|
||
|
||
return null;
|
||
} catch(e) {
|
||
console.log('获取位置错误:', e);
|
||
return null;
|
||
}
|
||
})();
|
||
''';
|
||
|
||
final result = await _controller.runJavaScriptReturningResult(getterScript);
|
||
debugPrint('页面返回的位置结果: $result');
|
||
|
||
if (result != null) {
|
||
String resultString = result.toString();
|
||
// 处理可能的双重编码
|
||
if (resultString.startsWith('"') && resultString.endsWith('"')) {
|
||
resultString = resultString.substring(1, resultString.length - 1);
|
||
}
|
||
|
||
final coords = _extractCoordinates(jsonDecode(resultString));
|
||
if (coords != null) {
|
||
setState(() {
|
||
_selectedLongitude = coords['longitude'];
|
||
_selectedLatitude = coords['latitude'];
|
||
});
|
||
debugPrint('从页面获取到坐标: $_selectedLongitude, $_selectedLatitude');
|
||
}
|
||
}
|
||
} catch (e) {
|
||
debugPrint('从页面获取位置失败: $e');
|
||
}
|
||
}
|
||
|
||
void _retryLocation() async {
|
||
setState(() {
|
||
_loading = true;
|
||
_locationError = false;
|
||
});
|
||
|
||
await _initializeMap();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: MyAppbar(
|
||
title: '地图选点',
|
||
actions: [
|
||
if (_selectedLongitude != null&&widget.canEdit)
|
||
TextButton(
|
||
onPressed: _confirmSelection,
|
||
child: const Text(
|
||
'确定',
|
||
style: TextStyle(color: Colors.white, fontSize: 17),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
body: Column(
|
||
children: [
|
||
// 状态提示栏
|
||
if (_locationError)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
color: Colors.orange[100],
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.warning_amber, color: Colors.orange[800], size: 16),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'定位失败,使用默认位置',
|
||
style: TextStyle(color: Colors.orange[800], fontSize: 12),
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: _retryLocation,
|
||
child: Text(
|
||
'重试',
|
||
style: TextStyle(color: Colors.orange[800], fontSize: 12),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
// 地图区域
|
||
Expanded(
|
||
child: Stack(
|
||
children: [
|
||
if (_mapUrl.isNotEmpty)
|
||
WebViewWidget(controller: _controller),
|
||
|
||
if (_loading)
|
||
const Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
CircularProgressIndicator(),
|
||
SizedBox(height: 16),
|
||
Text('地图加载中...'),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// GCJ02 -> BD09 坐标转换
|
||
List<double> _gcj02ToBd09(double lng, double lat) {
|
||
const double xPi = math.pi * 3000.0 / 180.0;
|
||
final double z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * xPi);
|
||
final double theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * xPi);
|
||
final double bdLng = z * math.cos(theta) + 0.0065;
|
||
final double bdLat = z * math.sin(theta) + 0.006;
|
||
return [bdLng, bdLat];
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
super.dispose();
|
||
debugPrint('地图页面销毁');
|
||
}
|
||
} |