Skip to content

Commit 65143fb

Browse files
committed
更新项目配置,添加 noImplicitAny 选项以增强类型检查;删除不再使用的 types.ts 文件以简化代码结构;优化 manifest.json 和 service worker 的缓存逻辑,确保更好的 PWA 支持;在页面组件中引入 framer-motion 以增强用户界面动画效果,提升用户体验。
1 parent 1012e93 commit 65143fb

18 files changed

Lines changed: 1492 additions & 398 deletions

public/manifest.json

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,5 @@
55
"start_url": "/",
66
"display": "standalone",
77
"background_color": "#ffffff",
8-
"theme_color": "#4f46e5",
9-
"icons": [
10-
{
11-
"src": "/icons/icon-192x192.png",
12-
"sizes": "192x192",
13-
"type": "image/png",
14-
"purpose": "any maskable"
15-
},
16-
{
17-
"src": "/icons/icon-512x512.png",
18-
"sizes": "512x512",
19-
"type": "image/png",
20-
"purpose": "any maskable"
21-
}
22-
]
8+
"theme_color": "#4f46e5"
239
}

public/sw.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22
const CACHE_NAME = "folda-scan-cache-v1";
33

44
// 需要缓存的资源列表
5-
const urlsToCache = [
6-
"/",
7-
"/index.html",
8-
"/manifest.json",
9-
"/icons/icon-192x192.png",
10-
"/icons/icon-512x512.png",
11-
];
5+
const urlsToCache = ["/", "/index.html", "/manifest.json"];
126

