10-2 上传之自定义上传/关闭页面中断请求beforeunload

上传组件:组件效果

功能说明:

1. 上传为post请求,请求需要携带四个参数,其中有file上传的文件

2. 自定义上传时显示进度条,正在上传的文件删除要杀死当前这个请求,这里是用的file.uid进行的对号查找

3. 当有任务上传时,关闭或者刷新页面要杀死所有请求。

组件代码: 

 

<template>
  <div class="children_box">
    <div class="inner">
      <span v-if="title" class="subTitle">
        <el-tooltip
          :disabled="disabledFn(childData.data.directoryName)"
          :content="childData.data.directoryName"
          placement="top"
          effect="light"
        >
          <span> {{ getDirNameFn(childData.data.directoryName) }}</span>
        </el-tooltip>
      </span>
      <div class="opt" :style="getOptStyle()">
        <svg-icon icon-class="btn_shangchuan"></svg-icon>
        <span class="s" @click="uploadClickFn(childData.data.id)">上传文件</span>
      </div>
    </div>
    <div class="upload_box">
      <el-upload
        ref="fileRefs"
        class="upload-box"
        action="#"
        :http-request="UploadFn"
        :on-remove="handleRemove"
        :before-remove="beforeRemove"
        :before-upload="beforeUpload"
        :on-success="handleSuccess"
        :file-list="fileList"
      >
        <el-button v-if="false" size="small" type="primary">点击上传</el-button>
      </el-upload>
    </div>
  </div>
</template>

<script>
import axios from 'axios'
import { taskDetailUpload, taskDetailFileList, taskDetailFileDelete } from '@/views/testManage/TDUpload/constants/API'
import { getStringLength } from '@/utils/getStringLength'

export default {
  props: {
    childData: {
      required: true
    },
    needObj: {
      required: true,
      type: Object
    },
    title: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      fileList: [],
      //  上传接口
      taskDetailUpload,
      //   上传入参
      sendData: {
        projectId: this.needObj.projectId,
        taskId: this.needObj.taskId,
        directoryId: ''
      },
      // 存储当前要取消的请求
      source: [],
      // 存储请求的数组
      requestArr: [],
      //  正在上传吗
      isUploading: false,
      // 是否在中断取消时候出现message提示框
      isShowCancel: false,
      // 上传文件是否符合规定大小,因为beforeUpload方法不符合大小会触发remove方法,所以做个判定,
      // 默认值为必须为true,为了回显文件列表的时候也能走beforeRomove删除逻辑
      fileSizeError: true
    }
  },
  watch: {},
  created() {
    this.getFilesListFn()
  },
  mounted() {
    window.addEventListener('beforeunload', this.sureCloseWinowFn)
    window.addEventListener('unload', this.windowUnloadFn)
  },
  destroyed() {
    window.removeEventListener('beforeunload', this.sureCloseWinowFn)
    window.removeEventListener('unload', this.windowUnloadFn)
  },
  methods: {
    // 页面被卸载(点击提示框中的重新加载或者关闭了页面)
    windowUnloadFn() {
      this.isShowCancel = true
      // 杀死请求
      this.requestArr.forEach(x => {
        x.cancel()
      })
    },
    // 刷新或关闭弹出的提示框
    sureCloseWinowFn(e) {
      e = e || window.event
      // 只有还有正在上传的时候才会出现提示
      if (this.isUploading) {
        e.preventDefault()
        e.returnValue = ''
      }
    },
    // 回显上传列表
    getFilesListFn() {
      const params = {
        directoryId: this.childData.data.id,
        taskId: this.sendData.taskId
      }
      taskDetailFileList(params).then(res => {
        // console.log(res, '回显文件列表数据')
        // 回显已经上传的文件列表
        this.fileList = res.map(item => {
          return {
            name: item.fileName,
            fileId: item.fileId,
            directoryId: item.directoryId,
            id: item.id
          }
        })
      })
    },
    // 自定义上传方式
    UploadFn(params) {
      console.log(params, '上传参数')
      const formData = new FormData()
      formData.append('file', params.file)
      formData.append('projectId', this.needObj.projectId)
      formData.append('taskId', this.needObj.taskId)
      formData.append('directoryId', this.childData.data.id)
      const CancelToken = axios.CancelToken
      const source = CancelToken.source()
      axios({
        method: 'post',
        url: taskDetailUpload,
        data: formData,
        cancelToken: source.token,
        onUploadProgress: progressEvent => {
          // axios自带api,获取上传进度
          const complete = parseInt(((progressEvent.loaded / progressEvent.total) * 100) | 0, 10)
          params.onProgress({ percent: complete }) // 调用组件自带进度条
          // 还没有上传成功标识
          this.isUploading = true
        }
      })
        .then(res => {
          // 触发handleSuccess函数
          // 显示完成按钮小图标
          params.onSuccess({
            uploading: false,
            id: res.data.data
          })
        })
        .catch(err => {
          // console.log(this.$refs.fileRefs.uploadFiles, '上传文件列表??', err)
          if (!this.isShowCancel) {
            this.$message.warning(err.message)
          }
          const uid = params.file.uid
          const idx = this.$refs.fileRefs.uploadFiles.findIndex(item => item.uid === uid) // 关键作用代码,去除文件列表失败文件(uploadFiles为el-upload中的ref值)
          if (idx > 0) {
            this.$refs.fileRefs.uploadFiles.splice(idx, 1) // 关键作用代码,去除文件列表失败文件
          }
        })
      // 存储一份uid,知道在删除的时候是杀死的哪个请求
      this.source.push({
        source: source,
        uid: params.file.uid
      })
      // 这个直接存储,为了关闭页面刷新页面全部杀死请求
      this.requestArr.push(source)
      //
      // console.log(this.fileList, '上传列表长度')
    },
    uploadClickFn(id) {
      this.sendData.directoryId = id
      this.$refs['fileRefs'].$refs['upload-inner'].handleClick()
    },
    handleSuccess(response, file, fileList) {
      console.log(response, '----', file, '====', fileList)
      this.isUploading = response.uploading

      // this.getFilesListFn()//删除后及时显示在列表中,不用刷新回显列表
    },
    beforeUpload(file) {
      // 判断文件是否大于10G  10000
      const isSize = file.size / 1024 / 1024 < 10000
      if (!isSize) {
        this.$message.warning('文件不得大于10G')
        this.fileSizeError = false
      } else {
        this.fileSizeError = true
      }
      return isSize
    },
    handleRemove(file, fileList) {
      console.log(file, fileList)
    },
    beforeRemove(file) {
      let params = {}
      if (this.fileSizeError) {
        return this.$confirm(`确定删除 ${file.name}?`).then(() => {
          // 如果有id,说明是已经传上去的文件做删除,
          // file.response.id是在上传成功后我们自动装进去的后端返回的id,目的在于因为上传后不重新调回显接口(调了后我们对filelist做了渲染的组装数据),不调是因为万一有大文件在上传就会丢失当前上传列表,所以这里是file.response是我们自己组装的数据,目的在于我们删除一个刚上传的数据又做了删除操作。能取到我们组装好的id
          if (file.id || file.response) {
            if (file.id) {
              // 针对已经上传的(回显接口返回了上传文件的id)
              params = { id: file.id }
            }
            if (file.response) {
              // 针对上传完成后没有刷新页面(调回显文件列表接口)直接删除
              params = { id: file.response.id }
            }
            // 删除接口
            taskDetailFileDelete(params).then(() => {
              this.$message.success('删除成功')
              // this.getFilesListFn() //删除后及时显示在列表中,不用刷新回显列表(防止还有别的大文件在上传)
            })
          } else {
            // 没上传完成点击删除就中断请求
            this.source.forEach(item => {
              if (item.uid === file.uid) {
                item.source.cancel('您已取消了请求') // 这里的提示会在axios的catch的error中捕获到
                // 杀死请求后,设置为true,表明不处于上传状态了
                this.isUploading = false
              }
            })
          }
        })
      }
    },
    getDirNameFn(val) {
      return getStringLength(val) <= 20 ? val : val.substr(0, 18) + '...'
    },
    // 判断节点的文字长度是否出现tool-tip
    disabledFn(attr) {
      if (!attr) return true
      if (getStringLength(attr) < 20) return true
    },
    getOptStyle() {
      return this.title ? { marginLeft: '26px' } : { marginLeft: 0 }
    }
  }
}
</script>

