// 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> covers; final ValueChanged>? 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 createState() => _BaiduMapWebViewState(); } class _BaiduMapWebViewState extends State { 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 '''
'''; } 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; 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 日志'), ), ], ), ), ), ), ), ], ); } }