From efcf61e32dcadbf89570961e41349af293dcef71 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 12 Jul 2026 16:22:16 +0800 Subject: [PATCH] feat(web): polish zone list and settings layout Show Zone list as a table, align detail back navigation with proxy route detail, and move edit/delete actions into the settings tab. --- .../(main)/websites/[zoneId]/page-client.tsx | 108 +++++----- frontend/app/(main)/websites/page.tsx | 192 +++++++++++------- frontend/tests/zone/websites-page.test.tsx | 11 +- frontend/tests/zone/zone-page.test.tsx | 2 +- 4 files changed, 193 insertions(+), 120 deletions(-) diff --git a/frontend/app/(main)/websites/[zoneId]/page-client.tsx b/frontend/app/(main)/websites/[zoneId]/page-client.tsx index bdf57b4d..9dcbbc66 100644 --- a/frontend/app/(main)/websites/[zoneId]/page-client.tsx +++ b/frontend/app/(main)/websites/[zoneId]/page-client.tsx @@ -1,6 +1,5 @@ 'use client'; -import Link from 'next/link'; import {usePathname, useRouter, useSearchParams} from 'next/navigation'; import {useCallback, useMemo, useState} from 'react'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; @@ -118,11 +117,14 @@ export function ZonePageClient({zoneId}: {zoneId: number}) { if (overviewQuery.isError || !overviewQuery.data) { return (
- -
+
+ +
@@ -149,32 +161,6 @@ export function ZonePageClient({zoneId}: {zoneId: number}) {

Zone 网站管理

-
- - - -
@@ -211,20 +197,48 @@ export function ZonePageClient({zoneId}: {zoneId: number}) { -
-

Zone 设置

-

- 根域和备注可在此 Zone 的编辑操作中维护。 -

- +
+
+

基本信息

+

+ 维护 Zone 的可注册根域。根域变更后,请确认其下域名仍归属该根域。 +

+
+
+

当前根域

+

{overview.zone.domain}

+
+
+

域名数量

+

{overview.domains.length}

+
+
+ +
+ +
+

危险操作

+

+ 删除 Zone 前须清空其下全部域名;删除后不可恢复。 +

+ +
diff --git a/frontend/app/(main)/websites/page.tsx b/frontend/app/(main)/websites/page.tsx index 8c1e0a01..edcbe2d9 100644 --- a/frontend/app/(main)/websites/page.tsx +++ b/frontend/app/(main)/websites/page.tsx @@ -3,20 +3,24 @@ import Link from 'next/link'; import {useMemo, useState} from 'react'; import {useQuery} from '@tanstack/react-query'; -import {Globe, Plus, Search} from 'lucide-react'; +import {useRouter} from 'next/navigation'; +import {Eye, Globe, Plus, Search} from 'lucide-react'; import {Button} from '@/components/ui/button'; -import {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@/components/ui/card'; import {EmptyStateWithBorder} from '@/components/layout/empty'; import {ErrorInline} from '@/components/layout/error'; import {Input} from '@/components/ui/input'; import {LoadingStateWithBorder} from '@/components/layout/loading'; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from '@/components/ui/table'; +import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip'; import {ZoneService, zoneQueryKey} from '@/lib/services/openflare'; +import {formatDateTime} from '@/lib/utils'; import {ZoneEditorDialog} from './[zoneId]/components/zone-editor-dialog'; import {getErrorMessage} from './components/website-utils'; export default function WebsitesPage() { + const router = useRouter(); const [search, setSearch] = useState(''); const [editorOpen, setEditorOpen] = useState(false); const zonesQuery = useQuery({ @@ -34,83 +38,133 @@ export default function WebsitesPage() { }, [search, zonesQuery.data]); return ( -
-
+
+

网站

-
- - -
- Zone 列表 - - 以可注册根域组织网站,并在详情中维护明确 FQDN、证书与路由关联。 - -
-
- - setSearch(event.target.value)} - className="h-8 pl-8 text-xs" - /> -
-
- - {zonesQuery.isLoading ? ( - - ) : zonesQuery.isError ? ( -
- void zonesQuery.refetch()} - /> -
- ) : zones.length === 0 ? ( - - ) : ( -
- {zones.map((zone) => ( -
-
-
-

{zone.domain}

-

- {zone.domain_count ?? 0} 个域名 -

-
-
+ + {zonesQuery.isLoading ? ( + + ) : zonesQuery.isError ? ( +
+ void zonesQuery.refetch()} + /> +
+ ) : zones.length === 0 ? ( + + ) : ( +
+ + + + + + ID + + + 根域 + + + 域名数 + + + 创建时间 + + + 更新时间 + + + 操作 + + + + + {zones.map((zone) => ( + router.push(`/websites/${zone.id}`)} + > + + {zone.id} + + + {zone.domain} + + + {zone.domain_count ?? 0} + + + {formatDateTime(zone.created_at)} + + + {formatDateTime(zone.updated_at)} + + event.stopPropagation()} > - 管理 - - - - ))} - - )} - - +
+ + + + + + 管理 Zone + + +
+
+
+ ))} +
+
+
+
+ )}
diff --git a/frontend/tests/zone/websites-page.test.tsx b/frontend/tests/zone/websites-page.test.tsx index 7de45078..84820773 100644 --- a/frontend/tests/zone/websites-page.test.tsx +++ b/frontend/tests/zone/websites-page.test.tsx @@ -11,6 +11,10 @@ vi.mock('next/link', () => ({ ), })); +vi.mock('next/navigation', () => ({ + useRouter: () => ({push: vi.fn()}), +})); + vi.mock('@/lib/services/openflare', async (importOriginal) => { const actual = await importOriginal(); return {...actual, ZoneService: {list: vi.fn()}}; @@ -46,9 +50,10 @@ describe('WebsitesPage', () => { renderPage(); - expect(await screen.findByRole('heading', {name: 'arctel.de'})).toBeVisible(); - expect(screen.getByText(/3 个域名/)).toBeVisible(); - expect(screen.getByText(/0 个域名/)).toBeVisible(); + expect(await screen.findByText('arctel.de')).toBeVisible(); + expect(screen.getByText('3')).toBeVisible(); + expect(screen.getByText('0')).toBeVisible(); + expect(screen.getByRole('columnheader', {name: '根域'})).toBeVisible(); fireEvent.change(screen.getByPlaceholderText('搜索 Zone 根域'), { target: {value: 'arctel'}, diff --git a/frontend/tests/zone/zone-page.test.tsx b/frontend/tests/zone/zone-page.test.tsx index c193ae36..f86a90a1 100644 --- a/frontend/tests/zone/zone-page.test.tsx +++ b/frontend/tests/zone/zone-page.test.tsx @@ -14,7 +14,7 @@ vi.mock('next/link', () => ({ })); vi.mock('next/navigation', () => ({ - useRouter: () => ({replace: replaceMock}), + useRouter: () => ({replace: replaceMock, back: vi.fn()}), usePathname: () => '/websites/42', useSearchParams: () => new URLSearchParams(), }));