☰
Uniapp H5升级PWA:解决添加到桌面、缓存更新与推送接收难题
2026/10/9 3:53:08 网站建设 项目流程

先交代一下背景。我手头有个Uniapp写的H5商城项目,为了提升用户粘性,把它升级成了PWA——想让用户能在手机上像原生App一样“添加到桌面”,离线能打开,顺便接上消息推送。功能上线当天,用户的反馈就集中炸到了三个点上:安卓手机上“添加到桌面”点了没反应;iOS用户说昨天打开的页面今天打开还是旧版,怎么刷新都不变;推送更夸张,服务端明明显示发送成功,用户手机一条通知都没收到。

这三个问题单看都像“配置没写对”,但我逐个排查下来,发现每一个表面现象背后都牵扯着一整套浏览器机制。这一篇就当作“Uniapp + PWA 问题排查”系列的第一期,把完整的排查思路、踩过的坑、最终落地方案都整理出来。如果你也在用Uniapp做H5、想上PWA、并且卡在这三个问题上,这篇应该能帮你省下大半天时间。

1. 排查前先把三个概念对齐:HTTPS、Service Worker、Manifest 的角色边界

很多PWA问题排查不下去,不是因为代码写错,而是“以为问题出在A,实际上机制在B”。我这次深有体会:用户反馈“添加到桌面失败”,我第一反应是查manifest配置,结果折腾半天发现是Service Worker根本没注册成功。所以动手之前,先把三个基础概念的角色边界对齐。

1.1 HTTPS与安全上下文:PWA的入场券,不是可选项

Service Worker是PWA的核心,而Service Worker只能在安全上下文中工作。所谓安全上下文,简单说就是HTTPS页面,或者是localhost环回地址。这意味着如果你正在用一个内网IP,比如http://192.168.x.x:8080去访问Uniapp的H5页面,那么navigator.serviceWorker几乎是不可用的,后续所有PWA功能都没有地基。

我这次的项目在开发阶段一直在内网服务器上联调,同事用局域网IP访问时就会遇到“有没有PWA功能全看浏览器心情”的奇葩状况。Chrome对localhost网开一面,但对局域网IP是严格要求的,必须HTTPS。所以第一件要做的事就是确认:你的H5页面是否通过HTTPS域名对外提供服务。如果只是内网测试,建议直接本地起localhost环境,或者用反向代理套一层HTTPS,而不是在HTTP内网环境里死磕PWA。

1.2 Service Worker的作用范围:缓存排查的另一半答案

Service Worker有一个非常关键、但又特别容易被忽略的属性——作用范围(scope)。默认情况下,SW脚本文件在哪个目录,它的作用范围就是那个目录。

打个比方,如果你的sw.js放在站点根目录下,它就能控制整个站点的页面和请求;但如果把它放到了/static/sw.js,那么它只能拦截/static/路径下的请求,首页主文档、页面跳转它一概管不着。我见过不少Uniapp开发者图省事把sw.js扔进static目录,结果注册成功,缓存也建了,但页面死活不生效——就是作用范围的问题。

网上有一种方案是通过Service-Worker-Allowed响应头强行扩大作用范围,但这样需要服务器配合,对Uniapp H5这种纯静态部署的项目反而绕弯子。最省心的办法就是:把sw.js放到部署域名的根目录。另外一个冷知识:首次注册SW时,浏览器不会让SW立即控制当前已打开的页面,需要刷新一次之后才完成接管。这个“刷新一次”的体感很像“缓存没生效”,容易被误判成代码问题。

1.3 Manifest决定“能装”,SW+HTTPS决定“算不算PWA”,推送则依赖另一条链路

这里还要纠正一个高频误区:Uniapp项目里的manifest.json,和PWA需要的manifest文件,根本是两回事。Uniapp的manifest.json是给Uniapp编译器看的,管理appid、小程序配置、H5标题栏这些;而PWA的manifest是一个独立的manifest.webmanifest(或manifest.json)文件,通过<link rel="manifest">引入页面,负责告诉浏览器“这个应用的名称、图标、启动方式是什么”。

