• 102 上传之自定义上传/关闭页面中断请求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 {
           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事件

     
  • 相关阅读:
    为什么使用GitHub
    java学习笔记
    mysql 内置功能 存储过程 创建有参存储过程
    mysql 字符串类型 char varchar
    前端 HTML 注释
    mysql 内置功能 存储过程 删除存储过程
    前端开发 目录
    mysql 内置功能 存储过程 创建无参存储过程
    mysql 内置功能 存储过程 目录
    mysql 内置功能 存储过程介绍
  • 原文地址:https://www.cnblogs.com/haoqiyouyu/p/15925479.html
Copyright © 2020-2023  润新知