有几个服务器
有几个后台
直接通过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),
),
);
}
}