137
// 安装Service Worker
148
self.addEventListener("install", (event) => {

src/app/page.tsx

Lines changed: 181 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
"use client";
22

3+
import { useEffect, useState } from "react";
4+
import { useAtom } from "jotai";
5+
import { directoryHandleAtom } from "../lib/store";
6+
import { useTranslations } from "@/components/LocaleProvider";
7+
import { useTransitionRouter } from "next-view-transitions";
8+
import { slideInOut } from "../lib/publicCutscene";
9+
import { motion, AnimatePresence } from "framer-motion";
310
import FolderPicker from "../components/FolderPicker";
411
import ScanControls from "../components/ScanControls";
512
import ResultDisplay from "../components/ResultDisplay";
613
import ThemeToggle from "../components/ThemeToggle";
714
import VersionManager from "../components/VersionManager";
815
import SettingsButton from "../components/SettingsModal";
9-
import { useAtom } from "jotai";
10-
import { directoryHandleAtom } from "../lib/store";
11-
import { useTranslations } from "@/components/LocaleProvider";
12-
import { useTransitionRouter } from "next-view-transitions";
13-
import { slideInOut } from "../lib/publicCutscene";
16+
import BrowserCompatCheck from "../components/BrowserCompatCheck";
17+
1418
export default function Home() {
1519
const [directoryHandle] = useAtom(directoryHandleAtom);
1620
const { t } = useTranslations();
1721
const router = useTransitionRouter();
22+
const [mounted, setMounted] = useState(false);
23+
const [showVersionModal, setShowVersionModal] = useState(false);
24+
25+
// 确保组件在客户端挂载后才渲染,避免水合错误
26+
useEffect(() => {
27+
setMounted(true);
28+
}, []);
1829

1930
// 跳转到统计页面
2031
const handleGoToStatistics = () => {
@@ -23,61 +34,175 @@ export default function Home() {
2334
});
2435
};
2536

37+
if (!mounted) return null;
38+
2639
return (
27-
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8 transition-colors duration-300">
28-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
29-
<header className="mb-8 flex justify-between items-center">
30-
<div>
31-
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
32-
{t("app.title")}
33-
</h1>
34-
<p className="mt-2 text-lg text-gray-600 dark:text-gray-300">
35-
{t("app.description")}
36-
</p>
37-
</div>
38-
<div className="flex items-center space-x-3">
39-
{directoryHandle && (
40-
<>
41-
<VersionManager />
42-
<button
43-
onClick={handleGoToStatistics}
44-
className="px-3 py-2 bg-teal-600 text-white rounded-md hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 transition-colors duration-300 flex items-center space-x-1"
45-
title="项目统计"
40+
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 transition-colors duration-300 flex flex-col">
41+
{/* 浏览器兼容性检查组件 */}
42+
<BrowserCompatCheck />
43+
44+
{/* Mac风格顶部导航栏 */}
45+
<header className="bg-white/80 dark:bg-gray-800/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700 transition-colors duration-300 sticky top-0 z-10">
46+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
47+
<div className="flex justify-between items-center">
48+
{/* 左侧标题和Logo */}
49+
<div className="flex items-center">
50+
<div className="flex-shrink-0">
51+
<svg
52+
xmlns="http://www.w3.org/2000/svg"
53+
className="h-8 w-8 text-blue-500"
54+
fill="none"
55+
viewBox="0 0 24 24"
56+
stroke="currentColor"
4657
>
47-
<svg
48-
xmlns="http://www.w3.org/2000/svg"
49-
className="h-5 w-5"
50-
viewBox="0 0 20 20"
51-
fill="currentColor"
58+
<path
59+
strokeLinecap="round"
60+
strokeLinejoin="round"
61+
strokeWidth={2}
62+
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
63+
/>
64+
</svg>
65+
</div>
66+
<h1 className="ml-2 text-xl font-semibold text-gray-900 dark:text-white">
67+
Folda-Scan
68+
</h1>
69+
</div>
70+
71+
{/* 右侧工具区 */}
72+
<div className="flex items-center space-x-4">
73+
<ThemeToggle />
74+
<SettingsButton />
75+
76+
{directoryHandle && (
77+
<>
78+
<button
79+
onClick={handleGoToStatistics}
80+
className="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium flex items-center"
81+
>
82+
<svg
83+
xmlns="http://www.w3.org/2000/svg"
84+
className="h-5 w-5 mr-1"
85+
fill="none"
86+
viewBox="0 0 24 24"
87+
stroke="currentColor"
88+
>
89+
<path
90+
strokeLinecap="round"
91+
strokeLinejoin="round"
92+
strokeWidth={2}
93+
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
94+
/>
95+
</svg>
96+
{t("nav.statistics")}
97+
</button>
98+
99+
<button
100+
onClick={() => setShowVersionModal(true)}
101+
className="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-colors dark:bg-purple-700 dark:hover:bg-purple-800"
52102
>
53-
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
54-
<path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
55-
</svg>
56-
<span>统计</span>
57-
</button>
58-
</>
59-
)}
60-
<SettingsButton />
61-
<ThemeToggle />
103+
{t("versionManager.title")}
104+
</button>
105+
</>
106+
)}
107+
</div>
62108
</div>
63-
</header>
64-
65-
<main className="space-y-6">
66-
<section className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow transition-colors duration-300">
67-
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
68-
{t("folderPicker.selectFolder")}
69-
</h2>
70-
<FolderPicker />
71-
<ScanControls />
72-
</section>
73-
74-
<ResultDisplay />
75-
</main>
76-
77-
<footer className="mt-12 text-center text-gray-500 dark:text-gray-400 text-sm">
78-
<p>{t("about.copyright")}</p>
79-
</footer>
80-
</div>
109+
</div>
110+
</header>
111+
112+
<AnimatePresence mode="wait">
113+
{!directoryHandle ? (
114+
/* 全屏文件夹选择界面 */
115+
<motion.div
116+
key="folder-picker"
117+
initial={{ opacity: 0 }}
118+
animate={{ opacity: 1 }}
119+
exit={{ opacity: 0 }}
120+
className="flex-1 flex items-center justify-center p-4"
121+
>
122+
<div className="max-w-md w-full mx-auto">
123+
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl overflow-hidden transition-colors duration-300">
124+
<div className="p-8">
125+
<div className="text-center mb-8">
126+
<div className="inline-block p-4 bg-blue-50 dark:bg-blue-900/30 rounded-full mb-4">
127+
<svg
128+
xmlns="http://www.w3.org/2000/svg"
129+
className="h-12 w-12 text-blue-500"
130+
fill="none"
131+
viewBox="0 0 24 24"
132+
stroke="currentColor"
133+
>
134+
<path
135+
strokeLinecap="round"
136+
strokeLinejoin="round"
137+
strokeWidth={2}
138+
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
139+
/>
140+
</svg>
141+
</div>
142+
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white">
143+
{t("folderPicker.welcomeTitle")}
144+
</h2>
145+
<p className="mt-2 text-gray-600 dark:text-gray-300">
146+
{t("folderPicker.welcomeDescription")}
147+
</p>
148+
</div>
149+
150+
<FolderPicker />
151+
152+
<div className="mt-6 text-center text-sm text-gray-500 dark:text-gray-400">
153+
<p>{t("folderPicker.privacyNote")}</p>
154+
</div>
155+
</div>
156+
</div>
157+
</div>
158+
</motion.div>
159+
) : (
160+
/* 主应用界面 */
161+
<motion.main
162+
key="main-content"
163+
initial={{ opacity: 0, y: 20 }}
164+
animate={{ opacity: 1, y: 0 }}
165+
exit={{ opacity: 0 }}
166+
className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6"
167+
>
168+
<div className="space-y-6">
169+
<section className="bg-white dark:bg-gray-800 rounded-xl shadow-md overflow-hidden transition-colors duration-300">
170+
<div className="p-6">
171+
<div className="flex items-center justify-between mb-4">
172+
<FolderPicker />
173+
</div>
174+
<ScanControls />
175+
</div>
176+
</section>
177+
178+
<ResultDisplay />
179+
</div>
180+
</motion.main>
181+
)}
182+
</AnimatePresence>
183+
184+
<footer className="mt-auto py-4 text-center text-gray-500 dark:text-gray-400 text-sm">
185+
<p>{t("about.copyright")}</p>
186+
</footer>
187+
188+
{directoryHandle && showVersionModal && (
189+
<div
190+
className="fixed inset-0 z-[9999]"
191+
style={{
192+
position: "fixed",
193+
top: 0,
194+
left: 0,
195+
right: 0,
196+
bottom: 0,
197+
display: "flex",
198+
alignItems: "center",
199+
justifyContent: "center",
200+
backgroundColor: "rgba(0, 0, 0, 0.5)",
201+
}}
202+
>
203+
<VersionManager onClose={() => setShowVersionModal(false)} />
204+
</div>
205+
)}
81206
</div>
82207
);
83208
}

src/app/providers.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import { Provider as JotaiProvider } from "jotai";
44
import { ReactNode, useEffect } from "react";
5-
import { LocaleProvider } from "@/components/LocaleProvider";
6-
import { useViewport } from "@/lib/useViewport";
7-
import { setupGlobalViewport } from "@/lib/viewportScript";
8-
import { registerServiceWorker, captureInstallPrompt } from "@/lib/pwaUtils";
5+
import { LocaleProvider } from "../components/LocaleProvider";
6+
import { useViewport } from "../lib/useViewport";
7+
import { setupGlobalViewport } from "../lib/viewportScript";
8+
import { registerServiceWorker, captureInstallPrompt } from "../lib/pwaUtils";
99

1010
export function Providers({ children }: { children: ReactNode }) {
1111
// 直接在Providers组件中调用useViewport hook
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { isServiceWorkerSupported } from "@/lib/pwaUtils";
5+
import { isFileSystemObserverSupported } from "@/lib/fileObserver";
6+
7+
// 检查浏览器是否支持File System Access API
8+
function isFileSystemAccessSupported(): boolean {
9+
return "showDirectoryPicker" in window;
10+
}
11+
12+
export default function BrowserCompatCheck() {
13+
const [showWarning, setShowWarning] = useState(false);
14+
const [unsupportedFeatures, setUnsupportedFeatures] = useState<string[]>([]);
15+
16+
useEffect(() => {
17+
// 只在客户端执行
18+
if (typeof window === "undefined") return;
19+
20+
const unsupported: string[] = [];
21+
22+
// 检查File System Access API支持
23+
if (!isFileSystemAccessSupported()) {
24+
unsupported.push("文件系统访问");
25+
}
26+
27+
// 检查Service Worker支持
28+
if (!isServiceWorkerSupported()) {
29+
unsupported.push("Service Worker");
30+
}
31+
32+
// 若有不支持的功能,显示警告
33+
if (unsupported.length > 0) {
34+
setUnsupportedFeatures(unsupported);
35+
setShowWarning(true);
36+
}
37+
}, []);
38+
39+
if (!showWarning) return null;
40+
41+
return (
42+
<div className="fixed inset-x-0 top-4 flex justify-center z-50">
43+
<div className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 max-w-xl shadow-lg rounded-md">
44+
<div className="flex items-start">
45+
<div className="flex-shrink-0">
46+
<svg
47+
className="h-5 w-5 text-yellow-500"
48+
xmlns="http://www.w3.org/2000/svg"
49+
viewBox="0 0 20 20"
50+
fill="currentColor"
51+
>
52+
<path
53+
fillRule="evenodd"
54+
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
55+
clipRule="evenodd"
56+
/>
57+
</svg>
58+
</div>
59+
<div className="ml-3">
60+
<h3 className="text-sm font-medium">浏览器兼容性警告</h3>
61+
<div className="mt-1 text-sm">
62+
<p>
63+
您的浏览器不支持以下必要功能:
64+
<span className="font-bold">
65+
{" "}
66+
{unsupportedFeatures.join(", ")}
67+
</span>
68+
</p>
69+
<p className="mt-1">
70+
建议使用 Chrome、Edge 或 Safari 最新版本,并确保安装了所有更新。
71+
</p>
72+
<button
73+
onClick={() => setShowWarning(false)}
74+
className="mt-2 bg-yellow-200 hover:bg-yellow-300 text-yellow-800 py-1 px-2 rounded text-xs"
75+
>
76+
暂时忽略
77+
</button>
78+
</div>
79+
</div>
80+
</div>
81+
</div>
82+
</div>
83+
);
84+
}

0 commit comments

Comments
 (0)