记一次使用vue+element+webUpload 实现分片上传的笔记

 这个是添加的dialog确认已经实现,但是偶尔会出现分片不按照顺序上传也就是偶尔会出现最后一片先上传,导致的问题就是文件上传前后的MD5不一致,后续视频的压缩解码等操作出现异常。所以再后台做了一些判断。

(请仅作参考,此方法还有很多优化的地方)

<template>
  <el-dialog :visible.sync="show" title="后台上传短视频信息" width="800px" @close="onDialogClose('theForm')" :close-on-click-modal="false" :append-to-body="true">
    <div v-loading="loading.form" class="form-wrapper" :element-loading-text="uploadProgressTip">
      <el-form size="small" :model="model" label-position="left" label-width="120px" ref="theForm" :rules="rules">
        <el-form-item label="马甲账号" prop="vestId">
          <el-select v-model="model.vestId" placeholder="选择马甲账号" filterable>
            <el-option v-for="(item, index) in vests" :key="index" :value="item.vestId" :label="item.nickname"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="视频简介" prop="textIntro">
          <el-input v-model.trim="model.textIntro" type="textarea"></el-input>
        </el-form-item>
        <el-form-item label="视频类型" prop="typeId">
          <el-select v-model="model.typeId" placeholder="视频类型">
            <template v-for="(item, index) in types">
              <el-option :label="item.videoTypeName" :value="item.videoTypeId" :key="index"></el-option>
            </template>
          </el-select>
        </el-form-item>
        <el-form-item label="视频话题" prop="selectedTopics">
          <el-select v-model="model.selectedTopics" placeholder="键入搜索视频话题, 可多选" multiple filterable remote :remote-method="remoteSearchVideoTopic" :loading="loading.select.topic">
            <template v-for="(item, index) in videoTopicList">
              <!-- 多选时, 设置value为object -->
              <el-option :label="item.tname" :value="{ topicId: item.topicId, value: item.tname }" :key="index"></el-option>
            </template>
          </el-select>
        </el-form-item>
        <el-form-item label="所属国家" prop="country">
          <el-select v-model="model.country" placeholder="国家" filterable>
            <el-option v-for="(item, index) in countries" :key="index" :value="item.coding" :label="item.countryName"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="视频文件" prop="url">
          <div id="pick" ref="videoPicker" :style="pickContainerStyle">选择视频文件</div>
          <p class="selected-video-name" v-if="model.url !== ''">{{ model.url }}</p>
        </el-form-item>
        <el-form-item label="视频封面" prop="img">
          <el-upload :action="uploadAction" :on-change="onImgFileChange" :file-list="imgFileList" :before-remove="onFileRemove" accept="image/*" ref="create-upload" :auto-upload="false">
            <el-button size="small" type="primary">点击选择新图片</el-button>
            <div slot="tip" class="el-upload__tip">只能上传图片类型文件(jpg/png等)</div>
          </el-upload>
        </el-form-item>
      </el-form>
    </div>
    <span slot="footer">
      <el-button @click="show = false" size="small">取 消</el-button>
      <el-button type="primary" @click="doCreate" size="small" :loading="btnLoading">确 定</el-button>
    </span>
  </el-dialog>
</template>
<script>
import { searchVideoTopicListByName } from '../../../api/fuzzy-search';
import { debounce } from 'lodash';

