优化公告页面布局
This commit is contained in:
1 parent
ab24de67f5
commit
1a0d406284
7 files changed
+104
-91
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {Activity, LayoutGrid, List, LogIn, Server, Settings} from 'lucide-react';
|
||||
import {getCurrentUser} from '../api/auth';
|
||||
import {Link} from "react-router-dom";
|
||||
import {Link, useLocation} from "react-router-dom";
|
||||
|
||||
interface PublicHeaderProps {
|
||||
viewMode?: 'grid' | 'list';
|
||||
@@ -15,7 +15,7 @@ const PublicHeader = ({
|
||||
showViewToggle = false
|
||||
}: PublicHeaderProps) => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const currentPath = window.location.pathname;
|
||||
let location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
// 检查本地是否有 token
|
||||
@@ -41,6 +41,7 @@ const PublicHeader = ({
|
||||
}, []);
|
||||
|
||||
// 判断导航是否激活
|
||||
const currentPath = location.pathname;
|
||||
const isDeviceActive = currentPath === '/';
|
||||
const isMonitorActive = currentPath === '/monitors';
|
||||
|
||||
|
||||
@@ -363,8 +363,8 @@ const MonitorDetail = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||
<div className="mx-auto flex min-h-screen max-w-7xl flex-col px-4 pb-10 pt-6 sm:px-6 lg:px-8">
|
||||
<div className="bg-slate-50">
|
||||
<div className="mx-auto flex max-w-7xl flex-col px-4 pb-10 pt-6 sm:px-6 lg:px-8">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden rounded-3xl bg-gradient-to-r from-slate-900 via-slate-800 to-blue-900 p-6 text-white shadow-xl">
|
||||
<div className="absolute inset-0 opacity-30 [background-image:radial-gradient(circle_at_top,rgba(255,255,255,0.35),transparent_55%)]"/>
|
||||
@@ -654,10 +654,6 @@ const MonitorDetail = () => {
|
||||
</div>
|
||||
</Card>
|
||||
</main>
|
||||
|
||||
<footer className="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
|
||||
© {new Date().getFullYear()} Pika Monitor · 保持洞察,稳定运行。
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {useState} from 'react';
|
||||
import {useEffect} from 'react';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {AlertCircle, CheckCircle2, Clock, Loader2, Shield} from 'lucide-react';
|
||||
import {getPublicMonitors} from '../../api/monitor';
|
||||
import type {PublicMonitor} from '../../types';
|
||||
import PublicHeader from '../../components/PublicHeader';
|
||||
import PublicFooter from '../../components/PublicFooter';
|
||||
import {usePublicLayout} from '../PublicLayout';
|
||||
|
||||
type ViewMode = 'grid' | 'list';
|
||||
|
||||
@@ -84,7 +83,13 @@ const EmptyState = () => (
|
||||
|
||||
const MonitorList = () => {
|
||||
const navigate = useNavigate();
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||
const {viewMode, setShowViewToggle} = usePublicLayout();
|
||||
|
||||
// 挂载时启用视图切换,卸载时禁用
|
||||
useEffect(() => {
|
||||
setShowViewToggle(true);
|
||||
return () => setShowViewToggle(false);
|
||||
}, [setShowViewToggle]);
|
||||
|
||||
const {data: monitors = [], isLoading, dataUpdatedAt} = useQuery<PublicMonitor[]>({
|
||||
queryKey: ['publicMonitors'],
|
||||
@@ -330,43 +335,21 @@ const MonitorList = () => {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-slate-900 flex flex-col">
|
||||
<PublicHeader
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
showViewToggle={true}
|
||||
/>
|
||||
<main className="flex-1 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
|
||||
<LoadingSpinner/>
|
||||
</div>
|
||||
</main>
|
||||
<PublicFooter/>
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
|
||||
<LoadingSpinner/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-slate-900 flex flex-col">
|
||||
<PublicHeader
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
showViewToggle={true}
|
||||
/>
|
||||
|
||||
<main className="flex-1 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
||||
{monitorSummaries.length === 0 ? (
|
||||
<EmptyState/>
|
||||
) : viewMode === 'grid' ? (
|
||||
renderGridView()
|
||||
) : (
|
||||
renderListView()
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<PublicFooter/>
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
||||
{monitorSummaries.length === 0 ? (
|
||||
<EmptyState/>
|
||||
) : viewMode === 'grid' ? (
|
||||
renderGridView()
|
||||
) : (
|
||||
renderListView()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -795,8 +795,8 @@ const ServerDetail = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||
<div className="mx-auto flex min-h-screen max-w-7xl flex-col px-4 pb-10 pt-6 sm:px-6 lg:px-8">
|
||||
<div className="bg-slate-50">
|
||||
<div className="mx-auto flex max-w-7xl flex-col px-4 pb-10 pt-6 sm:px-6 lg:px-8">
|
||||
<section
|
||||
className="relative overflow-hidden rounded-3xl bg-gradient-to-r from-slate-900 via-slate-800 to-blue-900 p-6 text-white shadow-xl">
|
||||
<div
|
||||
@@ -1296,10 +1296,6 @@ const ServerDetail = () => {
|
||||
</Card>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<footer className="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
|
||||
© {new Date().getFullYear()} Pika Monitor · 保持洞察,稳定运行。
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {type ReactNode, useState} from 'react';
|
||||
import {type ReactNode, useEffect} from 'react';
|
||||
import {Link, useNavigate} from 'react-router-dom';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {Cpu, EthernetPortIcon, HardDrive, Loader2, MemoryStick, Network} from 'lucide-react';
|
||||
import {listAgents} from '../../api/agent';
|
||||
import type {Agent, LatestMetrics} from '../../types';
|
||||
import PublicHeader from '../../components/PublicHeader';
|
||||
import PublicFooter from '../../components/PublicFooter';
|
||||
import {usePublicLayout} from '../PublicLayout';
|
||||
|
||||
interface AgentWithMetrics extends Agent {
|
||||
metrics?: LatestMetrics;
|
||||
@@ -116,7 +115,13 @@ const getProgressColor = (percent: number) => {
|
||||
|
||||
const ServerList = () => {
|
||||
const navigate = useNavigate();
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||
const {viewMode, setShowViewToggle} = usePublicLayout();
|
||||
|
||||
// 挂载时启用视图切换,卸载时禁用
|
||||
useEffect(() => {
|
||||
setShowViewToggle(true);
|
||||
return () => setShowViewToggle(false);
|
||||
}, [setShowViewToggle]);
|
||||
|
||||
const {data: agents = [], isLoading, dataUpdatedAt} = useQuery<AgentWithMetrics[]>({
|
||||
queryKey: ['agents', 'online'],
|
||||
@@ -437,29 +442,17 @@ const ServerList = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-slate-900 flex flex-col">
|
||||
<PublicHeader
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
showViewToggle={true}
|
||||
/>
|
||||
|
||||
<main className="flex-1 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
||||
{filteredAgents.length === 0 ? (
|
||||
<EmptyState
|
||||
title='暂无在线服务器'
|
||||
description='当前没有任何探针在线,请稍后再试。'
|
||||
/>
|
||||
) : viewMode === 'grid' ? (
|
||||
renderGridView()
|
||||
) : (
|
||||
renderListView()
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<PublicFooter/>
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
||||
{filteredAgents.length === 0 ? (
|
||||
<EmptyState
|
||||
title='暂无在线服务器'
|
||||
description='当前没有任何探针在线,请稍后再试。'
|
||||
/>
|
||||
) : viewMode === 'grid' ? (
|
||||
renderGridView()
|
||||
) : (
|
||||
renderListView()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {Outlet, useOutletContext} from 'react-router-dom';
|
||||
import {useState} from 'react';
|
||||
import PublicHeader from '../components/PublicHeader';
|
||||
import PublicFooter from '../components/PublicFooter';
|
||||
|
||||
type ViewMode = 'grid' | 'list';
|
||||
|
||||
interface PublicLayoutContextType {
|
||||
viewMode: ViewMode;
|
||||
setViewMode: (mode: ViewMode) => void;
|
||||
showViewToggle: boolean;
|
||||
setShowViewToggle: (show: boolean) => void;
|
||||
}
|
||||
|
||||
const PublicLayout = () => {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||
const [showViewToggle, setShowViewToggle] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-slate-900 flex flex-col">
|
||||
<PublicHeader
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
showViewToggle={showViewToggle}
|
||||
/>
|
||||
<main className="flex-1">
|
||||
<Outlet context={{viewMode, setViewMode, showViewToggle, setShowViewToggle}}/>
|
||||
</main>
|
||||
<PublicFooter/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function usePublicLayout() {
|
||||
return useOutletContext<PublicLayoutContextType>();
|
||||
}
|
||||
|
||||
export default PublicLayout;
|
||||
+20
-14
@@ -5,6 +5,7 @@ import PrivateRoute from '../components/PrivateRoute';
|
||||
const LoginPage = lazy(() => import('../pages/Login'));
|
||||
const GitHubCallbackPage = lazy(() => import('../pages/Login/GitHubCallback'));
|
||||
const OIDCCallbackPage = lazy(() => import('../pages/Login/OIDCCallback'));
|
||||
const PublicLayout = lazy(() => import('../pages/PublicLayout'));
|
||||
const AdminLayout = lazy(() => import('../pages/AdminLayout'));
|
||||
const AgentListPage = lazy(() => import('../pages/Agents/AgentList'));
|
||||
const AgentDetailPage = lazy(() => import('../pages/Agents/AgentDetail'));
|
||||
@@ -45,20 +46,25 @@ const router = createBrowserRouter([
|
||||
},
|
||||
// 公开页面 - 不需要登录
|
||||
{
|
||||
path: '/',
|
||||
element: lazyLoad(ServerListPage),
|
||||
},
|
||||
{
|
||||
path: '/servers/:id',
|
||||
element: lazyLoad(ServerDetailPage),
|
||||
},
|
||||
{
|
||||
path: '/monitors',
|
||||
element: lazyLoad(PublicMonitorListPage),
|
||||
},
|
||||
{
|
||||
path: '/monitors/:id',
|
||||
element: lazyLoad(PublicMonitorDetailPage),
|
||||
element: lazyLoad(PublicLayout),
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: lazyLoad(ServerListPage),
|
||||
},
|
||||
{
|
||||
path: '/servers/:id',
|
||||
element: lazyLoad(ServerDetailPage),
|
||||
},
|
||||
{
|
||||
path: '/monitors',
|
||||
element: lazyLoad(PublicMonitorListPage),
|
||||
},
|
||||
{
|
||||
path: '/monitors/:id',
|
||||
element: lazyLoad(PublicMonitorDetailPage),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 管理员页面 - 需要登录
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user