Flutter接django背景文件通道

打印 上一主题 下一主题

主题 916|帖子 916|积分 2748

flutter接django背景


  1. import 'package:flutter/material.dart';  // Material设计库库
  2. import 'package:flutter/cupertino.dart'; // iOS风格组件库
  3. import 'package:webview_flutter/webview_flutter.dart';// WebView核心库
  4. // import './web_channel_controller.dart';// webview文件上传控制器
  5. // import 'package:flutter/gestures.dart';  // 导入手势库,用于处理WebView的手势
  6. // import 'package:webview_flutter_android/webview_flutter_android.dart';  // Android平台WebView支持
  7. // import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; // iOS平台WebView支持
  8. import 'package:flutter/foundation.dart'; // Flutter基础库
  9. import 'package:file_picker/file_picker.dart';// 文件选择器
  10. // import 'dart:io'; // IO操作
  11. import 'package:get/get.dart';
  12. import 'package:image_picker/image_picker.dart';  // 图片选择器
  13. import 'dart:convert';  // 用于Base64编码
  14. import 'package:flutter/foundation.dart'; // Flutter基础库
  15. import 'dart:io';// 需要导入这个包来使用 HttpException 和 SocketException// IO操作
  16. import 'package:flutter/material.dart';//弹窗颜色要用到颜色组件
  17. import 'package:flutter/cupertino.dart';//iOS风格组件库
  18. import 'package:flutter/gestures.dart';  // 导入手势库,用于处理WebView的手势
  19. import 'package:get/get.dart';
  20. import 'package:webview_flutter/webview_flutter.dart';// WebView核心库
  21. import 'package:webview_flutter_android/webview_flutter_android.dart';// Android平台WebView支持
  22. import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';// iOS平台WebView支持
  23. import 'web_page_load_error_prompt.dart';//页面加载错误提示组件【IOS】风格
  24. import 'package:file_picker/file_picker.dart';// 文件选择器
  25. import 'package:image_picker/image_picker.dart';  // 图片选择器
  26. import 'dart:convert';// 用于Base64编码
  27. import 'dart:async';
  28. class OneWebPage extends GetView<WebHistoryController> {
  29.   final String initialUrl;
  30.   final String pageTitle;
  31.   OneWebPage({Key? key, required this.initialUrl, required this.pageTitle}) : super(key: key);
  32.   @override
  33.   Widget build(BuildContext context) {
  34.     final web_history_Controller = Get.find<WebHistoryController>();
  35.     // 初始化时加载URL
  36.     WidgetsBinding.instance.addPostFrameCallback((_) {web_history_Controller.loadUrl(initialUrl);});
  37.    
  38.     return PopScope(
  39.       canPop: true,// 指示页面是否可以被弹出
  40.       onPopInvokedWithResult: (didPop, result) async {
  41.         if (web_history_Controller.canGoBack()) {
  42.           await web_history_Controller.goBack();// 在网页历史中后退
  43.         } else {Get.back(); }
  44.       },
  45.       child: SafeArea(
  46.         child: CupertinoPageScaffold(
  47.           backgroundColor: CupertinoColors.systemBackground,
  48.           //导航栏
  49.           navigationBar: CupertinoNavigationBar(
  50.             // 用于调整高度的自定义填充
  51.             padding: const EdgeInsetsDirectional.only(top: 0, start: 2, end: 0,bottom: 3),
  52.             backgroundColor: CupertinoColors.systemBackground.withOpacity(0.8),
  53.             border: null,
  54.             middle: Text(pageTitle,style: TextStyle(fontSize: 14,fontWeight: FontWeight.w600,letterSpacing: -0.41,color: CupertinoColors.label,),),
  55.             leading: Transform.scale(
  56.               scale: 0.85, // Adjust the scale factor as needed
  57.               child: CupertinoNavigationBarBackButton(
  58.                 color: CupertinoColors.activeBlue,
  59.                 onPressed: () => Get.back(),// 处理返回按钮的点击事件
  60.               ),
  61.             ),
  62.             trailing: Row(
  63.               mainAxisSize: MainAxisSize.min,
  64.               children: [
  65.                 // 后退按钮
  66.                 Obx(() => CupertinoButton(
  67.                   padding: const EdgeInsets.symmetric(horizontal: 8),
  68.                   onPressed: web_history_Controller.canGoBack() ? () async {await web_history_Controller.goBack();} : null,
  69.                   child: Icon(CupertinoIcons.chevron_back,color: web_history_Controller.canGoBack()? CupertinoColors.activeBlue: CupertinoColors.inactiveGray,),
  70.                 )),
  71.                 // 前进按钮
  72.                 Obx(() => CupertinoButton(
  73.                   padding: const EdgeInsets.symmetric(horizontal: 8),
  74.                   onPressed: web_history_Controller.canGoForward() ? () async {await web_history_Controller.goForward();} : null,
  75.                   child: Icon(CupertinoIcons.chevron_forward,color: web_history_Controller.canGoForward()? CupertinoColors.activeBlue: CupertinoColors.inactiveGray,),
  76.                 )),
  77.                 // 刷新按钮
  78.                 CupertinoButton(padding: const EdgeInsets.symmetric(horizontal: 8),onPressed: web_history_Controller.reload,child: const Icon(CupertinoIcons.refresh,color: CupertinoColors.activeBlue,),),
  79.               ],
  80.             ),
  81.           ),
  82.           child: Stack(
  83.             children: [
  84.               Obx(() {
  85.                 final controller = web_history_Controller.webViewController.value;
  86.                 return controller != null
  87.                     ? WebViewWidget(controller: controller)
  88.                     : const Center(child: CupertinoActivityIndicator());
  89.               }),
  90.               Obx(() {
  91.                 final isLoading = web_history_Controller.isLoading.value;
  92.                 final hasController = web_history_Controller.webViewController.value != null;
  93.                 return isLoading && hasController
  94.                     ? const Positioned.fill(
  95.                         child: Center(
  96.                           child: CupertinoActivityIndicator(),
  97.                         ),
  98.                       )
  99.                     : const SizedBox.shrink();
  100.               }),
  101.             ],
  102.           ),
  103.         ),
  104.       ),
  105.     );
  106.   }
  107. }
  108. class OneWebBinding extends Bindings {
  109.   @override
  110.   void dependencies() {
  111.     Get.lazyPut(() => WebHistoryController());
  112.   }
  113. }
  114. // 文件上传状态值
  115. enum FileUploadState {
  116.   idle,
  117.   picking,
  118.   uploading,
  119.   success,
  120.   error
  121. }
  122. class WebHistoryController extends GetxController {
  123.   // 添加 ImagePicker 实例
  124.   final _imagePicker = ImagePicker();
  125.   //文件上传状态值
  126.     // 1[状态管理]--->
  127.   // 1.1 页面导航状态
  128.   final RxBool isLoading = true.obs;
  129.   final RxBool hasError = false.obs;
  130.   final RxList<String> history = <String>[].obs;
  131.   final RxInt currentIndex = (-1).obs;
  132.   // ----------------------------------------------------------------
  133.   // 1.2 文件上传状态
  134.   final Rx<FileUploadState> uploadState = FileUploadState.idle.obs;
  135.   final RxDouble uploadProgress = 0.0.obs;
  136.   final RxString uploadError = ''.obs;
  137.   // ----------------------------------------------------------------
  138.     // 1.3 WebView控制器
  139.   final webViewController = Rxn<WebViewController>();
  140.   // ----------------------------------------------------------------
  141.   // [导航功能]--->
  142.   // 1.4 判断是否可以后退
  143.   // 1.5 判断是否可以前进
  144.   bool canGoBack() => currentIndex.value > 0;
  145.   bool canGoForward() => currentIndex.value < history.length - 1;
  146.   @override
  147.   void onInit() {
  148.     super.onInit();
  149.     initializeWebView();
  150.   }
  151.   void initializeWebView() {
  152.     late final PlatformWebViewControllerCreationParams params;
  153.     if (WebViewPlatform.instance is WebKitWebViewPlatform) {
  154.       params = WebKitWebViewControllerCreationParams(
  155.         allowsInlineMediaPlayback: true,
  156.         mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
  157.       );
  158.     } else {
  159.       params = const PlatformWebViewControllerCreationParams();
  160.     }
  161.    
  162.     final controller = WebViewController.fromPlatformCreationParams(params);
  163.     if (controller.platform is AndroidWebViewController) {
  164.       AndroidWebViewController.enableDebugging(true);
  165.       AndroidWebViewController androidController = controller.platform as AndroidWebViewController;
  166.       androidController.setMediaPlaybackRequiresUserGesture(false);
  167.     }
  168.     // 配置控制器(只调用一次)
  169.     configureController(controller);
  170.     webViewController.value = controller;
  171.    
  172.     // 等待下一帧再注入脚本,确保 WebView 已经准备好
  173.     WidgetsBinding.instance.addPostFrameCallback((_) {
  174.       _debouncedInject();
  175.       _setupFileUploadChannel();
  176.     });
  177.   }
  178.   void configureController(WebViewController controller) {
  179.     controller
  180.       ..setJavaScriptMode(JavaScriptMode.unrestricted)
  181.       ..setBackgroundColor(const Color(0x00000000))
  182.       ..setNavigationDelegate(
  183.         NavigationDelegate(
  184.           onPageStarted: (String url) {
  185.             isLoading.value = true;
  186.             handlePageStarted(url);
  187.           },
  188.           onPageFinished: (String url) {
  189.             isLoading.value = false;
  190.             // 页面加载完成后注入文件上传脚本
  191.             _debouncedInject();
  192.           },
  193.           onWebResourceError: (WebResourceError error) {
  194.             _handleWebResourceError(error);
  195.           },
  196.           onNavigationRequest: (NavigationRequest request) {
  197.             return NavigationDecision.navigate;
  198.           },
  199.         ),
  200.       );
  201.   }
  202.   // 2[添加资源加载检查]--->
  203.   Future<void> _checkPageLoadComplete() async {
  204.     try {
  205.       final isComplete = await webViewController.value?.runJavaScriptReturningResult(
  206.         'document.readyState === "complete"'
  207.       );
  208.       if (isComplete == true) {
  209.         isLoading.value = false;
  210.       }
  211.     } catch (e) {
  212.       // 忽略JavaScript执行错误
  213.     }
  214.   }
  215.     // 注入文件上传脚本
  216.   void _injectFileUploadScript() {
  217.     webViewController.value?.runJavaScript('''
  218.       (function() {
  219.         // 防止重复初始化
  220.         if (window._fileUploadInitialized) return;
  221.         window._fileUploadInitialized = true;
  222.         // 1[初始化]--->仅处理文件输入框的点击事件
  223.         function initFileInputs() {
  224.           document.querySelectorAll('input[type="file"]').forEach(function(input) {
  225.             if (!input.dataset.initialized) {
  226.               input.dataset.initialized = 'true';
  227.               input.dataset.inputId = 'file_input_' + Math.random().toString(36).substr(2, 9);
  228.               
  229.               // 只处理点击事件,让 Flutter 处理文件选择
  230.               input.addEventListener('click', function(e) {
  231.                 e.preventDefault();
  232.                 e.stopPropagation();
  233.                
  234.                 // 发送消息到 Flutter
  235.                 window.FileUpload.postMessage(JSON.stringify({
  236.                   'action': 'pick',
  237.                   'type': input.accept.includes('image/') ? 'image' : 'file',
  238.                   'accept': input.accept || '',
  239.                   'inputId': input.dataset.inputId,
  240.                   'multiple': input.multiple
  241.                 }));
  242.               });
  243.             }
  244.           });
  245.         }
  246.         // 2[文件处理]--->只负责设置文件到输入框
  247.         window.handleFileSelection = function(inputId, fileData) {
  248.           const input = document.querySelector('input[data-input-id="' + inputId + '"]');
  249.           if (!input) return;
  250.          
  251.           try {
  252.             // 创建文件对象
  253.             const byteCharacters = atob(fileData.data);
  254.             const byteArrays = [];
  255.             
  256.             for (let offset = 0; offset < byteCharacters.length; offset += 512) {
  257.               const slice = byteCharacters.slice(offset, offset + 512);
  258.               const byteNumbers = new Array(slice.length);
  259.               for (let i = 0; i < slice.length; i++) {
  260.                 byteNumbers[i] = slice.charCodeAt(i);
  261.               }
  262.               byteArrays.push(new Uint8Array(byteNumbers));
  263.             }
  264.             
  265.             // 确保使用完整的文件名创建文件对象
  266.             const blob = new Blob(byteArrays, { type: fileData.type });
  267.             const file = new File([blob], fileData.name, {
  268.               type: fileData.type,
  269.               lastModified: new Date().getTime()
  270.             });
  271.             
  272.             console.log('文件名:', file.name); // 调试日志
  273.             console.log('文件类型:', file.type); // 调试日志
  274.             
  275.             // 设置文件到 input
  276.             const dt = new DataTransfer();
  277.             dt.items.add(file);
  278.             input.files = dt.files;
  279.             
  280.             // 触发 change 事件,让 Django admin 处理预览和其他逻辑
  281.             const event = new Event('change', { bubbles: true });
  282.             input.dispatchEvent(event);
  283.             
  284.           } catch (error) {
  285.             console.error('File processing error:', error);
  286.             window.FileUpload.postMessage(JSON.stringify({
  287.               'action': 'error',
  288.               'message': error.message
  289.             }));
  290.           }
  291.         };
  292.         // 3[监听变化]--->监听新添加的文件输入框
  293.         const observer = new MutationObserver(function(mutations) {
  294.           mutations.forEach(function(mutation) {
  295.             if (mutation.addedNodes.length) {
  296.               initFileInputs();
  297.             }
  298.           });
  299.         });
  300.         observer.observe(document.body, {
  301.           childList: true,
  302.           subtree: true
  303.         });
  304.         // 4[初始化]--->初始化现有的文件输入框
  305.         initFileInputs();
  306.       })();
  307.     ''');
  308.   }
  309.   // 设置文件上传通道
  310.   void _setupFileUploadChannel() {
  311.     webViewController.value?.addJavaScriptChannel(
  312.       'FileUpload',
  313.       onMessageReceived: (JavaScriptMessage message) async {
  314.         try {
  315.           final data = jsonDecode(message.message);
  316.          
  317.           if (data['action'] == 'pick') {
  318.             Map<String, dynamic>? fileData;
  319.             
  320.             if (data['type'] == 'image') {
  321.               // 显示图片来源选择对话框
  322.               final source = await Get.dialog<ImageSource>(
  323.                 CupertinoAlertDialog(
  324.                   title: Text('选择图片来源'),
  325.                   actions: [
  326.                     CupertinoDialogAction(
  327.                       child: Text('相机'),
  328.                       onPressed: () => Get.back(result: ImageSource.camera),
  329.                     ),
  330.                     CupertinoDialogAction(
  331.                       child: Text('相册'),
  332.                       onPressed: () => Get.back(result: ImageSource.gallery),
  333.                     ),
  334.                   ],
  335.                 ),
  336.               );
  337.               
  338.               if (source != null) {
  339.                 fileData = await _pickImage(source);
  340.               }
  341.             } else {
  342.               fileData = await _pickFile(data['accept'] ?? '');
  343.             }
  344.             
  345.             if (fileData != null) {
  346.               final js = '''
  347.                 window.handleFileSelection('${data['inputId']}', ${jsonEncode(fileData)});
  348.               ''';
  349.               await webViewController.value?.runJavaScript(js);
  350.             }
  351.           }
  352.         } catch (e) {
  353.           print('处理文件选择错误: $e');
  354.           Get.snackbar(
  355.             '错误',
  356.             '文件处理失败: ${e.toString()}',
  357.             snackPosition: SnackPosition.BOTTOM,
  358.             backgroundColor: Colors.red,
  359.             colorText: Colors.white,
  360.           );
  361.         }
  362.       },
  363.     );
  364.   }
  365.   // 添加图片选择方法
  366.   Future<Map<String, dynamic>?> _pickImage(ImageSource source) async {
  367.     try {
  368.       final XFile? image = await _imagePicker.pickImage(
  369.         source: source,
  370.         imageQuality: 85,
  371.       );
  372.       if (image != null) {
  373.         final bytes = await image.readAsBytes();
  374.         final fileName = image.name;
  375.         
  376.         return {
  377.           'name': fileName,
  378.           'data': base64Encode(bytes),
  379.           'type': 'image/${fileName.split('.').last}',
  380.           'size': bytes.length,
  381.           'extension': fileName.split('.').last
  382.         };
  383.       }
  384.     } catch (e) {
  385.       print('选择图片错误: $e');
  386.       Get.snackbar(
  387.         '错误',
  388.         '选择图片失败: ${e.toString()}',
  389.         snackPosition: SnackPosition.BOTTOM,
  390.         backgroundColor: Colors.red,
  391.         colorText: Colors.white,
  392.       );
  393.     }
  394.     return null;
  395.   }
  396.   // 处理文件选择
  397.   Future<Map<String, dynamic>?> _pickFile(String accept) async {
  398.     try {
  399.       final result = await FilePicker.platform.pickFiles(
  400.         type: FileType.any,
  401.         allowMultiple: false,
  402.         withData: true,
  403.         // 不限制扩展名,让系统处理
  404.         allowedExtensions: null,
  405.       );
  406.       if (result != null && result.files.isNotEmpty) {
  407.         final file = result.files.first;
  408.         if (file.bytes != null) {
  409.           // 添加文件大小限制
  410.           const maxSize = 2048 * 1024 * 1024;
  411.           if (file.size > maxSize) {
  412.             Get.snackbar('错误', '文件大小不能超过2048MB');
  413.             return null;
  414.           }
  415.           // 确保使用完整的文件名(包括扩展名)
  416.           final fileName = file.name;
  417.           final fileExtension = fileName.contains('.') ? fileName.split('.').last : '';
  418.           final mimeType = _getMimeType(fileName);
  419.           print('选择的文件名: $fileName'); // 调试日志
  420.           print('文件扩展名: $fileExtension'); // 调试日志
  421.           print('MIME类型: $mimeType'); // 调试日志
  422.           return {
  423.             'name': fileName,                    // 使用完整文件名
  424.             'data': base64Encode(file.bytes!),   // 文件数据
  425.             'type': mimeType,
  426.             'size': file.size,
  427.             'extension': fileExtension          // 显式包含扩展名
  428.           };
  429.         }
  430.       }
  431.     } catch (e) {
  432.       print('选择文件错误: $e');
  433.       Get.snackbar(
  434.         '错误',
  435.         '选择文件失败: ${e.toString()}',
  436.         snackPosition: SnackPosition.BOTTOM,
  437.         backgroundColor: Colors.red,
  438.         colorText: Colors.white,
  439.       );
  440.     }
  441.     return null;
  442.   }
  443.   // 添加 MIME 类型判断方法
  444.   String _getMimeType(String fileName) {
  445.     final ext = fileName.split('.').last.toLowerCase();
  446.     switch (ext) {
  447.       case 'mp4':
  448.         return 'video/mp4';
  449.       case 'mp3':
  450.         return 'audio/mpeg';
  451.       case 'wav':
  452.         return 'audio/wav';
  453.       case 'pdf':
  454.         return 'application/pdf';
  455.       case 'doc':
  456.         return 'application/msword';
  457.       case 'docx':
  458.         return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
  459.       case 'xls':
  460.         return 'application/vnd.ms-excel';
  461.       case 'xlsx':
  462.         return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  463.       case 'zip':
  464.         return 'application/zip';
  465.       case 'rar':
  466.         return 'application/x-rar-compressed';
  467.       default:
  468.         return 'application/octet-stream';
  469.     }
  470.   }
  471.   void handlePageStarted(String url) {
  472.     if (history.isEmpty || history[currentIndex.value] != url) {
  473.         if (history.isNotEmpty && currentIndex.value != history.length - 1) {
  474.             history.removeRange(currentIndex.value + 1, history.length);
  475.         }
  476.         history.add(url);
  477.         currentIndex.value = history.length - 1;
  478.     }
  479.     printDebugInfo();
  480.   }
  481.   // 打印调试信息【查看首次加载时是否正常】
  482.   void printDebugInfo() {
  483.     print('History: ${history.toString()}');
  484.     print('Current Index: ${currentIndex.value}');
  485.     print('Can Go Back: ${canGoBack()}');
  486.     print('Can Go Forward: ${canGoForward()}');
  487.   }
  488.   ///-------------------------------------------------------------------------------------------
  489.   
  490.   ///-------------------------------------------------------------------------------------------
  491.   
  492.   // 2. 文件上传进度监控
  493.   Future<void> _handleFileResult(Map<String, dynamic> result) async {
  494.     final progressController = RxDouble(0.0);
  495.     try {
  496.       // 显示进度对话框
  497.       Get.dialog(
  498.         Obx(() => CupertinoAlertDialog(
  499.           title: Text('上传中...'),
  500.           content: Column(
  501.             mainAxisSize: MainAxisSize.min,
  502.             children: [
  503.               CupertinoActivityIndicator(),
  504.               SizedBox(height: 10),
  505.               Text('${(progressController.value * 100).toStringAsFixed(1)}%'),
  506.             ],
  507.           ),
  508.         )),
  509.         barrierDismissible: false,
  510.       );
  511.       // 监听上传进度
  512.       if (result['status'] == 'progress') {
  513.         progressController.value = result['progress'] as double;
  514.       } else if (result['status'] == 'complete') {
  515.         await Future.delayed(Duration(milliseconds: 500)); // 稍微延迟以显示100%
  516.         Get.back(); // 关闭进度对话框
  517.         Get.snackbar('成功', '文件上传完成');
  518.       } else if (result['status'] == 'error') {
  519.         Get.back(); // 关闭进度对话框
  520.         Get.snackbar('错误', '上传失败: ${result['error']}');
  521.       }
  522.     } catch (e) {
  523.       Get.back(); // 关闭进度对话框
  524.       print('处理文件结果错误: $e');
  525.       Get.snackbar('错误', '处理文件失败: $e');
  526.     }
  527.   }
  528.   ///-------------------------------------------------------------------------------------------
  529.   /// 加载URL
  530.   Future<void> loadUrl(String url) async {
  531.     final controller = webViewController.value;
  532.     if (controller == null) {Get.snackbar('错误', 'WebView未初始化');return;}
  533.     try {
  534.       isLoading.value = true; // 开始加载时设置
  535.       await controller.loadRequest(Uri.parse(url));
  536.     } catch (e) {
  537.       isLoading.value = false; // 发生错误时设置为 false
  538.       print("加载url失败: ${e.toString()}");
  539.       print("加载url失败");
  540.       // Get.snackbar('错误', '页面加载失败: ${e.toString()}');
  541.       Get.snackbar(
  542.         '错误',
  543.         '页面加载失败: ${e.toString()}',
  544.         snackPosition: SnackPosition.BOTTOM,
  545.         backgroundColor: Colors.red,
  546.         colorText: Colors.white,
  547.         duration: Duration(seconds: 3),
  548.       );
  549.       rethrow;
  550.     }
  551.   }
  552.   Future<void> goBack() async {
  553.     if (!canGoBack()) {Get.back();return;}
  554.     try {
  555.         currentIndex.value--;
  556.         final prevUrl = history[currentIndex.value];
  557.         print('Going back to: $prevUrl');// 添加调试信息
  558.         print('History: ${history.toString()}');
  559.         print('New Index: ${currentIndex.value}');
  560.         printDebugInfo();
  561.         await loadUrl(prevUrl);
  562.     } catch (e) {
  563.         currentIndex.value++;
  564.         _showErrorSnackbar('页面加载失败', _getErrorMessage(e));
  565.         rethrow;
  566.     }
  567.   }
  568.   Future<void> goForward() async {
  569.     if (!canGoForward()) {Get.snackbar('提示', '已经是最后一个页面');return;}
  570.     try {
  571.         // 更新当前索引到下一个位置
  572.         currentIndex.value++;
  573.         final nextUrl = history[currentIndex.value];
  574.         print('Going forward to: $nextUrl'); // 添加调试信息
  575.         print('History: ${history.toString()}');
  576.         print('New Index: ${currentIndex.value}');
  577.         printDebugInfo();
  578.         await loadUrl(nextUrl);
  579.     } catch (e) {
  580.         currentIndex.value--;
  581.         _showErrorSnackbar('页面加载失败', _getErrorMessage(e));
  582.         rethrow;
  583.     }
  584.   }
  585.   String _getErrorMessage(Object e) {
  586.     if (e is SocketException) {
  587.         return '网络连接问题: 请检查您的网络连接。';
  588.     } else if (e is FormatException) {
  589.         return '无效的URL: ${e.message}';
  590.     } else if (e is HttpException) {
  591.         return 'HTTP错误: ${e.message}';
  592.     } else {
  593.         return '发生未知错误: ${e.toString()}';
  594.     }
  595.   }
  596.   /// 刷新当前页面
  597.   /// 检查 WebView 控制器是否已初始化,如果已初始化则刷新页面。
  598.   ///错误处理:在刷新过程中捕获并处理可能的错误。
  599.   Future<void> reload() async {
  600.     final controller = webViewController.value;
  601.     if (controller == null) {Get.snackbar('错误', 'WebView未初始化');return;}
  602.     try {
  603.       isLoading.value = true; // 开始刷新时设置
  604.       await controller.reload();
  605.     } catch (e) {
  606.       isLoading.value = false; // 出错时设置
  607.       // 定义具体的错误消息
  608.       String errorMessage;
  609.       if (e is SocketException) {
  610.         errorMessage = '网络连接问题: 请检查您的网络连接。';
  611.       } else if (e is FormatException) {
  612.         errorMessage = '无效的URL: ${e.message}';
  613.       } else if (e is HttpException) {
  614.         errorMessage = 'HTTP错误: ${e.message}';
  615.       } else {
  616.         errorMessage = '发生未知错误: ${e.toString()}';
  617.       }
  618.       // 显示错误消息
  619.       Get.snackbar(
  620.         '刷新失败',
  621.         errorMessage,
  622.         snackPosition: SnackPosition.BOTTOM,
  623.         backgroundColor: Colors.red,
  624.         colorText: Colors.white,
  625.         duration: Duration(seconds: 3),
  626.       );
  627.       rethrow;
  628.     }
  629.   }
  630.   /// 清理缓存:在 onClose() 方法中调用 webViewController.value?.clearCache();
  631.   /// 是为了在控制器被销毁时清理 WebView 的缓存数据。
  632.   /// 这有助于释放内存资源,减少应用程序的内存占用,从而提高性能和稳定性。
  633.   /// 清理缓存还可以确保在下次使用 WebView 时,加载的内容是最新的。
  634.   ///
  635.   /// 确保父类的 onClose() 被调用:通过调用 super.onClose();,
  636.   /// 确保在自定义的 onClose() 方法执行完之后,
  637.   /// 父类(GetX 的 GetxController)的 onClose() 方法也被调用。
  638.   /// 这样做是为了确保父类的清理工作或其他重要操作不会被忽略。
  639.   @override
  640.   void onClose() {
  641.     webViewController.value?.clearCache();
  642.     // 不要将 webViewController.value 设置为 null,除非有必要
  643.     // webViewController.value = null;
  644.     super.onClose();
  645.   }
  646.   // 添加缺失的错误处理方法
  647.   void _handleWebResourceError(WebResourceError error) {
  648.     isLoading.value = false;
  649.     hasError.value = true;
  650.    
  651.     String errorMessage = '加载失败: ${error.description}';
  652.     if (error.errorCode == -2) {
  653.       errorMessage = '网络连接失败,请检查网络设置';
  654.     } else if (error.errorCode == -6) {
  655.       errorMessage = '无法连接到服务器';
  656.     }
  657.    
  658.     Get.snackbar(
  659.       '页面错误',
  660.       errorMessage,
  661.       snackPosition: SnackPosition.BOTTOM,
  662.       backgroundColor: Colors.red,
  663.       colorText: Colors.white,
  664.       duration: Duration(seconds: 3),
  665.     );
  666.   }
  667.   // 添加缺失的错误提示方法
  668.   void _showErrorSnackbar(String title, String message) {
  669.     Get.snackbar(
  670.       title,
  671.       message,
  672.       snackPosition: SnackPosition.BOTTOM,
  673.       backgroundColor: Colors.red,
  674.       colorText: Colors.white,
  675.       duration: Duration(seconds: 3),
  676.     );
  677.   }
  678.   Timer? _debounceTimer;
  679.   void _debouncedInject() {
  680.     _debounceTimer?.cancel();
  681.     _debounceTimer = Timer(Duration(milliseconds: 300), () {
  682.       _injectFileUploadScript();
  683.     });
  684.   }
  685. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表