前端切片上传大文件

<template>
  <div>
    <el-upload
      accept=".mp3, .m4a, .aac, .mp4, .m4v"
      :before-upload="beforeUpload"
      :http-request="upload"
      :show-file-list="false"
      :disabled="disabled"
      style="display: inline-block"
      class="m-x-12"
    >
      <el-tooltip placement="bottom">
        <template #content>
          可上传本地录音录像,支持上传的
          <br />音频格式为:mp3、m4a、aac
          <br />
          视频格式为:mp4、m4v
        </template>
        <el-button type="primary" style="font-size: 12px" :disabled="disabled">
          上传录音录像
        </el-button>
      </el-tooltip>
    </el-upload>

    <div
      v-if="props.modelValue && props.isShow"
      style="margin-top: 10px; width: 100%"
    >
      <input
        disabled="false"
        v-model="props.modelValue"
        placeholder-class="input-placeholder"
        style="width: 80%; padding: 10px; margin-right: 10px"
        id="myInput"
      />
    </div>
    <br />
    <el-dialog
      v-model="dialogVisible"
      :fullscreen="true"
      :show-close="false"
      custom-class="dispute-upload-dialog"
    >
      <div class="center">
        <div class="fz-18 ellipsis">正在上传:{{ fileData.name }}</div>
        <el-progress
          :text-inside="true"
          :stroke-width="16"
          :percentage="percentage"
        />
        <el-button style="margin-top: 10px" @click="cancel">取消上传</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script setup>
import { getToken } from "@/utils/auth";
import axios from "axios";
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const props = defineProps({
  modelValue: [String, Object, Array],
  // 是否显示删除
  isShow: {
    type: Boolean,
    default: true,
  },
});
const beforeUpload = (file) => {
  const mimeTypes = [
    "audio/mpeg",
    "audio/x-m4a",
    "audio/aac",
    "video/mp4",
    "video/x-m4v",
  ];
  if (!mimeTypes.includes(file.type)) {
    proxy.$modal.msgError({
      type: "error",
      message: "只能上传 MP3、M4A、AAC、MP4、M4V 格式的文件",
      duration: 6000,
    });
    return false;
  }
  if (file.size / 1024 / 1024 / 1024 > 1.5) {
    proxy.$modal.msgError("文件大小不能超过 1.5G");
    proxy.$modal.msgError({
      type: "error",
      message: "文件大小不能超过 1.5G",
      duration: 6000,
    });
    return false;
  }
  return true;
};

const dialogVisible = ref(false); //上传的进度条弹框
const cancelUpload = ref(false); //是否继续循环调上传接口
let controller = null;
const chunkSize = 5 * 1024 * 1024; // 切片大小5M
const percentage = ref(0); //进度条的数字
const fileData = ref({
  name: "",
  size: 0,
  type: "",
  suffix: "",
  md5: "",
});
const cancel = () => {
  const formData = new FormData();
  formData.append("name", fileData.value.name);
  formData.append("md5", fileData.value.md5);
  dialogVisible.value = false;
  cancelUpload.value = true;
  controller?.abort();
  axios
    .post(baseUrl + "/system/oss/endUpload", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
        Authorization: "Bearer " + getToken(),
      },
    })
    .then(() => {
      proxy.$modal.msgSuccess("取消上传");
    });
};

let counter = 0;
const getFileMd5 = () => {
  let guid = (+new Date()).toString(32);
  for (let i = 0; i < 5; i++) {
    guid += Math.floor(Math.random() * 65535).toString(32);
  }
  return "wu_" + guid + (counter++).toString(32);
};

const updateUrl = (fileUrl) => {
  axios.post("saveUrl", {
    fileName: fileData.value.name,
    fileUrl,
  });
};

