最近想用
flutter
作一个简单的web后台管理系统,须要文件上传功能,无赖网上找了半天资料,没有web版的上传组件,只有本身动手丰衣足食了。html
既然是web,那天然想到的就是须要dart:html
这个包了,基本步骤主要有三个:web
不用设计了,将就看吧,咱们重在功能 api
首先导入包bash
import 'dart:html' as html;
import 'package:flutter/material.dart';
复制代码
组件上面部分,点击弹出选择文件框app
var picker = InkWell(
hoverColor: Colors.blue[50],
splashColor: Colors.blue[10],
onTap: _selectFile,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Text(
'Click to select file',
style: TextStyle(fontWeight: FontWeight.w500, color: Colors.grey),
),
),
);
复制代码
重点来了,下面是如何选择文件的代码:async
_selectFile() {
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.click();
uploadInput.onChange.listen((e) {
final files = uploadInput.files;
var fileItem = UploadFileItem(files[0]);
setState(() {
_files.add(fileItem);
});
});
}
复制代码
这里获取到文件是一个dart:html
里面的一个File
实例,自定义一个UploadFileItem
包装一下:ide
import 'dart:html' as html;
enum FileStatus {
normal,
uploading,
success,
fail,
}
class UploadFileItem {
html.File file;
FileStatus fileStatus;
double progress = 0.0;
UploadFileItem(this.file) {
fileStatus = FileStatus.normal;
}
}
复制代码
到这里,如何选择文件咱们已经会了,而且获得了文件对象,那么如何实现上传呢?优化
网上查询了好多资料,基本上都是用dart:html
里面的FileReader
读取文件后再经过http
或者dio
这个包上传文件内容,可是要把文件内容都读取出来,真的好么?我要上传的多是大文件,试了试,果然卡得起飞😂。ui
😔就快要放弃了this
最后仍是在StackOverflow
找到了一个条有用的信息
原来解决办法仍是用dart:html
,这个包好强大有没有,竟然有一个HttpRequest
,使用还黑方便😃
直接看代码:
final html.FormData formData = html.FormData()..appendBlob('file', file);
var url = 'http://localhost:8088/api/file/upload';
html.HttpRequest.request(
url,
method: 'POST',
sendData: formData,
onProgress: onProgress,
).then((httpRequest) {
handleRequest(httpRequest);
}).catchError((e) {
setState(() {
widget.fileItem.fileStatus = FileStatus.fail;
});
});
复制代码
啊,胜利的曙光终于来了,写完代码,跑一遍,速度够快,运行很流畅。
完工!
最后附上源码,总共三个文件:
file_item.dart
import 'dart:html' as html;
enum FileStatus {
normal,
uploading,
success,
fail,
}
class UploadFileItem {
html.File file;
FileStatus fileStatus;
double progress = 0.0;
UploadFileItem(this.file) {
fileStatus = FileStatus.normal;
}
}
复制代码
upload_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:html' as html;
import 'package:web_file_upload/file_item.dart';
import 'package:web_file_upload/file_item_widget.dart';
class UploadWidget extends StatefulWidget {
@override
_UploadWidgetState createState() => _UploadWidgetState();
}
class _UploadWidgetState extends State<UploadWidget> {
List<UploadFileItem> _files = [];
_selectFile() {
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.click();
uploadInput.onChange.listen((e) {
final files = uploadInput.files;
var fileItem = UploadFileItem(files[0]);
setState(() {
_files.add(fileItem);
});
});
}
@override
Widget build(BuildContext context) {
var picker = InkWell(
hoverColor: Colors.blue[50],
splashColor: Colors.blue[10],
onTap: _selectFile,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Text(
'Click to select file',
style: TextStyle(fontWeight: FontWeight.w500, color: Colors.grey),
),
),
);
var filesList = _files.map(_fileItem);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[picker, SizedBox(height: 10.0), ...filesList],
);
}
_delete(UploadFileItem fileItem) {
print('delete ${fileItem.file.name}');
setState(() {
_files.remove(fileItem);
});
}
Widget _fileItem(UploadFileItem fileItem) {
return FileItemWidget(
fileItem: fileItem,
onDeleteFile: _delete,
);
}
}
复制代码
file_item_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:html' as html;
import 'package:web_file_upload/file_item.dart';
typedef void DeleteFile(UploadFileItem fileItem);
class FileItemWidget extends StatefulWidget {
final UploadFileItem fileItem;
final DeleteFile onDeleteFile;
const FileItemWidget({Key key, this.fileItem, this.onDeleteFile}) : super(key: key);
@override
_FileItemWidgetState createState() => _FileItemWidgetState();
}
class _FileItemWidgetState extends State<FileItemWidget> {
@override
void initState() {
super.initState();
// _readFile();
_sendFormData(widget.fileItem.file);
}
_sendFormData(final html.File file) async {
setState(() {
widget.fileItem.fileStatus = FileStatus.uploading;
});
final html.FormData formData = html.FormData()..appendBlob('file', file);
handleRequest(html.HttpRequest httpRequest) {
print('upload resp: ${httpRequest.responseText}');
switch (httpRequest.status) {
case 200:
setState(() {
widget.fileItem.fileStatus = FileStatus.success;
});
return;
default:
setState(() {
widget.fileItem.fileStatus = FileStatus.fail;
});
break;
}
}
onProgress(e) {
print('upload sending: ${e.loaded} ${e.total}');
double progress = e.lengthComputable ? (e.loaded * 100 ~/ e.total) / 100.0 : e.loaded / 100.0;
print('upload sending: $progress');
if (widget.fileItem.progress == progress) return;
setState(() {
widget.fileItem.progress = progress;
});
}
// var url = 'https://www.mocky.io/v2/5cc8019d300000980a055e76';
var url = 'http://localhost:8088/api/file/upload';
html.HttpRequest.request(
url,
method: 'POST',
sendData: formData,
onProgress: onProgress,
).then((httpRequest) {
handleRequest(httpRequest);
}).catchError((e) {
setState(() {
widget.fileItem.fileStatus = FileStatus.fail;
});
});
// final html.HttpRequest httpRequest = html.HttpRequest();
// httpRequest
// ..onProgress.listen(onProgress)
// ..onLoadEnd.listen((e) {
// handleRequest(httpRequest);
// })
// ..open('POST', url)
// ..send(formData);
}
@override
Widget build(BuildContext context) {
UploadFileItem fileItem = widget.fileItem;
var prefixIcon;
if (fileItem.fileStatus == FileStatus.uploading || fileItem.fileStatus == FileStatus.normal) {
prefixIcon = CupertinoActivityIndicator(
animating: true,
radius: 8.0,
);
} else {
prefixIcon = Icon(
Icons.attach_file,
color: Colors.lightBlue,
size: 20.0,
);
}
return InkWell(
hoverColor: Colors.blue[100],
onTap: () {},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
prefixIcon,
SizedBox(width: 8.0),
Expanded(
child: Text(
'${fileItem.file.name}',
style: TextStyle(color: fileItem.fileStatus == FileStatus.fail ? Colors.red : Colors.black87, fontSize: 18.0),
),
),
IconButton(
padding: EdgeInsets.all(2.0),
iconSize: 18.0,
icon: Icon(Icons.delete),
onPressed: () {
widget.onDeleteFile(fileItem);
},
color: Colors.red[500],
),
],
),
if (fileItem.fileStatus == FileStatus.uploading)
LinearProgressIndicator(
value: fileItem.progress,
backgroundColor: Colors.transparent,
),
],
),
),
);
}
}
复制代码
功能算实现了,还有好多优化的地方,好比作一个统一平台的插件,支持移动端,你们以为有用就本身去优化吧😎