跪拜 Guibai
← Back to the summary

Flyfish Viewer Handles 206 Office File Formats Where pdf.js and pptx.js Fall Short

@[TOC](How does the frontend implement the online preview of common Office files?)

Foreword

Recently, I was working on an online attachment preview feature, which I believe most frontend developers are familiar with. At first, I jumped right in and downloaded pdf.js/pptx.js/vue-office, but it wasn't very friendly. I found that no matter what I did, it didn't support files with the .doc and .xls extensions. However, by chance, I discovered a very nice plugin, which is Flyfish Viewer.

Supported Formats

It supports 206 extension mappings, covering 24 preview chains. Insert image description here It's simply fantastic.

Integration Methods

It can be used through multiple integration methods. Insert image description here

Issues During Use

My project is a legacy project using Vue 2.6. However, when I tried to install @file-viewer/vue2.6 @file-viewer/preset-office via npm, it simply wouldn't download and got completely stuck. I spent a long time troubleshooting but couldn't figure out the problem. If any experts have successfully installed it via npm, please share your experience in the comments.

Could this stump me?

So, I used the pure JS integration method.

<div id="viewer" style="height:720px"></div>

<script src="https://cdn.jsdelivr.net/npm/@file-viewer/web-full@latest/dist/flyfish-file-viewer-web-full.iife.js"></script>
<script>
  FlyfishFileViewerWebFull.mountViewer(document.getElementById('viewer'), {
    url: '/files/demo.pdf',
    options: {
      theme: 'light',
      toolbar: { position: 'bottom-right' }
    }
  })
</script>

Although it could preview Excel, Word, and some .stl files normally, there was still one problem: it failed to preview pptx. Insert image description here It kept throwing this error. It said the PPTX Worker failed because a file failed to load. There are many theories about the cause, but regardless of why, how do we solve the problem?

So, I directly downloaded all the folders and files from the link https://cdn.jsdelivr.net/npm/@file-viewer/web-full@latest/dist/ to my local machine. Insert image description here If you're too lazy to download, you can click the link to get the source files above

How to Use

Insert image description here After getting the files, put them directly into the project's public directory, and then simply import them in the index.html file to use them normally.

<script src="/file-viewer/flyfish-file-viewer-web-full.iife.js" onerror="console.error('Failed to load flyfish-file-viewer')"></script>

Using in a Component

My component is named previewFile.vue, and the code is as follows:

<template>
  <el-dialog
    class="preview-file-dialog"
    :class="{ 'is-fullscreen': isFullscreen }"
    :title="dialogTitle"
    :visible.sync="dialogVisible"
    :fullscreen="isFullscreen"
    :close-on-click-modal="false"
    :append-to-body="true"
    :modal-append-to-body="true"
    width="70%"
    @close="handleClose"
  >
    <template #title>
      <div class="preview-dialog-header">
        <span class="preview-dialog-title">{{ dialogTitle }}</span>
        <i
          class="header-icon"
          :class="isFullscreen ? 'el-icon-copy-document' : 'el-icon-full-screen'"
          :title="isFullscreen ? 'Exit Fullscreen' : 'Fullscreen View'"
          @click="isFullscreen = !isFullscreen"
        ></i>
      </div>
    </template>
    <div
      class="preview-file-body"
      :style="bodyStyle"
      v-loading="loading"
      element-loading-text="File loading, please wait..."
    >
      <div
        ref="fileViewerRef"
        style="height: 100%; width: 100%"
      ></div>
    </div>
  </el-dialog>
</template>

<script lang="ts">
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';

@Component({
  name: 'PreviewFile'
})
export default class extends Vue {
  // Whether to show
  @Prop({ type: Boolean, default: false }) visible!: boolean;
  // File data
  @Prop({ type: Object, default: () => ({ name: '', url: '' }) }) fileData!: any;

  public loading = false;
  public isFullscreen = true;
  // Dialog title
  public dialogTitle = 'Preview Attachment';

  // Content area height changes with fullscreen state
  get bodyStyle() {
    return {
      height: this.isFullscreen ? 'calc(100vh - 54px)' : '70vh',
      width: '100%'
    };
  }

  get dialogVisible() {
    return this.visible;
  }
  set dialogVisible(val: boolean) {
    this.$emit('update:visible', val);
  }

  @Watch('visible', { immediate: true })
  visibleChange(val: boolean) {
    if (val) {
      Object.keys(this.fileData).length && this.renderFile(this.fileData);
    }
    if (!val) {
      this.resetData();
    }
  }

  // Render flyfish-file-viewer (Web Component)
  private renderFlyfishViewer(url: string, fileName: string) {
    const viewerRef = this.$refs.fileViewerRef as any;
    if (!viewerRef || !viewerRef) return;
    (window as any).FlyfishFileViewerWebFull.mountViewer(viewerRef, {
      url: url,
      filename: fileName,
      options: {
        theme: 'light',
        toolbar: false
      },
      onEvent(event: any) {
        switch (event.type) {
          case 'load-start':
            this.loading = true;
            break;
          case 'load-complete':
            this.loading = false;
            console.log('Preview loading complete');
            break;
          case 'unload-start':
            this.loading = true;
            break;
        }
      },
      onError(event: any) {
        console.log(event);
        this.loading = false;
      }
    });
  }

  // Reset data
  public resetData() {
    this.isFullscreen = false;
    this.dialogTitle = 'File Preview';
  }

  // Close dialog
  public handleClose() {
    this.dialogVisible = false;
  }

  // Start file rendering
  public async renderFile(data: any) {
    try {
      this.loading = true;
      if (!data.name && !data.url) {
        return;
      }
      if (data.name) {
        this.dialogTitle = data.name;
      }
      if (data.name.indexOf('.') === -1) {
        data.name += `.${data.url.split('.').pop()}`;
      }
      let filePreviewUrl = data.url || '';
      filePreviewUrl = filePreviewUrl;
      // If it's a format supported by flyfish-file-viewer, call the Web Component to load
      this.$nextTick(() => {
        this.renderFlyfishViewer(filePreviewUrl, data.name);
      });
    } finally {
      this.loading = false;
    }
  }
}
</script>

<style lang="scss" scoped>
.preview-file-dialog {
  ::v-deep .el-dialog__header {
    padding: 15px 20px;
    border-bottom: 1px solid #e8e8e8;
  }
  ::v-deep .el-dialog__body {
    padding: 0;
  }
  ::v-deep .el-dialog__headerbtn {
    top: 17px;
  }
}
.preview-dialog-header {
  display: flex;
  align-items: center;
  .preview-dialog-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
  }
  .header-icon {
    font-size: 16px;
    color: #909399;
    cursor: pointer;
    margin-right: 30px;
    &:hover {
      color: #409eff;
    }
  }
}
.preview-file-body {
  height: 400px;
  overflow: auto;
  box-sizing: border-box;
}
</style>

Component usage code

<!-- File preview dialog -->
<preview-file
  v-if="previewVisible"
  :visible.sync="previewVisible"
  :fileData="{ name: 'word file', url: 'https://demo.file-viewer.app/example/word.docx' }"
></preview-file>

Summary

Using file-viewer is not difficult, but there are quite a few issues during use. Follow me to stay on track. I publish frontend development tips articles from time to time.