application [cat-admin-web] layout development

This commit is contained in:
NVWA Code Agent
2025-12-11 16:48:26 +00:00
parent fb4ef01791
commit 22578bab2e
8 changed files with 101 additions and 29 deletions

View File

@@ -41,7 +41,7 @@ export function AuthRouter({ children }: AuthRouterProps) {
redirectToLogin()
return
}
} catch (error) {
} catch {
// 获取用户信息失败,跳转到登录页
redirectToLogin()
return

View File

@@ -1,15 +1,36 @@
import { useRoutes } from 'react-router-dom'
import { useRoutes, useLocation, Link } from 'react-router-dom'
import {
Home,
PawPrint,
List,
Eye,
} from 'lucide-react'
// vite-plugin-pages 会自动生成路由配置
// @ts-expect-error - 动态导入,类型会在构建时生成
import autoRoutes from "~react-pages"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from './ui/sidebar'
import { Separator } from './ui/separator'
/**
* Layout 组件
*
*
* 这个组件会包裹所有需要 layout 的页面(除了 /user/login 和 /user/register
* 它使用 vite-plugin-pages 生成的路由配置来根据 pathname 动态渲染对应的页面组件
*
*
* 用户可以在生成代码时自定义这个 Layout添加侧边栏、导航栏等公共布局元素
*/
const noLayoutRoutes = [
@@ -17,21 +38,77 @@ const noLayoutRoutes = [
'/user/register',
]
const navigation = [
{
title: 'Dashboard',
url: '/dashboard',
icon: Home,
},
{
title: 'Cat Management',
url: '/cat-management',
icon: PawPrint,
},
{
title: 'Queue Management',
url: '/queue-management',
icon: List,
},
{
title: 'Reservation View',
url: '/reservation-view',
icon: Eye,
},
]
export function Layout() {
const location = useLocation()
// 过滤掉登录和注册页面,因为它们不应该被 layout 包裹
const layoutRoutes = autoRoutes.filter(
(route: any) => !noLayoutRoutes.includes(route.path)
(route: { path: string }) => !noLayoutRoutes.includes(route.path)
)
// 使用 useRoutes 来渲染当前路径对应的组件
const element = useRoutes(layoutRoutes)
return (
<div>
{/* 这里可以添加侧边栏、导航栏等公共布局元素 */}
{/* 用户可以在生成代码时自定义这个 Layout */}
{element}
</div>
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarGroupLabel>Cat Shelter Admin</SidebarGroupLabel>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{navigation.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={location.pathname === item.url}>
<Link to={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" />
<h1 className="text-lg font-semibold">Cat Shelter Management</h1>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
{element}
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -6,7 +6,7 @@ type User = {
name?: string;
email?: string;
username?: string;
[key: string]: any;
[key: string]: unknown;
};
type AuthState = {

View File

@@ -6,7 +6,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';

View File

@@ -1,11 +1,6 @@
import { Navigate } from 'react-router-dom'
export default function HomePage() {
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4"></h1>
<p className="text-muted-foreground"></p>
</div>
</div>
);
return <Navigate to="/dashboard" replace />
}

View File

@@ -89,7 +89,7 @@ export default function QueueManagement() {
setCats(cats.map(c => c.id === cat.id ? { ...c, is_available: !c.is_available } : c));
};
const updateRules = (key: keyof QueueRules, value: any) => {
const updateRules = (key: keyof QueueRules, value: number | string) => {
setRules(prev => ({ ...prev, [key]: value }));
};

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { entities } from '@/lib/nvwa';
import { Button } from '@/components/ui/button';
@@ -35,7 +35,7 @@ const ReservationView: React.FC = () => {
const pageSize = 10; // 每页显示数量
// 获取预约数据
const fetchReservations = async () => {
const fetchReservations = useCallback(async () => {
setLoading(true);
try {
// 构建查询,使用关联查询获取用户和猫宠信息
@@ -91,11 +91,11 @@ const ReservationView: React.FC = () => {
} finally {
setLoading(false);
}
};
}, [status, page]);
useEffect(() => {
fetchReservations();
}, [status, page]);
}, [fetchReservations]);
// 处理状态筛选
const handleStatusChange = (newStatus: string) => {
@@ -218,7 +218,7 @@ const ReservationView: React.FC = () => {
<TableCell>{reservation.catName || '-'}</TableCell>
<TableCell>¥{reservation.deposit.toFixed(2)}</TableCell>
<TableCell>
<Badge variant={statusColors[reservation.status] as any}>
<Badge variant={statusColors[reservation.status]}>
{statusLabels[reservation.status]}
</Badge>
</TableCell>

View File

@@ -76,7 +76,7 @@ export default function LoginPage() {
const redirectPath = backUrl && backUrl !== "/user/login" ? backUrl : "/";
navigate(redirectPath);
}
} catch (error) {
} catch {
// 未登录,继续显示登录页
}
};