纵有疾风起
人生不言弃

基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh

一、依赖包

  • vue-pdf "^4.0.12"
  • v-viewer "^1.5.1"
  • jquery "^3.4.1"
  • element-ui "^2.13.0"

二、初衷、思路、想法

绝大数后台管理项目和少数C端项目都离不开文件的上传及预览,所以我就做了一个这个自认为适用于绝大多数项目的组件~
用户上传图片orPDF,预览上传的文件无论图片orPDF,由于在现在的公司做的主要项目针对的是后台管理,且有的页面上传的文件较多,有的文件必传、有的文件需要显示示例图、有的文件需要对文件类型及文件大小做限制、有的场景希望上传的区域较大或较小、直接拖拽文件到某一特定区域上传、在文件较多的页面希望用户“直接”定位到有哪一个或哪一些文件上传的校验没通过
对于开发者,希望在用户上传特定的一些文件后立即触发回调事件对用户上传的文件及时做处理。

三、组件预览图

  • 总揽

    基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh插图
  • pdf预览

    基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh插图1
  • 图片预览

    基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh插图2
  • gif

    基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh插图3
    QQ20200826-174338-HD.gif

四、使用

<uploadFileNew      :fileImgList="fileNameList"      :fileSizeByte="10240"      ref="uploadFiles"      type="orange"      size="mini"    />fileNameList: Array<object> = [    {      id: "idCardFront",      fileName: "身份证正面",      required: true, // 必上传      fileSizeByte: 15360, // 自定义当前文件最大15兆      fileType: ["pdf"],      callback: (file: any) => {        console.log("回调成功", file);      },    },    {      id: "idCardBack",      fileName: "身份证反面",    },    {      id: "vin",      fileName: "vim码",    },    {      id: "businessLicense",      fileName: "营业执照",    },    {      id: "engine",      fileName: "车辆发动机舱全景",      initImg: require("@/assets/img/11592459733_.pic.jpg"),    },  ];// 获取上传的所有文件(this as any).$refs.filePdf.getFiles("object");

五、源码

基本上全都写了注释

  • 文件上传组件
<template>  <el-row class="clearfix upload-files">    <el-col      :xs="24"      :sm="12"      :md="12"      :lg="6"      :xl="6"      v-for="(item, index) in fileImgListDrawing"      tag="div"      :key="index"      :id="item.id"      class="upload-show-box"      :class="{'upload-show-box-mini':size=='mini'}"    >      <!-- 目标文件名称 -->      <span class="file-info-top">{{item.fileName}}</span>      <main class="file-container" ref="fileBox" :data-index="index">        <!-- 文件展示区域 -->        <div          class="file-image-area"          :class="{'file-image-area-orange':type=='orange','no-border':!item.border}"          @click.stop="uploadImg(index)"        >          <!-- 初始化展示 -->          <div class="file-image-init-display" v-show="!item.data&&!item.pdfData">            <img class="initial-img-url" :src="initialImgUrl" />            <!-- 蒙板示例图片 -->            <img class="mask-sample-img" :src="item.initImg" v-if="item.initImg" />            <p class="initial-title">              <span>将图片拖放到这里&nbsp或&nbsp</span>              <span class="underline">点击选择图片</span>            </p>          </div>          <!-- 图片展示 -->          <img class="file-img" v-show="item.data" :src="item.data" alt />          <!-- pdf展示 -->          <pdf v-show="item.pdfData" :src="item.pdfData" :page="1" />          <input type="file" id="upLoad" ref="fileImage" @change.stop="uploadFile($event,index)" />        </div>        <!-- 文件名及文件操作区域 -->        <div class="file-text-bottom">          <!-- 文件名 -->          <el-tooltip            class="item"            :disabled="item.fileData.name?false:true"            effect="light"            :content="item.fileData.name"            placement="top-start"          >            <span class="file-name">{{item.fileData.name}}</span>          </el-tooltip>          <!-- 操作区域 -->          <div class="operating-area">            <i class="el-icon-folder-opened" @click.stop="uploadImg(index)"></i>            <i              class="el-icon-zoom-in"              v-show="item.data||item.pdfData"              @click="filePreview(item,index)"            ></i>            <el-popconfirm              confirmButtonText="确定"              cancelButtonText="取消"              icon="el-icon-info"              title="您是否删除该已上传图片?"              popper-class="deleteUpload"              @onConfirm="deleteFile(index,item.id)"              v-show="item.data||item.pdfData"            >              <i class="el-icon-delete" slot="reference"></i>            </el-popconfirm>          </div>        </div>      </main>      <!-- 文件验证错误 -->      <span class="file-info-err" v-show="item.errInfo">{{item.errInfo}}</span>    </el-col>    <!-- 图片预览 -->    <img-viewer ref="viewer" />    <!-- pdf预览 -->    <div class="pdf-preview" v-show="pdf.pdfPreview" @click.self="pdf.pdfPreview=false">      <div class="pdf-preview-box">        <div class="pdf-preview-box-top">          <span class="pdf-title">{{pdf.name}}</span>          <i class="el-icon-circle-close" @click="pdf.pdfPreview=false"></i>        </div>        <div class="pdf-preview-box-main">          <pdf            v-for="i in 1"            :key="i"            :src="pdf.pdf"            :page="pdf.page"            style="display: inline-block; width: 100%"          ></pdf>        </div>        <div class="pdf-preview-box-paging">          <i class="el-icon-arrow-left" @click="pdfPaging(false)"></i>          <span>{{pdf.page}}</span>          <span style="margin:0px">/</span>          <span>{{pdf.size}}</span>          <i class="el-icon-arrow-right" @click="pdfPaging(true)"></i>        </div>      </div>    </div>  </el-row></template><script lang="ts">// 引入vue-pdfimport pdf from "vue-pdf";import CMapReaderFactory from "vue-pdf/src/CMapReaderFactory.js";// 图片查看import imgViewer from "components/imgViewer/index.vue";import { Component, Vue, Prop, Watch } from "vue-property-decorator";import type from "@/utils/type";@Component({  components: {    pdf: Vue.extend(pdf),    imgViewer,  },})export default class UploadFiles extends Vue {  // pdf预览  pdf = {    pdfPreview: false,    pdf: "",    page: 1,    size: 1,    name: "",  };  // 组件大小  @Prop({    type: String,  })  private size?: string;  // 组件风格  @Prop({    type: String,    default: "blue",  })  private type?: string;  // 初始化默认图片橙色  initialImgUrlConfigOrange =    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAA0CAYAAAAzMZ5zAAAFeElEQVRoQ+1aa2wUVRg9Z7oWaH1AAsluWx6+wEcUBDEN8sNHIgkqMb4iRqpYOtMfPlA0oCZKTCSBIAY1sdMiaDRBMT5iTCSKwUeiQSGAJkABBQ3dXREloqgt7Ryz5SG73e3c2W3TGeL+nfN93zln7zf33rmX+P+X5QBz/ZBEuDW1sLwrAAwHcAAWN6Le/opc6J3q/mUZoubELZAWSzg3VziBvYC1gI3JN09lU04YIrdqqeTN8xNL4kU66fv9cFF93m2I3Pg8CUtNRZB8gk5qkSk+SjiqpboGnrdL0mBT4iQ7YJVfwIYf95jGRAVHNSUWCXosKGFaXEY75dtiQfMONJ5yE5slTQhKhOQOOqkLg8aFHU81xX8XcGZQogTb2ZgybrOg+QcKnxkhhyVVBCVAoBNOqpykgsaGGZ95h+wQNC4oycy6hI3ps4PGhR1PufEXJNwXlCjJFXRSDUHjwo6n3JpLoM4tAixTsiQEi5PZkNpkGhMV3NGFWXP8eXkwXn0SbGFjyo6KyCA8j61UJ50Gtb0j4Aa/YJIfYWTlDE7f3e6HjeLz//Yya24rw8EvHgcwX0JlrhiCf4NcirFjn+bVn3ZGUawJ557bf7dqOKCbAEzu3v4Lv4LYiCEV77Hu+/0mSfsDo5erxrE+2dofuU/O2cOQ/i5YTH6tqDkPnZ2bwbIZdNrWF5PDNCb0hkgLLTS7n0maSiIJWOPpJA+YCgyKC78hbmKupOeOCyPwARvTNwYVaooPtSFaVXM+2ru2ChqS1ecW59JOLTcVGQQXWkNObpU8M147LKuWdtuWIGJNsOE1JKdV8pjSiooRk1j37WEToaaYUBpSqFV6moJX2JiebSrWBBc6Q3prlXyCWGbdyYbkahOxJpg+M0RufCWIl2invzEpXAgjN/GQpGWmOQgcQnn5Zbz3px9MY3rD9Ykhaq66Q563msQRwHoStr2kmEMt01bJ0zpfg9VT6Ww6UqopJRsit6oCUKukmhNrBWI9Bsdm8e59baYEu1vFdT8XdKVpTNZUTCyhk55fTGxWnlITyE08IymzKcz6kfwNxBzaqXdNagRtlZ71Mt9orGlsSH5sUq8QpqQRopWjzkHHkW2CBhUsQLoAH6aT/Kvge6PAAiyoMAJpVFSOL2UTWpohbvx9Cb7LaJLbEbNmsr5ta67IUlslz8hcCzs5vdiP30UboubENHlaa/ovZo4tQCyAnVx+MtlSWyXvVEw8Qif9rCm3kt8hcjNf2JLfFfW1nlwLa/A9bNjzc7Gzip/Q7tlOnMLG1EY/bJ4ZK2hI5nC86lHJWxI88mgEgf2wyurheQuKnVX8apPYjdhZE1nf+ocftqQRolVj4uj4Z6eEM4IUGggsydfppGYFqR34HaKmxKuC6oIUGUgsLdbRTr1myiGQIWqproXX9aWUGfXR+BH4E4NiEzl73y4TxsbCjm26Nki63CRxmDAkN2HYsCm8fVuHHy9zQ9zEHEktfgnD+tz0PouRIVo1Zig62ndKGhFWwX68uo9fietppz/sDWtmiJtYLukBv6Jhf07yF5QPupSz96YLcfU1RM2jLobXkTkMj4VdsAk/kutgJ68rtLT3N6Qp/omAa0yKRQXDo/dtF+fj26shaonfqi68FRWhpjy7bz8xNpXOvg25MQUN0ZqaITjYtV3SaNNCUcKR2IPK0yfwrt2HjJbucqsWSt5TURIZlCuJN+ikZ/oaohWJ0ejE9twTs6AFo4CnZdXTTq48zjVvy8hNvC3p5igIKpUjicOIWZOOX7XoeT+kqfpaoWtdqYWiFE9yC0ZW1mZuRWUZovVXxdDamjlcvihKgvqCK8nn6aQezDbE5zy1LwqHOQdhzcg1xAY4NMyk+5UbceBfXqQOZiFnaPcAAAAASUVORK5CYII=";  // 初始化默认图片蓝色  initialImgUrlConfigBlue =    "data:image/png;base64,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";  // 初始化展示默认图片  initialImgUrl =    this.type === "blue"      ? this.initialImgUrlConfigBlue      : this.initialImgUrlConfigOrange;  // 上传文件的列表  @Prop({    type: Array,    required: true,  })  private fileImgList?: Array<any>;  // 上传的文件限制大小  @Prop({    type: Number,    default: 5120, // 默认5兆  })  private fileSizeByte?: number;  // 上传的文件类型限制  @Prop({    type: Array,    default: () => ["jpg", "jpeg", "pdf", "png", "txt", "webp"],  })  private fileType?: Array<string>;  // 错误滚动盒子  @Prop({    type: String,    default: ".el-scrollbar__wrap",  })  private errScrollBox?: string;  // 渲染列表  fileImgListDrawing: Array<any> = [];  // 向外暴露出去的总文件数组  exposedFileArr: Array<any> = [];  created() {    // 初始化上传文件列表    this.fileImgListDrawing = (this as any).fileImgList.map((v: any) => {      // 文件错误提示      v.errInfo = "";      // 文件      v.fileData = {};      // border虚线      v.border = true;      return v;    });  }  mounted() {    // 拖拽文件上传    const that = this;    document.addEventListener(      "drop",      function (e) {        e.preventDefault();      },      false    );    document.addEventListener(      "dragover",      function (e) {        e.preventDefault();      },      false    );    (this.$refs.fileBox as any).forEach((el: any) => {      el.ondragleave = (e: any) => {        e.preventDefault(); //阻止离开时的浏览器默认行为      };      el.ondrop = (e: any) => {        //阻止拖放后的浏览器默认行为        e.preventDefault();        const data = e.dataTransfer.files;        // 检测是否有文件拖拽到页面        if (data.length < 1) {          return;        }        that.uploadFile(e, $(el).data("index"));      };      el.ondragenter = (e: any) => {        //阻止拖入时的浏览器默认行为        e.preventDefault();      };      el.ondragover = (e: any) => {        //阻止拖来拖去的浏览器默认行为        e.preventDefault();      };    });  }  // 代理打开文件上传  uploadImg(i: number) {    (this.$refs as any).fileImage[i].dispatchEvent(new MouseEvent("click"));  }  // 文件上传-预览文件至“文件展示区域”  uploadFile(el: any, i: number) {    // 将读出的文件保存    const elFile = el.dataTransfer      ? el.dataTransfer.files[0]      : el.target.files[0];    if (!elFile) return;    // 文件验证不通过    if (!this.fileLimit(elFile, i)) {      return this.deleteFile(i, this.fileImgListDrawing[i].id);    }    const reader = new FileReader();    this.$set(this.fileImgListDrawing[i], "fileData", elFile);    reader.readAsDataURL(elFile);    reader.onloadstart = () => {      const that = this;      // 判断是否为pdf文件      if (elFile.type == "application/pdf") {        reader.onload = function () {          that.fileImgListDrawing[i].pdfData = (pdf as any).createLoadingTask({            url: this.result,            CMapReaderFactory,          });          that.fileImgListDrawing[i].data = "";          that.fileImgListDrawing[i].border = false;          that.$set(that.fileImgListDrawing, i, that.fileImgListDrawing[i]);        };      } else {        reader.onload = function () {          that.fileImgListDrawing[i].data = this.result;          that.fileImgListDrawing[i].pdfData = "";          that.fileImgListDrawing[i].border = false;          that.$set(that.fileImgListDrawing, i, that.fileImgListDrawing[i]);        };      }      // 触发回调      this.fileImgListDrawing[i].callback &&        this.fileImgListDrawing[i].callback(elFile);    };    elFile.typefileId = this.fileImgListDrawing[i].id;    this.integrationFileArr(elFile);  }  // 文件大小限制及类型限制  fileLimit(file: any, i: number): boolean {    const arrFile = this.fileImgListDrawing[i];    // 获取文件大小    const fileSize = file.size / 1024,      // 单个文件最大大小      onesBiggestFileSize = arrFile.fileSizeByte,      // 最终文件最大大小      biggestFileSize = onesBiggestFileSize        ? onesBiggestFileSize        : this.fileSizeByte,      // 验证文件大小是否符合      fileSizeValidation = fileSize > biggestFileSize;    if (fileSizeValidation) {      arrFile.errInfo = `文件不能超过${biggestFileSize / 1024}兆`;      return false;    }    // 获取文件类型    const fileType = file.type        .substring(file.type.lastIndexOf("/") + 1)        .toLowerCase(),      // 单个文件最终验证类型      onesUltimatelyFileType = arrFile.fileType,      // 文件最终验证类型      ultimatelyFileType = onesUltimatelyFileType        ? onesUltimatelyFileType        : this.fileType,      // 验证文件类型是否符合      fileTypeValidation = ultimatelyFileType.some(        (v: string) => v == fileType      );    if (!fileTypeValidation) {      arrFile.errInfo = `只能上传${ultimatelyFileType.join("/")}文件`;      return false;    }    arrFile.errInfo = "";    return true;  }  // 文件必传拦截  fileErrorToIntercept() {    const errArr: any = [];    this.fileImgListDrawing = this.fileImgListDrawing.map((v, i) => {      if (v.required && !v.fileData.size) {        v.errInfo = `${v.fileName}必须上传`;        errArr.push(v.id);      }      return v;    });    const noRulesLength = errArr.length;    if (!noRulesLength) {      return {        isValidation: true,        // 必传但未传的文件数量        noRulesLength,      };    }    // 锚点滚动    $((this as any).errScrollBox).animate(      { scrollTop: $("#" + errArr[0])[0].offsetTop },      300    );    return {      isValidation: false,      noRulesLength,    };  }  // 文件整合数组 去重  integrationFileArr(val: any) {    if (this.exposedFileArr.length === 0) {      this.exposedFileArr.push(val);    }    const index = this.exposedFileArr.findIndex(      (e) => e.typefileId === val.typefileId    );    if (      this.exposedFileArr.findIndex(        (element) => element.typefileId == val.typefileId      ) === -1    ) {      this.exposedFileArr.push(val);    } else {      this.exposedFileArr.splice(index, 1, val);    }  }  // 获取向外暴露出去的文件集合  getFiles(type?: string) {    let newList: any = this.exposedFileArr;    if (type === "object") {      newList = {};      this.exposedFileArr.forEach((v) => {        newList[v.typefileId] = v;      });    }    const { isValidation, noRulesLength } = this.fileErrorToIntercept();    return {      fileList: newList,      // 总共上传的文件数量      length: this.exposedFileArr.length,      // 需上传的文件是否全部验证成功      isValidation,      // 必传但未传的文件数量      noRulesLength,    };  }  // 文件删除  deleteFile(i: number, id: string) {    const fileObj = this.fileImgListDrawing[i];    fileObj.data = fileObj.pdfData = "";    fileObj.fileData = {};    fileObj.border = true;    this.$set(this.fileImgListDrawing, i, fileObj);    // 清空数据    (this.$refs as any).fileImage[i].value = "";    // 删除文件    this.exposedFileArr.forEach((v, i) => {      if (v.typefileId === id) return this.exposedFileArr.splice(i, 1);    });  }  // 文件预览  filePreview(val: any, index: number) {    const file = this.fileImgListDrawing[index];    // pdf预览    if (file.pdfData) {      this.pdfScrollBarTop();      this.pdf.pdfPreview = true;      this.pdf.pdf = file.pdfData;      this.pdf.page = 1;      this.pdf.name = file.fileName;      file.pdfData.promise.then((pdf: any) => {        this.pdf.size = pdf.numPages ? pdf.numPages : 1;      });    }    // 图片预览    if (file.data) {      let imgArr: any = [];      imgArr.push({        thumbnail: file.data,        source: file.data,      });      (this.$refs as any).viewer.show(imgArr, 0);    }  }  // pdf滚动条置顶  pdfScrollBarTop() {    $(".pdf-preview-box-main").animate({ scrollTop: 0 }, 300);  }  // pdf分页  pdfPaging(isAdd: boolean) {    this.pdfScrollBarTop();    this.pdf.page =      isAdd && this.pdf.page < this.pdf.size ? ++this.pdf.page : this.pdf.page;    this.pdf.page =      !isAdd && this.pdf.page > 1 ? --this.pdf.page : this.pdf.page;  }}</script><style scoped lang="scss">$gobal-color-big: rgba(75, 108, 246, 1);$gobal-color-orange: #fe9818;.upload-files {  .upload-show-box {    margin-bottom: 20px;    position: relative;  }  .upload-show-box-mini {    .file-container {      width: 174px;      height: 160px;      box-shadow: 0px 0px 8px 0px rgba(39, 59, 100, 0.19);      .file-image-area {        width: 162px;        height: 120px;        margin: 6px auto;        .file-image-init-display {          .initial-img-url {            margin: 28px auto 10px;          }        }      }      .file-text-bottom {        width: 162px;        height: 20px;        .file-name {          width: 95px;          line-height: 20px;        }        .operating-area {          i,          span {            height: 20px;            line-height: 20px;            font-size: 16px;            &:hover {              color: $gobal-color-orange;            }          }        }      }    }  }  .file-info-top {    font-size: 12px;    font-weight: 400;    color: rgba(81, 81, 81, 1);    margin-bottom: 6px;    display: inline-block;  }  .file-info-err {    left: 0;    bottom: -20px;    position: absolute;    font-size: 12px;    font-weight: 400;    color: red;    display: inline-block;  }  .file-container {    width: 230px;    box-sizing: border-box;    height: 210px;    background: rgba(255, 255, 255, 1);    box-shadow: 0px 0px 8px 0px rgba(39, 59, 100, 0.08);    border-radius: 1px;    overflow: hidden;    .no-border {      border: none !important;    }    .file-image-area {      width: 214px;      height: 156px;      margin: 8px auto;      background: rgba(252, 254, 255, 1);      border-radius: 1px;      overflow: auto;      border: 1px dashed $gobal-color-big;      #upLoad {        display: none;      }      .file-image-init-display {        width: 100%;        height: 100%;        cursor: pointer;        display: inline-block;        position: relative;        .initial-img-url {          margin: 48px auto 20px;          width: 32px;          display: block;          position: relative;          z-index: 1;        }        .mask-sample-img {          width: 100%;          height: 100%;          position: absolute;          left: 0;          top: 0;          right: 0;          bottom: 0;          opacity: 0.2;          -webkit-mask: -webkit-linear-gradient(            rgba(0, 0, 0, 0.5),            rgba(0, 0, 0, 1),            rgba(0, 0, 0, 1),            rgba(0, 0, 0, 0.5)          );        }        .initial-title {          font-size: 12px;          font-weight: 400;          color: rgba(81, 81, 81, 1);          text-align: center;          .underline {            text-decoration: underline;          }        }      }      .file-img {        cursor: pointer;        width: 100%;        height: 100%;        object-fit: cover;      }    }    .file-image-area-orange {      border: 1px dashed $gobal-color-orange;    }    .file-text-bottom {      width: 214px;      height: 30px;      margin: 0 auto;      background: rgba(255, 255, 255, 1);      box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.08);      display: flex;      .file-name {        width: 135px;        height: 100%;        line-height: 30px;        font-size: 12px;        color: #414141;        font-weight: 600;        padding-left: 8px;        box-sizing: border-box;        overflow: hidden;        text-overflow: ellipsis;        white-space: nowrap;        cursor: pointer;      }      .operating-area {        flex: 1;        display: flex;        margin-right: 4px;        i,        span {          flex: 1;          font-size: 18px;          color: #bbbbbb;          cursor: pointer;          text-align: right;          height: 30px;          line-height: 30px;          &:hover {            color: $gobal-color-big;          }        }      }    }  }  .pdf-preview {    position: fixed;    left: 0;    top: 0;    right: 0;    bottom: 0;    background-color: #0000004d;    z-index: 999;    overflow: auto;    .pdf-preview-box {      width: 600px;      height: 450px;      border-radius: 10px;      overflow: hidden;      background-color: #fff;      margin: 0 auto;      position: relative;      top: 50%;      transform: translateY(-50%);      display: flex;      flex-direction: column;      .pdf-preview-box-top {        width: 100%;        height: 24px;        background-color: #233e48;        text-align: center;        line-height: 24px;        position: relative;        .pdf-title {          vertical-align: super;          font-size: 12px;          font-weight: 400;          color: rgba(255, 255, 255, 1);        }        .el-icon-circle-close {          position: absolute;          font-size: 16px;          color: #fff;          right: 10px;          cursor: pointer;          top: 50%;          transform: translateY(-50%);        }      }      .pdf-preview-box-main {        flex: 1;        overflow: auto;      }      .pdf-preview-box-paging {        width: 100%;        height: 24px;        background-color: #233e484d;        text-align: center;        font-size: 14px;        font-weight: 400;        line-height: 24px;        i {          color: #000;          cursor: pointer;          margin: 0 10px;        }        span {          margin: 0 10px;          user-select: none;        }      }    }  }}</style>
  • 图片预览组件
<template>  <div>    <viewer      :images="images"      :options="options"      class="viewer"      ref="viewer"      @inited="inited"      v-if="images && images.length"    >      <img        v-for="{source, thumbnail} in images"        :src="thumbnail"        :data-source="source"        :key="source"        class="image"      />    </viewer>  </div></template><script  lang="ts">import { Component, Vue } from "vue-property-decorator";import vue from "vue";import Viewer from "v-viewer";import "viewerjs/dist/viewer.css";vue.use(Viewer);@Componentexport default class ImgViewer extends Vue {  options = {    url: "data-source",  };  index = 0;  images = [];  inited(viewer: any) {    (this as any).$viewer = viewer;    (this as any).$viewer.view(this.index);  }  view(index: any) {    this.index = index;    (this as any).$viewer.view(this.index);  }  show(images: any, index = 0) {    if (this.images === images) {      this.view(index);      return;    }    this.images = images;    this.index = index;  }}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style lang="scss" scoped>.image {  display: none;}</style><style>.viewer-loading > img {  display: none; /* hide big images when it is loading */}.viewer-list > li {  opacity: 0.3;}</style>
  • 删除弹框样式
.deleteUpload {  width: 160px;  height: 68px;  padding: 10px 5px 0 7px;  margin-left: -120px;  box-sizing: border-box;  .el-popconfirm__main {    font-size: 12px;    color: #515151;    margin-bottom: 8px;    i {      display: none;    }  }  &[x-placement^=top] {    // margin-bottom: 0px;    .popper__arrow {      left: 132px !important;      right: 18px;    }  }  &[x-placement^=bottom] {    // margin-top: 1px;    .popper__arrow {      left: 132px !important;      right: 18px;    }  }  .el-popconfirm__action {    text-align: center;    .el-button {      width: 60px;      height: 20px;      padding: 0;    }    .el-button--text {      border: 1px solid rgba(239, 244, 255, 1);      color: #AEAEAE;    }    .el-button--primary {      background-color: #4b6cf6;      border-color: #4b6cf6;    }  }}

文章转载于:https://www.jianshu.com/p/c8e94c9b77ea

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » 基于vue+ts 的上传组件封装(上传+预览<包括图片+PDF>) 功能超全hh
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录