feat: Initialize PolyWeather frontend project with Next.js, including core dependencies, root layout, and a dashboard detail panel.

This commit is contained in:
2569718930@qq.com
2026-03-11 11:14:03 +08:00
parent d8cc193618
commit 3cbef28b13
4 changed files with 90 additions and 4 deletions
+2
View File
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
import "./globals.css";
export const metadata: Metadata = {
@@ -28,6 +29,7 @@ export default function RootLayout({
<body className="min-h-screen font-sans antialiased">
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
+43 -4
View File
@@ -2,6 +2,7 @@
import { ChartConfiguration } from "chart.js/auto";
import clsx from "clsx";
import { useEffect, useRef } from "react";
import { ForecastTable } from "@/components/dashboard/PanelSections";
import { useChart } from "@/hooks/useChart";
import { useDashboardStore } from "@/hooks/useDashboardStore";
@@ -127,6 +128,7 @@ export function DetailPanel() {
const store = useDashboardStore();
const { locale, t } = useI18n();
const detail = store.selectedDetail;
const panelRef = useRef<HTMLElement | null>(null);
const isOverlayOpen =
Boolean(store.futureModalDate) ||
store.historyState.isOpen ||
@@ -139,18 +141,49 @@ export function DetailPanel() {
!isOverlayOpen;
const profileStats = detail ? getCityProfileStats(detail, locale) : [];
const scenery = getCityScenery(detail?.name);
const blurActiveElement = () => {
if (typeof document === "undefined") return;
const active = document.activeElement;
if (active instanceof HTMLElement) {
active.blur();
}
};
useEffect(() => {
const panel = panelRef.current;
if (!panel) return;
if (!isVisible) {
panel.setAttribute("inert", "");
if (
typeof document !== "undefined" &&
panel.contains(document.activeElement)
) {
const active = document.activeElement;
if (active instanceof HTMLElement) {
active.blur();
}
}
return;
}
panel.removeAttribute("inert");
}, [isVisible]);
return (
<aside
ref={panelRef}
className={clsx("detail-panel", isVisible && "visible")}
aria-hidden={!isVisible}
>
<div className="panel-header">
<button
type="button"
className="panel-close"
aria-label={t("detail.closeAria")}
onClick={store.closePanel}
onClick={() => {
blurActiveElement();
store.closePanel();
}}
>
×
</button>
@@ -169,7 +202,10 @@ export function DetailPanel() {
type="button"
className="history-btn"
title={t("detail.todayAnalysis")}
onClick={() => void store.openTodayModal()}
onClick={() => {
blurActiveElement();
void store.openTodayModal();
}}
disabled={!detail}
>
{t("detail.todayAnalysis")}
@@ -178,7 +214,10 @@ export function DetailPanel() {
type="button"
className="history-btn"
title={t("detail.history")}
onClick={() => void store.openHistory()}
onClick={() => {
blurActiveElement();
void store.openHistory();
}}
disabled={!detail}
>
{t("detail.history")}
+44
View File
@@ -10,6 +10,7 @@
"dependencies": {
"@radix-ui/react-slot": "^1.1.2",
"@vercel/analytics": "^1.6.1",
"@vercel/speed-insights": "^2.0.0",
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -822,6 +823,43 @@
}
}
},
"node_modules/@vercel/speed-insights": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@vercel/speed-insights/-/speed-insights-2.0.0.tgz",
"integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==",
"peerDependencies": {
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
"nuxt": ">= 3",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
"vue-router": "^4"
},
"peerDependenciesMeta": {
"@sveltejs/kit": {
"optional": true
},
"next": {
"optional": true
},
"nuxt": {
"optional": true
},
"react": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
},
"vue-router": {
"optional": true
}
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
@@ -2509,6 +2547,12 @@
"integrity": "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==",
"requires": {}
},
"@vercel/speed-insights": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@vercel/speed-insights/-/speed-insights-2.0.0.tgz",
"integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==",
"requires": {}
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"@radix-ui/react-slot": "^1.1.2",
"@vercel/analytics": "^1.6.1",
"@vercel/speed-insights": "^2.0.0",
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",