uni-app 安卓端纯前端预览 DOCX 的实现思路

uni-app 安卓端纯前端预览 DOCX 的实现思路

在 uni-app 项目中,docx 文件预览看起来像是一个简单需求,但在安卓 App 环境下会遇到不少坑。

本文结合当前项目的实现,说明为什么不能直接在页面中渲染 docx,以及如何通过 docx-preview + 本地 web-view 实现纯前端预览。

一、DOCX 为什么可以纯前端预览

docx 本质上不是传统二进制文件,而是一个压缩包。

它内部主要包含:

  • XML 文档内容
  • 样式文件
  • 图片资源
  • 字体、编号、页眉页脚等配置

所以前端可以通过 JS 解压并解析这些 XML,再渲染成 HTML。

常见纯前端库有:

  • docx-preview
  • mammoth.js

其中:

  • docx-preview 更偏向"还原 Word 页面效果"
  • mammoth.js 更偏向"提取正文内容"

如果目标是"预览文档",更推荐使用 docx-preview。

二、为什么 DOC 不能一样处理

doc 是老版本 Word 二进制格式,不是 zip + XML 结构。

浏览器端没有成熟稳定的纯 JS 方案可以完整解析 .doc 文件。

所以:

text 复制代码
docx -> 可以尝试纯前端预览
doc  -> 不建议纯前端预览

对于 .doc,更推荐:

  • 后端转 PDF
  • 调用系统应用打开
  • 使用文档服务或原生插件

三、为什么不能直接在 uni-app 页面里渲染

一开始最容易想到的写法是:

js 复制代码
import { renderAsync } from 'docx-preview';

const renderDocx = async () => {
  const bodyEl = document.getElementById('docxBodyBox');

  await renderAsync(buffer, bodyEl);
};

在 H5 里这通常没问题。

但在 uni-app 安卓 App 端,普通页面里的 <script setup> 并不是稳定的浏览器 DOM 执行环境。

常见问题包括:

  • document 不存在
  • document.getElementById() 找不到容器
  • DOM 已渲染但原生层还没准备好
  • 文件读取能力和浏览器 H5 不一致

因此,直接在 filePreview.vue 里使用 docx-preview,在安卓 App 端并不可靠。

四、最终方案:本地 HTML + web-view

更稳的方案是:

text 复制代码
uni-app 页面
-> web-view 打开本地 viewer.html
-> viewer.html 加载 docx-preview
-> viewer.html 下载或读取 docx 文件
-> docx-preview 渲染 HTML

也就是说,把 docx-preview 放到真正的 WebView 页面里执行。

项目结构示例:

text 复制代码
static/docx-preview/
├── viewer.html
├── docx-preview.min.js
└── jszip.min.js

其中:

  • viewer.html 是本地预览页面
  • docx-preview.min.js 是预览库
  • jszip.min.js 是 docx 解压依赖

五、App 页面如何打开 DOCX 预览

在 filePreview.vue 中,docx 文件走单独分支:

html 复制代码
<view v-else-if="isDocxFile" class="docx-preview-container">
  <!-- #ifdef APP-PLUS -->
  <view v-if="docxRendering" class="preview-loading">
    <view class="loading-spinner"></view>
    <text class="loading-text">文档下载中...</text>
  </view>

  <view v-else-if="docxRenderError" class="preview-error">
    <text class="error-text">{{ docxRenderError }}</text>
    <view class="retry-btn" @click="prepareAppDocxPreview">重新加载</view>
    <view class="retry-btn secondary" @click="previewWithSystemViewer">
      系统应用打开
    </view>
  </view>

  <view v-else class="app-native-preview">
    <web-view
      :src="getDocxViewerUrl"
      class="docx-webview"
      :fullscreen="false"
      style="width: 100%; height: 100%; border: 0"
      :webview-styles="{
        width: '100%',
        height: '100%',
        progress: { color: '#16a5fb' }
      }"
    ></web-view>
  </view>
  <!-- #endif -->
</view>

预览地址:

js 复制代码
const getDocxViewerUrl = computed(() => {
  const previewUrl = docxLocalPath.value || fileInfo.value.url;
  if (!previewUrl) return '';
  return `/static/docx-preview/viewer.html?file=${encodeURIComponent(previewUrl)}`;
});

六、为什么要先用 uni.downloadFile 下载

一开始可以让 viewer.html 自己下载远程 docx:

js 复制代码
fetch(fileUrl)

或:

js 复制代码
plus.downloader.createDownload(fileUrl)

但在真实项目中经常会失败:

  • 文件地址需要登录态
  • 下载接口需要 token
  • 本地 HTML 不一定能拿到业务请求头
  • 远程文件可能有跨域限制
  • plus.downloader 在 web-view 内部回调不稳定

所以更稳的方式是:

text 复制代码
filePreview.vue 用 uni.downloadFile 下载
-> 带上 token
-> 拿到 tempFilePath
-> 传给 viewer.html

代码示例:

