从0到1封装一个image/pdf预览组件

iShot_2024-10-14_16.47.10

目录结构

content.vue
javascript 复制代码
<template>
  <div class="no-content-block">
    <i class="iconfont icondocument large-file" />
    <div class="text-wrapper">{{ t('__ui__.siPreview.previewSupported') }}</div>
    <div class="buttons-default" @click="donwload(url)">
      <i class="iconfont el-icon-download" />
      <div class="button-name">{{ t('__ui__.siPreview.downloadAttachment') }}</div>
    </div>
  </div>
</template>

<script>
import Locale from '../../../../mixins/locale'
export default {
  name: 'BlankContent',
  mixins: [Locale],
  inject: ['app'],
  props: {
    url: {
      type: String,
      default: ''
    }
  },
  methods: {
    donwload(url) {
      this.app.download()
      window.open(url, '_blank')
    }
  }
}
</script>
previewContent.vue
javascript 复制代码
<template>
  <div class="pre-view-content">
    <div class="buttons-icons-con" @click="donwload(fileUrl)">
      <i class="iconfont el-icon-download" />
    </div>
    <div class="buttons-icons-set" @click="closeDialogue(fileType)">
      <i class="iconfont el-icon-close" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'PreviewContent',
  inject: ['app'],
  props: {
    fileUrl: {
      type: String,
      default: ''
    },
    fileType: {
      type: String,
      default: ''
    }

  },
  methods: {
    donwload(url) {
      this.app.donwload(url)
    },
    closeDialogue(type) {
      this.app.closeDialogue(type)
    }
  }
}
</script>
index.js
javascript 复制代码
/*
  Exposure to the outside world
    fileUrl、Other parameters;

  Transfer data
    fileUrl address;
    openLoad Yes, it's all function calls;

  Exclusive introduction
    import Vue from 'vue'
    import { FilePreview } from '@payermax/components-manage-base'

    Vue.use(FilePreview)

    (= ̄ω ̄=) file you use this component
    this.$filePreview('xxx');
  */
// import Vue from 'vue'
import _component from './index.vue'
import { prefix } from '../../../const'

const FilePreview = {
  install(Vue, options) {
    Vue.prototype.$filePreview = (url, downLoadCallback, closeCallback) => {
      // Builder
      const ComponentInstance = Vue.extend({
        render(h) {
          return h(_component, {
            props: {
              fileUrl: url,
              downloadFile: downLoadCallback,
              closeFile: closeCallback,
              openLoad: true
            }
          })
        }
      })
      // Create a singleton
      const instance = new ComponentInstance().$mount()
      document.body.appendChild(instance.$el)
    }
    Vue.component(`${prefix}${_component.name}`, _component)
  }
}

export default FilePreview

index.js 代码解释

当前代码定义了一个名为 $filePreview 的 Vue 实例方法,该方法用于在页面中预览文件。以下是对代码的详细解释:

  1. 方法定义

    javascript

    javascript 复制代码
    Vue.prototype.$filePreview = (url, downLoadCallback, closeCallback) => {
      //...
    }

    这里定义了一个 Vue 实例方法 $filePreview,它接受三个参数:url(文件的 URL 地址),downLoadCallback(下载文件的回调函数),closeCallback(关闭文件预览的回调函数)。

  2. 组件实例化

    javascript

    javascript 复制代码
      const ComponentInstance = Vue.extend({
        render(h) {
          return h(_component, {
            props: {
              fileUrl: url,
              downloadFile: downLoadCallback,
              closeFile: closeCallback,
              openLoad: true
            }
          })
        }
      })

    这里使用 Vue.extend 创建了一个 Vue 组件的子类 ComponentInstance,并在其 render 函数中使用 h 函数渲染 _component 组件,并传递了四个属性:fileUrldownloadFilecloseFileopenLoad

  3. 创建实例并挂载

    javascript

    javascript 复制代码
      const instance = new ComponentInstance().$mount()
      document.body.appendChild(instance.$el)

    这里创建了 ComponentInstance 的实例 instance,并将其挂载到一个新的 DOM 元素上,然后将这个 DOM 元素添加到文档的 body 中。

总结来说,$filePreview 方法通过创建一个 Vue 组件实例,并将其渲染到页面上,实现了文件预览的功能。用户可以通过传递文件的 URL 和相应的回调函数来使用这个方法。

