基于vue+ts 的上傳組件封裝(上傳+預(yù)覽<包括圖片+PDF>) 功能超全hh

一辫塌、依賴包

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

二原押、初衷分冈、思路圾另、想法

絕大數(shù)后臺管理項目和少數(shù)C端項目都離不開文件的上傳及預(yù)覽,所以我就做了一個這個自認(rèn)為適用于絕大多數(shù)項目的組件~
用戶上傳圖片orPDF丈秩,預(yù)覽上傳的文件無論圖片orPDF盯捌,由于在現(xiàn)在的公司做的主要項目針對的是后臺管理,且有的頁面上傳的文件較多蘑秽,有的文件必傳饺著、有的文件需要顯示示例圖、有的文件需要對文件類型及文件大小做限制肠牲、有的場景希望上傳的區(qū)域較大或較小幼衰、直接拖拽文件到某一特定區(qū)域上傳、在文件較多的頁面希望用戶“直接”定位到有哪一個或哪一些文件上傳的校驗沒通過
對于開發(fā)者缀雳,希望在用戶上傳特定的一些文件后立即觸發(fā)回調(diào)事件對用戶上傳的文件及時做處理渡嚣。

三、組件預(yù)覽圖

  • 總攬


  • pdf預(yù)覽


  • 圖片預(yù)覽


  • gif


    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, // 自定義當(dāng)前文件最大15兆
      fileType: ["pdf"],
      callback: (file: any) => {
        console.log("回調(diào)成功", file);
      },
    },
    {
      id: "idCardBack",
      fileName: "身份證反面",
    },
    {
      id: "vin",
      fileName: "vim碼",
    },
    {
      id: "businessLicense",
      fileName: "營業(yè)執(zhí)照",
    },
    {
      id: "engine",
      fileName: "車輛發(fā)動機艙全景",
      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'}"
    >
      <!-- 目標(biāo)文件名稱 -->
      <span class="file-info-top">{{item.fileName}}</span>
      <main class="file-container" ref="fileBox" :data-index="index">
        <!-- 文件展示區(qū)域 -->
        <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>
        <!-- 文件名及文件操作區(qū)域 -->
        <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>
          <!-- 操作區(qū)域 -->
          <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>
    <!-- 圖片預(yù)覽 -->
    <img-viewer ref="viewer" />
    <!-- pdf預(yù)覽 -->
    <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-pdf
import 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預(yù)覽
  pdf = {
    pdfPreview: false,
    pdf: "",
    page: 1,
    size: 1,
    name: "",
  };

  // 組件大小
  @Prop({
    type: String,
  })
  private size?: string;

  // 組件風(fēng)格
  @Prop({
    type: String,
    default: "blue",
  })
  private type?: string;

  // 初始化默認(rèn)圖片橙色
  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=";
  // 初始化默認(rèn)圖片藍(lán)色
  initialImgUrlConfigBlue =
    "data:image/png;base64,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";
  // 初始化展示默認(rèn)圖片
  initialImgUrl =
    this.type === "blue"
      ? this.initialImgUrlConfigBlue
      : this.initialImgUrlConfigOrange;

  // 上傳文件的列表
  @Prop({
    type: Array,
    required: true,
  })
  private fileImgList?: Array<any>;

  // 上傳的文件限制大小
  @Prop({
    type: Number,
    default: 5120, // 默認(rèn)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> = [];
  // 向外暴露出去的總文件數(shù)組
  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(); //阻止離開時的瀏覽器默認(rèn)行為
      };
      el.ondrop = (e: any) => {
        //阻止拖放后的瀏覽器默認(rèn)行為
        e.preventDefault();

        const data = e.dataTransfer.files;

        // 檢測是否有文件拖拽到頁面
        if (data.length < 1) {
          return;
        }
        that.uploadFile(e, $(el).data("index"));
      };
      el.ondragenter = (e: any) => {
        //阻止拖入時的瀏覽器默認(rèn)行為
        e.preventDefault();
      };
      el.ondragover = (e: any) => {
        //阻止拖來拖去的瀏覽器默認(rèn)行為
        e.preventDefault();
      };
    });
  }

  // 代理打開文件上傳
  uploadImg(i: number) {
    (this.$refs as any).fileImage[i].dispatchEvent(new MouseEvent("click"));
  }
  // 文件上傳-預(yù)覽文件至“文件展示區(qū)域”
  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]);
        };
      }

      // 觸發(fā)回調(diào)
      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,
        // 必傳但未傳的文件數(shù)量
        noRulesLength,
      };
    }

    // 錨點滾動
    $((this as any).errScrollBox).animate(
      { scrollTop: $("#" + errArr[0])[0].offsetTop },
      300
    );

    return {
      isValidation: false,
      noRulesLength,
    };
  }
  // 文件整合數(shù)組 去重
  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,
      // 總共上傳的文件數(shù)量
      length: this.exposedFileArr.length,
      // 需上傳的文件是否全部驗證成功
      isValidation,
      // 必傳但未傳的文件數(shù)量
      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);

    // 清空數(shù)據(jù)
    (this.$refs as any).fileImage[i].value = "";
    // 刪除文件
    this.exposedFileArr.forEach((v, i) => {
      if (v.typefileId === id) return this.exposedFileArr.splice(i, 1);
    });
  }
  // 文件預(yù)覽
  filePreview(val: any, index: number) {
    const file = this.fileImgListDrawing[index];
    // pdf預(yù)覽
    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;
      });
    }
    // 圖片預(yù)覽
    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>
  • 圖片預(yù)覽組件
<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);

@Component
export 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;
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腹鹉,隨后出現(xiàn)的幾起案子藏畅,更是在濱河造成了極大的恐慌,老刑警劉巖功咒,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愉阎,死亡現(xiàn)場離奇詭異,居然都是意外死亡力奋,警方通過查閱死者的電腦和手機榜旦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來景殷,“玉大人溅呢,你說我怎么就攤上這事”醭梗” “怎么了藕届?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亭饵。 經(jīng)常有香客問我,道長梁厉,這世上最難降的妖魔是什么辜羊? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮词顾,結(jié)果婚禮上八秃,老公的妹妹穿的比我還像新娘。我一直安慰自己肉盹,他們只是感情好昔驱,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著上忍,像睡著了一般骤肛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窍蓝,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天腋颠,我揣著相機與錄音,去河邊找鬼吓笙。 笑死淑玫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播絮蒿,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼尊搬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了土涝?” 一聲冷哼從身側(cè)響起佛寿,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎回铛,沒想到半個月后狗准,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡茵肃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年腔长,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片验残。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡捞附,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出您没,到底是詐尸還是另有隱情鸟召,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布氨鹏,位于F島的核電站欧募,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仆抵。R本人自食惡果不足惜跟继,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镣丑。 院中可真熱鬧舔糖,春花似錦、人聲如沸莺匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趣竣。三九已至摇庙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間期贫,已是汗流浹背跟匆。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留通砍,地道東北人玛臂。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓烤蜕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迹冤。 傳聞我的和親對象是個殘疾皇子讽营,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容