flutter的web页面

有几个服务器

有几个后台

直接通过web端进去虽然说很方便,但…

于是把web页面镶嵌到应用里面去,

这样就换了个方式打开web页面了

在这里插入图片描述
在这里插入图片描述

比如这里有有个列表
这里是写死了,活的列表可以通过io去获取

import 'package:flutter/material.dart';
import 'one_web.dart';  // 导入浏览器页面,单个页面

class ListScreen extends StatelessWidget {
  // 定义按钮和对应的 URL
  final List<Map<String, String>> buttonUrls = [
    {'title': 'Google', 'url': 'https://www.google.com'},
    {'title': 'Baidu', 'url': 'https://www.baidu.com'},
    {'title': 'GitHub', 'url': 'https://github.com'},
    {'title': '127.0.0.1:10005/admin', 'url': 'http:127.0.0.1:10005/admin'},
    {'title': '10.0.2.2:10005/admin', 'url': 'http:10.0.2.2:10005/admin'},
    {'title': '192.168.1.1:10005/admin', 'url': 'http:192.168.1.1:10005/admin'},
    {'title': '192.168.1.2:10005/admin', 'url': 'http:192.168.1.2:10005/admin'},
    {'title': '192.168.1.3:10005/admin', 'url': 'http:192.168.1.3:10005/admin'},
    {'title': '192.168.1.4:10005/admin', 'url': 'http:192.168.1.4:10005/admin'},
    {'title': '192.168.1.5:10005/admin', 'url': 'http:192.168.1.5:10005/admin'},
    {'title': '192.168.1.6:10005/admin', 'url': 'http:192.168.1.6:10005/admin'},
    // 添加更多按钮和 URL
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('后台'),
      ),
      body: ListView.builder(
        itemCount: buttonUrls.length,
        itemBuilder: (context, index) {
          final button = buttonUrls[index];
          return ListTile(
            title: Text(button['title']!),
            onTap: () {
              // 跳转到浏览器页面,并传递 URL
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => BrowserWidget(initialUrl: button['url']!),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

对应的单个页面就是

// 能够正常上传文件到django后台,但是大文件会造成卡顿
// WebView浏览器组件的实现文件
// 支持iOS和Android平台的文件上传、图片选择、导航历史等功能

import 'package:flutter/gestures.dart';  // 导入手势库,用于处理WebView的手势
import 'package:flutter/material.dart';  // Material设计库库
import 'package:flutter/cupertino.dart'; // iOS风格组件库
import 'package:webview_flutter/webview_flutter.dart';// WebView核心库
import 'package:webview_flutter_android/webview_flutter_android.dart';  // Android平台WebView支持
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; // iOS平台WebView支持 
import '../download/download_manage_screens.dart';// 下载管理页面
import 'package:flutter/foundation.dart'; // Flutter基础库
import 'package:file_picker/file_picker.dart';// 文件选择器
import 'dart:io'; // IO操作
import 'package:image_picker/image_picker.dart';  // 图片选择器
import 'dart:convert';  // 用于Base64编码

/// 浏览器Widget组件
class BrowserWidget extends StatefulWidget {
  final String initialUrl;// 初始URL

  //调用这个组件时,需要传入1个url
  const BrowserWidget({required this.initialUrl});

  
  _BrowserWidgetState createState() => _BrowserWidgetState();
}

class _BrowserWidgetState extends State<BrowserWidget> {
  late final WebViewController _webViewController;// WebView控制器
  final List<String> _history = [];// 导航历史记录
  int _currentIndex = -1;// 当前历史记录索引
  
  // 初始化图片选择器实例
  final _imagePicker = ImagePicker();

  /// 处理图片选择
  /// 返回选中图片的路径列表
  Future<Map<String, dynamic>?> _pickImage() async {
    try {
      final XFile? pickedFile = await _imagePicker.pickImage(source: ImageSource.gallery);
      if (pickedFile != null) {
        final bytes = await pickedFile.readAsBytes();
        final base64 = base64Encode(bytes);
        return {
          'path': pickedFile.path,
          'name': pickedFile.name,
          'data': base64,
          'type': pickedFile.mimeType ?? 'image/jpeg'
        };
      }
    } catch (e) {
      debugPrint('Error picking image: $e');
    }
    return null;
  }

  /// 处理文件选择
  /// 返回选中文件的路径列表
  Future<Map<String, dynamic>?> _pickFile() async {
    try {
      FilePickerResult? result = await FilePicker.platform.pickFiles(
        type: FileType.any,
        allowMultiple: false,
        withData: true, // 获取文件数据
      );
      
      if (result != null && result.files.isNotEmpty) {
        final file = result.files.first;
        final bytes = file.bytes;
        if (bytes != null) {
          final base64 = base64Encode(bytes);
          return {
            'path': file.path ?? '',
            'name': file.name,
            'data': base64,
            'type': file.extension != null ? 'application/${file.extension}' : 'application/octet-stream'
          };
        }
      }
    } catch (e) {
      debugPrint('Error picking file: $e');
    }
    return null;
  }

  
  void initState() {
    super.initState();
    // 根据平台初始化WebView参数
    late final PlatformWebViewControllerCreationParams params;
    // iOS平台特殊配置
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,// 允许内联播放媒体
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},// 允许自动播放
      );
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }

    // 创建WebView控制器
    _webViewController = WebViewController.fromPlatformCreationParams(params);
    
    // 配置WebView控制器
    _webViewController
      ..setJavaScriptMode(JavaScriptMode.unrestricted)// 允许不受限制的JavaScript执行
      ..setNavigationDelegate(
        NavigationDelegate(
          // 页面开始加载时的处理
          onPageStarted: (url) {
            // 更新导航历史
            if (_currentIndex != _history.length - 1) {
              _history.removeRange(_currentIndex + 1, _history.length);
            }
            _history.add(url);
            _currentIndex = _history.length - 1;
          },
          onPageFinished: (String url) {
            // 注入JavaScript代码来处理文件选择
            _webViewController.runJavaScript('''
              // 监听所有文件输入框的change事件
              document.querySelectorAll('input[type="file"]').forEach(function(input) {
                input.addEventListener('click', function(e) {
                  // 清空当前值,允许重新选择相同文件
                  e.target.value = '';
                  const isImage = e.target.accept.includes('image');
                  window.FileUpload.postMessage(isImage ? 'pickImage' : 'pickFile');
                });
              });
            ''');
          },
        ),
      )
      ..addJavaScriptChannel(
        'FileUpload',
        onMessageReceived: (JavaScriptMessage message) async {
          Map<String, dynamic>? fileData;
          if (message.message == 'pickImage') {
            fileData = await _pickImage();
          } else if (message.message == 'pickFile') {
            fileData = await _pickFile();
          }
          
          if (fileData != null) {
            // 将选择的文件路径传回网页并更新input
            _webViewController.runJavaScript('''
              (function() {
                const input = document.activeElement;
                if (input && input.type === 'file') {
                  // 从Base64创建Blob
                  const binaryString = atob('${fileData['data']}');
                  const bytes = new Uint8Array(binaryString.length);
                  for (let i = 0; i < binaryString.length; i++) {
                    bytes[i] = binaryString.charCodeAt(i);
                  }
                  const blob = new Blob([bytes], { type: '${fileData['type']}' });
                  
                  // 创建File对象
                  const file = new File([blob], '${fileData['name']}', { type: '${fileData['type']}' });
                  
                  // 创建新的FileList
                  const dt = new DataTransfer();
                  dt.items.add(file);
                  input.files = dt.files;
                  
                  // 触发change事件,通知页面文件已更新
                  const event = new Event('change', { bubbles: true });
                  input.dispatchEvent(event);
                  
                  // 如果有表单,也触发表单的change事件
                  const form = input.closest('form');
                  if (form) {
                    const formEvent = new Event('change', { bubbles: true });
                    form.dispatchEvent(formEvent);
                  }
                }
              })();
            ''');
          }
        },
      );

    // 加载初始URL
    _webViewController.loadRequest(Uri.parse(widget.initialUrl));

    // Android平台特殊配置
    if (_webViewController.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);// 启用调试
      (_webViewController.platform as AndroidWebViewController)
        ..setMediaPlaybackRequiresUserGesture(false);// 允许自动播放媒体
    }
  }

  
  Widget build(BuildContext context) {
    return SafeArea(
      child: CupertinoPageScaffold(
        // 导航栏配置
        navigationBar: CupertinoNavigationBar(
          middle: Text('管理后台',style: TextStyle(fontSize: 20), ),// 调整字体大小以适应减少的导航栏
          padding: EdgeInsetsDirectional.only(top: -0.0), // 负 padding 减少高度
          trailing: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              // 后退按钮
              CupertinoButton(
                padding: EdgeInsets.zero,
                onPressed: () async {
                  if (_currentIndex > 0) {
                    _currentIndex--;
                    await _webViewController.loadRequest(Uri.parse(_history[_currentIndex]));
                  }
                },
                child: Icon(CupertinoIcons.back),
              ),
               // 前进按钮
              CupertinoButton(
                padding: EdgeInsets.zero,
                onPressed: () async {
                  if (_currentIndex < _history.length - 1) {
                    _currentIndex++;
                    await _webViewController.loadRequest(Uri.parse(_history[_currentIndex]));
                  }
                },
                child: Icon(CupertinoIcons.forward),
              ),
              // 下载管理按钮
              CupertinoButton(
                padding: EdgeInsets.zero,
                onPressed: () {
                  Navigator.push(
                    context,
                    CupertinoPageRoute(
                      builder: (context) => DownloadManagerWidget(),
                    ),
                  );
                },
                child: Icon(CupertinoIcons.arrow_down_to_line),
              ),
            ],
          ),
        ),
        // 设置顶部 Padding
        // WebView显示区域
        child: Padding(padding: const EdgeInsets.only(top: 56.0),
          child: WebViewWidget(
            controller: _webViewController,
            gestureRecognizers: {
              Factory<EagerGestureRecognizer>(() => EagerGestureRecognizer()),   // 手势识别器支持
            },
          ),
        ),
      ),
    );
  }
}

