ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

企业微信H5文件预览:Vue2.0接入JS-SDK与wx.previewFile

企业微信H5文件预览:Vue2.0接入JS-SDK与wx.previewFile 上周帮一家做企业服务的团队收拾一个烂摊子。他们在企业微信里内嵌了一套vue2.0的 H5 审批系统附件列表点了没反应——点 PDF 白屏一片点 Word 直接跳到空白页用户在群里开发问“为什么下载不了”。他们一开始以为是 WebView 兼容问题改了两天 CSS 和 UA 判断最后发现根子上是压根没走JS-SDK直接用window.open和a download硬来。把wx.previewFile接上之后PDF、Word、Excel、图片全部能在企业微信内部打开用户还能自己点右上角转发或保存前端代码反而比之前那套 hack 少了一半。这篇就把这套方案从头到尾捋一遍企业微信内嵌 H5 的环境到底特殊在哪JS-SDK 怎么引入签名怎么做wx.previewFile的参数怎么填vue2.0 里怎么封装成能直接抄的工具函数以及我在真机上踩过的那些坑。适合正在做企业微信自建应用、审批流、OA 后台的前端同学也适合后端同学——因为签名那部分躲不开服务端。只要你的 H5 里有“附件”“报表”“合同”“发票”这类需要点开看的文件这篇基本能直接用。1. 先把问题看清楚企微内嵌H5的文件预览到底卡在哪很多人第一次做企业微信内嵌 H5会习惯性地把它当成一个普通手机浏览器。跑起来确实像Vue 能跑axios 能发请求路由也正常于是就觉得“这不就是个浏览器吗”。但一旦涉及调用系统能力——比如打开文件、保存到本地、拉起相册——立刻就翻车。原因不复杂企业微信内置的 WebView 是一个被包装过的沙箱容器iOS 上跑的是 WKWebViewAndroid 上一般是系统 WebView 或 X5 内核PC 端则是类似 Chromium Embedded Framework 的容器。它对 H5 标准的支持是完整的但对“越界”的行为卡得很死。具体到文件预览这件事卡点主要有三个。第一a hrefxxx.pdf download这种写法在企业微信里基本无效download属性会被忽略点击后要么没反应要么在当前页把二进制流当成文本渲染出一堆乱码。第二window.open(url)在 Android 端有时能跳出但 iOS 端经常被当成弹窗拦截用户看到的是“什么也没发生”。第三也是最容易被忽略的Android 内嵌 WebView 默认没有 PDF 渲染器你给一个 PDF 直链它不知道用什么打开结果就是白屏——这不是你的代码问题是容器本身没这个能力。所以正确的思路不是“绕开限制”而是“借客户端的原生能力”。企业微信提供了一套 JS-SDK本质是 H5 和原生层之间的一条桥。你在 H5 里调wx.previewFile实际执行的是企业微信客户端自己去下载文件、调用系统预览组件、渲染出来。文件在你这边只是一个 URL渲染和交互全交给原生跨平台差异也就被抹平了。1.1 内置 WebView 和普通浏览器的差别把差异列清楚后面选方案的时候就不容易走错路。普通手机浏览器Chrome、Safari通常内置了 PDF 预览window.open一个 PDF 链接能直接看企业微信内嵌 H5 没有这个默认行为。普通浏览器里a download在多数场景下能触发下载企业微信里这个属性不被支持。普通浏览器可以用URL.createObjectURL(blob)生成一个临时地址给用户看企业微信的原生预览组件拿不到blob:协议的地址它需要一个能被原生层独立访问的 http/https 直链。最后一点是最坑的因为它决定了你后端接口该返回什么——很多团队一开始就是栽在这里。还有一点是 PC 端。企业微信 PC 版包括 Windows、Mac以及部分国产操作系统上的客户端对 JS-SDK 的支持程度和手机端不完全一致老版本客户端可能压根没有previewFile这个方法。所以在 PC 端必须准备降级路径后面第 6 节会详细讲。1.2 几种预览方案的取舍对比我把自己试过的几种方案摆出来对比一下这样你选型的时候心里有数方案Android 表现iOS 表现PC 端表现是否需要改后端a href download多数无效可能乱码无效部分可用否window.open(url)偶尔可跳体验差常被拦截可用否URL.createObjectURL(blob)部分机型可预览基本无效可用否第三方在线预览服务可用但依赖外网可用但依赖外网可用是需转链wx.previewFile稳定原生预览稳定原生预览新版本可用是需文件直链结论很清楚只要你的产品主要跑在企业微信里wx.previewFile是唯一一个不用折腾客户端差异的方案。代价是两件事——后端需要提供可被原生层直接访问的文件地址以及前端要正确完成 JS-SDK 的鉴权注入。这两件事组成了本文后面 80% 的内容。2. 工程准备JS-SDK 引入与企微后台配置动手写代码之前有几件事必须先在后台配好否则你在本地怎么调试都是invalid signature。这一节把配置项和 SDK 引入方式说清楚顺序上建议先做后台配置再做前端接入。2.1 SDK 引入方式与版本选择企业微信的 JS-SDK 沿用了微信的那套jweixin引入方式有两种。第一种是直接在public/index.html里加 script 标签简单直接缺点是全局变量、不好做按需加载第二种是走 npm 包weixin-js-sdkVue 项目里更整洁但要注意版本。我个人的选择是用 script 标签引官方 CDN理由是版本可控、不会因为打包工具的 tree-shaking 或者依赖提升而引到老版本。在public/index.html的head里加上script srchttps://res.wx.qq.com/open/js/jweixin-1.6.0.js/script如果你还要用wx.agentConfig这类应用级接口previewFile不需要但有些场景比如获取当前外部联系人会用到那就再补一个企业微信自己的 SDKscript srchttps://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js/script这里有个细节要提醒jweixin版本不要太老。1.2.0 虽然有previewFile但在部分 Android 机型上对新文件格式比如 xlsx识别不准。1.6.0 是目前比较稳的版本官方 CDN 上一直有。另外SDK 必须在wx.config之前加载完成如果你用动态插入 script 的方式做懒加载务必等onload之后再调wx.config否则会报wx is not defined。2.2 可信域名、可信IP 与 JS-SDK 权限企业微信后台的配置是绕不过去的坎。进入管理后台找到对应的自建应用页面上有两个容易混淆的配置网页授权及 JS-SDK和企业可信 IP。前者是给 H5 用的后者是给服务端调接口用的别配错了。网页授权及 JS-SDK里要设置“可信域名”。这个域名有硬性要求必须是已备案的域名而且需要把一个校验文件放到域名的根目录下通常是WW_verify_xxxxxx.txt能通过https://你的域名/WW_verify_xxxxxx.txt访问到才校验通过。校验通过后只有这个域名下的页面才能成功调用 JS-SDK。开发阶段如果用的是内网 IP 或者测试域名会卡在这里所以建议提前准备一个正式域名。企业可信 IP是配置服务端出口 IP 的。你在服务端调gettoken和get_jsapi_ticket接口时企业微信会校验请求来源 IP 是否在可信 IP 列表里。很多人签名一直失败最后发现是服务器换了一台机器、出口 IP 变了没同步这个坑值得记一笔。另外JS-SDK 的权限是按应用隔离的。你在这个自建应用里配了可信域名不代表另一个应用也能用。每个应用都要单独配。2.3 签名服务jsapi_ticket 的获取、缓存与签名拼装签名是整个流程里最容易出错的一环但它其实逻辑非常简单就三步拿access_token用access_token换jsapi_ticket用ticket拼串做 SHA1。第一步获取企业级access_tokenGET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的corpidcorpsecret你的secret返回里有个access_token字段有效期 7200 秒。第二步用这个access_token换jsapi_ticketGET https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token上一步拿到的token返回的ticket同样有效期 7200 秒。第三步拼串签名。规则是固定的字段顺序不能变const crypto require(crypto) function sha1(str) { return crypto.createHash(sha1).update(str, utf8).digest(hex) } // ticket: 第二步拿到的 jsapi_ticket // noncestr: 随机字符串建议 16 位 // timestamp: 秒级时间戳字符串 // url: 前端传来的当前页面 URL必须不含 # 及其后面部分 function buildSignature(ticket, noncestr, timestamp, url) { const raw jsapi_ticket${ticket}noncestr${noncestr}timestamp${timestamp}url${url} return sha1(raw) }这里有三个我踩过的坑必须单独说。第一个坑是url不能做 URL 编码。有些人习惯性地对参数做encodeURIComponent结果签出来的 signature 永远不对。原样拼进去就行除非 URL 里本身含有特殊字符。第二个坑是noncestr全小写。拼串的时候字段名是noncestr但返回给前端、传给wx.config的时候要写成驼峰nonceStr。这个大小写不对称是无数人卡半天的原因我第一次遇到以为是加密算法有问题查了两小时。第三个坑是timestamp用字符串别传数字某些语言里数字会被序列化成科学计数法或者带小数位。缓存策略上access_token和jsapi_ticket都必须全局缓存绝对不能每个请求都去调一次接口。企业微信对这两个接口有调用频率限制而且多台服务器同时去拉会导致互相顶掉。稳妥的做法是丢进 Rediskey 是应用维度过期时间设成 7000 秒留 200 秒余量并且加一个简单的分布式锁避免缓存刚好过期时多个请求同时去刷。服务端最终返回给前端的结构大致是这样{ corpid: wwxxxxxxxxxxxxxxxx, timestamp: 1710000000, nonceStr: a1b2c3d4e5f6g7h8, signature: 9f8e7d6c5b4a39281706f5e4d3c2b1a0 }签名的url从哪里来由前端传给后端不能由后端自己拼。因为服务端根本不知道用户当前打开的是哪个页面。前端把location.href.split(#)[0]当参数带过去即可注意这里有个 iOS 的特殊处理下一节细讲。3. 前端接入vue2.0 里边把 wx.config 和 previewFile 封装好准备工作做完进入前端部分。我的习惯是把这一整块抽成一个独立模块src/utils/wecom-sdk.js对外只暴露两个方法initWxConfig()和previewFile(options)。这样业务组件里只需要一行调用不用关心签名细节。3.1 环境判断与 SDK 就绪等待第一件事是判断当前是不是在企业微信里。UA 里带wxwork字段就是export function isWeCom() { return /wxwork/i.test(navigator.userAgent) }注意这里要判断的是wxwork而不是micromessenger。企业微信的 UA 里两个都有因为它的内核还是微内核但反过来普通微信里没有wxwork。如果你的 H5 既要跑企业微信又要跑微信用这个字段就能区分开。第二件事是处理 SDK 就绪。wx.config是异步生效的配置完成后微信会回调wx.ready或者wx.error。如果你在wx.config之后立刻调wx.previewFile有大概率报错。所以要用 Promise 包一层let readyPromise null export function initWxConfig() { if (readyPromise) return readyPromise readyPromise new Promise((resolve, reject) { if (!isWeCom()) { reject(new Error(not in wecom)) return } if (!window.wx) { reject(new Error(wx sdk not loaded)) return } const signUrl getSignUrl() // 调你自己的后端签名接口把当前页面 URL 带过去 api.getJsSdkSignature({ url: signUrl }).then(res { window.wx.config({ beta: true, // 企业微信必须开 debug: false, appId: res.corpid, // 注意是 corpid不是应用的 agentid timestamp: res.timestamp, nonceStr: res.nonceStr, signature: res.signature, jsApiList: [previewFile, previewImage, getNetworkType] }) window.wx.ready(() resolve(window.wx)) window.wx.error(err { readyPromise null // 失败要清掉允许重试 reject(err) }) }).catch(reject) }) return readyPromise }两个细节。beta: true必须加企业微信的部分 JS-SDK 接口要求在 beta 模式下才生效不加会报permission denied。appId填的是企业 corpid不是自建应用的 agentid这个和企业微信的其他接口不一样容易搞混。3.2 签名 URL 的取法iOS 与 Android 差别这是 vue2.0 的 SPA 项目里最隐蔽的一个坑。企业微信在 iOS 和 Android 上对“签名 URL”的理解不一样。Android 端每次页面 URL 变化之后重新签名用的是当前页面的 URL。所以你在 A 页面签的跳到 B 页面就必须重签否则wx.config报invalid signature。iOS 端WKWebView 在单页应用里不会因为前端路由变化而改变真实的页面地址wx.config校验用的是进入这个页面的第一个 URL也就是用户在浏览器/webview 里加载的那个初始地址。你如果拿路由变化后的location.href去签名反而不通过。通用的解法是在页面最早期把入口 URL 记录下来iOS 用这个记录值Android 用当前值。记录的位置放在public/index.html最靠前的 script 里最保险script // 记录进入页面时的完整 URL不含 hash供 iOS 端签名使用 window.__enterPageUrl location.href.split(#)[0] /script然后在工具函数里取export function getSignUrl() { const isIOS /iphone|ipad|ipod/i.test(navigator.userAgent) const current location.href.split(#)[0] if (isIOS) { return window.__enterPageUrl || current } return current }第二个坑是URL 里必须不含#及其后面的内容。Vue2 项目很多用 hash 模式路由location.href里全是#/order/detail?id1。签名必须用#之前的部分因为原生层根本没有 hash 的概念。有些人图省事把整个location.href传过去结果 Android 能过、iOS 不过查起来非常痛苦。第三个坑是如果页面 URL 里带了查询参数比如?tokenabcfromgroup这些参数必须原样带上不要为了“干净”把它们裁掉。因为这个 URL 会参与签名计算裁了就对不上了。同时要注意中文参数要保证编码一致性建议前端传之前先encodeURI一遍并在服务端对应解码避免不同环境下的编码差异。3.3 previewFile 的参数细节与封装函数wx.previewFile的参数不多但每一个都有讲究参数是否必填类型说明url是String文件的可访问地址http/https不能是 blobsize建议填Number文件大小单位字节超过 10MB 时强烈建议传name建议填String文件名含扩展名客户端靠它判断用什么方式打开关于size官方文档的说法是超过一定体积的文件建议传入文件大小客户端会先检查网络再决定是否下载。实测下来不传size的时候几十兆的文件在某些 Android 机上会静默失败页面没有任何反应传了之后至少会给出下载进度。所以我的做法是后端返回文件信息时一并把size带上前端原样透传。关于name这个字段其实决定了预览方式。你传合同.pdf客户端按 PDF 打开传月报.xlsx客户端调表格预览组件如果你传的名字没有扩展名客户端可能就当成未知类型直接走下载。所以哪怕后端没给文件名前端也要从 URL 里把扩展名抠出来补一个比如文件_${Date.now()}.pdf。封装函数长这样export function previewFile({ url, size, name }) { if (!url) { return Promise.reject(new Error(url is required)) } // 非企业微信环境直接降级 if (!isWeCom() || !window.wx) { window.open(url, _blank) return Promise.resolve({ fallback: true }) } return initWxConfig().then(wx { return new Promise((resolve, reject) { wx.previewFile({ url, size: size || undefined, name: name || guessNameFromUrl(url), success: resolve, fail: reject }) }) }) } function guessNameFromUrl(url) { try { const path url.split(?)[0] const seg path.substring(path.lastIndexOf(/) 1) return seg || file_${Date.now()} } catch (e) { return file_${Date.now()} } }注意fail回调。wx.previewFile失败的时候不会抛异常只会走fail参数里带errMsg。如果你不接这个回调出问题时前端一点日志都没有。我一般会在fail里上报一条埋点把errMsg、文件类型、文件大小、客户端 UA 一起带上后面排查特别省事。4. 完整实操从后端返回到点击预览跑通全流程理论说完走一遍完整链路。假设你有一个“合同列表”页面数据从后端接口来用户点某一行打开合同。4.1 后端文件地址的三种形态与分别怎么处理后端返回的文件信息通常是这三种形态之一处理方式完全不同。第一种是绝对直链。比如对象存储返回的https://cdn.example.com/contract/2024/03/abc.pdf带签名参数保证时效。这种最省事直接把 url 丢给previewFile就行。要注意链接的过期时间要足够长用户体验上从打开列表到点开文件可能间隔几分钟如果链接 60 秒就失效用户点开就是 403。第二种是带鉴权的接口路径。比如/api/contract/download?id123需要带 token 才能访问。这时候有个关键点原生层去下载这个文件时是不会带上你的 Cookie 或者 Authorization 头的。它就是一个不带凭证的 HTTP 请求。所以后端必须支持在 URL 上用查询参数传临时凭证比如/api/contract/download?id123signxxxxexpire1710000000服务端校验这个sign。这是最容易翻车的地方——本地调试的时候因为浏览器带了 Cookie 一切正常一上真机就 401。第三种是直接返回二进制流。这是最麻烦的必须改后端。因为前端拿到Blob之后只能生成blob:协议的临时地址而企业微信原生层不认这个协议。你能看到的典型现象是Android 上勉强能弹出一次预览退出后再点就失效iOS 上直接没反应。正确做法是让后端把这个文件先落到对象存储或者临时目录返回一个有时效的直链。提示如果后端短期内改不了可以在前端做一层中转——把流转成 base64 DataURL 传给previewFile。但这种方式对超过 2MB 的文件基本不可用因为 DataURL 字符串过长会被原生层截断而且内存占用极高。只建议作为临时应急不要写进正式方案。4.2 组件里怎么调一行搞定有了前面的封装业务组件里的代码就很干净了。假设src/api/contract.js里已经有列表接口template div classcontract-list div v-foritem in list :keyitem.id classcontract-item clickhandlePreview(item) span classname{{ item.fileName }}/span span classsize{{ formatSize(item.fileSize) }}/span /div /div /template script import { previewFile } from /utils/wecom-sdk export default { name: ContractList, data() { return { list: [] } }, created() { this.fetchList() }, methods: { async fetchList() { const res await this.$api.getContractList() this.list res.data || [] }, async handlePreview(item) { try { await previewFile({ url: item.fileUrl, size: item.fileSize, name: item.fileName }) } catch (err) { // 失败埋点 用户提示 this.$toast(文件打开失败请稍后重试) console.error([previewFile fail], err) } }, formatSize(bytes) { if (!bytes) return if (bytes 1024) return bytes B if (bytes 1024 * 1024) return (bytes / 1024).toFixed(1) KB return (bytes / 1024 / 1024).toFixed(2) MB } } } /script注意我在这里没有在组件挂载时提前调initWxConfig而是等用户点击的时候才触发。原因是签名有有效期提前几分钟签好可能就过期了。点击时懒加载签名、成功后缓存 Promise同一次会话里多个文件预览就只签一次这个平衡点比较合适。如果你的页面进来就要用previewImage之类的接口那就在created里预初始化一次但记得在wx.error里把缓存清掉否则一次失败会污染整个会话。4.3 一次 8MB PDF 的实测记录我把上周的一次实测记录贴出来你能看到真实的耗时分布。测试机型Android 中端机8 核 iOS 一台三年老机型文件是一个 8.2MB 的 PDF 合同链路是“冷启动进入列表页 → 点击合同”。阶段Android 耗时iOS 耗时说明首次wx.config注入约 320ms约 410ms含一次签名接口请求签名接口返回约 120ms约 150ms服务端查 Redis 缓存客户端开始下载约 200ms约 260ms原生层发起请求下载 8.2MB约 3.1s约 4.4s4G 网络实际速率有波动唤起预览组件约 400ms约 700msiOS 老机型明显更慢几个结论。第一wx.config只签一次就够第二次点击同一个文件几乎秒开因为 SDK 已经就绪。第二iOS 老机型在唤起原生预览组件那一步会明显卡顿这是系统层面的前端优化空间不大建议在点击后立刻给一个 loading 遮罩否则用户会以为没反应然后反复点击。第三8MB 这个体量的文件从点击到看到内容差不多 4 到 5 秒如果你的业务里文件动辄几十兆强烈建议在列表页只展示前几页的缩略图让用户先确认是不是要看的文件。size参数在这次实测里起了作用不传的时候Android 端在下载阶段完全没有回调用户等了 10 秒以为死了传了之后客户端界面会出现一个下载进度提示体验好很多。5. 踩坑与排查previewFile 不生效的那些原因这一节是我最想写的因为前面所有内容你在官方文档里都能找到影子但下面这些坑文档里基本不会写。5.1 现象与原因速查表先给一张速查表遇到问题直接对号入座现象大概率原因排查动作点击没反应控制台无报错不在企业微信环境 / SDK 未加载打印isWeCom()和typeof window.wx报invalid signature签名 URL 不对 / ticket 用错 / noncestr 大小写服务端把拼串原文打日志手动算一遍 SHA1报permission deniedjsApiList没加previewFile/ 没开beta检查 config 参数确认可信域名已生效预览界面空白文件地址返回了 403 / 是 blob 协议直接拿这个 URL 在手机浏览器里打开看看只有 PDF 能看Word 不行name没传扩展名 / 客户端版本过老补上文件名引导用户升级客户端Android 能看 iOS 不能签名 URL 用了路由变化后的地址改用入口 URL 记录值PC 端无反应客户端不支持或版本过老加降级走window.open第一次能看第二次失败直链的签名过期拉长链接有效期或每次点都重新取地址5.2 签名类问题的排查顺序签名错了是最高频的问题我总结了一个固定排查顺序基本三分钟能定位。第一步先确认后端拿到的url和前端浏览器地址栏里#之前的部分完全一致包括大小写、查询参数顺序。参数顺序变了签名也会变因为拼串的是原始字符串。第二步让服务端把参与 SHA1 的原始字符串完整打出来格式应该是jsapi_ticketxxxnoncestrxxxtimestampxxxurlxxx。拿这个字符串到一个在线的 SHA1 工具里手动算一次看结果是否等于返回给前端的 signature。如果不等说明服务端算错了通常是编码问题。第三步确认jsapi_ticket的层级对不对。企业微信里有两套 ticket企业级的用企业 corpid secret 换的 access_token 去取和应用级的用应用的 secret 换。wx.config用的是企业级的wx.agentConfig用的是应用级的。混用就是invalid signature。第四步检查access_token和jsapi_ticket是不是被别的服务顶掉了。企业微信同一套凭证在多处调用会互相失效如果你的项目里有定时任务或者别的服务也在拉很容易出现“刚才还好好的突然不行了”。统一收口到一个服务里管理能省很多事。第五步确认服务器的出口 IP 在可信 IP 列表里。这一条最容易被忽略因为表现是调接口直接返回错误码但如果你把错误吞掉了前端只会看到签名失败。5.3 格式、大小与平台差异说几个非签名类的坑。文件格式方面。previewFile支持的格式取决于客户端。PDF、图片、txt、Office 三件套doc/docx、xls/xlsx、ppt/pptx覆盖得都不错。但一些冷门格式比如.dwg、.zip、.rar客户端大概率会提示“暂不支持预览”然后让你用其他应用打开。这是正常的不要以为是接口调错了。如果你的业务里这类文件很多建议在列表里根据扩展名决定是否走预览——不支持的格式直接提示用户去 PC 端处理。文件大小方面。实测下来几十兆的文件在企业微信里能下载并预览但等待时间很长而且部分 Android 机型会因为内存原因失败。超过 50MB 的文件建议引导用户在企业微信 PC 端打开或者在 H5 里给一个明确的“文件较大下载可能需要时间”的提示。PC 端方面。企业微信 PC 端包括各种国产操作系统上的客户端版本对 JS-SDK 的支持程度差异较大。老版本的客户端里可能压根没有previewFile调用后fail回调会带一个permission denied或者直接不回调。所以 PC 端一定要有降级判断wx.previewFile是否存在不存在就用window.open打开PC 端浏览器的预览能力反而比较完整。const canPreview window.wx typeof window.wx.previewFile function if (!canPreview) { window.open(url, _blank) return }跨端判断方面。如果你的 H5 同时跑在企业微信、普通微信和 PC 浏览器里我建议按这个顺序判断先判断是不是企业微信UA 含wxwork是就走 JS-SDK再判断是不是微信内置浏览器UA 含micromessenger是就提示用户“请在浏览器中打开”或者走公众号的 JS-SDK最后兜底走普通浏览器的下载逻辑。判断逻辑尽量放在一个函数里别散落在各个组件不然以后加端会漏。6. 工程化收尾多端兼容与长期维护功能跑通只是第一步真正上线之后你会发现还得处理缓存、埋点和版本这些事。6.1 一套代码兼容企业微信、微信和普通浏览器把多端逻辑收口到一个统一的openFile方法里业务组件永远只调这一个入口。这样以后接入新的端比如某个 App 的内嵌 WebView只需要在这个函数里加分支不用满项目改代码。export function openFile({ url, size, name }) { const ua navigator.userAgent.toLowerCase() // 企业微信 if (/wxwork/.test(ua)) { return previewFile({ url, size, name }).catch(() { window.open(url, _blank) }) } // 普通微信内置浏览器 if (/micromessenger/.test(ua)) { window.open(url, _blank) return Promise.resolve({ fallback: wechat }) } // 普通浏览器 window.open(url, _blank) return Promise.resolve({ fallback: browser }) }previewFile失败之后自动降级到window.open这一步很重要。线上情况千奇百怪可能用户的客户端版本老、可能你的签名服务刚好在重启有个兜底至少不会让用户完全卡死。6.2 缓存、埋点与版本管理缓存方面wx.config的结果在同一个页面的生命周期内复用是安全的。但页面刷新之后要重新签因为 timestamp 变了。我用一个模块级的 Promise 做缓存失败时置空允许重试这个在第 3.1 节的代码里已经体现了。另外access_token和jsapi_ticket在服务端必须缓存这点前面说过不再重复。埋点方面至少上报三类事件preview_start用户点击、preview_successsuccess 回调、preview_failfail 回调带上 errMsg。有了这三类数据你就能算出各端的预览成功率一旦某天某个客户端的失败率突然飙升多半是客户端发了新版本或者是你的签名服务出了问题。版本管理方面jweixin的版本号最好在项目里用常量声明别散落在 HTML 里。我在实际项目里见过有人为了排查一个问题把两个页面引了不同版本的 SDK结果一个页面能预览一个不能查了半天。另外如果你用的是 npm 包方式记得锁版本package.json里别用^这种基础能力类的依赖稳定压倒一切。注意企业微信客户端的版本迭代比较快接口行为偶有调整。建议在项目里留一份“基线测试清单”每次客户端大版本更新后用几台测试机跑一遍 PDF、Word、图片三种类型的预览确认没回归。这个习惯能帮你挡掉不少线上事故。最后分享一个我自己用了一年的小技巧在开发环境里给previewFile包一层日志把url、size、name、UA、errMsg全部打到控制台并且用一段显眼的颜色区分成功和失败。这套日志在真机调试的时候特别有用因为你没办法像在 PC 浏览器里那样从容地打断点很多时候就是靠这几行日志定位问题——尤其是那些“用户说点不开但复现不了”的工单让他截个日志出来基本一眼就能看出是链接过期还是格式不支持。
返回列表