拖拽上传功能的实现及原理

在这里插入图片描述
代码块

<template>
  <div class="management">
    <h3 class="manage-title">附件简历管理</h3>
    <ul class="pdf-list">
      <li class="list-item" v-for="item in fileList" :key="item.att_id">
        <div class="enclosure-name title-nowrap" @click="preview(item, item.att_type)">{{ item.name }}</div>
        <span class="list-shanchu iconfont icon-shanchu" @click="onDel(item.att_id)"></span>
      </li>
      <div class="up-enclosure-but">
        <el-button @click="onUploadResume">上传附件简历</el-button>
      </div>
    </ul>
    <el-dialog
      :visible.sync="uploadResume"
      width="370px"
      center
      custom-class="preview-dialog"
      :before-close="uploadResumeClose"
      >
      <div class="up-enclosure-but enclosure-but-bar">
        <div class="preview-center" @dragover="fileDragover" @drop="fileDrop">
          <div v-if="fileName" class="file-name">{{ fileName }}</div>
          <div v-else>
            <h4 class="title-bar">拖拽文件到这里</h4>
            <p class="center-text">简历建议使用PDF文件,也支持PPT、DOC、DOCX 格式,文件大小不超过8M</p>
          </div>
          <input
            type="file"
            ref="evfile"
            @change="zh_uploadFile_change"
            style="display: none"
            accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
          />
        </div>
        <el-progress :percentage="percent" v-if="loading"></el-progress>
        <el-button @click="onUpload">上传附件简历</el-button>
      </div>
    </el-dialog>
    <el-dialog
      title="文件预览"
      :visible.sync="dialogVisible"
      width="1100px"
      top="10vh"
      :fullscreen="false"
      custom-class="preview-dialog"
      :before-close="handleClose"
    >
      <iframe
        :src="fileData"
        frameborder="0"
        style="width: 100%; height: 560px"
      ></iframe>
    </el-dialog>
  </div>
</template>