index.vue
javascript 复制代码
<template>
  <div ref="filePreviewContainer" class="file-preview-container">
    <div class="download-card">
      <div class="preview-wrap">
        <div v-if="openLoad" />
        <div v-else class="click-default">
          <!-- Click arera -->
          <div @click="previewFile()">
            <div v-if="!hasSlot" />
            <slot v-else />
          </div>
        </div>
        <!--  Preview types -->
        <template v-if="pdfContainerVisible && fileType == 'pdf'">
          <div>
            <div id="pdf-container" />
            <div v-loading="loading" class="pdf-loading" />
            <div class="pdf-download">
              <div class="pdf-download-container">
                <PreviewContent :file-url="fileUrl" :file-type="fileType" />
              </div>
            </div>
          </div>
        </template>
        <div v-if="imageVisible && fileType == 'image'">
          <div class="other-container">
            <div class="header">
              <PreviewContent :file-url="fileUrl" :file-type="fileType" />
            </div>
            <div class="other-containe-content">
              <img loading="lazy" alt="" :src="fileUrl">
            </div>
          </div>
        </div>
        <div v-if="otherVisible" class="other-container">
          <div class="header">
            <PreviewContent :file-url="fileUrl" :file-type="fileType" />
          </div>
          <div class="other-containe-content">
            <div v-if="openLoad" class="no-content-block">
              <i class="iconfont icondocument large-file" />
            </div>
            <BlankContent v-else :url="fileUrl" />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>

import PDFObject from 'pdfobject'
import BlankContent from './components/content.vue'
import PreviewContent from './components/previewContent.vue'

import Locale from '../../../mixins/locale'

const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
const pdfExtensions = ['pdf']
const SCOPE_NAME = 'FilePreview'

export default {
  name: SCOPE_NAME,
  components: {
    BlankContent,
    PreviewContent
  },
  mixins: [Locale],
  provide() {
    return {
      app: this
    }
  },
  inheritAttrs: false,
  props: {
    fileUrl: {
      type: String,
      default: ''
    },
    closeFile: {
      type: Function,
      default: () => { }
    },
    downloadFile: {
      type: Function,
      default: () => { }
    },
    openLoad: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      fileType: '',
      pdfContainerVisible: false,
      imageVisible: false,
      otherVisible: false,
      loading: false,
      hasSlot: false
    }
  },
  mounted() {
    this.init()
    this.mountPlugIn()
  },
  methods: {
    mountPlugIn() {
      if (this.openLoad) {
        this.previewFile()
      }
    },
    init() {
      this.justFileType()
      this.checkSlots()
    },
    checkSlots() {
      if (this.$slots.default) {
        this.hasSlot = true
      } else {
        this.hasSlot = false
      }
    },
    justFileType() {
      const extension = this.fileUrl.split('.').pop().toLowerCase() || ''
      if (imageExtensions.includes(extension)) {
        this.fileType = 'image'
      } else if (pdfExtensions.includes(extension)) {
        this.fileType = 'pdf'
      } else {
        this.fileType = 'other'
      }
    },
    close() {
      this.$emit('closeFile')
      this.openLoad && this.closeFile()
      this.openLoad && this.dealPluginPreviewNode()
    },
    download() {
      this.$emit('downloadFile')
      this.openLoad && this.downloadFile()
      this.openLoad && this.dealPluginPreviewNode()
    },
    dealPluginPreviewNode() {
      var containers = document.querySelectorAll('.file-preview-container')
      if (containers.length > 0) {
        var lastContainer = containers[containers.length - 1]
        var parent = lastContainer.parentNode
        parent.removeChild(lastContainer)
      }
    },
    previewFile() {
      switch (this.fileType) {
        case 'pdf':
          this.pdfPreview(this.fileUrl)
          break
        case 'image':
          this.imagePreview(this.fileUrl)
          break
        case 'other':
          this.otherPreview()
          break
        default:
          break
      }
    },
    async pdfPreview(fileUrl) {
      this.loading = true
      try {
        const response = await fetch(fileUrl)
        const status = response?.status || ''
        if (status === 200) {
          this.pdfContainerVisible = true
          this.loading = false
          this.$nextTick(() => {
            let url = ''
            if (fileUrl.startsWith('http://')) {
              url = fileUrl.substring(5)
            } else if (fileUrl.startsWith('https://')) {
              url = fileUrl.substring(6)
            } else {
              url = fileUrl
            }
            PDFObject.embed(url, '#pdf-container', {
              width: '100%'
            })
          })
        } else {
          this.loading = false
          this.otherVisible = true
        }
      } catch (error) {
        this.loading = false
        this.otherVisible = true
      }
    },
    imagePreview(fileUrl) {
      this.loading = true
      this.checkImageAccessibility(fileUrl, (accessible) => {
        if (accessible) {
          this.imageVisible = true
        } else {
          this.otherVisible = true
        }
        this.loading = false
      })
    },
    checkImageAccessibility(url, callback) {
      const img = new Image()
      img.onload = function() {
        callback(true)
      }
      img.onerror = function() {
        callback(false)
      }
      img.src = url
    },
    otherPreview() {
      this.otherVisible = true
    },
    closeDialogue(type) {
      switch (type) {
        case 'pdf':
          this.pdfContainerVisible = false
          this.close()
          break
        case 'image':
          this.imageVisible = false
          this.close()
          break
        case 'other':
          this.otherVisible = false
          this.close()
          break
        default:
          break
      }
    },
    donwload(url) {
      this.download()
      window.open(url, '_blank')
    }
  }
}
</script>
全局引入
javascript 复制代码
import locale from './locale'
import { prefix } from './const'

