fix(frontend): support static export dynamic routes compliance via client useParams

This commit is contained in:
ryan
2026-07-12 18:07:43 +08:00
parent fc569d1758
commit 50717d1baf
3 changed files with 29 additions and 21 deletions
@@ -1,7 +1,7 @@
'use client'; 'use client';
import {usePathname, useRouter, useSearchParams} from 'next/navigation'; import {useParams, usePathname, useRouter, useSearchParams} from 'next/navigation';
import {useCallback, useMemo, useState} from 'react'; import {useCallback, useEffect, useMemo, useState} from 'react';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import {ArrowLeft, Globe, Pencil, Trash2} from 'lucide-react'; import {ArrowLeft, Globe, Pencil, Trash2} from 'lucide-react';
import {toast} from 'sonner'; import {toast} from 'sonner';
@@ -40,7 +40,14 @@ function getZonePageTab(value: string | null | undefined): ZonePageTab {
return zoneTabs.includes(value as ZonePageTab) ? (value as ZonePageTab) : 'overview'; return zoneTabs.includes(value as ZonePageTab) ? (value as ZonePageTab) : 'overview';
} }
export function ZonePageClient({zoneId}: {zoneId: number}) { export function ZonePageClient() {
const params = useParams();
const zoneId = Number(params?.zoneId);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
@@ -96,7 +103,13 @@ export function ZonePageClient({zoneId}: {zoneId: number}) {
onError: (error) => toast.error(getErrorMessage(error)), onError: (error) => toast.error(getErrorMessage(error)),
}); });
if (!mounted) {
return (
<div className="py-6 px-1">
<LoadingStateWithBorder icon={Globe} description="加载 Zone 详情中..." />
</div>
);
}
if (!Number.isInteger(zoneId) || zoneId <= 0) { if (!Number.isInteger(zoneId) || zoneId <= 0) {
return ( return (
+6 -15
View File
@@ -1,29 +1,20 @@
import {Suspense} from 'react' import {Suspense} from 'react'
import {Globe} from 'lucide-react' import {Globe} from 'lucide-react'
import dynamic from 'next/dynamic'
import {LoadingStateWithBorder} from '@/components/layout/loading' import {LoadingStateWithBorder} from '@/components/layout/loading'
import {ZonePageClient} from './page-client'
const ZonePageClient = dynamic( export async function generateStaticParams() {
() => import('./page-client').then((mod) => mod.ZonePageClient), return [{zoneId: '1'}];
{ }
ssr: false,
loading: () => (
<div className="py-6 px-1">
<LoadingStateWithBorder icon={Globe} description="加载 Zone 详情中..." />
</div>
),
}
)
export default async function ZonePage({params}: PageProps<'/websites/[zoneId]'>) { export default async function ZonePage() {
const {zoneId} = await params
return ( return (
<Suspense fallback={ <Suspense fallback={
<div className="py-6 px-1"> <div className="py-6 px-1">
<LoadingStateWithBorder icon={Globe} description="加载 Zone 详情中..." /> <LoadingStateWithBorder icon={Globe} description="加载 Zone 详情中..." />
</div> </div>
}> }>
<ZonePageClient zoneId={Number(zoneId)} /> <ZonePageClient />
</Suspense> </Suspense>
) )
} }
+6 -2
View File
@@ -13,6 +13,8 @@ class ResizeObserverMock {
vi.stubGlobal('ResizeObserver', ResizeObserverMock); vi.stubGlobal('ResizeObserver', ResizeObserverMock);
let mockZoneId = '42';
const replaceMock = vi.fn(); const replaceMock = vi.fn();
vi.mock('next/link', () => ({ vi.mock('next/link', () => ({
@@ -23,8 +25,9 @@ vi.mock('next/link', () => ({
vi.mock('next/navigation', () => ({ vi.mock('next/navigation', () => ({
useRouter: () => ({replace: replaceMock, back: vi.fn()}), useRouter: () => ({replace: replaceMock, back: vi.fn()}),
usePathname: () => '/websites/42', usePathname: () => `/websites/${mockZoneId}`,
useSearchParams: () => new URLSearchParams(), useSearchParams: () => new URLSearchParams(),
useParams: () => ({zoneId: mockZoneId}),
})); }));
vi.mock('@/lib/services/openflare', async (importOriginal) => { vi.mock('@/lib/services/openflare', async (importOriginal) => {
@@ -47,6 +50,7 @@ vi.mock('@/lib/services/openflare', async (importOriginal) => {
}); });
function renderPage(zoneId: number) { function renderPage(zoneId: number) {
mockZoneId = String(zoneId);
const client = new QueryClient({ const client = new QueryClient({
defaultOptions: { defaultOptions: {
queries: {retry: false, gcTime: 0}, queries: {retry: false, gcTime: 0},
@@ -54,7 +58,7 @@ function renderPage(zoneId: number) {
}); });
return render( return render(
<QueryClientProvider client={client}> <QueryClientProvider client={client}>
<ZonePageClient zoneId={zoneId} /> <ZonePageClient />
</QueryClientProvider>, </QueryClientProvider>,
); );
} }