我也是在排查中才意识到,原来H5编译产物里根本没有PWA的manifest文件,得自己手动建一个,再在Uniapp H5的index.html模板里手动链接。这个点不搞清楚,后面所有“无法添加到桌面”的排查都会走弯路。

2. 无法添加到桌面:配置正确但按钮不出现的定位思路

“添加到桌面”这个需求的完整名称叫“Web App Installability”,意思是浏览器判定你的网站已经具备App的体感,于是向用户提供“安装”入口。这个判定不是拍脑袋,而是一套明确的检查机制。如果你的页面条件不满足,浏览器连安装按钮都不给你。

2.1 先跑一次Lighthouse,用审计结果代替瞎猜

我排查这类问题的第一步永远是打开Chrome DevTools的Lighthouse面板,切到PWA分类,直接跑一次审计。Lighthouse会把安装条件逐项列出来,比如“注册了Service Worker”“响应是200”“有192px和512px的图标”“有name和short_name”“配置了start_url”等等。

这次审计结果直接告诉我两件事:一是我当时只放了一张192px的图标,512px缺失,不符合Android Chrome安装条件;二是我的页面没有配置theme_color,虽然这个不影响安装,但会影响启动屏的颜色观感。照着审计结果改了图标之后,安装按钮马上就出现了。

Lighthouse的好处是不会跟你扯皮,每一项都会给“通过/不通过”和明确定位。比自己在浏览器里乱点乱猜要高效得多。建议任何PWA排查都从这一条开始。

2.2 图标、start_url、display:Manifest里最容易出事的三个字段

PWA的manifest文件内容不多,但每个字段都有讲究。我踩过的坑主要集中在三个字段。

第一个是图标。Android Chrome要求至少提供192px和512px两个PNG图标,且必须是真实存在的文件路径,不能用SVG。很多用Uniapp的开发者习惯用src/static/logo.png这个默认图标,但它尺寸不达标,直接导致安装条件不通过。第二个是start_url。如果你不显式配置,浏览器会把用户当前页面的完整URL(包括query参数)当成启动URL存起来。我发现有的同事测试时URL上带了一长串调试参数,结果用户“添加到桌面”后,每次启动都带着那串参数,请求都串了。显式写成"start_url": "/"最稳妥。第三个是display,要设置成"standalone",否则即使能添加到桌面,打开时也会保留浏览器地址栏,完全不像App。

我还建议在manifest里加上"purpose": "any maskable"的图标声明。这个词看着专业,实际就是告诉浏览器“我这个图标允许被裁切成圆形、圆角矩形等不同形状”。不加的话,部分安卓机型会自动给图标加个白底,那视觉体验相当违和。

2.3 iOS Safari的“添加到主屏幕”和安卓不是一回事

iOS用户反馈“无法添加到桌面”,几乎有一半情况不算bug,而是交互方式不同。安卓Chrome是浏览器主动判定后弹出安装气泡;iOS Safari则一直保持“用户主动操作”的模式:点击分享按钮,向下滑动,找到“添加到主屏幕”。

iOS对PWA的判定比安卓宽松一些,只要页面有manifest、有HTTPS,就可以添加。但有个细节很容易踩:如果你没在HTML里单独加apple-touch-icon链接,iOS会自作主张对网页截个图当作图标,效果相当难看。正确的做法是在index.html模板里加:

<link rel="apple-touch-icon" href="/static/icon-192.png">

另外,iOS真正支持Web Push(通知推送)是从16.4版本开始的,而且要求必须先把网页“添加到主屏幕”,在普通Safari标签页里反而收不到推送。这个限制不搞清楚,推送问题排查起来会怀疑人生。

2.4 国内安卓内核浏览器的安装机制差异

Chrome是PWA的“标准学生”,但国内安卓用户不一定在用Chrome,他们可能用华为浏览器、小米浏览器、UC、QQ浏览器,甚至各种App内嵌的WebView。