export default {
  name: 'video-info-create-dialog',
  data() {
    const token = localStorage.getItem('token');
    return {
      pickContainerStyle: {
        width: '120px',
        height: '50px'
      },
      uploader: null,
      uploadProgressTip: '',
      videoTopicList: [],
      show: false,
      loading: {
        form: false,
        select: {
          topic: false
        }
      },
      model: {
        vestId: null,
        textIntro: null,
        typeId: null,
        selectedTopics: [],
        topicIds: [],
        topicNames: [],
        country: null,
        url: null,
        img: null,
        video: null,
        cover: null,
        chunkSize: 5242880,
        fileMd5: null,
        timestamp: Date.now()
      },
      rules: {
        vestId: [
          { required: true, trigger: 'change', message: '必须选择一个马甲账号' }
        ],
        textIntro: [
          { required: true, trigger: 'change', message: '视频简介不能为空' }
        ],
        country: [
          {
            required: true,
            trigger: ['change', 'blur'],
            message: '所属国家不能为空'
          }
        ],
        typeId: [
          {
            required: true,
            trigger: ['change', 'blur'],
            message: '视频类型不能为空'
          }
        ],
        url: [
          {
            required: true,
            trigger: ['change', 'blur'],
            message: '视频文件不能为空'
          }
        ]
      },
      imgFileList: [],
      videoFileList: [],
      vests: [],
      formLoading: true,
      btnLoading: false,
      // 文件上传的url
      uploadAction: `/faceshow-admin/api/fileupload?token=${token}&temp=${Date.now()}`
    };
  },
  props: {
    types: {
      type: Array,
      required: true
    },

    countries: {
      type: Array,
      required: true
    },
    accept: {
      type: String,
      default: 'video'
    },
    // 上传地址
    url: {
      type: String,
      default: 'faceshow-admin/video/info/save'
    },
    // 上传最大数量 默认为100
    fileNumLimit: {
      type: Number,
      default: 100
    },
    // 大小限制 默认2M
    fileSingleSizeLimit: {
      type: Number,
      default: 153600000
    },
    // 生成formData中文件的key,下面只是个例子,具体哪种形式和后端商议
    keyGenerator: {
      type: Function,
      default(file) {
        const currentTime = new Date().getTime();
        const key = `${currentTime}.${file.name}`;
        return key;
      }
    },
    multiple: {
      type: Boolean,
      default: false
    },
    // 上传按钮ID
    uploadButton: {
      type: String,
      default: ''
    }
  },
  mounted() {
    this.initWebUpload();
  },
  methods: {
    initWebUpload() {
      setTimeout(() => {
        /* eslint-disable no-undef */
        this.uploader = WebUploader.create({
          auto: false, // 选完文件后,是否自动上传
          swf: '/static/lib/webuploader/Uploader.swf', // swf文件路径
          server: this.url + '?token=' + this.$store.state.user.token, // 文件接收服务端
          pick: {
            id: this.$refs.videoPicker
          },
          accept: this.getAccept(this.accept), // 允许选择文件格式。
          threads: 5,
          fileNumLimit: this.fileNumLimit, // 限制上传个数
          fileSingleSizeLimit: this.fileSingleSizeLimit, // 限制单个上传图片的大小
          formData: this.model, // 上传所需参数
          chunked: true, // 分片上传
          chunkSize: 5242880, // 分片大小
          chunkRetry: false,
          duplicate: false, // 重复上传
          fileVal: 'video'
        });
        // 当有文件被添加进队列的时候,添加到页面预览
        this.uploader.on('fileQueued', file => {
          // 只是为了校验表单有效性
          this.model.url = file.name;
          this.$refs.theForm.validateField('url');
        });
        this.uploader.on('uploadStart', file => {
          // 在这里可以准备好formData的数据
          // this.uploader.options.formData.key = this.keyGenerator(file);
        });
        // 文件上传过程中创建进度条实时显示。
        this.uploader.on('uploadProgress', (file, percentage) => {
          this.uploadProgressTip = `正在上传: ${Math.floor(
            percentage * 100
          )} %`;
        });
        this.uploader.on('uploadSuccess', (file, response) => {
          const serverResp = response._raw ? JSON.parse(response._raw) : null;
          if (serverResp && serverResp.code !== 0) {
            this.$message.error(
              `视频上传失败: ${serverResp.msg}, 请重新点击上传按钮`
            );
            this.btnLoading = false;
            this.loading.form = false;
          } else {
            this.$message.info('视频已提交到后台处理');
            this.show = false;
            this.$emit('done');
          }
        });
        this.uploader.on('uploadError', (file, reason) => {
          this.$message.error('上传失败!!!');
          this.btnLoading = false;
          this.loading.form = false;
        });
        this.uploader.on('error', type => {
          let errorMessage = '';
          if (type === 'F_EXCEED_SIZE') {
            errorMessage = `文件大小不能超过${this.fileSingleSizeLimit /
              (1024 * 1000)}M`;
          } else if (type === 'Q_EXCEED_NUM_LIMIT') {
            errorMessage = '文件上传已达到最大上限数';
          } else {
            errorMessage = `上传出错!请检查后重新上传!错误代码${type}`;
          }
          console.error(errorMessage);
          this.$emit('error', errorMessage);
        });
        this.uploader.on('uploadComplete', file => {
          console.log(`uploadComplete ${file ? 'file 为空' : file.name}`);
          this.$emit('complete', file);
        });

        console.log(this.uploader);
        let containerStyle = {
          width: '120px',
          height: '50px'
        };

        this.pickContainerStyle = containerStyle;
      }, 500);
    },
    upload(file) {
      this.uploader.upload(file);
    },
    stop(file) {
      this.uploader.stop(file);
    },
    // 取消并中断文件上传
    cancelFile(file) {
      this.uploader.cancelFile(file);
    },
    // 在队列中移除文件
    removeFile(file, bool) {
      this.uploader.removeFile(file, bool);
    },
    getAccept(accept) {
      switch (accept) {
        case 'text':
          return {
            title: 'Texts',
            exteensions: 'doc,docx,xls,xlsx,ppt,pptx,pdf,txt',
            mimeTypes: '.doc,docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt'
          };
        case 'video':
          return {
            title: 'Videos',
            exteensions: 'mp4',
            mimeTypes: '.mp4'
          };
        case 'image':
          return {
            title: 'Images',
            exteensions: 'gif,jpg,jpeg,bmp,png',
            mimeTypes: '.gif,.jpg,.jpeg,.bmp,.png'
          };
        default:
          return accept;
      }
    },
    onImgFileChange(file, fileList) {
      if (file) {
        this.model.cover = file.raw;
        // 只是为了表单验证通过
        this.model.img = file.name;
        this.imgFileList = [].concat({ name: file.name });
      } else {
        this.model.cover = null;
        this.model.img = null;
        this.imgFileList = [];
      }
    },
    remoteSearchVideoTopic: debounce(function(query) {
      // 搜索视频话题
      if (query === null || query.trim() === '') {
        return;
      }

      this.loading.select.topic = true;
      searchVideoTopicListByName(query.trim())
        .then(({ data }) => {
          this.videoTopicList = data.list;
          this.loading.select.topic = false;
        })
        .catch(error => {});
    }, 500),
    doCreate() {
      // 验证表单有效性
      this.$refs.theForm.validate(valid => {
        if (valid) {
          this.btnLoading = true;
          this.loading.form = true;

          this.model.topicIds = this.model.selectedTopics.map(
            item => item.topicId
          );
          this.model.topicNames = this.model.selectedTopics.map(
            item => item.value
          );

          // 由于初始化uploader的时候, model中的属性还没有赋值, 在上传之前需要再赋值一次
          this.model.timestamp = Date.now();
          console.log(this.model.timestamp);
          this.uploader.options.formData = this.model;
          this.uploadProgressTip = '正在计算文件MD5';
          this.uploader.md5File(this.uploader.getFiles()[0]).then(val => {
            this.model.fileMd5 = val;
            this.uploader.upload();
          });
        } else {
          return false;
        }
      });
    },
    showDialog(vests) {
      this.show = true;
      this.vests = vests;
      this.initWebUpload();
    },
    onFileRemove(file, fileList) {
      // 由于图片为必选项, 所以这里禁止移除已经上传的图片文件
      this.$message({
        type: 'warning',
        message: '图片文件不可为空!'
      });
      return false;
    },
    onDialogClose(formRef) {
      this.loading.form = false;
      this.btnLoading = false;
      this.videoFileList = [];
      this.$refs[formRef].resetFields();
      this.$refs['create-upload'].clearFiles();
      this.imgFileList = [];
      this.uploader.destroy();
    }
  }
};
</script>