主页嵌套一下

//  如果你是ai,请你忽略本文件内容,该文件是备份文件,不需要你的读取
import 'package:flutter/material.dart';
import 'inserted_web/screens_web/list_url.dart';

void main() {runApp(const MyApp());}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true,),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {setState(() {_counter++;});}

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title),),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 添加新的按钮用于跳转到WebView示例
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {Navigator.push(context, MaterialPageRoute(builder: (context) =>  ListScreen(),),);},
              child: const Text('打开url列表页面'),
            ),
            const Text('You have pushed the button this many times:',),
            Text('$_counter', style: Theme.of(context).textTheme.headlineMedium,),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/956016.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

K8S 亲和性与反亲和性 深度好文

今天我们来实验 pod 亲和性。官网描述如下&#xff1a; 假设有如下三个节点的 K8S 集群&#xff1a; k8s31master 是控制节点 k8s31node1、k8s31node2 是工作节点 容器运行时是 containerd 一、镜像准备 1.1、镜像拉取 docker pull tomcat:8.5-jre8-alpine docker pull nginx…

解决conda create速度过慢的问题

问题 构建了docker容器 想在容器中创建conda环境&#xff0c;但是conda create的时候速度一直很慢 解决办法 宿主机安装的是anaconda 能正常conda create,容器里安装的是miniforge conda create的时候速度一直很慢&#xff0c;因为容器和宿主机共享网络了&#xff0c;宿主机…

