PWA支持
1
Web/PWA-README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,15 +1,37 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover, user-scalable=no">
|
||||||
|
|
||||||
<!-- PWA - 让应用在添加到主屏幕后以全屏模式运行 -->
|
<!-- PWA Manifest -->
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
||||||
|
<!-- iOS Safari -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<meta name="apple-mobile-web-app-title" content="账单管理">
|
<meta name="apple-mobile-web-app-title" content="账单管理">
|
||||||
|
<link rel="apple-touch-icon" href="/icons/icon-152x152.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="96x96" href="/icons/icon-96x96.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="128x128" href="/icons/icon-128x128.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="384x384" href="/icons/icon-384x384.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.svg">
|
||||||
|
|
||||||
|
<!-- Android Chrome -->
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="theme-color" content="#1989fa">
|
||||||
|
|
||||||
|
<!-- Microsoft -->
|
||||||
|
<meta name="msapplication-TileColor" content="#1989fa">
|
||||||
|
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
|
||||||
|
|
||||||
|
<meta name="description" content="个人账单管理与邮件解析系统">
|
||||||
|
<meta name="keywords" content="账单管理,邮件解析,财务管理">
|
||||||
|
|
||||||
<title>账单管理</title>
|
<title>账单管理</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
10
Web/public/icons/icon-128x128.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad128" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="128" height="128" fill="url(#grad128)" rx="12.8"/>
|
||||||
|
<text x="50%" y="50%" font-size="51.2" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 554 B |
10
Web/public/icons/icon-144x144.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="144" height="144" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad144" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="144" height="144" fill="url(#grad144)" rx="14.4"/>
|
||||||
|
<text x="50%" y="50%" font-size="57.6" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 554 B |
10
Web/public/icons/icon-152x152.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="152" height="152" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad152" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="152" height="152" fill="url(#grad152)" rx="15.200000000000001"/>
|
||||||
|
<text x="50%" y="50%" font-size="60.800000000000004" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 582 B |
10
Web/public/icons/icon-192x192.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="192" height="192" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad192" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="192" height="192" fill="url(#grad192)" rx="19.200000000000003"/>
|
||||||
|
<text x="50%" y="50%" font-size="76.80000000000001" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 581 B |
10
Web/public/icons/icon-384x384.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="384" height="384" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad384" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="384" height="384" fill="url(#grad384)" rx="38.400000000000006"/>
|
||||||
|
<text x="50%" y="50%" font-size="153.60000000000002" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 582 B |
10
Web/public/icons/icon-512x512.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad512" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="512" height="512" fill="url(#grad512)" rx="51.2"/>
|
||||||
|
<text x="50%" y="50%" font-size="204.8" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 555 B |
10
Web/public/icons/icon-72x72.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="72" height="72" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad72" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="72" height="72" fill="url(#grad72)" rx="7.2"/>
|
||||||
|
<text x="50%" y="50%" font-size="28.8" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 547 B |
10
Web/public/icons/icon-96x96.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="96" height="96" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad96" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#1989fa;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#0b5fd6;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="96" height="96" fill="url(#grad96)" rx="9.600000000000001"/>
|
||||||
|
<text x="50%" y="50%" font-size="38.400000000000006" fill="white" text-anchor="middle" dy=".35em" font-family="Arial, Microsoft YaHei, sans-serif" font-weight="bold">账</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 575 B |
4
Web/public/icons/icon-temp.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width='512' height='512' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<rect width='512' height='512' fill='#1989fa'/>
|
||||||
|
<text x='50%' y='50%' font-size='200' fill='white' text-anchor='middle' dy='.3em' font-family='Arial, sans-serif' font-weight='bold'>账</text>
|
||||||
|
</svg>
|
||||||
76
Web/public/manifest.json
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "账单管理系统",
|
||||||
|
"short_name": "账单管理",
|
||||||
|
"description": "个人账单管理与邮件解析系统",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#1989fa",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-72x72.svg",
|
||||||
|
"sizes": "72x72",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-96x96.svg",
|
||||||
|
"sizes": "96x96",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-128x128.svg",
|
||||||
|
"sizes": "128x128",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-144x144.svg",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-152x152.svg",
|
||||||
|
"sizes": "152x152",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-192x192.svg",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-384x384.svg",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-512x512.svg",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": ["finance", "productivity"],
|
||||||
|
"screenshots": [],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "查看账单",
|
||||||
|
"short_name": "账单",
|
||||||
|
"description": "快速查看账单列表",
|
||||||
|
"url": "/",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icons/icon-96x96.png",
|
||||||
|
"sizes": "96x96"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
141
Web/public/service-worker.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
const CACHE_NAME = 'emailbill-v1';
|
||||||
|
const urlsToCache = [
|
||||||
|
'/',
|
||||||
|
'/index.html',
|
||||||
|
'/favicon.ico',
|
||||||
|
'/manifest.json'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 安装 Service Worker
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
console.log('[Service Worker] 安装中...');
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then((cache) => {
|
||||||
|
console.log('[Service Worker] 缓存文件');
|
||||||
|
return cache.addAll(urlsToCache);
|
||||||
|
})
|
||||||
|
.then(() => self.skipWaiting())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 激活 Service Worker
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
console.log('[Service Worker] 激活中...');
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((cacheNames) => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map((cacheName) => {
|
||||||
|
if (cacheName !== CACHE_NAME) {
|
||||||
|
console.log('[Service Worker] 删除旧缓存:', cacheName);
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}).then(() => self.clients.claim())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拦截请求
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
const { request } = event;
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// 跳过跨域请求
|
||||||
|
if (url.origin !== location.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API请求使用网络优先策略
|
||||||
|
if (url.pathname.startsWith('/api/')) {
|
||||||
|
event.respondWith(
|
||||||
|
fetch(request)
|
||||||
|
.then((response) => {
|
||||||
|
// 克隆响应以便缓存
|
||||||
|
const responseClone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(request, responseClone);
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 网络失败时尝试从缓存获取
|
||||||
|
return caches.match(request);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态资源使用缓存优先策略
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(request)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
return fetch(request).then((response) => {
|
||||||
|
// 检查是否是有效响应
|
||||||
|
if (!response || response.status !== 200 || response.type !== 'basic') {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseClone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(request, responseClone);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 返回离线页面或默认内容
|
||||||
|
if (request.destination === 'document') {
|
||||||
|
return caches.match('/index.html');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 后台同步
|
||||||
|
self.addEventListener('sync', (event) => {
|
||||||
|
console.log('[Service Worker] 后台同步:', event.tag);
|
||||||
|
if (event.tag === 'sync-data') {
|
||||||
|
event.waitUntil(syncData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 推送通知
|
||||||
|
self.addEventListener('push', (event) => {
|
||||||
|
console.log('[Service Worker] 收到推送消息');
|
||||||
|
const options = {
|
||||||
|
body: event.data ? event.data.text() : '您有新的账单消息',
|
||||||
|
icon: '/icons/icon-192x192.png',
|
||||||
|
badge: '/icons/icon-72x72.png',
|
||||||
|
vibrate: [200, 100, 200],
|
||||||
|
tag: 'emailbill-notification',
|
||||||
|
requireInteraction: false
|
||||||
|
};
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification('账单管理', options)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知点击
|
||||||
|
self.addEventListener('notificationclick', (event) => {
|
||||||
|
console.log('[Service Worker] 通知被点击');
|
||||||
|
event.notification.close();
|
||||||
|
event.waitUntil(
|
||||||
|
clients.openWindow('/')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 数据同步函数
|
||||||
|
async function syncData() {
|
||||||
|
try {
|
||||||
|
// 这里添加需要同步的逻辑
|
||||||
|
console.log('[Service Worker] 执行数据同步');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Service Worker] 同步失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<van-config-provider :theme="theme">
|
<van-config-provider :theme="theme">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
<van-tabbar v-model="active" v-show="showTabbar">
|
<van-tabbar v-model="active" v-show="showTabbar" safe-area-inset-bottom>
|
||||||
<van-tabbar-item icon="notes-o" to="/calendar">
|
<van-tabbar-item icon="notes-o" to="/calendar">
|
||||||
日历
|
日历
|
||||||
</van-tabbar-item>
|
</van-tabbar-item>
|
||||||
@@ -61,3 +61,19 @@ const handleTabClick = (path) => {
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 为 Tabbar 添加额外的底部安全区域适配 */
|
||||||
|
:deep(.van-tabbar) {
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
/* 在原有高度基础上增加安全区域 */
|
||||||
|
height: calc(50px + constant(safe-area-inset-bottom));
|
||||||
|
height: calc(50px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保内容不被 Tabbar 遮挡 */
|
||||||
|
:deep(.van-tabbar) + * {
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
@import './base.css';
|
@import './base.css';
|
||||||
|
|
||||||
|
/* 禁用页面弹性缩放和橡皮筋效果 */
|
||||||
|
html, body {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
overscroll-behavior-y: none;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
touch-action: pan-y;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import vant from 'vant'
|
|||||||
import { ConfigProvider } from 'vant';
|
import { ConfigProvider } from 'vant';
|
||||||
import 'vant/lib/index.css'
|
import 'vant/lib/index.css'
|
||||||
|
|
||||||
|
// 注册 Service Worker
|
||||||
|
import { register } from './registerServiceWorker'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
@@ -17,3 +20,8 @@ app.use(vant)
|
|||||||
app.use(ConfigProvider);
|
app.use(ConfigProvider);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
// 在生产环境注册 Service Worker
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
register()
|
||||||
|
}
|
||||||
|
|||||||
92
Web/src/registerServiceWorker.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
export function register() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `/service-worker.js`;
|
||||||
|
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then((registration) => {
|
||||||
|
console.log('[SW] Service Worker 注册成功:', registration.scope);
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = registration.installing;
|
||||||
|
console.log('[SW] 发现新版本');
|
||||||
|
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// 新的 Service Worker 已安装,提示用户刷新
|
||||||
|
console.log('[SW] 新版本可用,请刷新页面');
|
||||||
|
showUpdateNotification();
|
||||||
|
} else {
|
||||||
|
// 首次安装
|
||||||
|
console.log('[SW] 内容已缓存,可离线使用');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定期检查更新
|
||||||
|
setInterval(() => {
|
||||||
|
registration.update();
|
||||||
|
}, 60 * 60 * 1000); // 每小时检查一次
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('[SW] Service Worker 注册失败:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 Service Worker 控制器变化
|
||||||
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||||
|
console.log('[SW] 控制器已更改,页面将刷新');
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready
|
||||||
|
.then((registration) => {
|
||||||
|
registration.unregister();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示更新提示
|
||||||
|
function showUpdateNotification() {
|
||||||
|
// 你可以使用 Vant 的 Dialog 或 Notify 组件
|
||||||
|
if (window.confirm('发现新版本,是否立即更新?')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求通知权限
|
||||||
|
export function requestNotificationPermission() {
|
||||||
|
if ('Notification' in window && 'serviceWorker' in navigator) {
|
||||||
|
Notification.requestPermission().then((permission) => {
|
||||||
|
if (permission === 'granted') {
|
||||||
|
console.log('[SW] 通知权限已授予');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后台同步
|
||||||
|
export function registerBackgroundSync(tag = 'sync-data') {
|
||||||
|
if ('serviceWorker' in navigator && 'SyncManager' in window) {
|
||||||
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
|
return registration.sync.register(tag);
|
||||||
|
}).then(() => {
|
||||||
|
console.log('[SW] 后台同步已注册:', tag);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('[SW] 后台同步注册失败:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ const handleLogin = async () => {
|
|||||||
try {
|
try {
|
||||||
await authStore.login(password.value)
|
await authStore.login(password.value)
|
||||||
showToast({ type: 'success', message: '登录成功' })
|
showToast({ type: 'success', message: '登录成功' })
|
||||||
router.push('/')
|
router.push('/calendar')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast({ type: 'fail', message: error.message || '登录失败' })
|
showToast({ type: 'fail', message: error.message || '登录失败' })
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -15,4 +15,18 @@ export default defineConfig({
|
|||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
// 确保 Service Worker 和 manifest 被正确复制
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: fileURLToPath(new URL('./index.html', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
headers: {
|
||||||
|
// 允许 Service Worker 在开发环境中工作
|
||||||
|
'Service-Worker-Allowed': '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||