CI / backend (push) Canceled after 0s
用 vite-plugin-pwa 生成 manifest 与 Service Worker,不自己写缓存逻辑。 - 装到桌面:manifest 名字/主题色/三个图标(any + maskable + apple-touch-icon), 图标和页头、安卓桌面是同一枚;首页底部给安装引导,能装就弹「安装」, iOS 给「分享 → 添加到主屏幕」,关掉记在 localStorage - 发版提示:registerType=prompt,收到新版本只在底部提示,点「刷新」才换。 故意不自动刷新——Vditor、图表是懒加载分片,后台换 chunk 会让面板 404 - 缓存边界:只预缓存页面骨架(约 0.8MB),/api/ 一律 NetworkOnly, 离线宁可报错也不端旧数据;/vditor/ 走 CacheFirst,不拖慢首次安装 - 顺手修:UpdatePrompt 居中改用 flex 包一层,animate-slide-up 的 keyframes 自带 transform,会和 -translate-x-1/2 抢属性,手机窄屏会把卡片推出屏幕 验证:web/e2e/pwa.mjs 19 项全绿(manifest/SW 激活/预缓存/安装引导/iOS 文案/ 发版提示/断网仍能打开);tsc 通过、vite build 通过。 已知限制:Service Worker 只在 https 或 localhost 注册,生产入口还是 http, 所以内网 IP 上装不了——需要域名 + 证书反代或隧道,已写进 DEPLOY.md 第 6 节。
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { Button } from "./ui";
|
||
import { applyServiceWorkerUpdate, PWA_EVENT } from "../lib/pwa";
|
||
|
||
/**
|
||
* 装到手机/桌面以后,发版就不是"让用户重新输网址"了:
|
||
* 有新版本时在这里提一句,点"刷新"才换,不点就继续用手上的这版。
|
||
*
|
||
* 为什么不用自动刷新(registerType: "autoUpdate"):Vditor、图表这些是懒加载分片,
|
||
* 后台悄悄换掉之后,用户下一次点开某个面板就是 404 —— 编辑器直接白屏。
|
||
*/
|
||
export function UpdatePrompt() {
|
||
const [ready, setReady] = useState(false);
|
||
const [applying, setApplying] = useState(false);
|
||
const [dismissed, setDismissed] = useState(false);
|
||
|
||
useEffect(() => {
|
||
const onReady = () => setReady(true);
|
||
window.addEventListener(PWA_EVENT.updateReady, onReady);
|
||
return () => window.removeEventListener(PWA_EVENT.updateReady, onReady);
|
||
}, []);
|
||
|
||
if (!ready || dismissed) return null;
|
||
|
||
return (
|
||
// 居中用 flex 包一层:animate-slide-up 的 keyframes 自带 transform,
|
||
// 会和 -translate-x-1/2 抢同一个属性,手机窄屏上卡片会被推出屏幕右侧。
|
||
<div className="pointer-events-none fixed inset-x-0 bottom-6 z-[70] flex justify-center px-4">
|
||
<div
|
||
role="status"
|
||
className="animate-slide-up pointer-events-auto w-full max-w-[26rem] rounded-xl border border-line-200 bg-ink-900 px-4 py-3 text-paper-50 shadow-pop"
|
||
>
|
||
<p className="text-sm">有新版了。刷新一下就用上,手头的输入会丢,先存了再点。</p>
|
||
<div className="mt-2 flex justify-end gap-2">
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="text-paper-300 hover:text-paper-50"
|
||
onClick={() => setDismissed(true)}
|
||
>
|
||
稍后
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
disabled={applying}
|
||
onClick={() => {
|
||
setApplying(true);
|
||
void applyServiceWorkerUpdate();
|
||
}}
|
||
>
|
||
{applying ? "正在刷新…" : "刷新"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|