我实测下来,华为、小米这类系统浏览器已经支持了大部分PWA能力,但安装入口的位置和叫法各不相同,有的叫“添加到桌面”,有的叫“安装应用”。UC这类第三方浏览器的内核不完全支持标准PWA安装,即使你的manifest完美,也不一定能弹安装提示。更别说微信、支付宝里的WebView,压根不是浏览器环境,SW注册都会被禁止。我这次项目管理了用户预期:明确提示“请在系统浏览器(如Chrome、华为浏览器)中打开本页面再安装”,而不是让用户在不支持的环境里反复点按钮。

3. 缓存失效:80%的情况不是缓存坏了,而是版本管理没做好

用户说的“缓存失效”其实包含两种截然不同的现象:一种是“该刷新的没刷新,一直看到旧页面”,另一种是“该缓存的没缓存,一断网就白屏”。这两个方向的排查逻辑完全不同。我这次被用户反馈“打开页面是旧版”折磨得不轻,最后定位到的核心是:Service Worker的版本更新和页面资源的缓存策略没有配合好。

3.1 先分清“缓存没更新”和“资源被误清”

收到缓存类反馈,我第一步会在用户手机上复现,同时用Chrome DevTools的Network面板看关键的请求。请求有两种标识:from ServiceWorker和from memory cache。如果主文档index.html显示from ServiceWorker,那就说明浏览器直接用了SW给的缓存;如果显示from disk cache或from memory cache,说明SW根本没接管这个请求,是HTTP缓存的问题。

这两个来源的处理方式完全不一样:HTTP缓存可以通过改资源URL或响应头解决,而SW缓存必须通过SW代码逻辑解决。很多新手把Cache-Control头加到index.html上,发现页面还是旧版,就是因为请求被SW拦下了,HTTP头压根没生效。反之亦然,改SW缓存策略,但对HTTP缓存里的旧资源也无能为力。我在项目里就把这两个缓存层面戏称为“双层缓存”,排查时必须先确定是哪一层在起作用。

3.2 SW文件与HTTP缓存的关系:为什么改了代码就是不生效

Service Worker更新机制有个容易被忽略的规则:浏览器通过对比sw.js文件内容是否发生变化,来判断要不要安装新SW。但sw.js本身也是网络请求,也会被HTTP缓存影响。如果你在服务器端给sw.js设置了强缓存,比如Cache-Control: max-age=86400,那浏览器在一天之内都拿不到新SW,你发布的代码再改也没用,用户永远是旧逻辑。

我的处理办法是在Nginx配置里单独给sw.js和manifest.webmanifest关闭缓存:

location = /sw.js { add_header Cache-Control "no-cache, no-store"; } location = /manifest.webmanifest { add_header Cache-Control "no-cache"; }

注意我用了no-cache和no-store的组合,确保每个版本都能让浏览器重新校验SW文件。这是PWA版本更新机制能跑起来的前提。

3.3 用Cache版本号+skipWaiting控制更新节奏

SW的更新流程分为三步:install(安装新SW)、waiting(等待旧SW让位)、activate(激活新SW并接管页面)。默认情况下,如果旧SW还在控制页面,新SW会一直处于waiting状态,既不会接管页面,也不会清理旧缓存。这就会造成用户明明发了新版,但线上还是旧版的现象。

我在项目里的做法是,在sw.js开头维护一个版本号,并把它拼进缓存名称:

const CACHE_NAME = 'uniapp-pwa-v1.4.2';

然后install事件里调用self.skipWaiting(),让新SW安装后直接跳过等待阶段:

self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(['/', '/index.html']) ) ); self.skipWaiting(); });

activate事件里做两件事:删除旧版本的缓存、立即clients.claim()接管所有页面:

self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) ) ) ); self.clients.claim(); });

这样每次发布时,我只要手动改一下CACHE_NAME的版本号,SW就会自动缓存新版本,并清掉旧缓存。用户刷新页面时,大概率就直接拿到新版了。

3.4 uni-app H5打包场景下的缓存策略推荐

