文件
hyb-oa/client/vite.config.ts
T
wangsli和Claude Opus 5.5 0931b1ea49 修复慢网络下审批后页面显示旧状态:SW API 缓存仅在离线时回退 [09-26-approve-success-state-unchanged]
app-api-runtime 对 /api/me、/api/workflow/tasks*、/api/workflow/approvers*、
/api/notifications* 使用 NetworkFirst + networkTimeoutSeconds: 3。网络响应
超过 3 秒时 Service Worker 返回审批前缓存的旧响应,真实响应被丢弃,页面
仍显示「待派发」。审批写入本身正确。自 2026-03(9112383d)起存在。

去掉网络超时,只在网络失败(离线)时回退缓存;退出登录清理缓存不变。
新增回归测试锁定该规则。负载复现修复前 3 轮命中 2 轮,修复后 3 轮 0 命中。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 18:58:45 +08:00

179 行
5.0 KiB
TypeScript

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";
function resolveManualChunk(id: string) {
if (id.includes("node_modules")) {
if (["react", "react-dom", "react-router-dom"].some((pkg) => id.includes(`/node_modules/${pkg}/`))) {
return "vendor-react";
}
if (id.includes("/node_modules/@tanstack/react-query/")) {
return "vendor-query";
}
if (id.includes("/node_modules/@zxing/browser/")) {
return "vendor-scan";
}
if (
id.includes("/node_modules/@radix-ui/") ||
id.includes("/node_modules/class-variance-authority/") ||
id.includes("/node_modules/clsx/") ||
id.includes("/node_modules/tailwind-merge/") ||
id.includes("/node_modules/sonner/")
) {
return "vendor-ui";
}
return undefined;
}
if (
id.includes("/src/api/http.ts") ||
id.includes("/src/api/types.ts") ||
id.includes("/src/api/errorMessages.ts") ||
id.includes("/src/api/auth.ts") ||
id.includes("/src/auth/") ||
id.includes("/src/components/common/") ||
id.includes("/src/hooks/") ||
id.includes("/src/lib/") ||
id.includes("/src/pwa.ts")
) {
return "app-core";
}
if (id.includes("/src/api/masterData.ts") || id.includes("/src/components/ai/")) {
return "app-domain-shared";
}
return undefined;
}
function normalizePublicBuildSha(value: string | undefined, fallback: string) {
const normalized = String(value ?? "").trim().toLowerCase();
return /^[0-9a-f]{7,64}$/.test(normalized) ? normalized.slice(0, 12) : fallback;
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, ".", "VITE_");
const publicBuildSha = normalizePublicBuildSha(
env.VITE_BUILD_SHA,
mode === "production" ? "unknown" : "development",
);
return {
define: {
"import.meta.env.VITE_BUILD_SHA": JSON.stringify(publicBuildSha),
},
plugins: [
react(),
VitePWA({
disable: false,
registerType: "autoUpdate",
injectRegister: false, // pwa.ts owns native registration and update reloads.
includeAssets: ["pwa-192x192.png", "pwa-512x512.png", "pwa-maskable-512x512.png"],
manifest: {
name: "hyb-oa 移动工作台",
short_name: "hyb-oa",
description: "海音达工作台移动端入口,支持离线 PDF 发票上传与移动审批。",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#2563eb",
icons: [
{
src: "pwa-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "pwa-512x512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "pwa-maskable-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
],
},
workbox: {
cleanupOutdatedCaches: true,
clientsClaim: true,
navigateFallback: "/index.html",
navigateFallbackDenylist: [/^\/api\//],
globPatterns: ["**/*.{js,css,html,ico,png,svg,webmanifest}"],
skipWaiting: true,
runtimeCaching: [
{
urlPattern: ({ request, url }) =>
["script", "style", "font", "image"].includes(request.destination) || url.pathname.endsWith(".webmanifest"),
handler: "StaleWhileRevalidate",
options: {
cacheName: "app-static-runtime",
cacheableResponse: {
statuses: [0, 200],
},
expiration: {
maxEntries: 120,
maxAgeSeconds: 7 * 24 * 60 * 60,
},
},
},
{
urlPattern: ({ request, url }) => {
if (request.method !== "GET") {
return false;
}
return (
url.pathname === "/api/me" ||
url.pathname.startsWith("/api/workflow/tasks") ||
url.pathname.startsWith("/api/workflow/approvers") ||
url.pathname.startsWith("/api/notifications")
);
},
// Fall back to cache only when the network fails (offline). A network
// timeout here served pre-approval task state to slow-but-online clients.
handler: "NetworkFirst",
options: {
cacheName: "app-api-runtime",
cacheableResponse: {
statuses: [200],
},
expiration: {
maxEntries: 80,
maxAgeSeconds: 30 * 60,
},
},
},
],
},
devOptions: {
enabled: false,
},
}),
],
build: {
rollupOptions: {
output: {
manualChunks(id) {
return resolveManualChunk(id);
},
},
},
},
server: {
proxy: {
"/api": {
target: "http://127.0.0.1:3000",
changeOrigin: false,
},
},
},
};
});