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 (
|
return (
|
||||||
<html lang="en" className={GeistSans.className}>
|
<html lang="en" className={GeistSans.className}>
|
||||||
<Script src="//fw-cdn.com/11368617/4047428.js" chat="true"></Script>
|
<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">
|
<body className="bg-background text-foreground">
|
||||||
<main className="min-h-screen flex flex-col items-center">
|
<main className="min-h-screen flex flex-col items-center">
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -69,6 +69,6 @@ export const config = {
|
||||||
* - favicon.ico (favicon file)
|
* - favicon.ico (favicon file)
|
||||||
* Feel free to modify this pattern to include more paths.
|
* 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": "写论文最高效的方式",
|
"description": "写论文最高效的方式",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/app/to/android-chrome-192x192.png",
|
"src": "/android-chrome-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/app/android-chrome-512x512.png",
|
"src": "/android-chrome-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/app/apple-touch-icon.png",
|
"src": "/apple-touch-icon.png",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/app/favicon-16x16.png",
|
"src": "/favicon-16x16.png",
|
||||||
"sizes": "16x16",
|
"sizes": "16x16",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/app/favicon-32x32.png",
|
"src": "/favicon-32x32.png",
|
||||||
"sizes": "32x32",
|
"sizes": "32x32",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/app/favicon.ico",
|
"src": "/favicon.ico",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image/x-icon",
|
"type": "image/x-icon",
|
||||||
"purpose": "any"
|
"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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|