import FilePreview from './src/Other/FilePreview'

const components = [
  FilePreview
]

export {
  FilePreview
}

function install(Vue, options = {}) {
  locale.i18n(options.i18n)

  components.forEach((component) => {
    console.log(component.name, component)
    if (component.install) {
      component.install(Vue, options)
    } else {
      Vue.component(`${prefix}${component.name}`, component)
    }
  })
}

export default install
按需引入

Text.vue

html 复制代码
         <el-row>
            <el-col :span="6">
              <h2>图片(默认)</h2>
              <m-FilePreview
                :file-url="fileUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              />
            </el-col>
            <el-col :span="6">
              <h2>其他类文件(默认)</h2>
              <m-FilePreview
                :file-url="otherFileUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              />
            </el-col>
            <el-col :span="6">
              <h2>PDF文件(默认)</h2>
              <m-FilePreview
                :file-url="PdfUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              />
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <h2>图片(自定义按钮样式)</h2>
              <m-FilePreview
                :file-url="fileUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              >
                <div class="haoren">
                  <el-button type="primary" plain>图片按钮</el-button>
                </div>
              </m-FilePreview>
            </el-col>
            <el-col :span="6">
              <h2>其他类文件(自定义按钮样式)</h2>
              <m-FilePreview
                :file-url="otherFileUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              >
                <div class="haoren">
                  <el-button type="success" plain>无法预览的文件</el-button>
                </div>
              </m-FilePreview>
            </el-col>
            <el-col :span="6">
              <h2>PDF文件 (自定义按钮样式)</h2>
              <m-FilePreview
                :file-url="PdfUrl"
                @closeFile="closeTest"
                @downloadFile="downloadTest"
              >
                <div
                  style="color:red;
                  width:200px;
                  height: 80px;
                  background-color: aquamarine;
                  display: flex;
                  font-weight: 700;
                  justify-content: center;
                  align-items: center;
                  border-radius: 12px;
                  cursor: pointer;"
                >PDF预览演示</div>
              </m-FilePreview>
            </el-col>
          </el-row>
          <el-row style="margin-top: 80px;">
            <el-col :span="6">
              <h2>图片(函数式调用)</h2>
              <div @click="TestMess('img')">
                <el-button type="primary">函数式预览图片</el-button>
              </div>
            </el-col>
            <el-col :span="6">
              <h2>PDF (函数式调用)</h2>
              <div @click="TestMess('pdf')">
                <el-button type="info">函数式预览PDF</el-button>
              </div>
            </el-col>
            <el-col :span="6">
              <h2>其他类型</h2>
              <div @click="TestMess('other')"><button>函数式调用其他类型</button></div>
            </el-col>
          </el-row>
html 复制代码
 fileUrl: 'https://s3.ap-s%29.gif',
 PdfUrl: 'https://s3.ap-south36596/NL2SQL.pdf',
 otherFileUrl: 'https://gimg4.baidu.com/poster/f',
javascript 复制代码
  TestMess(val) {
      if (val === 'img') {
        this.$filePreview(this.fileUrl, this.downloadTest, this.closeTest)
      } else if (val === 'pdf') {
        this.$filePreview(this.PdfUrl, () => { console.log('click download!') }, () => { console.log('close window!') })
      } else {
        this.$filePreview(this.otherFileUrl, () => { console.log('click download!') }, () => { console.log('close window!') })
      }
    }
代码使用