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 环境里处理。

相关推荐
x_y_1 小时前
分享一个自己总结的前端开发skill~ requirement-to-delivery
前端·ai编程
梨子同志1 小时前
CSS Grid
前端·css
子兮曰1 小时前
SuperSplat 深度解析:7.6K Stars 的浏览器端 3D 高斯泼溅编辑器 — 在 Web 上编辑现实
前端·javascript·webgl
小徐_23331 小时前
Wot UI v1 升级 v2?这份迁移指南帮你少踩坑!
前端·微信小程序·uni-app
xiangxiongfly9151 小时前
Vue3 动态加载静态资源
前端·javascript·vue.js
子兮曰1 小时前
whisper.cpp 深度解析:从边缘设备到实时语音识别
前端·c++·后端
子兮曰1 小时前
Ruflo 深度解析:49K Stars 的 AI Agent 编排平台 — 给 Claude Code 装上分布式神经系统
前端·后端·ai编程
小皮咖1 小时前
发给那个让你加班的同事
前端
克里斯蒂亚诺更新1 小时前
ruoyi切换新版本初始化需要修改的地方
前端·javascript·vue.js