开始编写一个Flutter web 文件上传组件 WebFileUploadWidget

最近想用flutter作一个简单的web后台管理系统,须要文件上传功能,无赖网上找了半天资料,没有web版的上传组件,只有本身动手丰衣足食了。html

既然是web,那天然想到的就是须要dart:html这个包了,基本步骤主要有三个:web

  • 选择文件
  • 上传文件
  • UI设计

UI

不用设计了,将就看吧,咱们重在功能 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,
              ),
          ],
        ),
      ),
    );
  }
}
复制代码

功能算实现了,还有好多优化的地方,好比作一个统一平台的插件,支持移动端,你们以为有用就本身去优化吧😎

相关文章
相关标签/搜索