<script>
import html2canvas from "html2canvas";
import printJS from "print-js";
import Pdf from "vue-pdf";
import utils from "@/utils/index.js";
import axios from "axios";
import * as qiniu from "qiniu-js";
import {
  saveUserResumeAtt,
  userResumeAttRemove,
  userResumeAttList,
} from "@/api/public";
import { extend } from 'dayjs';
export default {
  components: {
    Pdf,
  },
  data() {
    return {
      fileList: [],
      dialogVisible: false,
      uploadResume: false,
      src: "",
      fileData: "",
      loading: false,
      percent:0,
      fileName: '',
      filedropShow: false
    };
  },
  created() {
    this.getUserResumeAttList()
  },
  methods: {
    uploadResumeClose() {
      this.uploadResume = false
    },
    onUploadResume () {
      if (this.fileList.length < 3) {
        this.uploadResume = true
      } else {
        this.$message({
          message: '附件简历最多只能上传三个',
          type: 'warning'
        })
      }
    },
    // 预览
    preview(item, type) {
      // this.src = Pdf.createLoadingTask(item.att_dir);
      this.dialogVisible = true;
      let wordUrl = 'http://xxxxx'
      if (type === 'doc' || type === 'docx') {
        this.fileData = wordUrl + item.att_dir
        // this.fileData = wordUrl
        console.log(this.fileData)
      } else if (type === 'pdf' || type === 'PDF') {
        this.fileData = 'http://'+item.att_dir
        console.log('文件路径',this.fileData)
      }
    },
    // 关闭弹框
    handleClose() {
      this.src = "";
      this.dialogVisible = false;
    },
    // 打印
    print() {
      console.log(this.$refs.myPdf);
      this.$refs.myPdf && this.$refs.myPdf.print();
    },
    // 转图片之后打印
    printImg() {
      html2canvas(this.$refs.printContent, {
        backgroundColor: null,
        useCORS: true,
        windowHeight: document.body.scrollHeight,
      }).then((canvas) => {
        const url = canvas.toDataURL();
        printJS({
          printable: url,
          type: "image",
          documentTitle: this.printName,
        });
        // console.log(url)
      });
    },
    // 上传附件
    onUpload() {
      this.$refs.evfile.click();
    },
    zh_uploadFile_change(evfile) {
      let file = {}
      if (this.filedropShow) {
          file = evfile.dataTransfer.files[0] //Blob 对象,上传的文件
        }
      // console.log('上传',evfile.target.files[0])
      this.loading = true
      axios.post('http://xxxxx').then(res=>{
        if (!this.filedropShow) {
          file = evfile.target.files[0] //Blob 对象,上传的文件
        }
        var uptoken = res.data.data.upToken
        
        let date = new Date()
        let key =  'resume/'+date.valueOf()//dateFormat("YYYYmmddHHMM", date)+ran
        let config = {
            useCdnDomain: true,   //表示是否使用 cdn 加速域名,为布尔值,true 表示使用,默认为 false。
            region: qiniu.region.z2     // 根据具体提示修改上传地区,当为 null 或 undefined 时,自动分析上传域名区域
        };

        let putExtra = {
            fname: "",  //文件原文件名
            params: {}, //用来放置自定义变量
            mimeType: null,  //用来限制上传文件类型,为 null 时表示不对文件类型限制;限制类型放到数组里: ["image/png", "image/jpeg", "image/gif"]
        };
        var observable = qiniu.upload(file, key, uptoken, putExtra, config);
        observable.subscribe({
            next: (result) => {
            // 主要用来展示进度
                // console.log(result)
                // this.$Loading.update(result.total.percent)
                this.$set(this,'percent',Math.round(result.total.percent))
                
            },
            error: (errResult) => {
            // 失败报错信息
                this.$message({
                  message: '上传成功',
                  type: "error",
                });
                this.loading = false
                this.$set(this,'percent',0)
                if (!this.filedropShow) {
                  evfile.target.value = ''
                }
                
            },
            complete: (result) => {
              // this.$message({
              //   message: '上传成功',
              //   type: "success",
              // });
              this.filedropShow = false
              this.uploadResume = false
              this.loading = false
              this.$set(this,'percent',0)
              this.fileName = ''
              if (!this.filedropShow) {
                evfile.target.value = ''
              }
              let data = {
                key: result.key,
                file_name: file.name,
                size: file.size,
              };
              this.onSaveUserResumeAtt(data);
            }
        })
    })
    },
    // 保存上传附件简历
    onSaveUserResumeAtt(data) {
      saveUserResumeAtt(data).then((res) => {
        if (res.status === 200) {
          this.getUserResumeAttList()
          this.$message({
            message: res.msg,
            type: "success",
          });
        }
      }).catch(res => {
        this.$message({
            message: res.msg,
            type: "error",
          });
      })
    },
    // 获取附件简历列表
    getUserResumeAttList () {
      userResumeAttList().then(res => {
        if (res.status === 200) {
          console.log('请求成功')
          console.log(res.data)
          this.fileList = res.data.data
        }
      })
    },
    // 删除附件简历
    onDel(att_id) {
      this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // this.$message({
        //   type: 'success',
        //   message: '删除成功!'
        // });
        userResumeAttRemove(att_id).then(res => {
          if (res.status === 200) {
            this.getUserResumeAttList()
            this.$message({
              message: res.msg,
              type: "success",
            });
          }
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });          
      });
      
    },

    // 拖拽上传
    fileDragover (e) {
      e.preventDefault()
    },
    fileDrop (e) {
      e.preventDefault()
      const file = e.dataTransfer.files[0] // 获取到第一个上传的文件对象
      this.fileName = file.name
      this.filedropShow = true
      this.zh_uploadFile_change(e)
    },

  },
};
</script>

<style lang="less" scoped>