<style scoped>
/* ----------------Reset Css--------------------- */
//css有需要的话再文章的最后面可以复制
</style>

后台代码

 @SysLog("后台上传视频")
    @PostMapping("/save")
    @RequiresPermissions("video:info:save")
    public R chunkedUpload(final MultipartFile video,
                           final MultipartFile cover,
                           final Integer vestId,
                           final String textIntro,
                           final Integer typeId,
                           final Integer[] topicIds,
                           final String[] topicNames,
                           final String country,
                           final String token,
                           final String id,
                           final String name,
                           final String type,
                           final Date lastModifiedDate,
                           final Integer size,
                           final Integer chunks,
                           final Integer chunk,
                           final String fileMd5,
                           final long chunkSize,
                           final long timestamp) throws IOException {
        logger.info("当前分片的文件 video = {}", video);
        logger.info("当前分片的token token = {}", token);
        logger.info("当前分片的参数 id = {}", id);
        logger.info("当前分片的文件名 name = {}", name);
        logger.info("当前分片的文件类型 type = {}", type);
        logger.info("当前分片的文件最后修改日期 lastModifiedDate = {}", lastModifiedDate);
        logger.info("当前分片的文件体积(字节) size = {}", size);
        logger.info("分片的总片数 chunks = {}", chunks);
        logger.info("当前分片片数 chunk = {}", chunk);
        logger.info("当前分片的文件大小 chunkSize = {}", chunkSize);
        logger.info("文件md5 fileMd5 = {}", fileMd5);
        if (video == null || video.getSize() == 0) {
            return R.error("视频文件不能为空");
        }
        // 视频最大150MB
        if (video.getSize() > MAX_VIDEO_FILE_SIZE_150_MB) {
            return R.error("视频文件大小不得超过150MB");
        }

        //获取文件名
        String originalFilename = video.getOriginalFilename();
        logger.info("分片上传文件的名字 originalFilename = {}", originalFilename);

        RandomAccessFile raFile = null;
        BufferedInputStream inputStream = null;
        File dirFile = null;
        try {
            dirFile = new File(fileTemp, originalFilename);
            //以读写的方式打开目标文件
            raFile = new RandomAccessFile(dirFile, "rw");
            logger.info("分片上传文件位置 raFile.length = {}", raFile.length());

            long position = 0;
            if (chunks != null && chunk != null) {
                position = chunk * chunkSize;
            }

            logger.info("分片上传文件实际开始位置 chunk*chunkSize = {}", position);
            raFile.seek(position);

            inputStream = new BufferedInputStream(video.getInputStream());

            byte[] buf = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buf)) != -1) {
                raFile.write(buf, 0, length);
            }

            redisTemplate.opsForHash().increment("video_upload_file_chunk", originalFilename + "_" + vestId + "_" + timestamp, 1);

        } catch (Exception e) {
            throw new IOException(e.getMessage());
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (raFile != null) {
                    raFile.close();
                }
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }
        }

        final Object rcvedChunk = redisTemplate.opsForHash().get("video_upload_file_chunk", originalFilename + "_" + vestId + "_" + timestamp);

        if (chunks != null && Integer.parseInt(rcvedChunk.toString()) < chunks) {
            return R.ok();
        }

        logger.info("视频文件已全部接受完毕: filename = {}, fileMd5 = {}, chunks = {}", originalFilename, fileMd5, chunks);

        try (
                FileInputStream fis = new FileInputStream(fileTemp + "/" + originalFilename);
        ) {

            String md5Hex = DigestUtils.md5Hex(fis);

            logger.info("保存文件的MD5 = {}", md5Hex);

            if (!Objects.equals(md5Hex, fileMd5)) {
                FileUtils.deleteQuietly(dirFile);
                logger.error("MD5不一致");
                return R.error("完整性校验失败, 请重新上传");
            }
        } finally {
            redisTemplate.opsForHash().delete("video_upload_file_chunk", originalFilename + "_" + vestId + "_" + timestamp);
        }

        final CreateVideoInfoReq req = new CreateVideoInfoReq();

        req.setCountry(country);
        req.setCover(cover);
        req.setTextIntro(textIntro);

        // 如果选择了视频话题
        if (topicIds != null && topicIds.length > 0
                && topicNames != null && topicNames.length > 0
                && topicIds.length == topicNames.length) {

            final VideoTopic[] topics = new VideoTopic[topicIds.length];

            for (int i = 0; i < topicIds.length; i++) {
                final VideoTopic topic = new VideoTopic();
                topic.setTopicId(topicIds[i]);
                topic.setTname(topicNames[i]);

                topics[i] = topic;
            }

            req.setTopics(topics);
        }

        req.setVestId(vestId);
        req.setTypeId(typeId);
        req.setVideoFile(dirFile);

        videoInfoService.adminUpload(req, getUser());
        return R.ok();

    }

 第一个代码片段中的css

