天天看點

封裝附件預覽元件 圖檔 pdf預覽(vue+elementui)目錄

附件預覽元件

  • 目錄
    • preview
    • props
    • event
    • usage
    • 效果圖
    • 元件源碼

目錄

preview

元件描述:附件預覽元件。

适用場景:根據檔案流前端預覽。

props

props type default explain
previewDownLoad Boolean true 是否啟用内部下載下傳接口,預設啟用
prop Object { 配置字段
prewKey: ‘url’, prewKey:預覽接口發送的key
name: ‘name’, name:檔案名
url: ‘url’ url:檔案下載下傳接口發送的url
}
previewApi Object { 預覽接口,非必傳,預設使用内置接口
url:

/${window.fe.project}/file/preview

,
url:請求路徑
type: ‘post’ type:請求方式
}
downloadApi Object { 下載下傳接口,非必傳,預設使用内置接口
url:

/${window.fe.project}/upload/download

,
url:請求路徑
type: ‘get’ type:請求方式
}

event

event default explain
downLoad file 點選下載下傳按鈕時觸發,當previewDownLoad為fasle時有效

usage

  1. 頁面引入
<template>
       <div>
         <preview 
           ref="preview"
           :downloadApi="{
             url: '/purchaser/upload/download',
             type: 'get'
           }"
           :previewApi="{
             url: '/purchaser/file/preview',
           type: 'post'
           }"
           :prop="{
             prewKey: 'url',
             name: 'name',
             url: 'url'
           }"></preview>
       </div>
   </template>
           
  1. 注冊
import preview from 'fe-coms/components/business/preview/index.vue' //改成你自己的路徑
           
  1. 使用
調用預覽方法:
  this.$refs['preview'].preview(file, fileList) // 參數說明:file 目前點選的檔案 fileList檔案集合
           

效果圖

  1. 預覽圖檔格式
    封裝附件預覽元件 圖檔 pdf預覽(vue+elementui)目錄
  2. 預覽pdf格式
    封裝附件預覽元件 圖檔 pdf預覽(vue+elementui)目錄
  3. 無法預覽檔案
    封裝附件預覽元件 圖檔 pdf預覽(vue+elementui)目錄

元件源碼

<template>
  <div
    class="sy-preview"
    v-if="visible"
  >
    <!-- 遮罩層 -->
    <div class="sy-image-viewer__mask"></div>
    <!-- 頭部 -->
    <div class="header">
      <div class="file-title">{{imgItem.name}}</div>
      <i
        class="el-icon-circle-close"
        @click="close()">
      </i>
      <i
        class="el-icon-download"
        @click="downloadClick()">
      </i>
    </div>
    <!-- 檔案預覽區域 -->
    <div
      class="sy-image-viewer__canvas">
      <div
        v-for="(item, i) in imgBlobList"
        :key="item">
        <template
          v-if="i === index">
          <template
            v-if="accept.indexOf(imgItem.type) > -1">
            <img
              v-if="imgItem.type !== 'pdf'"
              :style="imgStyle"
              :src="imgItem.src"
              @error="handleImgError">
            <embed v-else :src="imgItem.src" type="application/pdf" style="position:fixed;top:72px;left:0;right:0;bottom:0;" width="100%" height="100%">
          </template>
        </template>
      </div>
      <div
        v-show="accept.indexOf(imgItem.type) === -1"
        class="not-support__wapper">
        <span class="link-box">
          <i class="el-icon-link"></i>
        </span>
        <p class="text mt-10">我們不能預覽該檔案。</p>
        <p class="text">您要先下檔案以檢視。</p>
        <el-button class="mt-10" @click="downloadClick()" plain>下載下傳</el-button>
      </div>
    </div>
    <!-- 切換 上一個 下一個 -->
    <span
      class="image-viewer__btn image-viewer__prev"
      @click="prev">
      <i class="el-icon-arrow-left"></i>
    </span>
    <span
      class="image-viewer__btn image-viewer__next"
      @click="next">
      <i class="el-icon-arrow-right"></i>
    </span>
    <!-- 放大縮小旋轉 -->
    <div 
      class="image-viewer__btn sy-image-viewer__actions"
      v-if="imgItem.type !== 'pdf'">
      <div class="sy-image-viewer__actions__inner">
        <i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
        <i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
        <i :class="mode.icon" @click="toggleMode"></i>
        <i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
        <i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
      </div>
    </div>
  </div>
</template>