<style scoped lang="scss">
@mixin buttonStyle {
  padding-left: 16px;
  line-height: 28px;
  padding-right: 24px;
  border: 1px solid #fe992c;
  border-radius: 8px;
  color: #fe992c;
  text-align: center;
  cursor: pointer;
  svg {
    vertical-align: middle;
  }
  .s {
    font-size: 14px;
    padding-left: 5px;
    vertical-align: middle;
  }
}
.children_box {
  margin: 20px 40px 0 -20px;
  .inner {
    display: flex;
    align-items: center;
    .subTitle {
      color: rgba(191, 191, 191, 1);
    }
    .opt {
      width: 130px;
      height: 32px;
      @include buttonStyle;
    }
  }
  .upload_box {
    // height: 200px;
    // background: red;
    padding-bottom: 10px;
  }
  ::v-deep .el-upload-list__item-name [class^='el-icon'],
  ::v-deep .el-upload-list__item .el-icon-close-tip {
    color: rgba(250, 140, 22, 1);
  }
}
</style>

附:引入的 getStringLength方法:

// 统计输入字符长度
export const getStringLength = str => {
  let totalLength = 0
  if (str) {
    const list = str.split('')
    for (let i = 0; i < list.length; i++) {
      const s = list[i]
      if (s.match(/[\u0000-\u00ff]/g)) {
        // 半角
        totalLength += 1
      } else if (s.match(/[\u4e00-\u9fa5]/g)) {
        // 中文
        totalLength += 2
      } else if (s.match(/[\uff00-\uffff]/g)) {
        // 全角
        totalLength += 2
      }
    }
  }
  return totalLength
}
// 统计输入字节长度(无论中英文,字母数组,符号都算一字节)
export const getByteLength = str => {
  let totalLength = 0
  const list = str.split('')
  for (let i = 0; i < list.length; i++) {
    totalLength += 1
  }
  return totalLength
}

 特别说明:

beforeunload和unload的使用:
beforeunload 的是会触发:浏览器提示框

 unload 会在页面卸载(刷新关闭页面),它是由点击了确认按钮(刷新对应的是重新加载,关闭页面对应的是离开),点取消不会触发unload函数

代码表现:

 简而言之就是beforeunload会触发浏览器提示框,并且只有在浏览器提示框中点击了确定按钮后才会触发unload事件

 
posted @ 2022-02-22 23:22  猎奇游渔  阅读(274)  评论(0编辑  收藏  举报