feat: pwa可离线访问 service worker
162
app/[lng]/globals.css
Normal 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; /* 平滑过渡效果 */
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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).*)",
|
||||
],
|
||||
};
|
||||
|
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|