js 复制代码
const prepareAppDocxPreview = () => {
  docxLocalPath.value = '';
  docxRenderError.value = '';
  docxRendering.value = true;

  uni.downloadFile({
    url: fileInfo.value.url,
    header: {
      token: uni.getStorageSync('token') || ''
    },
    success: (res) => {
      docxRendering.value = false;

      if (res.statusCode !== 200 || !res.tempFilePath) {
        docxRenderError.value = `文档下载失败: ${res.statusCode || '未知错误'}`;
        return;
      }

      try {
        docxLocalPath.value =
          plus.io.convertLocalFileSystemURL(res.tempFilePath) ||
          res.tempFilePath;
      } catch (error) {
        docxLocalPath.value = res.tempFilePath;
      }
    },
    fail: () => {
      docxRendering.value = false;
      docxRenderError.value = '文档下载失败,请检查文件地址或权限';
    }
  });
};

七、viewer.html 如何渲染 DOCX

viewer.html 需要先加载依赖:

html 复制代码
<script src="./jszip.min.js"></script>
<script src="./docx-preview.min.js"></script>

然后读取文件并渲染:

js 复制代码
await window.docx.renderAsync(
  buffer,
  document.getElementById('docxBodyBox'),
  document.getElementById('docxStyleBox'),
  {
    className: 'docx-rendered',
    inWrapper: true,
    ignoreWidth: false,
    ignoreHeight: false,
    ignoreFonts: false,
    breakPages: true,
    experimental: false,
    useBase64URL: true
  }
);

八、本地文件读取的坑

安卓 App 端下载后的路径可能是:

text 复制代码
_doc/uniapp_temp_xxx/download/xxx.docx

也可能被转换成:

text 复制代码
/storage/emulated/0/Android/data/xxx/xxx.docx

这些路径不是标准浏览器 URL。

常见坑包括:

1. FileReader 不能读取 plus.io 的 File 对象

错误示例:

text 复制代码
Failed to execute 'readAsArrayBuffer' on 'FileReader':
parameter 1 is not of type 'Blob'

原因是 Android WebView 里的 plus.io File 对象不一定是标准 Blob。

2. XHR 不能直接读取 _doc/...

错误示例:

text 复制代码
read local file failed

原因是 _doc/... 是 5+ 私有路径,不是普通浏览器可直接访问路径。

3. 需要多路径兜底

所以读取本地文件时,需要尝试多种路径:

  • entry.nativeURL
  • entry.toLocalURL()
  • entry.toURL()
  • _doc/...
  • /storage/...
  • file:///storage/...

示例逻辑:

js 复制代码
function readLocalByPlus(fileUrl) {
  return new Promise(function(resolve, reject) {
    plus.io.resolveLocalFileSystemURL(fileUrl, function(entry) {
      const paths = [];

      if (entry.nativeURL) paths.push(entry.nativeURL);
      if (typeof entry.toLocalURL === 'function') paths.push(entry.toLocalURL());
      if (typeof entry.toURL === 'function') paths.push(entry.toURL());

      paths.push(fileUrl);

      readFirstAvailablePath(paths).then(resolve).catch(reject);
    }, reject);
  });
}

九、web-view 全屏问题

在安卓 App 端,web-view 是原生子窗口,不是普通 DOM 节点。

所以仅写:

css 复制代码
width: 100%;
height: 100%;

不一定能限制它的真实大小。

需要通过当前页面的原生子 WebView 设置尺寸:

js 复制代码
const childWebview = currentWebview.children()[0];

childWebview.setStyle({
  top: Math.round(rect.top),
  left: Math.round(rect.left),
  width: Math.round(rect.width),
  height: Math.round(rect.height)
});

这样才能避免 docx 预览的 web-view 在安卓端覆盖全屏。

十、最终链路总结

当前推荐链路是:

text 复制代码
用户打开 docx 文件
-> filePreview.vue 判断是 docx
-> uni.downloadFile 带 token 下载文件
-> 得到本地 tempFilePath
-> 转换本地路径
-> web-view 打开 static/docx-preview/viewer.html?file=本地路径
-> viewer.html 读取本地 docx
-> docx-preview 渲染 HTML

这套方案的特点:

  • 不依赖后端转 PDF
  • 不依赖 Office Online
  • 不依赖 X5 插件
  • 属于纯前端预览
  • 只支持 .docx
  • .doc 仍然需要系统应用或后端转换

十一、局限性

docx-preview 不是 Word 官方内核,因此存在一些限制:

  • 复杂页眉页脚可能不完全一致
  • 批注、修订痕迹支持有限
  • 特殊字体可能不一致
  • 复杂表格可能变形
  • 大文件渲染慢
  • 加密文档不支持
  • .doc 不支持

所以它适合"在线预览",不适合对版式要求极高的正式排版校验。

十二、结论

在 uni-app 安卓 App 端,docx 纯前端预览不能简单地把 docx-preview 写在页面脚本里。

更稳的方式是:

text 复制代码
docx-preview 放到本地 HTML
通过 web-view 执行
App 页面负责下载和传递本地文件路径
viewer 页面负责解析和渲染

一句话总结:

DOCX 可以纯前端预览,但在 App 端必须把渲染放到真正的 WebView 环境里处理。

相关推荐
wangruofeng42 分钟前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng1 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈4 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技4 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569154 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马7 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng7 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding8 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天8 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron
FungLeo11 小时前
成为全栈·Vue 管理后台篇·分类树与标签管理:层级数据如何落到管理界面
前端·javascript·vue.js·分类树·naive ui·标签管理·前端数据结构