LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

[点晴永久免费OA]纯JS前端文档预览,还要支持所有主流格式(PDF/DOCX/XLSX/PPTX),有这一篇就足够了

admin
2022年4月15日 12:7 本文热度 5116

写在前面

        纯前端的文档预览功能,是非常常见的需求,但就是这么简单的需求,难住了许多可爱的小伙伴们。别急,先访问一下解决方案,给你一个惊喜,再往下看:

文件在线预览DEMO

服务器文件预览DEMO

实现效果

word文档预览


Excel文档预览 


 PPT文档预览


 PDF文档预览


 图片预览


文本预览


 视频预览


看完了之后,废话不多说,来给大家梳理梳理实现思路。

现存的方案和不足

笔者在接到这个功能需求后,对市面上目前的实现方案进行了归纳和梳理,不外乎就三种:

  1. PDF预览使用pdfjs,Office文档使用微软的提供的预览URL。该方案确实省事,而且效果是最好的,但是有个很大的问题,文件链接必须是公网链接,这对于在企业网或局域网部署的系统来说,基本上是不可行的方案,pass
  2. 使用Java后端统一转换为PDF,然后在前端预览。该方案兼容性较好,效果仅次于在线Office,但是对于服务器的压力比较大,在动辄要“高并发,高可用,高吞吐量”的互联网场景下总是不那么合适,基于OpenOffice的文件转换非常耗费IO,pass
  3. 客户端本地安装Office,利用浏览器的Office插件进行预览。这种实现方式对客户端要求较高,基本上不考虑,毕竟我们是web预览嘛,谁知道客户用的是啥浏览器,pass

 