<script>
import download from '../../../utils/download'
const Accept = ['jpg', 'png', 'pdf'] //後端支援的檔案類型枚舉
const Mode = {
  CONTAIN: {
    name: 'contain',
    icon: 'el-icon-full-screen'
  },
  ORIGINAL: {
    name: 'original',
    icon: 'el-icon-c-scale-to-original'
  }
}
const defaultApi = { //附件預覽内置api
  downloadApi: {
    url: `/${window.fe.project}/upload/download`, // window.fe.project == `purchaser`
    type: 'get' // type
  },
  previewApi: { //預覽api
    url: `/${window.fe.project}/file/preview`, // url
    type: 'post' // type
  }
}
export default {
  name: 'preview',
  data () {
    return {
      visible: false,
      fileList: [],
      imgBlobList: [],
      imgItem: {
        'src': null,
        'type': '',
        'name': '',
        'raw': null,
        'key': ''
      },
      index: 0,
      mode: Mode.CONTAIN,
      accept: Accept,
      transform: {
        scale: 1,
        deg: 0,
        offsetX: 0,
        offsetY: 0,
        enableTransition: false
      }
    }
  },
  props: {
    prop: {
      type: Object,
      default: {
        prewKey: 'url',
        fileReviewBusinessCode: 'DEMO', //檔案預覽業務枚舉code
        bizCode: null, //業務code
        name: 'name', //檔案名稱
        url: 'url' //檔案下載下傳接口的url參數取值字段
      }
    },
    downloadApi: { //下載下傳api
      type: Object,
      default: defaultApi.downloadApi
    },
    previewApi: { //預覽api
      type: Object,
      default: defaultApi.previewApi
    },
    previewDownLoad: { //是否啟用預覽附件元件的下載下傳方式 預設啟用
      type: Boolean,
      default: true
    }
  },
  computed: {
    imgStyle () {
      const { scale, deg, offsetX, offsetY, enableTransition } = this.transform
      const style = {
        transform: `scale(${scale}) rotate(${deg}deg)`,
        transition: enableTransition ? 'transform .3s' : '',
        'margin-left': `${offsetX}px`,
        'margin-top': `${offsetY}px`
      }
      if (this.mode === Mode.CONTAIN) {
        style.maxWidth = style.maxHeight = '100%'
      }
      return style
    }
  },
  methods: {
    // 關閉彈窗
    close () {
      this.visible = false
    },
    // 預覽
    preview (file, fileList) {
      this.fileList = fileList
      this.imgBlobList = []
      this.imgItemReset()
      this.setPreviewData(file, fileList)
      this.visible = true
    },
    // 組裝預覽的資料
    setPreviewData (file, fileList) {
      console.log(file, fileList)
      fileList.forEach((element, i) => {
        let nameIndex = element[this.prop.name].split('.')
        let type = nameIndex[nameIndex.length - 1]
        console.log(type, Accept)
        let imgItem = {
          'src': type !== 'pdf' && element.raw ? URL.createObjectURL(element.raw) : null, //預覽圖檔的字段 前端用
          'raw': element.raw || null, //檔案流
          'type': type, //檔案類型 做區分用 不同類型預覽方式不一樣
          'name': element[this.prop.name], //檔案名稱 傳給後端也是這個
          'key': element[this.prop.url] || '' //檔案下載下傳位址 傳給後端也是這個
        }
        this.imgBlobList.push(imgItem)
        console.log('imgBlobList', this.imgBlobList)
        if (file.uid === element.uid) {
          this.index = i
          if (file.hasOwnProperty('raw')) { //有檔案流 直接預覽
            this.imgItem = imgItem
            console.log(this.imgItem)
          } else { //否則 從伺服器擷取檔案流
            this.imgItem = imgItem
            this.getFileBlob(file)
          }
        }
      })
    },
    // 預覽接口
    async getFileBlob (file) {
      let nameIndex = file[this.prop.name].split('.')
      let type = nameIndex[nameIndex.length - 1]
      if (this.accept.indexOf(type) === -1) return //不支援的類型 不走接口
      let api = this.previewApi
      let params = {
        'key': file[this.prop.prewKey],
        'fileReviewBusinessCode': this.prop.fileReviewBusinessCode || 'DEMO',
        'bizCode': this.prop.bizCode || null
      }
      const loading = this.$loading({
        lock: true,
        text: '檔案加載中,請稍等...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      })
      // 使用axios接受檔案流
      this.axios.post(api.url, params, {responseType: 'arraybuffer'})
        .then((response) => {
          loading.close()
          if (response.status === 200) {
            const blobType = (type === 'pdf' ? `application/pdf` : `image/${type}`)
            const src = new Blob([response.data], { type: blobType })
            this.imgItem.src = window.URL.createObjectURL(src)
            this.imgBlobList[this.index].src = window.URL.createObjectURL(src)
            this.imgBlobList[this.index].raw = response.data
          }
        })
        .catch((error) => {
          loading.close()
          console.log(error)
        })
    },
    // 放大 縮小 旋轉
    handleActions (action, options = {}) {
      const { zoomRate, rotateDeg, enableTransition } = {
        zoomRate: 0.2,
        rotateDeg: 90,
        enableTransition: true,
        ...options
      }
      const { transform } = this
      switch (action) {
        case 'zoomOut':
          if (transform.scale > 0.2) {
            transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3))
          }
          break
        case 'zoomIn':
          transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3))
          break
        case 'clocelise':
          transform.deg += rotateDeg
          break
        case 'anticlocelise':
          transform.deg -= rotateDeg
          break
      }
      transform.enableTransition = enableTransition
    },
    // 圖檔重置 style transform
    reset () {
      this.transform = {
        scale: 1,
        deg: 0,
        offsetX: 0,
        offsetY: 0,
        enableTransition: false
      }
    },
    // 重置目前顯示的圖檔
    imgItemReset () {
      this.imgItem = {
        'src': null,
        'type': '',
        'name': '', //檔案名稱
        'raw': null,
        'key': ''
      }
    },
    // 全屏模式切換
    toggleMode () {
      const modeNames = Object.keys(Mode)
      const modeValues = Object.values(Mode)
      const index = modeValues.indexOf(this.mode)
      const nextIndex = (index + 1) % modeNames.length
      this.mode = Mode[modeNames[nextIndex]]
      this.reset()
    },
    // 圖檔加載失敗
    handleImgError (e) {
      // this.loading = false
      e.target.alt = '加載失敗'
    },
    // 切換 上一個
    prev () {
      this.imgItemReset()
      const len = this.imgBlobList.length
      this.index = (this.index - 1 + len) % len
      if (this.imgBlobList[this.index].src) {
        this.imgItem = this.imgBlobList[this.index]
      } else {
        this.imgItem = this.imgBlobList[this.index]
        this.getFileBlob(this.fileList[this.index])
      }
    },
    // 切換 下一個
    next () {
      this.imgItemReset()
      const len = this.imgBlobList.length
      this.index = (this.index + 1) % len
      if (this.imgBlobList[this.index].src) {
        this.imgItem = this.imgBlobList[this.index]
      } else {
        this.imgItem = this.imgBlobList[this.index]
        this.getFileBlob(this.fileList[this.index])
      }
    },
    // 點選下載下傳按鈕
    async downloadClick () {
      if (this.previewDownLoad) { //使用元件内的下載下傳方法下載下傳檔案
        this.downloadAjax()
      } else { //将檔案向外部抛出 滿足個性化下載下傳需求
        console.log('外部下載下傳')
        this.$emit('downLoad', this.fileList[this.index])
      }
    },
    // 下載下傳接口
    async downloadAjax () {
      let api = this.downloadApi
      let params = {
        'url': this.imgItem.key,
        'name': this.imgItem.name
      }
      download(api.type, api.url, params)
    }
  }
}
</script>