// 上传切片
const uploadChunkFile = async (i, fileObj, chunkCount) => {
  const start = i * chunkSize;
  const end = Math.min(fileData.value.size, start + chunkSize);
  const chunkFile = fileObj.slice(start, end);
  const formData = new FormData();
  formData.append("encrypt", "true");
  formData.append("name", fileData.value.name);
  formData.append("md5", fileData.value.md5);
  formData.append("file", chunkFile, String(i + 1));
  formData.append("size", fileObj.size);
  formData.append("chunk", i);
  formData.append("chunks", chunkCount);

  controller = new AbortController();
  return await axios
    .post(baseUrl + "/system/oss/uploadBigFile", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
        Authorization: "Bearer " + getToken(),
      },
      onUploadProgress: (data) => {
        percentage.value = Number(
          (
            (Math.min(fileData.value.size, start + data.loaded) /
              fileData.value.size) *
            100
          ).toFixed(2)
        );
      },
      signal: controller.signal,
    })
    .then((res) => {
      if (res.data.code === 200) {
        return res;
      } else {
        // 关闭弹框
        dialogVisible.value = false;
        // 结束循环
        cancelUpload.value = true;
        proxy.$modal.msgError({
          message: `${fileData.value.name}上传失败`,
          title: "提示",
        });
      }
      // updateUrl(res.data);
    })
    .catch((err) => {
      // 关闭弹框
      dialogVisible.value = false;
      // 结束循环
      cancelUpload.value = true;
      proxy.$modal.msgError({
        message: `${fileData.value.name}上传失败`,
        title: "提示",
      });
    });
};

// 计算切片数量
const batchUpload = async (fileObj) => {
  percentage.value = 0;
  dialogVisible.value = true;
  cancelUpload.value = false;
  const chunkCount = Math.ceil(fileData.value.size / chunkSize); // 切片数量
  fileData.value.md5 = getFileMd5(); // 文件唯一标识
  for (let i = 0; i < chunkCount; i++) {
    if (cancelUpload.value) return;
    const res = await uploadChunkFile(i, fileObj, chunkCount);

    if (res && res.data.code === 200 && res.data.data.url) {
      proxy.$modal.msgSuccess({
        message: `${fileData.value.name}上传成功`,
        title: "提示",
      });
      dialogVisible.value = false;

      emit("videoUrl", res.data.data.url);
    } else if (res && res.data.code !== 200) {
      dialogVisible.value = false;
      proxy.$modal.msgError({
        message: `${fileData.value.name}上传失败`,
        title: "提示",
      });
    }

    // if (i === chunkCount - 1) {
    //   console.log("循环上传");
    //   return;
    //   setTimeout(() => {
    //     dialogVisible.value = false;
    //     proxy.$modal.msgSuccess({
    //       message: `${fileData.value.name}上传成功`,
    //       title: "提示",
    //     });
    //     axios
    //       .post("/system/oss/uploadTest", {
    //         folder: fileData.value.md5,
    //       })
    //       .then((res) => {
    //         console.log(res);
    //         return;
    //         updateUrl(res.data);
    //       });
    //   }, 500);
    // }
  }
};

// 选择文件
const disabled = ref(false);
const upload = async (file) => {
  const fileObj = file.file;
  const formData = new FormData();
  formData.append("file", file.file);
  const nameList = fileObj.name.split(".");
  fileData.value.name = fileObj.name;
  fileData.value.size = fileObj.size;
  fileData.value.type = fileObj.type;
  fileData.value.suffix = nameList[nameList.length - 1];

  if (chunkSize > fileData.value.size) {
    proxy.$modal.loading("正在上传文件,请稍候...");
    // 说明是小文件只有1M
    disabled.value = true;
    axios
      .post(baseUrl + "/system/oss/upload", formData, {
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: "Bearer " + getToken(),
        },
      })
      .then((res) => {
        proxy.$modal.msgSuccess({
          message: `${fileData.value.name}上传成功`,
          title: "提示",
        });
        proxy.$modal.closeLoading();
        emit("update:modelValue", res.data.data.url);
        emit("videoUrl", res.data.data.url);
      })
      .catch(() => {
        proxy.$modal.msgError({
          message: `${fileData.value.name}上传失败`,
          title: "提示",
        });
        proxy.$modal.closeLoading();
      })
      .finally(() => (disabled.value = false));
    return;
  }
  batchUpload(fileObj);
};
</script>

<style lang="scss">
.dispute-upload-dialog {
  background: none;
}
</style>

<style lang="scss" scoped>
.center {
  color: #fff;
  width: 50%;
  text-align: center;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值