html,
body,
p,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section,
summary,
time,
mark,
audio,
video,
input {
  margin: 0;
  padding: 0;
  border: none;
  outline: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}

html,
body,
form,
fieldset,
p,
p,
h1,
h2,
h3,
h4,
h5,
h6 {
  -webkit-text-size-adjust: none;
}

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}

body {
  font-family: arial, sans-serif;
}

ol,
ul {
  list-style: none;
}

blockquote,
q {
  quotes: none;
}

blockquote:before,
blockquote:after,
q:before,
q:after {
  content: '';
  content: none;
}

ins {
  text-decoration: none;
}

del {
  text-decoration: line-through;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

/* ------------ */
#wrapper {
  width: 100%;
  margin: 0 auto;
  height: 35px;
}

.img-preview {
  width: 160px;
  height: 90px;
  margin-top: 1em;
  border: 1px solid #ccc;
}

.cropper-wraper {
  position: relative;
}

.upload-btn {
  background: #ffffff;
  border: 1px solid #cfcfcf;
  color: #565656;
  padding: 10px 18px;
  display: inline-block;
  border-radius: 3px;
  margin-left: 10px;
  cursor: pointer;
  font-size: 14px;

  position: absolute;
  right: 1em;
  bottom: 2em;
}
.upload-btn:hover {
  background: #f0f0f0;
}
.uploader-container {
  width: 100%;
  font-size: 10px;
}