<style lang="scss" scoped>
  .sy-preview {
    z-index: 999;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    .mt-10 {
      margin-top: 10px;
    }
    .sy-image-viewer__mask {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      opacity: .5;
      background: #000;
      z-index: -1;
    }
    .el-icon-circle-close {
      font-size: 32px;
      position: absolute;
      top: 20px;
      right: 20px;
      color: #fff;
      cursor: pointer;
    }
    .el-icon-download {
      font-size: 32px;
      position: absolute;
      top: 20px;
      right: 72px;
      color: #fff;
      cursor: pointer;
    }
    .image-viewer__btn {
      position: absolute;
      z-index: 1;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      -webkit-box-pack: center;
      -ms-flex-pack: center;
      justify-content: center;
      border-radius: 50%;
      opacity: .8;
      cursor: pointer;
      -webkit-box-sizing: border-box;
      box-sizing: border-box;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    .image-viewer__next,
    .image-viewer__prev {
      top: 50%;
      -webkit-transform: translateY(-50%);
      transform: translateY(-50%);
      width: 44px;
      height: 44px;
      font-size: 24px;
      color: #fff;
      background-color: #606266;
      border-color: #fff;
    }
    .image-viewer__next {
      right: 40px;
      text-indent: 2px;
    }
    .image-viewer__prev {
      left: 40px;
    }
    .sy-image-viewer__canvas {
      width: 100%;
      height: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-pack: center;
      -ms-flex-pack: center;
      justify-content: center;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
    }
    .sy-image-viewer__actions {
      left: 50%;
      bottom: 30px;
      transform: translateX(-50%);
      width: 282px;
      height: 44px;
      padding: 0 23px;
      background-color: #606266;
      border-color: #fff;
      border-radius: 22px;
    }
    .sy-image-viewer__actions__inner {
      width: 100%;
      height: 100%;
      text-align: justify;
      cursor: default;
      font-size: 23px;
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: space-around;
    }
    .header {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 72px;
      background-color: #000;
      line-height: 72px;
    }
    .file-title {
      color: #fff;
      text-align: center;
      font-size:20px;
      padding: 0 100px;
    }
    .not-support__box {
      width: 100%;
      height: 100%;
    }
    .not-support__wapper{
      // width: 200px;
      background-color: #000;
      color: #fff;
      padding: 50px;
      text-align: center;
      border: 1px solid #000;
      border-radius: 4px;
      .link-box {
          width: 100px;
          height: 100px;
          display: inline-block;
          border: 2px solid #fff;
          border-radius: 10px;
        .el-icon-link {
          font-size: 100px;
        }
      }
      .text {
        font-size: 16px;
        line-height: 24px;
      }
    }
  }
</style>