feat: pwa可离线访问 service worker

This commit is contained in:
liuweiqing 2024-02-21 15:04:02 +08:00
parent b92e425517
commit ff09eee993
12 changed files with 221 additions and 7 deletions

162
app/[lng]/globals.css Normal file
View File

@ -0,0 +1,162 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 200 20% 98%;
--btn-background: 200 10% 91%;
--btn-background-hover: 200 10% 89%;
--foreground: 200 50% 3%;
}
/* @media (prefers-color-scheme: dark) {
:root {
--background: 200 50% 3%;
--btn-background: 200 10% 9%;
--btn-background-hover: 200 10% 12%;
--foreground: 200 20% 96%;
}
} */
}
@layer base {
* {
@apply border-foreground/20;
}
}
.animate-in {
animation: animateIn 0.3s ease 0.15s both;
}
@keyframes animateIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-slide-in-right {
animation: slideInFromRight 0.5s ease-out forwards;
}
@keyframes slideInFromRight {
0% {
transform: translateX(100%); /* 从右侧外开始 */
}
100% {
transform: translateX(0); /* 完全进入视图 */
}
}
.component-container {
@apply fixed top-1/4 right-0;
transform: translateX(100%); /* 动画开始前,确保组件位于视图右侧之外 */
}
/* 想给上标添加一个鼠标放上去变手型的效果 */
.ql-editor .ql-super {
cursor: pointer;
}
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.vip-icon {
animation: flash 1s linear infinite;
}
/* 动画的基本样式 */
.slide-enter {
opacity: 0;
transform: translateY(100%); /* 从底部滑入 */
}
.slide-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 300ms ease-out, transform 300ms ease-out;
}
.slide-exit {
opacity: 1;
}
.slide-exit-active {
opacity: 0;
transform: translateY(100%); /* 向底部滑出 */
transition: opacity 300ms ease-in, transform 300ms ease-in;
}
.paper-management-container {
position: fixed; /* 或者使用 `absolute` 根据需要 */
top: 50%; /* 调整到视口的垂直中心 */
left: 50%; /* 调整到视口的水平中心 */
background-color: rgba(255, 255, 255, 0.5);
transform: translate(
-50%,
-50%
); /* 从中心点向上和向左偏移自身的50%,确保组件居中 */
z-index: 1000; /* 确保悬浮层在其他内容之上 */
/* 可以添加其他样式来美化组件,如背景色、阴影等 */
}
#editor {
/* width: calc(100vw - 20px); */
min-height: 250px;
max-height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
}
/* 适配手机样式 */
@media (max-width: 768px) {
#editor {
width: 100%; /* 适应屏幕宽度 */
min-height: 200px; /* 调整为更适合移动设备的尺寸 */
padding: 5px; /* 减少内边距 */
}
#Qtoolbar {
flex-direction: column; /* 在较小屏幕上垂直堆叠工具栏元素 */
align-items: stretch; /* 拉伸按钮以填充容器 */
}
#Qtoolbar > button,
#Qtoolbar > select {
margin-bottom: 10px; /* 增加元素之间的间距 */
}
}
/* 输入框基本样式 */
.textarea-focus-expand {
height: 50px; /* 默认高度 */
flex-grow: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px 15px;
margin-right: 8px;
color: #333;
transition: height 0.3s ease; /* 平滑过渡效果 */
}
/* 输入框获得焦点时的样式 */
.textarea-focus-expand:focus {
height: 100px; /* 聚焦时的高度 */
border-color: #007bff; /* 改变边框颜色以提供视觉反馈 */
}
.icon-hover:hover {
transform: scale(1.2); /* 放大到原大小的1.2倍 */
transition: transform 0.3s ease; /* 平滑过渡效果 */
}

View File

@ -54,6 +54,19 @@ export default function RootLayout({
return (
<html lang="en" className={GeistSans.className}>
<Script src="//fw-cdn.com/11368617/4047428.js" chat="true"></Script>
<Script>{`
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// 注册成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// 注册失败 :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
`}</Script>
<body className="bg-background text-foreground">
<main className="min-h-screen flex flex-col items-center">
{children}

View File

@ -69,6 +69,6 @@ export const config = {
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
"/((?!_next/static|_next/image|favicon.ico|twitter-image.png|opengraph-image.png).*)",
"/((?!_next/static|_next/image|favicon.ico|twitter-image.png|opengraph-image.png|manifest.json|site.webmanifest|favicon-32x32.png|favicon-16x16.png|apple-touch-icon.png|android-chrome-512x512.png|android-chrome-192x192.png|service-worker.js|serviceregister.js).*)",
],
};

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -8,37 +8,37 @@
"description": "写论文最高效的方式",
"icons": [
{
"src": "/app/to/android-chrome-192x192.png",
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/app/android-chrome-512x512.png",
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/app/apple-touch-icon.png",
"src": "/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "any"
},
{
"src": "/app/favicon-16x16.png",
"src": "/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png",
"purpose": "any"
},
{
"src": "/app/favicon-32x32.png",
"src": "/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png",
"purpose": "any"
},
{
"src": "/app/favicon.ico",
"src": "/favicon.ico",
"sizes": "48x48",
"type": "image/x-icon",
"purpose": "any"

22
public/service-worker.js Normal file
View File

@ -0,0 +1,22 @@
const cacheName = "v1";
const cacheClone = async (e) => {
const res = await fetch(e.request);
const resClone = res.clone();
const cache = await caches.open(cacheName);
await cache.put(e.request, resClone);
return res;
};
const fetchEvent = () => {
self.addEventListener("fetch", (e) => {
e.respondWith(
cacheClone(e)
.catch(() => caches.match(e.request))
.then((res) => res)
);
});
};
fetchEvent();

17
public/serviceregister.js Normal file
View File

@ -0,0 +1,17 @@
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js").then(
(registration) => {
// 注册成功
console.log(
"ServiceWorker registration successful with scope: ",
registration.scope
);
},
(err) => {
// 注册失败
console.log("ServiceWorker registration failed: ", err);
}
);
});
}