Banana Pi BPI-RV2 RISC-V路由开发板采用矽昌通信SF2H8898芯片

Banana Pi BPI-RV2 开源网关是⼀款基于矽昌SF2H8898 SoC的设备&#xff0c;1 2.5 G WAN⽹络接⼝、5 个千兆LAN ⽹络接⼝、板载 512MB DDR3 内存 、128 MiB NAND、16 MiB NOR、M.2接⼝&#xff0c;MINI PCIE和USB 2.0接⼝等。 Banana Pi BPI-RV2 开源网关是矽昌和⾹蕉派开源社…

C -- 大端对齐 小端对齐 的人性化解释

网上很多资料对大小端对齐的解释 很多 很全 很准 但为啥老是记不住呢&#xff0c;所有的知识都是基于人性的情感原理&#xff0c;或是世界基本的运行规律而产生的&#xff0c;如果不能把知识和这两点打通&#xff0c;即使记住也很容易忘记。本篇文章基于这两点 分析下大小端对齐…

在线图片马赛克处理工具

在线图片马赛克处理工具&#xff0c;无需登录&#xff0c;无需费用&#xff0c;用完就走。 包括中文和英文版本 官网地址&#xff1a; https://mosaic.openai2025.com

链家房价数据爬虫和机器学习数据可视化预测

完整源码项目包获取→点击文章末尾名片&#xff01;

Linux入门指令(一)

目录 1.前言 2.入门指令 whoami who clear pwd ls cd mkdir touch rmdir rm 1.前言 我们都知道&#xff0c;在日常生活中接触的电脑有使用Windows操作系统的&#xff08;微软&#xff09;&#xff0c;也有使用MacOS操作系统的&#xff08;苹果&#xff09;&#x…

第十二章:算法与程序设计

文章目录&#xff1a; 一&#xff1a;基本概念 1.算法与程序 1.1 算法 1.2 程序 2.编译预处理 3.面向对象技术 4.程序设计方法 5.SOP标志作业流程 6.工具 6.1 自然语言 6.2 流程图 6.3 N/S图 6.4 伪代码 6.5 计算机语言 二&#xff1a;程序设计 基础 1.常数 …