.management {
  width: 100%;
  background: #fff;
  padding: 20px;
  margin-bottom: 14px;
  border-radius: 4px;
  .manage-title {
    font-size: 14px;
    font-weight: 400;
    color: #6E7483;
    padding-bottom: 15px;
  }
  .pdf-list {
    .list-item {
      width: 261px;
      height: 23px;
      padding: 0 5px;
      // background: #FFFFFF;
      cursor: pointer;
      font-size: 12px;
      color: #6E7483;
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 5px;
      .enclosure-name {
        max-width: 220px;
      }
      &:hover {
        box-shadow: 0px 0px 6px 2px rgba(161, 162, 164, 0.12);
      }
      .list-shanchu {
        font-size: 14px;
        color: #6E7483;
      }
    }
  }
  .preview-dialog {
    height: 400px;
  }
}
.up-enclosure-but {
  padding-top: 11px;
  .el-button {
    width: 255px;
    height: 38px;
    background: #04C253;
    border: 1px solid #FFFFFF;
    font-size: 14px;
    font-weight: 400;
    color: #FFFFFF;
    padding: 0;
    letter-spacing: 1px;
    &:hover {
      background: #3cd37a;
      border-color: #3cd37a;
    }
  }
}
.enclosure-but-bar {
  padding-top: 0;
  .el-button {
    margin-top: 9px;
    width: 317px;
  }
  .preview-center {
    width: 317px;
    height: 130px;
    padding: 20px;
    cursor: pointer;
    border: 1px dashed #CBCFD2;
    .center-text {
      text-align: left;
      font-size: 12px;
      font-weight: 400;
      color: #A8ABB1;
    }
    .file-name {
      line-height: 73px;
      text-align: center;
    }
    .title-bar {
      text-align: center;
      padding: 14px 0 26px 0;
      font-size: 14px;
      font-weight: 400;
      color: #74777E;
    }
  }
}

</style>

核心原理说明
dragover和drop事件
第一个要说的就是拖拽中的这两个事件,因为这两个事件撑起了拖拽上传的核心功能。
对于拖拽这个动作而言,有二个核心概念,一个是拖拽元素,还一个是放置目标。这里,我只讲放置目标上的事件,对于拖拽元素的事件,请自行查阅。

那对于放置目标,它有什么事件呢?如下:
当某个元素被拖动到一个有效的放置目标上(如上例中虚线区域)时,下列事件会依次发生:
(1) dragenter
(2) dragover
(3) dragleave 或 drop
只要有元素被拖动到放置目标上,就会触发 dragenter 事件(类似于 mouseover 事件)。紧随其后的是 dragover 事件,而且在被拖动的元素还在放置目标的范围内移动时,就会持续触发该事件。如果元素被拖出了放置目标,dragover 事件不再发生,但会触发 dragleave 事件(类似于 mouseout事件)。如果元素被放到了放置目标中,则会触发 drop 事件而不是 dragleave 事件。

对于本例来说,我们只需要关注dragover和drop事件。但是drop事件却有点调皮,你想监听它,还得进行一些处理,因为默认情况下,元素是不允许放置的,在拖动元素经过某些无效放置目标时,可以看到一种特殊的光标(圆环中有一条反斜线),表示不能放置。如下:
clipboard.png
如果拖动元素经过不允许放置的元素,那无论用户如何操作,都不会发生 drop 事件。那怎么办呢?
我们可以重写 dragover 事件的默认行为,如上例代码中的e.preventDefault()。
细心的同学可能要问了,那drop事件中也有e.preventDefault(),去掉行不行呢?大家可以自行试下。

dataTransfer 对象
可能这个对象看着有些陌生,但是它的作用可不小。比如,你拖动一个图片到目标区域,那目标区域怎么获取这个图片的信息呢?就靠它!它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据。在本例中,我们可以通过它来获取拖动中的文件信息。

input的change事件
这个事件其实有坑的,它有这样一个特性,即:上传同一个文件,并不会触发change事件,即使该文件内容做过修改。
细思极恐!比如,用户要上传一个文档,但是拖拽到虚线区域后发现文档内容还需要修改下,他改完后再拖拽该文档,再提交到服务器,那么他上传到服务器的文档内容却是未修改之前的!
所以,我们需要代码e.target.value = ''来进行重置处理,这样,每次上传文件都会触发change事件。

注: 参考文章https://blog.csdn.net/weixin_34119545/article/details/88614771

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值