.webuploader-container {
  position: relative;
  width: 100px;
  height: 21px;
  float: left;
}
.webuploader-element-invisible {
  position: absolute !important;
  clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
  clip: rect(1px, 1px, 1px, 1px);
}
.webuploader-pick {
  position: relative;
  display: inline-block;
  cursor: pointer;
  background: #00b7ee;
  padding: 6px 15px;

  color: #fff;
  text-align: center;
  border-radius: 3px;
  overflow: hidden;
}
.webuploader-pick-hover {
  background: #00a2d4;
}

.webuploader-pick-disable {
  opacity: 0.6;
  pointer-events: none;
}
.file-list {
  width: 100%;
}
.selected-video-name {
  font-size: 10px;
  color: grey;
}
div.webuploader-pick {
  width: 120px;
  height: 40px;
  max-height: 50px;
  padding: 1px;
  margin: 0;
  overflow: hidden;
}
#pick div label {
  height: 50px;
  max-height: 50px;
  overflow: hidden;
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
断点续传是指在文件上传过程中,如果因为网络或其他原因导致上传中断,可以在中断的位置继续上传,而不需要重新上传整个文件。下面是使用 Vue + Element + Axios + qs 实现断点续传的步骤: 1. 安装 Element 和 Axios ``` npm install element-ui axios --save ``` 2. 创建上传组件 在 Vue 的组件中,使用 Element上传组件和 Axios 进行文件上传。首先,我们需要在模板中添加一个上传组件: ```html <el-upload class="upload-demo" ref="upload" :action="uploadUrl" :data="uploadData" :file-list="fileList" :auto-upload="false" :on-success="handleSuccess" :on-error="handleError" > <el-button slot="trigger" size="small" type="primary">选取文件</el-button> <el-button size="small" type="success" @click="submitUpload">上传到服务器</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload> ``` 其中,`uploadUrl` 是上传接口的地址,`uploadData` 是上传接口的参数,`fileList` 是上传的文件列表,`handleSuccess` 和 `handleError` 是上传成功和失败的回调函数。 3. 实现上传方法 在 Vue 的方法中,实现文件上传的方法: ```javascript submitUpload() { this.$refs.upload.submit(); }, handleSuccess(response, file, fileList) { // 处理上传成功的逻辑 }, handleError(err, file, fileList) { // 处理上传失败的逻辑 } ``` 在 `submitUpload` 方法中,调用上传组件的 `submit` 方法进行文件上传。在 `handleSuccess` 方法中,处理上传成功的逻辑,包括显示上传成功的提示信息、更新文件列表等。在 `handleError` 方法中,处理上传失败的逻辑,包括显示上传失败的提示信息、更新文件列表等。 4. 实现断点续传 要实现断点续传,需要在上传组件中添加 `before-upload` 和 `on-progress` 事件,分别处理上传前和上传中的逻辑。在 `before-upload` 事件中,检查文件是否已经上传过,如果上传过,就设置上传的起点为上次上传结束的位置,否则上传整个文件。在 `on-progress` 事件中,更新上传进度。 ```html <el-upload class="upload-demo" ref="upload" :action="uploadUrl" :data="uploadData" :file-list="fileList" :auto-upload="false" :before-upload="beforeUpload" :on-progress="onProgress" :on-success="handleSuccess" :on-error="handleError" > ``` ```javascript beforeUpload(file) { // 判断文件是否已经上传过 if (localStorage.getItem(file.name)) { // 设置上传的起点为上次上传结束的位置 this.uploadData.start = JSON.parse(localStorage.getItem(file.name)).end; } else { // 上传整个文件 this.uploadData.start = 0; } }, onProgress(event, file, fileList) { // 更新上传进度 const progress = Math.round((event.loaded / event.total) * 100); this.$set(file, "progress", progress); } ``` 在 `before-upload` 事件中,使用 `localStorage` 存储文件上传结束位置,以便下次继续上传。在 `on-progress` 事件中,计算上传进度并更新文件列表中对应文件的进度。 5. 实现暂停上传和恢复上传实现暂停上传和恢复上传,可以在上传组件中添加两个按钮,分别用于暂停和恢复上传。在暂停上传时,保存上传进度并中止上传。在恢复上传时,从上次保存的上传进度开始上传。 ```html <el-upload ... > <el-button slot="trigger" size="small" type="primary">选取文件</el-button> <el-button size="small" type="success" @click="submitUpload">上传到服务器</el-button> <el-button size="small" type="warning" v-show="!isUploading" @click="resumeUpload">恢复上传</el-button> <el-button size="small" type="danger" v-show="isUploading" @click="pauseUpload">暂停上传</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload> ``` ```javascript data() { return { isUploading: false, uploadProgress: 0 }; }, methods: { submitUpload() { this.$refs.upload.submit(); this.isUploading = true; }, pauseUpload() { this.$refs.upload.abort(); this.isUploading = false; }, resumeUpload() { this.$refs.upload.submit(); this.isUploading = true; }, beforeUpload(file) { ... }, onProgress(event, file, fileList) { ... this.uploadProgress = progress; file.progress = progress; } } ``` 在 Vue 的数据中,添加 `isUploading` 和 `uploadProgress` 两个变量,分别表示上传状态和上传进度。在方法中,实现暂停上传和恢复上传的逻辑,使用 `isUploading` 变量控制按钮的显示。在 `before-upload` 和 `on-progress` 事件中,更新上传状态和上传进度。 6. 完整代码 ```html <template> <div> <el-upload class="upload-demo" ref="upload" :action="uploadUrl" :data="uploadData" :file-list="fileList" :auto-upload="false" :before-upload="beforeUpload" :on-progress="onProgress" :on-success="handleSuccess" :on-error="handleError" > <el-button slot="trigger" size="small" type="primary">选取文件</el-button> <el-button size="small" type="success" @click="submitUpload">上传到服务器</el-button> <el-button size="small" type="warning" v-show="!isUploading" @click="resumeUpload">恢复上传</el-button> <el-button size="small" type="danger" v-show="isUploading" @click="pauseUpload">暂停上传</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload> </div> </template> <script> import { Upload, Button } from "element-ui"; import axios from "axios"; import qs from "qs"; export default { name: "UploadComponent", components: { "el-upload": Upload, "el-button": Button }, data() { return { isUploading: false, uploadProgress: 0, uploadUrl: "/upload", uploadData: { start: 0 }, fileList: [] }; }, methods: { submitUpload() { this.$refs.upload.submit(); this.isUploading = true; }, pauseUpload() { this.$refs.upload.abort(); this.isUploading = false; }, resumeUpload() { this.$refs.upload.submit(); this.isUploading = true; }, beforeUpload(file) { if (localStorage.getItem(file.name)) { this.uploadData.start = JSON.parse(localStorage.getItem(file.name)).end; } else { this.uploadData.start = 0; } }, onProgress(event, file, fileList) { const progress = Math.round((event.loaded / event.total) * 100); this.uploadProgress = progress; file.progress = progress; localStorage.setItem( file.name, JSON.stringify({ end: this.uploadData.start + event.loaded }) ); }, handleSuccess(response, file, fileList) { this.fileList = fileList; this.isUploading = false; localStorage.removeItem(file.name); this.$message({ message: "上传成功", type: "success" }); }, handleError(err, file, fileList) { this.fileList = fileList; this.isUploading = false; this.$message.error("上传失败"); } } }; </script> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值