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.
It's simply fantastic.
Integration Methods
It can be used through multiple integration methods.
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.
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.
If you're too lazy to download, you can click the link to get the source files above
How to Use
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.