手写前端视频上传组件

效果如下:

       

视频组件上传效果图

1.视频上传组件全部代码(包括删除,拖拽,中断功能),不过由于文件没有进行切片上传,下篇文章会更新文件切片上传

<template>
  <div class="content">
    <div
      class="n-upload"
      v-for="(item, index) in imgs"
      :key="item"
    >
      <video
        class="video"
        controls
        disablepictureinpicture
        :src="item"
      ></video>
      <div
        class="delete"
        @click="deleteImg(index)"
      >
        <el-icon color="#fff">
          <Delete />
        </el-icon>
      </div>
    </div>
    <div
      class="progress"
      v-if="isUplaoding"
    >
      <el-progress
        type="circle"
        :percentage="progress"
      />
      <div
        class="cancel"
        @click="cancel"
      >
        <el-icon
          :size="20"
          color="gray"
        >
          <Close />
        </el-icon>
      </div>
    </div>
    <div
      ref="loadFile"
      class="upload-file"
      v-show="!isDisableUploadFile"
      @dragover.prevent
      @drop.prevent="dragUploadFile"
      @click="clickUploadFile"
    >
      <el-icon
        :size="20"
        color="gray"
      >
        <Plus />
      </el-icon>
    </div>
    <input
      type="file"
      ref="file"
      style="display: none"
      accept=".mp4,.avi"
      @change="realUploadFile"
    />
  </div>
</template>

<script setup lang="ts">
  import axios from "axios";
  import { ElMessage } from "element-plus";
  import { computed, ref } from "vue";
  let file = ref<HTMLInputElement>();
  let isUplaoding = ref<Boolean>(false);
  let imgs = ref<Array<string>>([]);
  let progress = ref<number | undefined>(0);

  // 取消请求
  let controller: AbortController;
  const cancel = () => {
    controller.abort();
    progress.value = 0;
    isUplaoding.value = false;
  };
  // 点击上传
  const clickUploadFile = () => {
    file.value?.click();
  };
  const realUploadFile = (e: any) => {
    uploadFile(e.target.files[0]);
  };
  // 拖拽上传
  const dragUploadFile = (e: DragEvent) => {
    uploadFile(e.dataTransfer?.files[0]);
  };
  // 删除图片
  const deleteImg = (index: number) => {
    imgs.value.splice(index, 1);
  };
  const uploadFile = async (fileBlob: Blob | undefined) => {
    controller = new AbortController();
    if (typeof fileBlob == "undefined") {
      return;
    }
    isUplaoding.value = true;
    let formData = new FormData();
    formData.append("file", fileBlob);
    try {
      let res = await axios.post("/api/file/uploadSingleFile", formData, {
        onUploadProgress: (processEvent) => {
          progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
        },
        signal: controller.signal,
      });
      progress.value = 100;
      imgs.value.push(res.data.data);
      ElMessage({
        type: "success",
        message: "上传成功",
      });
    } catch {
    } finally {
      isUplaoding.value = false;
      progress.value = 0;
    }
  };
  // 是否禁止上传
  const isDisableUploadFile = computed(() => {
    return imgs.value.length >= 2 || isUplaoding.value;
  });
</script>

<style scoped lang="scss">
  *:disabled {
    cursor: not-allowed;
    opacity: 0.5;
  }
  .upload-file,
  .video {
    width: 150px;
    height: 100px;
    border: 2px dotted gray;
    border-radius: 2px;
    background-color: rgb(240, 243, 245);
    cursor: pointer;
  }
  .cancel,
  .delete {
    position: absolute;
    top: -10px;
    right: -10px;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    text-align: center;
    line-height: 20px;
    background-color: #000;
  }
  .content {
    display: flex;
    column-gap: 10px;
    justify-content: center;
    align-items: center;
    width: 100vw;
    height: 100vh;
    .n-upload {
      display: flex;
      column-gap: 10px;
      position: relative;
      height: 100px;
    }
    .progress {
      width: 100px;
      height: 100px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      :deep(.el-progress-circle) {
        width: 80px !important;
        height: 80px !important;
      }
    }
    .upload-file {
      width: 100px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
</style>

2.项目主要功能讲解
        2.1 点击上传,其实主要通过input标签来实现,但是要控制样式隐藏,然后设置一个change事件就可以获取用户选中的文件啦

<input
      type="file"
      ref="file"
      style="display: none"
      accept=".mp4,.avi"
      @change="realUploadFile"
    />
<div
      ref="loadFile"
      class="upload-file"
      v-show="!isDisableUploadFile"
      @dragover.prevent
      @drop.prevent="dragUploadFile"
      @click="clickUploadFile"
    >
      <el-icon
        :size="20"
        color="gray"
      >
        <Plus />
      </el-icon>
    </div>

这个才是展示给用户的标签,并绑定一个点击事件,但是其实触发的是input标签的点击事件,用户选择文件后,会触发input事件的change事件

let file = ref<HTMLInputElement>();
// 点击上传
  const clickUploadFile = () => {
    file.value?.click();
  };
  const realUploadFile = (e: any) => {
    uploadFile(e.target.files[0]);
  };
const uploadFile = async (fileBlob: Blob | undefined) => {
    controller = new AbortController();
    if (typeof fileBlob == "undefined") {
      return;
    }
    isUplaoding.value = true;
    let formData = new FormData();
    formData.append("file", fileBlob);
    try {
      let res = await axios.post("/api/file/uploadSingleFile", formData, {
        onUploadProgress: (processEvent) => {
          progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
        },
        signal: controller.signal,
      });
      progress.value = 100;
      imgs.value.push(res.data.data);
      ElMessage({
        type: "success",
        message: "上传成功",
      });
    } catch {
    } finally {
      isUplaoding.value = false;
      progress.value = 0;
    }
  };

然后将上传文件封装为一个方法,当然这里的提示信息给的并不完全,没有给出错误提示,可以自行完善
除此以外其实这里的进度条的精度还存在一些问题
        2.2 拖拽功能,其实完成了点击上传以后,拖拽就比较简单了,绑定dragover 和drop事件就可以了,但是要阻止默认事件,否则拖拽后,页面就会发生跳转

       

// 拖拽上传
  const dragUploadFile = (e: DragEvent) => {
    uploadFile(e.dataTransfer?.files[0]);
  };

       2.3 取消上传,可以利用axios的AbortController的实例对象来取消,注意:controller对象不能全局定义,而是在不同的请求中,不断的创建对象,并且signal等于请求的标识,取消请求也是基于标识。

// 取消请求
let controller: AbortController;
const cancel = () => {
    controller.abort();
    progress.value = 0;
    isUplaoding.value = false;
  };
controller = new AbortController();
signal: controller.signal,

        2.4 删除视频,这个比较简单,根据下标来删除imgs的路径就可以了

        2.5 还有一个视频上传进度, 在axios中配置一个处理原生的进度事件, 现存bug: 进度的精度存在问题

{
        onUploadProgress: (processEvent) => {
          progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
        },
        signal: controller.signal,
      });

这里主要参考的知识来源于b站:【面试至少多加3k】基于原生js和node实现文件上传和大文件切片上传_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值