feat: 添加PWA支持,支持离线访问和添加到主屏幕

This commit is contained in:
System Administrator
2026-03-28 00:47:13 +07:00
parent d437580500
commit 8ffadc8604
5 changed files with 172 additions and 2 deletions
+23 -2
View File
@@ -1,9 +1,16 @@
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#1890ff" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="轻远ERP" />
<meta name="description" content="轻远电力老挝分公司财务管理系统" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/logo.png" />
<title>轻远电力老挝ERP - Qingyuan Power Laos</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
@@ -12,5 +19,19 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW registered:', registration);
})
.catch((error) => {
console.log('SW registration failed:', error);
});
});
}
</script>
</body>
</html>
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Generate PWA Icons</title>
</head>
<body>
<h2>PWA图标生成器</h2>
<p>请使用浏览器打开此文件,然后右键保存生成的图标</p>
<div id="icons"></div>
<script>
const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
const container = document.getElementById('icons');
sizes.forEach(size => {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
// 背景
ctx.fillStyle = '#1890ff';
ctx.fillRect(0, 0, size, size);
// 文字
ctx.fillStyle = 'white';
ctx.font = `bold ${size * 0.5}px Arial, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('轻', size / 2, size / 2 + size * 0.05);
// 显示
const div = document.createElement('div');
div.style.margin = '10px';
div.innerHTML = `<p>${size}x${size}</p>`;
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
img.style.width = '64px';
img.style.height = '64px';
img.style.border = '1px solid #ccc';
div.appendChild(img);
// 下载链接
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = `icon-${size}x${size}.png`;
link.textContent = '下载';
link.style.marginLeft = '10px';
div.appendChild(link);
container.appendChild(div);
});
</script>
</body>
</html>
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#1890ff"/>
<text x="256" y="320" font-family="Arial, sans-serif" font-size="240" font-weight="bold" text-anchor="middle" fill="white"></text>
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,19 @@
{
"name": "轻远电力老挝ERP",
"short_name": "轻远ERP",
"description": "轻远电力老挝分公司财务管理系统",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1890ff",
"orientation": "portrait",
"scope": "/",
"icons": [
{
"src": "/logo.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}
]
}
@@ -0,0 +1,70 @@
// Service Worker for PWA
const CACHE_NAME = 'qingyuan-erp-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/logo.png'
];
// 安装时缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS);
}).catch((err) => {
console.log('Cache failed:', err);
})
);
self.skipWaiting();
});
// 激活时清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
self.clients.claim();
});
// 拦截请求,优先使用缓存
self.addEventListener('fetch', (event) => {
// 跳过非GET请求和API请求
if (event.request.method !== 'GET' ||
event.request.url.includes('/api/') ||
event.request.url.includes('chrome-extension')) {
return;
}
event.respondWith(
caches.match(event.request).then((response) => {
// 如果缓存中有,返回缓存
if (response) {
return response;
}
// 否则发起网络请求
return fetch(event.request).then((networkResponse) => {
// 缓存新请求
if (networkResponse.status === 200) {
const responseToCache = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
}
return networkResponse;
}).catch(() => {
// 网络失败时返回离线页面
if (event.request.mode === 'navigate') {
return caches.match('/index.html');
}
});
})
);
});