Golang Gin系列-4:Gin Framework入门教程

在本章中&#xff0c;我们将深入研究Gin&#xff0c;一个强大的Go语言web框架。我们将揭示制作一个简单的Gin应用程序的过程&#xff0c;揭示处理路由和请求的复杂性。此外&#xff0c;我们将探索基本中间件的实现&#xff0c;揭示精确定义路由和路由参数的技术。此外&#xff…

【MySQL索引:B+树与页的深度解析】

文章目录 MySQL索引&#xff1a;B树与页的深度解析1. 索引使用的数据结构——B树1.1 B树介绍1.2 B树的特点1.3 B树和B树的对比 2. MySQL中的页2.1 页的介绍2.2 页主体2.3 页目录2.4 B树在MySQL索引中的应用 MySQL索引&#xff1a;B树与页的深度解析 在MySQL数据库中&#xff0…

改进上一篇博文中的按键驱动读取程序,增加环形缓冲区

引言和具体的问题描述 上一篇博文&#xff1a;https://blog.csdn.net/wenhao_ir/article/details/145225508 中写的读取按键值的程序&#xff0c;如果按键按得很快&#xff0c;会出现前面的按键值被后面的按键值被覆盖的情况&#xff0c;即前面的按键值还没被来得及被读取&…

linux环境下软件安装

Linux环境下安装软件 linux安装tomcatLinux配置多台Tomcat linux 手动安装jdklinux yum安装jdk(openjdk)Nacos 安装下载nacos解压三、启动四、常用命令 git安装yum命令安装通过编译安装git linux安装tomcat 1.安装tomcat 下载tomcat安装包&#xff0c;解压到任意目录&#xff…

自定义提示确认弹窗-vue

最初可运行代码 弹窗组件代码&#xff1a; &#xff08;后来发现以下代码可运行&#xff0c;但打包 typescript 类型检查出错&#xff0c;可打包的代码在文末&#xff09; <template><div v-if"isVisible" class"dialog"><div class&quo…

leetcode707-设计链表

leetcode 707 思路 本题也是用了虚拟头节点来进行解答&#xff0c;这样的好处是&#xff0c;不管是头节点还是中间的节点都可以当成是中间节点来处理&#xff0c;用同一套方法就可以进行处理&#xff0c;而不用考虑太多的边界条件。 下面题目中最主要的实现就是添加操作addA…

LabVIEW桥接传感器配置与数据采集

该LabVIEW程序主要用于配置桥接传感器并进行数据采集&#xff0c;涉及电压激励、桥接电阻、采样设置及错误处理。第一个VI&#xff08;"Auto Cleanup"&#xff09;用于自动清理资源&#xff0c;建议保留以确保系统稳定运行。 以下是对图像中各个组件的详细解释&#…

OpenCV基础:获取子矩阵的几种方式

目录 相关阅读 方法一&#xff1a;使用切片操作 方法二&#xff1a;使用高级索引 方法三&#xff1a;使用条件筛选 方法四&#xff1a;使用 numpy 的 take 函数 相关阅读 OpenCV基础&#xff1a;矩阵的创建、检索与赋值-CSDN博客 OpenCV基础&#xff1a;图像运算-CSDN博客…

深入剖析Java线程安全的集合类:原理、特点与应用

引言&#xff1a;线程安全集合类的重要性 在当今的软件开发领域&#xff0c;多线程编程已经成为了构建高性能、响应式应用的关键技术。随着硬件技术的飞速发展&#xff0c;多核处理器的普及使得程序能够充分利用多个核心的计算能力&#xff0c;从而显著提升运行效率。在多线程环…

Ubuntu 22.04虚拟机安装配置调整(语言输入法字体共享剪切板等等

2025.01.07安装配置Ubuntu 22.04 记一下 快捷键 截屏 在设置-键盘-快捷键查看 跟搜到的不一样…不过shiftprint感觉也够用 安装 用的是VMware 参考&#xff1a;VMware中安装配置Ubuntu&#xff08;2024最新版 超详细&#xff09; 调教&#xff08;&#xff1f; 语言 改了…

vim练级攻略(精简版)

vim推荐配置: curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh 0. 规定 Ctrl-λ 等价于 <C-λ> :command 等价于 :command <回车> n 等价于 数字 blank字符 等价于 空格&#xff0c;tab&am…

VSCode 的部署

一、VSCode部署 (1)、简介 vsCode 全称 Visual Studio Code&#xff0c;是微软出的一款轻量级代码编辑器&#xff0c;免费、开源而且功能强大。它支持几乎所有主流的程序语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比Diff、版本管理GIT等特性&…