Uniapp H5构建产物分为几类:入口index.html、带hash的JS和CSS资源、静态图片、接口请求。每一类的缓存策略不应该一样。我的做法是:

  • index.html:网络优先,保证用户每次都能拿到最新页面,失败时降级到缓存,保证离线可用
  • 带hash的/static/js/*和/static/css/*:缓存优先,文件名带hash就是内容指纹,变了就是新文件,旧文件名放心缓存
  • 图片、字体等静态资源:缓存优先,后台更新(stale-while-revalidate的简化版)
  • /api/*接口:不缓存,直接走网络,尤其POST请求万万不能缓存

完整的sw.js代码我贴一下,供参考。这套逻辑不复杂,但已经能覆盖绝大多数Uniapp H5项目的需求:

const CACHE_NAME = 'uniapp-pwa-v1.4.2'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(['/', '/index.html'])) ); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))) ) ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { const request = event.request; const url = new URL(request.url); if (request.method !== 'GET') return; // 接口请求不缓存 if (url.pathname.startsWith('/api')) return; // 导航请求:网络优先,失败回退缓存 if (request.mode === 'navigate') { event.respondWith( fetch(request) .then((response) => { const copy = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, copy)); return response; }) .catch(() => caches.match(request)) ); return; } // 其余静态资源:缓存优先 event.respondWith( caches.match(request).then((cached) => { if (cached) return cached; return fetch(request).then((response) => { const copy = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, copy)); return response; }); }) ); });

注意一个细节:Uniapp H5如果用的是hash路由(默认就是hash模式),页面URL以#/pages/...变化,主文档始终是/或/index.html,所以navigation的缓存策略完全够用。但如果你把H5切成了history路由,Nginx还得多配一层try_files把所有路径回退到index.html,否则用户直接访问子路径会404。

3.5 前端主动提示用户“有新版本”

SW更新机制是后台完成的,就算新SW已激活,如果用户不刷新页面,看到的还是旧界面。所以我在Uniapp的main.js里监听SW更新事件,发现新版本已安装后弹一个提示框,引导用户点击刷新:

// #ifdef H5 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').then((reg) => { reg.onupdatefound = () => { const newWorker = reg.installing; newWorker.onstatechange = () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { uni.showModal({ title: '发现新版本', content: '点击确定刷新页面', success: (res) => { if (res.confirm) { newWorker.postMessage({ type: 'SKIP_WAITING' }); } } }); } }; }; }); }); } // #endif

对应的SW里加一段监听:

self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });

这里有个开发期很有用的调试技巧:Chrome DevTools的Application面板里,勾选“Update on reload”之后,每次刷新页面都会强制更新SW,非常适合开发阶段验证“新版本是否覆盖老缓存”。如果关掉这个选项,你会以为是代码没改对,实际上是浏览器还没触发SW更新。

4. 推送失败:从订阅到送达,整条链路上哪个环节都可能断

“服务端明明发送成功,用户一条通知都没收到”——这是所有推送问题里最让人头疼的,因为发送成功和送达成功之间,隔着好几层黑盒。Web Push不是直接从你的服务器推到用户手机,它是一条完整的链路,任何一个环节出问题,用户感知都是“没收到”。

4.1 Web Push的完整链路长什么样

标准Web Push链路是这样的:

  1. 浏览器向推送服务(Push Service)发起订阅,生成一个PushSubscription对象
  2. 你的前端拿到这个订阅对象后,通过接口发给自己的后端保存
  3. 后端在某些时机(比如后台管理员发了条公告)通过Web Push协议,把消息发给推送服务
  4. 推送服务再推送到对应的浏览器客户端
  5. 浏览器唤醒Service Worker,SW的push事件被触发,调用showNotification展示通知

注意这里有个关键区分:浏览器厂商的推送服务(比如Chrome走的是Google的FCM通道,Firefox走的是Mozilla的推送服务)和你的应用服务器是两套系统。你的服务端只是把请求发给了推送服务,并不是直接发到了用户手机。所以“HTTP协议显示201发送成功”,只是说明推送服务受理了消息,不表示用户手机上真的弹出了通知。

4.2 订阅这一步就失败:VAPID与权限请求的坑

排查推送问题,第一步永远是看“订阅是否成功”。打开Chrome DevTools的Application面板,展开Service Workers找到你的SW,再点开Push Subscription,如果能看到一个以https://...开头的endpoint字段,说明订阅成功。如果这个根本不存在,那就说明问题出在订阅阶段。

订阅阶段最常见的问题有三个。第一,页面不是HTTPS或SW没有注册成功,这个前面已经说过,是整个PWA的地基。第二,没有请求通知权限,或者用户在浏览器弹窗里点了拒绝。Notification.requestPermission()必须在用户点击操作(比如点击“开启通知”按钮)的上下文中调用,如果一进页面就自动弹窗,Chrome大概率会拦截,用户体验也很差。第三,订阅请求里漏了applicationServerKey,或者公钥格式不对。这个公钥就是VAPID体系里的应用服务器公钥,需要和后端手里的私钥配对。

我遇到过一种情况是后端给了公钥,但没告诉我要做base64到Uint8Array的转换,直接字符串塞进去,subscribe()直接抛异常。正确做法是先把base64公钥转成Uint8Array:

function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }

再把转换后的数组传给subscribe:

const publicKey = '你的VAPID公钥'; const reg = await navigator.serviceWorker.ready; const subscription = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey) });

订阅成功后,这项数据要尽快传回后端保存。注意不同浏览器拿到的subscription.endpoint前缀不一样,Chrome是fcm.googleapis.com,Firefox是push.services.mozilla.com,后端统一存完整对象就好。

4.3 服务端上报201但用户收不到:推送服务通道问题

如果订阅存在、后端也发送成功,用户还是收不到,问题多半出在“推送服务送达浏览器”这一段。前端代码已经无能为力。

Chrome在国内依赖Google的推送通道,而该通道在某些网络环境下可达性极差,这是客观存在的现实问题。我实测的结果是:后台管理发送成功率很高,但用户接收率忽高忽低,同一个环境下,Firefox能收到而Chrome收不到。这种状况不是改改前端代码能解决的。

我的应对思路是业务降级:项目里把Web Push当作“锦上添花”,核心的订单状态变更改成了页面内轮询+用户主动下拉刷新的方案。用户打开页面时优先展示最新状态,Web Push能送达就送,送不到也不影响核心流程。如果你想继续用Web Push,建议评估一下第三方推送聚合服务作为通道,然后把订阅信息同时上报给多套服务,这算是比较务实的取舍。

4.4 在uni-app里注册push和notificationclick的注意点

Uniapp的H5端和App端、小程序端推送不是一回事。Uniapp生态里的uni-push主要针对App和各家小程序,H5端的Web Push必须自己接。我通过条件编译把推送逻辑只编译到H5端:

// #ifdef H5 async function subscribePush() { if (!('serviceWorker' in navigator)) return; const reg = await navigator.serviceWorker.ready; const subscription = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey) }); uni.request({ url: '/api/push/subscribe', method: 'POST', data: JSON.parse(JSON.stringify(subscription)), success: () => { console.log('订阅已上报'); } }); } // #endif

SW里处理push事件时,showNotification的icon字段建议显式指定,我用的是项目里的192图标。如果这个字段缺失,有的浏览器会显示默认占位图,有的直接不出通知。

self.addEventListener('push', (event) => { const data = event.data ? event.data.json() : {}; event.waitUntil( self.registration.showNotification(data.title || '新消息', { body: data.body || '', icon: '/static/icon-192.png', data: { url: data.url || '/' } }) ); });

还有一个容易被忽视的点:用户点击通知后,需要通过notificationclick事件打开对应的页面。Uniapp是SPA,路由采用hash模式时,要用/#/pages/...这样的地址:

self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { for (const client of clientList) { if ('focus' in client) return client.focus(); } if (clients.openWindow) return clients.openWindow(event.notification.data.url); }) ); });

后端发送报错也是个重要排查线索。我做了一张错误码对照表挂在项目文档里,排查时直接对照:

状态码含义处理建议
201已受理并发送正常流程,等待推送
401/403VAPID认证失败检查服务端私钥和公钥是否匹配,检查主题mailto是否配置
404/410订阅已失效删除数据库里的旧订阅,提示用户重新订阅
413消息体超过4096字节压缩或精简payload内容
429发送太频繁被限流退避重试,合并消息降低频次

这里再强调一下:如果后端发送返回201,但用户手机确实收不到,就回到上一节说的“推送服务通道”问题,别在前端继续折腾了。

5. 三个问题并发时的排查顺序:先环境、再SW、后代码

当“无法安装”“缓存不更新”“推送收不到”三个问题同时出现时,它们往往不是三个独立bug,而是同一个根源在三个场景下的不同表现。我这次就是典型的例子——SW没在根目录注册成功,导致安装判定不通过;SW不工作,当然也不会有缓存更新逻辑;推送依赖SW事件,自然也全线崩溃。

5.1 一套我总结的排查checklist

现在碰到这类问题,我会强制自己按固定顺序查,而不是看到什么修什么。

第一层,检查环境:页面是不是HTTPS且公网可访问,是不是在系统浏览器(而非WebView)里打开,iOS版本是否高于16.4(涉及推送)。

第二层,检查SW:DevTools的Application面板看Service Workers区域,确认SW已注册并处于activated状态,同时确认作用范围覆盖了你的页面路径。

第三层,检查manifest:看Application面板的Manifest区域,有没有红色报错,图标是否能正确加载,start_url是否有效。

第四层,检查推送:确认Notification权限是granted,Push Subscription存在且未被后台删除。

这个顺序是有讲究的:环境问题是地基,SW不活则后续全免谈,manifest和推送都是建立在SW之上的业务能力。倒过来查,很容易查到一半发现是前面某一层的问题,白费功夫。

5.2 各浏览器DevTools里的关键入口

不同浏览器查PWA状态的入口不太一样,盲人摸象会浪费时间。Chrome直接在DevTools的Application面板,Manifest、Service Workers、Cache Storage、Notification信息都在这个面板里分栏展示。Firefox在地址栏输入about:debugging#/runtime/this-firefox,可以查看SW注册和更新。移动端设备连接Chrome后,还可以通过chrome://inspect做远程调试,直接看手机浏览器的Console和Network,这一步在排查“手机上不行、电脑上可以”的诡异问题时几乎是必须的。

我这次项目里有位同事反馈安装按钮不出现,我远程一看,他用的是手机上的一个第三方浏览器。电脑上DevTools完全正常的代码,在那个内核里就是不弹安装入口。这种时候不要怀疑代码,要向用户明确支持范围。

5.3 上线前的自测小技巧

我这套排查流程走顺之后,在项目里加了一个隐藏的自测页面,用一个小函数把这几个关键状态一次性打出来:

// #ifdef H5 async function checkPWAStatus() { const result = {}; result.isSecureContext = window.isSecureContext; result.swSupported = 'serviceWorker' in navigator; if (result.swSupported) { const reg = await navigator.serviceWorker.getRegistration('/sw.js'); result.swRegistered = !!reg; result.swScope = reg ? reg.scope : null; } result.manifestLinked = !!document.querySelector('link[rel="manifest"]'); result.notificationPermission = Notification.permission; if ('serviceWorker' in navigator) { const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.getSubscription(); result.pushSubscribed = !!sub; } console.table(result); } // #endif

上线之前用手机浏览器打开页面,把这个函数的输出和控制台日志截图发回群里,十分钟就能确认环境。这个自测函数成了我和测试同学之间沟通效率最高的工具。它也帮我省掉了大量“你那边试试看”式的来回沟通,算是这套排查流程里最有性价比的副产品。

做PWA这些机制本身不复杂,但琐碎,关键点都藏在浏览器规范的细节里。只要按“环境检查 → SW状态 → manifest → 推送订阅”的顺序一步步来,绝大多数问题都能定位到具体环节。这一期先记录到这里,后面我再接着写Uniapp PWA发布流程里和构建、部署有关的坑——尤其是多环境配置和版本号自动化那部分,踩一次够我记三年。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询