天天看点

封装附件预览组件 图片 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>