import 'dart:convert'; import 'dart:typed_data'; import 'package:dart_sm/dart_sm.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebViewMediaBridgePage extends StatefulWidget { const WebViewMediaBridgePage({super.key, required this.userId}); final String userId; @override State createState() => _WebViewMediaBridgePageState(); } class _WebViewMediaBridgePageState extends State { static const _jsChannelName = 'WebViewBridge'; static const _publicKey = '049818e94f1abefab63d7193dd71b16241b5e721ab936eb1d4abb84f5024cb5d5b503a1776163cce4dd0f0ffdc612329f3497da3ac9b643306d5bfbe844459d186'; static const _baseUrl = 'http://121.22.11.43:8091/emisApp/#/pages/index?user='; final ImagePicker _imagePicker = ImagePicker(); late final WebViewController _controller; @override void initState() { super.initState(); final encryptedUser = SM2.encrypt(widget.userId, _publicKey); final targetUrl = '$_baseUrl$encryptedUser'; _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(Colors.white) ..setNavigationDelegate( NavigationDelegate( onPageFinished: (_) => _injectBridge(), onWebResourceError: (error) { debugPrint('设备管理 WebView 加载错误: ${error.description}'); }, ), ) ..addJavaScriptChannel( _jsChannelName, onMessageReceived: (message) => _handleWebMessage(message.message), ) ..loadRequest(Uri.parse(targetUrl)); } Future _injectBridge() { return _controller.runJavaScript(''' window.flutterSendToDart = function(message) { if (window.$_jsChannelName && window.$_jsChannelName.postMessage) { window.$_jsChannelName.postMessage(String(message)); } }; '''); } Future _handleWebMessage(String raw) async { Map data; try { data = jsonDecode(raw) as Map; } catch (_) { data = {'action': raw}; } final action = data['action']?.toString(); final callbackId = data['callbackId']?.toString(); switch (action) { case 'camera': await _pickImageAndCallback(ImageSource.camera, callbackId); return; case 'gallery': await _pickImageAndCallback(ImageSource.gallery, callbackId); return; case 'back': if (mounted) Navigator.of(context).pop(); return; case 'ping': await _replyToWeb(callbackId, { 'ok': true, 'message': 'pong from flutter', 'time': DateTime.now().toIso8601String(), }); return; default: debugPrint('设备管理 WebView 未知 action: $action'); } } Future _pickImageAndCallback( ImageSource source, String? callbackId, ) async { try { final file = await _imagePicker.pickImage( source: source, imageQuality: 85, ); if (file == null) { await _replyToWeb(callbackId, { 'ok': false, 'cancelled': true, 'message': '用户取消选择', }); return; } final Uint8List bytes = await file.readAsBytes(); final base64Data = base64Encode(bytes); final mimeType = _guessMimeType(file.path); await _replyToWeb(callbackId, { 'ok': true, 'name': file.name, 'path': file.path, 'mimeType': mimeType, 'base64': base64Data, 'dataUrl': 'data:$mimeType;base64,$base64Data', }); } catch (error) { debugPrint('设备管理图片选择失败: $error'); await _replyToWeb(callbackId, {'ok': false, 'message': error.toString()}); } } String _guessMimeType(String path) { final lower = path.toLowerCase(); if (lower.endsWith('.png')) return 'image/png'; if (lower.endsWith('.webp')) return 'image/webp'; if (lower.endsWith('.gif')) return 'image/gif'; if (lower.endsWith('.heic')) return 'image/heic'; return 'image/jpeg'; } Future _replyToWeb(String? callbackId, Map payload) { final jsonText = jsonEncode(payload); if (callbackId == null || callbackId.isEmpty) { return _controller.runJavaScript(''' if (window.onFlutterResult) window.onFlutterResult($jsonText); '''); } return _controller.runJavaScript(''' if (window.WebViewCallbacks && window.WebViewCallbacks['$callbackId']) { window.WebViewCallbacks['$callbackId']($jsonText); delete window.WebViewCallbacks['$callbackId']; } else if (window.onFlutterResult) { window.onFlutterResult($jsonText); } '''); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( top: false, bottom: false, child: WebViewWidget(controller: _controller), ), ); } }