意外的收获

        到此为止,所有的方案都被pass掉了,非常绝望。无果后,我搭上梯子,疯狂Google,终于找到了一个jquery的开源插件,叫做officeToHtml,出于对开源的尊重,这里提供一下人家的访问链接(https://officetohtml.js.org/):OfficeJs | Demos


        这个开源项目非常好用,引用它的demo就能直接预览主流格式,但是它是基于JQuery的。事实上,当时我都已经通过这个方案实现了,结果我们领导说不是Vue,而且用的组件也太老了,强行pass掉了。现实总是残酷的,看着我头顶所剩不多的秀发,深深叹了口气,准备自己再次开整。

        有了国外大佬的思路提供,我的思路也渐渐清晰:

  1. 要解决这个问题,还是得用Vue实现
  2. 大佬的项目是jquery写的,我用Vue实现,也没说不让引用jQuery呀
  3. 分析一下大佬使用的开源组件,去GitHub上找最新的或者效果最好的,说不定有Vue版本呢
  4. 自己封装渲染入口,根据扩展名动态匹配渲染器,解析需要的格式。

        OK,思路清晰了,我们开始撸代码。

开始实现

       一、找替代框架

        大佬的框架已经老得不被待见了,大致整理后,笔者找到的最贴近且效果最好的框架都在下面的表里了:

文档格式        老的开源组件替代开源组件
word(docx)mammothdocx-preview(npm)

powerpoint(pptx)

pptxjs

pptxjs改造开发

excel(xlsx)sheetjs、handsontableexceljs(npm)、handsontable(npm)
pdf(pdf)pdfjspdfjs(npm)
图片jquery.verySimpleImageViewerv-viewer(npm)

        升级后的组件完全兼容npm,唯一不兼容的pptxjs也被我改造了,能够完美兼容。以下是package.json中相关的依赖。

  1. "@handsontable/vue": "^11.1.0",
  2. "docx-preview": "^0.1.8",
  3. "exceljs": "^4.3.0",
  4. "handsontable": "^11.1.0",
  5. "pdfjs-dist": "^2.12.313",
  6. "v-viewer": "^1.6.4",
  7. "vue": "^2.6.11"

        二、搭建简单的视图组件

        框架找好了,接下来我们开工。老样子,用vue-cli创建一个hello-world项目,把脚手架初始化出来。如果没安装过,先全局安装一下:

npm install -g @vue/cli-service-global

        创建项目,名字就叫file-viewer吧!

  1. cd ~/Projects
  2. vue create file-viewer

         然后我们在 src/components/HelloWorld.vue中,给他加一个容器,用于承载文档视图。再弄一个简单的loading容器,ok。

        注意,这里的 @/components/util 是一些常用工具类,主要做二进制数据和字节码、字符串互转的。当然,文档渲染入口也在里面,我们后面说。

  1. <template>
  2. <div :class="{hidden}">
  3. <div class="banner">
  4. <div class="container">
  5. <h1><a href="/">Vue在线文档查看器<input class="file-select" type="file" @change="handleChange"/></a></h1>
  6. </div>
  7. </div>
  8. <div class="container">
  9. <div v-show="loading" class="well loading">正在加载中,请耐心等待...</div>
  10. <div v-show="!loading" class="well" ref="output"></div>
  11. </div>
  12. </div>
  13. </template>
  14. <script>
  15. import { getExtend, readBuffer, render } from '@/components/util';
  16. import { parse } from 'qs';
  17. /**
  18. * 支持嵌入式显示,基于postMessage支持跨域
  19. * 示例代码:
  20. *
  21. */
  22. export default {
  23. name: 'HelloWorld',
  24. props: {
  25. msg: String
  26. },
  27. data() {
  28. return {
  29. // 加载状态跟踪
  30. loading: false,
  31. // 上个渲染实例
  32. last: null,
  33. // 隐藏头部,当基于消息机制渲染,将隐藏
  34. hidden: false,
  35. }
  36. },
  37. methods: {
  38. async handleChange(e) {
  39. this.loading = true;
  40. try {
  41. const [ file ] = e.target.files;
  42. const arrayBuffer = await readBuffer(file);
  43. this.loading = false
  44. this.last = await this.displayResult(arrayBuffer, file)
  45. } catch (e) {
  46. console.error(e)
  47. } finally {
  48. this.loading = false
  49. }
  50. },
  51. displayResult(buffer, file) {
  52. // 取得文件名
  53. const { name } = file;
  54. // 取得扩展名
  55. const extend = getExtend(name);
  56. // 输出目的地
  57. const { output } = this.$refs;
  58. // 生成新的dom
  59. const node = document.createElement('div');
  60. // 添加孩子,防止vue实例替换dom元素
  61. if (this.last) {
  62. output.removeChild(this.last.$el);
  63. this.last.$destroy();
  64. }
  65. const child = output.appendChild(node);
  66. // 调用渲染方法进行渲染
  67. return new Promise((resolve, reject) => render(buffer, extend, child)
  68. .then(resolve).catch(reject));
  69. }
  70. }
  71. }
  72. </script>
  73. <style scoped>
  74. .banner {
  75. overflow: auto;
  76. text-align: center;
  77. background-color: #12b6ff;
  78. color: #fff;
  79. }
  80. .hidden .banner {
  81. display: none;
  82. }
  83. .hidden .well {
  84. height: calc(100vh - 14px);
  85. }
  86. .file-select {
  87. position: absolute;
  88. left: 5%;
  89. top: 17px;
  90. margin-left: 20px;
  91. }
  92. .banner a {
  93. color: #fff;
  94. }
  95. .banner h1 {
  96. font-size: 20px;
  97. line-height: 2;
  98. margin: 0.5em 0;
  99. }
  100. .well {
  101. display: block;
  102. background-color: #f2f2f2;
  103. border: 1px solid #ccc;
  104. margin: 5px;
  105. width: calc(100% - 14px);
  106. height: calc(100vh - 73px);
  107. overflow: auto;
  108. }
  109. .loading {
  110. text-align: center;
  111. padding-top: 50px;
  112. }
  113. .messages .warning {
  114. color: #cc6600;
  115. }
  116. </style>

        三、实现渲染入口

            写好容器后,下一步就是重头戏,笔者这里使用匹配模式简单实现了一个渲染入口,代码如下:

  1. // 导入渲染器
  2. import renders from './renders';
  3. // 渲染入口函数,包含字节数组、文件类型、目标容器
  4. export async function render(buffer, type, target) {
  5. const handler = renders[type];
  6. if (handler) {
  7. return handler(buffer, target);
  8. }
  9. return renders.error(buffer, target, type);
  10. }

             具体渲染逻辑我们用声明式的方式进行配置,统一放置在vendors目录下,像这样:


        之后我们写一个策略配置器,去统一导入这些模块:

  1. import { defaultOptions, renderAsync } from 'docx-preview';
  2. import renderPptx from '@/vendors/pptx';
  3. import renderSheet from '@/vendors/xlsx';
  4. import renderPdf from '@/vendors/pdf';
  5. import renderImage from '@/vendors/image';
  6. import renderText from '@/vendors/text';
  7. import renderMp4 from '@/vendors/mp4';
  8. // 假装构造一个vue的包装,让上层统一处理销毁和替换节点
  9. const VueWrapper = el => ({
  10. $el: el,
  11. $destroy() {
  12. // 什么也不需要 nothing to do
  13. },
  14. });
  15. const handlers = [
  16. // 使用docxjs支持,目前效果最好的渲染器
  17. {
  18. accepts: [ 'docx' ],
  19. handler: async (buffer, target) => {
  20. const docxOptions = Object.assign(defaultOptions, {
  21. debug: true,
  22. experimental: true,
  23. });
  24. await renderAsync(buffer, target, null, docxOptions)
  25. return VueWrapper(target);
  26. }
  27. },
  28. // 使用pptx2html,已通过默认值更替
  29. {
  30. accepts: [ 'pptx' ],
  31. handler: async (buffer, target) => {
  32. await renderPptx(buffer, target, null);
  33. window.dispatchEvent(new Event('resize'));
  34. return VueWrapper(target);
  35. },
  36. },
  37. // 使用sheetjs + handsontable,无样式
  38. {
  39. accepts: [ 'xlsx' ],
  40. handler: async (buffer, target) => {
  41. return renderSheet(buffer, target);
  42. },
  43. },
  44. // 使用pdfjs,渲染pdf,效果最好
  45. {
  46. accepts: [ 'pdf' ],
  47. handler: async (buffer, target) => {
  48. return renderPdf(buffer, target);
  49. }
  50. },
  51. // 图片过滤器
  52. {
  53. accepts: [ 'gif', 'jpg', 'jpeg', 'bmp', 'tiff', 'tif', 'png', 'svg' ],
  54. handler: async (buffer, target) => {
  55. return renderImage(buffer, target);
  56. }
  57. },
  58. // 纯文本预览
  59. {
  60. accepts: [ 'txt', 'json', 'js', 'css', 'java', 'py', 'html', 'jsx', 'ts', 'tsx', 'xml', 'md', 'log' ],
  61. handler: async (buffer, target) => {
  62. return renderText(buffer, target)
  63. },
  64. },
  65. // 视频预览,仅支持MP4
  66. {
  67. accepts: [ 'mp4' ],
  68. handler: async (buffer, target) => {
  69. renderMp4(buffer, target)
  70. return VueWrapper(target);
  71. },
  72. },
  73. // 错误处理
  74. {
  75. accepts: [ 'error' ],
  76. handler: async (buffer, target, type) => {
  77. target.innerHTML = `<div style="text-align: center; margin-top: 80px">不支持.${type}格式的在线预览,请下载后预览或转换为支持的格式</div>
  78. <div style="text-align: center">支持docx, xlsx, pptx, pdf, 以及纯文本格式和各种图片格式的在线预览</div>`;
  79. return VueWrapper(target);
  80. }
  81. }
  82. ]
  83. // 匹配
  84. export default handlers.reduce((result, { accepts, handler }) => {
  85. accepts.forEach(type => result[type] = handler)
  86. return result;
  87. }, {});

          ok,大功告成!😄

       四、运行调试

        还好我们前期做足了工夫,一下子就运行起来了,但是很快就遇到了问题:

  1. pptxjs的npm版本无法使用,有很多bug,只能下载源码自己改bug,作者已经不维护了
  2. exceljs解析sheet的时候有超级多坑,踩到吐🤮
  3. 性能问题,遇到几十兆的pdf,打开超级慢,页面会假死(等待一会就好了)。这个也需要进一步优化

        好不容易修改好了这些,终于跑起来了。大家可以在我的在线demo看到效果file-viewerhttp://viewer.flyfish.group/

        五、总结一下

          实现这个功能总体来说还是非常困难的,除了有很多坑,找到可用的开源组件也是耗费了我大量的精力。好在前期做的努力没有白费,成功上线了产品,也得到了领导的认可罒ω罒,嘿嘿。现在我把我的项目共享出来,我已经把源码上传了。本着对技术尊重的态度,大家帮忙打赏一两块钱就可以拿到完整的源码。

        此外,我也会不断的优化更新,修改bug,提升性能,希望大家持续关注我,有人关注我就一定会一直努力的!谢谢大家!最后附上链接:

Web端文件预览,纯前端Vue实现的file-viewer,不需要后端,支持所有主流格式,附带接入文档和嵌入式引用demo-Web开发文档类资源-CSDN下载

        

        最后的最后,希望大家写代码都能无bug!如果文章确实帮到了你,麻烦给个关注,谢谢!


该文章在 2022/4/15 12:14:59 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved