在 Blazor Server 中集成 docx-preview.js 实现高保真 Word 预览

前言

这两天在做一个在线预览各种类型文档的模块,主要是针对pdf和word,pdf好说,方案一大把,选一个最合适的就好,我这里的管理项目是基于MudBlazor的,所以我使用了官方推荐的Pdf扩展组件Gotho.BlazorPdf,当然即便不用原生组件,自己基于pdf.js等前端方案来封装也是完全没问题的,这个我就不多说了。

这里主要想聊聊在线预览word文档的实现思路,总结起来基本就是3条路线

  • 第一条是先把word格式转成pdf,然后再通过pdf预览组件来预览,这条路线实现方案也很多,问题就是装换的实现如果你之前没有写过类似的功能,可能要费一番功夫,当然不差钱的话,可以用一些商用组件,比如Aspose, Spire.Doc等,虽然价格略高,但专业性高,功能极强,物有所值。当然除了转化成pdf还可以转换成html或者markdown等,总之就是转换的路线,这里不再赘述。
  • 第二条路线是借助微软原生的 Office Online 服务或者Google Viewer等方案,实现在线预览,当然这也有一个要求,就是你的文档需要能在公网访问,或者有运维能力的话,可以在本地部署一个私有Office Online服务,这个微软官方有详细的文档(https://learn.microsoft.com/zh-cn/officeonlineserver/deploy-office-online-server),对这种方式感兴趣的小伙伴可以试试(笔者不推荐私有部署的方式,如果你的场景里文档允许外网访问的话,推荐直接使用在线方式,最简单)。
  • 第三条路线是,使用一些纯前端方案,更加的轻量级,当然他会有一些限制条件,比如一些表现好的组件不支持原始的doc格式,只能是docx,超过10M的渲染可能也会很慢。所以选型时要考虑这些条件,笔者采用的就是这条路线。

方案介绍

近几年前端发展迅猛在线预览复杂的word文档已经有了成熟方案,比如mammoth.js,docx-preview.js,amis.js等,这里面mammoth还提供了.net的nuget包,方便Blazor环境使用,但有个问题是,他渲染出来的文档会影响原文布局,所以如果只是“看内容”不在乎排版受影响,那.net环境下,使用前端方案实现文档预览,mammoth毫无疑问是最佳方案。

但我这里是一个“审核”的场景,需要对源文件实现“公文级”,设置“像素级”的还原,也就是和word文档几乎一样,所以更适合我这里的方案是“docx-preview.js”,仓库地址👉:https://github.com/VolodymyrBaydalka/docxjs。

至于另外一个amis.js,这个是国内大厂百度出品的一个组件,文档很全,也是一个不错的路线。

实现步骤

引入组件

因为是客户端方案,我们可以在外边通过npm等方式先把核心组件拉到本地,当然直接在VS里添加客户端库也可以。

npminstalldocx-preview

编写隔离型 JS 互操作层

在 wwwroot/assets/js/docxInterop.js 中,我们不仅要处理预览逻辑,还要处理环境隔离。

/** * 我这里因为用到了Monaco Editor组件,因此要处理一下AMD加载器的冲突 */exportasyncfunctionrenderDocxFromUrl(url,containerId){constcontainer=document.getElementById(containerId);if(!container)return;constloadScriptWithIsolation=(src)=>{returnnewPromise((resolve,reject)=>{if(document.querySelector(`script[src="${src}"]`)){resolve();return;}// 临时屏蔽全局define函数,防止与Monaco Editor等库的AMD加载器冲突const_backupDefine=window.define;window.define=undefined;constscript=document.createElement('script');script.src=src;script.onload=()=>{window.define=_backupDefine;// 加载后立即还原resolve();};script.onerror=reject;document.head.appendChild(script);});};try{// 1. 加载依赖awaitloadScriptWithIsolation('./assets/js/jszip.min.js');awaitloadScriptWithIsolation('./assets/js/docx-preview.min.js');// 2. 获取文件流constresponse=awaitfetch(url);constarrayBuffer=awaitresponse.arrayBuffer();// 3. 调用预览逻辑constoptions={className:"docx-preview",inWrapper:true,breakPages:true};awaitwindow.docx.renderAsync(arrayBuffer,container,null,options);}catch(e){console.error("预览失败:",e);container.innerHTML="文档加载失败";}}

封装 Blazor 预览组件

使用 IJSObjectReference 确保 JS 逻辑的模块化,避免污染全局命名空间。

@inject IJSRuntime JS @implements IAsyncDisposable<divid="@_containerId"class="docx-render-area"style="height:@Height; overflow:auto;"></div>@code{[Parameter]publicstringHeight{get;set;}="700px";privatestring_containerId=$"docx-{Guid.NewGuid():N}";// JS 模块引用privateIJSObjectReference?_module;protectedoverrideasyncTaskOnAfterRenderAsync(boolfirstRender){if(firstRender){// 动态加载 JS 模块文件_module=awaitJS.InvokeAsync<IJSObjectReference>("import","/assets/js/docxInterop.js");}}publicasyncTaskLoadFromUrlAsync(stringurl){if(_module==null)return;try{//_isLoading = true;StateHasChanged();// 直接把 URL 传给 JS 处理,避免大数组在 SignalR 中传输await_module.InvokeVoidAsync("renderDocxFromUrl",url,_containerId);}finally{//_isLoading = false;StateHasChanged();}}publicasyncTaskLoadFromStreamAsync(Streamstream){if(_module==null)return;usingvarms=newMemoryStream();awaitstream.CopyToAsync(ms);await_module.InvokeVoidAsync("renderDocx",ms.ToArray(),_containerId);}// 释放模块引用,防止内存泄漏publicasyncValueTaskDisposeAsync(){if(_module!=null){try{// 只有当连接还活着的时候才去调用 Disposeawait_module.DisposeAsync();}catch(JSDisconnectedException){// 忽略连接断开导致的异常,这是正常的}}}}

父组件引入

父组件的引入的时候只要给一个高度参数就可以了

// blazor页面部分,引入组件<DocxViewer@ref="_docxViewer"Height="75vh"/>// code部分编写引入逻辑privateDocxViewer_docxViewer;privateasyncTaskLoadFile(stringurl){if(_docxViewer!=null)await_docxViewer.LoadFromUrlAsync(url);}

最后的效果如下

服务器配置

在服务注入的入口中适当调高 SignalR 的传输上限,这个仅限Blazor Server模式,BlazerWSAM或者Hybird方式不需要:

builder.Services.AddServerSideBlazor().AddHubOptions(options=>{options.MaximumReceiveMessageSize=32*1024*1024;// 32MB});

* 避坑

我在集成过程中,遇到了类似“Uncaught Error: Can only have one anonymous define call”的报错。排查后的原因是:

  1. 使用了Monaco Editor这样的库自带了 AMD 加载器(loader.js)。
  2. docx-preview 检测到define函数后会尝试注册模块,导致冲突。
  3. 解决方案:采用动态加载脚本,并在加载期间暂时“抹除”全局define。

总结

通过这种方式,不仅在 Blazor Server 中实现了 Word 文档的高保真预览,而且足够轻量化,还可以方便的将其用到任何需要预览功能的页面中。我真的越来越喜欢Blazor这个组件化的开发模式了,好了,就这些,下次再见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1146267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hbuilderx开发微信小程序事件处理:操作指南详述

HBuilderX开发微信小程序事件处理&#xff1a;从零到实战的深度指南 你有没有遇到过这样的情况&#xff1f;在HBuilderX里写好了按钮点击逻辑&#xff0c;结果真机调试时点下去毫无反应&#xff1b;或者父子组件传值越传越乱&#xff0c;最后只能靠全局变量“硬解”&#xff1…

Windows下32位打印驱动开发环境搭建操作指南

Windows下32位打印驱动开发环境搭建实战指南 在工业、医疗和金融等关键领域&#xff0c;许多核心业务系统仍基于32位架构运行。这些“老旧但不可替代”的应用对打印机的调用需求从未消失。然而&#xff0c;随着64位操作系统的全面普及&#xff0c;如何让一个运行在x64系统上的…

Multisim示波器使用技巧:教学场景完整示例

用Multisim示波器看懂RC电路&#xff1a;一次真实的“信号追踪”之旅 你有没有过这样的经历&#xff1f; 在《模拟电子技术》课上&#xff0c;老师讲了一堆关于 时间常数、充放电曲线、相位延迟 的概念&#xff0c;黑板上的公式写满一页&#xff0c;可你还是搞不清——这些抽…

Vitis使用教程:优化卷积运算的FPGA实践

如何用Vitis把卷积算得又快又省&#xff1f;FPGA加速实战全解析你有没有遇到过这样的问题&#xff1a;在边缘设备上跑一个轻量级CNN模型&#xff0c;CPU占用率直接飙到90%&#xff0c;帧率掉到个位数&#xff0c;功耗还高得离谱&#xff1f;这几乎是每个做嵌入式AI开发的人都踩…

工业电机控制中续流二极管的高可靠性优化

工业电机控制中续流二极管的高可靠性设计&#xff1a;从原理到实战优化在数控机床、工业机器人和自动化产线中&#xff0c;电机是驱动系统的核心。而在这类系统的“心脏”——逆变器里&#xff0c;有一个看似不起眼却至关重要的角色&#xff1a;续流二极管。它不主动开关&#…

电路仿真软件在电力电子中的应用:深度剖析

电路仿真如何重塑电力电子设计&#xff1a;从纳秒开关到实时闭环验证你有没有经历过这样的场景&#xff1f;一款LLC谐振变换器样机刚上电&#xff0c;输出电压“砰”地一下冲过额定值&#xff0c;电解电容冒烟&#xff1b;或者三相逆变器并网时THD超标&#xff0c;排查数周才发…

C++ 导入标准库

标准库头文件导入方法 在C中导入标准库通过#include指令实现&#xff0c;需指定对应的头文件名称。标准库头文件分为两类&#xff1a;带.h后缀的传统C头文件和不带后缀的现代C头文件。 // C风格标准库头文件&#xff08;推荐&#xff09; #include <iostream> #include…

我比较喜欢的游戏

1.一个只需要点点点的小游戏Neon Planet Idle Clicker &#x1f579;️ Play on CrazyGameshttps://www.crazygames.com/game/neon-planet-idle-clicker 2.一个又肝又爽的游戏https://florr.io/https://florr.io/ 3.一个只需要挖挖挖的小游戏https://digdig.io/https://digdi…

基于UVC协议的实时监控方案:深度剖析架构细节

基于UVC协议的实时监控方案&#xff1a;从原理到实战的深度拆解你有没有遇到过这样的场景&#xff1f;新采购的一批摄像头插上电脑后&#xff0c;不是提示“无法识别”&#xff0c;就是需要安装一堆驱动、运行特定软件才能使用。更头疼的是&#xff0c;换到另一台设备或操作系统…

Altium Designer教程:快速上手3D PCB可视化功能

Altium Designer实战指南&#xff1a;手把手教你玩转3D PCB可视化你有没有遇到过这样的情况&#xff1f;PCB板子做完&#xff0c;发出去打样&#xff0c;结果装机时发现——某个电解电容太高&#xff0c;顶住了外壳&#xff1b;或者USB插座方向反了&#xff0c;插头根本塞不进去…

Vitis使用教程:从零实现AI模型FPGA部署

从零开始&#xff1a;用Vitis把AI模型部署到FPGA上&#xff0c;我走过的每一步都算数 最近在做边缘AI推理项目时&#xff0c;被一个现实问题卡住了&#xff1a;GPU功耗太高&#xff0c;端侧跑不动&#xff1b;云端延迟又太大&#xff0c;实时性扛不住。于是我把目光转向了FPGA…

进程间的通信(1)(理解管道特性,匿名命名管道,进程池,systeam V共享内存是什么及优势)重点理解代码!

&#x1f3ac; 胖咕噜的稞达鸭&#xff1a;个人主页&#x1f525; 个人专栏: 《数据结构》《C初阶高阶》 《Linux系统学习》 《算法日记》⛺️技术的杠杆&#xff0c;撬动整个世界! 理解层面 为什么要进程间通信&#xff1f; • 数据传输&#xff1a;一个进程需要将它的数据发…

Xilinx官网License申请实操:超详细版图文教程

手把手教你搞定 Vivado License&#xff1a;从零开始的实战配置指南 你是不是也曾在打开 Vivado 时&#xff0c;突然弹出一个红色警告&#xff1a;“License checkout failed”&#xff1f; 或者刚建好工程、准备综合&#xff0c;却发现 IP 核用不了&#xff0c;提示“Featur…

Day 15:【99天精通Python】面向对象编程(OOP)中篇 - 封装、继承与多态

Day 15&#xff1a;【99天精通Python】面向对象编程(OOP)中篇 - 封装、继承与多态 前言 欢迎来到第15天&#xff01; 在昨天的课程中&#xff0c;我们学会了如何定义类和创建对象。但这只是 OOP 的冰山一角。面向对象编程之所以强大&#xff0c;归功于它的三大核心特性&#xf…

差分放大器在Multisim仿真电路图中的实战案例

差分放大器实战&#xff1a;用Multisim搭建高精度信号调理前端你有没有遇到过这样的情况&#xff1f;传感器输出的微弱信号刚进放大电路&#xff0c;就被工频干扰淹没&#xff1b;示波器上本该是平滑正弦波的输出&#xff0c;却出现了削顶失真&#xff1b;明明理论增益是10倍&a…

三脚电感布局布线对EMI性能的影响研究

三脚电感布局布线对EMI性能的影响研究&#xff1a;从理论到实战的深度解析当电子系统“吵”起来时&#xff0c;谁在负责降噪&#xff1f;在今天的电子产品设计中&#xff0c;我们常常追求更高的效率、更小的体积和更低的功耗。但当这些目标达成的同时&#xff0c;一个问题却悄然…

vivado许可证与高级综合(HLS)工具集成要点

从代码到硅片&#xff1a;绕不开的 Vivado 许可证与 HLS 集成实战指南 你有没有遇到过这样的场景&#xff1f; 写好了一段 C 算法&#xff0c;信心满满地打开 Vivado HLS&#xff0c;准备一键综合成硬件 IP。结果刚运行 open_solution &#xff0c;控制台就跳出一行红字&am…

逻辑门组合电路设计:超详细版基础入门指南

从零开始学数字电路&#xff1a;用逻辑门搭建你的第一个组合电路你有没有想过&#xff0c;手机里每秒执行数十亿条指令的处理器&#xff0c;其实是由最简单的“开关”一步步搭起来的&#xff1f;这些“开关”不是物理按钮&#xff0c;而是我们今天要讲的主角——逻辑门。在嵌入…

图解说明交叉编译工具链与驱动二进制生成过程

深入理解交叉编译&#xff1a;从驱动源码到ARM板上运行的.ko模块你有没有遇到过这样的场景&#xff1f;在x86_64的Linux电脑上写好了一个设备驱动&#xff0c;兴冲冲地拷贝到树莓派上执行insmod hello_driver.ko&#xff0c;结果系统报错&#xff1a;insmod: ERROR: could not …

电商市场的用户反馈分析与应用

电商市场的用户反馈分析与应用 关键词:电商市场、用户反馈分析、文本挖掘、情感分析、数据应用 摘要:本文聚焦于电商市场的用户反馈分析与应用。在电商行业竞争日益激烈的当下,用户反馈蕴含着巨大的价值。通过对用户反馈的深入分析,电商企业能够了解用户需求、改进产品与服…