Browse Source

fix: 优化内容区空白和页面布局,DashboardHome/rolesView体验提升

web
hyh 3 months ago
parent
commit
29dd65651f
  1. 9
      src/CellularManagement.WebUI/src/components/layout/Content.tsx
  2. 2
      src/CellularManagement.WebUI/src/components/layout/DashboardLayout.tsx
  3. 2
      src/CellularManagement.WebUI/src/components/layout/Tabs.tsx
  4. 41
      src/CellularManagement.WebUI/src/components/ui/button.tsx
  5. 26
      src/CellularManagement.WebUI/src/components/ui/dialog.tsx
  6. 2
      src/CellularManagement.WebUI/src/constants/menuConfig.ts
  7. 6
      src/CellularManagement.WebUI/src/pages/dashboard/DashboardHome.tsx
  8. 5
      src/CellularManagement.WebUI/src/pages/dashboard/UserManagePage.tsx
  9. 43
      src/CellularManagement.WebUI/src/pages/roles/RoleForm.tsx
  10. 45
      src/CellularManagement.WebUI/src/pages/roles/RoleTable.tsx
  11. 53
      src/CellularManagement.WebUI/src/pages/roles/RolesView.tsx
  12. 5
      src/CellularManagement.WebUI/src/routes/AppRouter.tsx
  13. 6
      src/CellularManagement.WebUI/src/services/roleService.ts

9
src/CellularManagement.WebUI/src/components/layout/Content.tsx

@ -10,13 +10,8 @@ export function Content({ children }: ContentProps) {
const { isCollapsed } = useSidebarToggle();
return (
<main
className={cn(
"flex-1 p-4 transition-all duration-300 ease-in-out sm:p-6",
isCollapsed ? "lg:pl-20" : "lg:pl-64"
)}
>
<div className="mx-auto max-w-7xl space-y-6">
<main className="flex-1 p-0 transition-all duration-300 ease-in-out">
<div className="w-full">
{children}
</div>
</main>

2
src/CellularManagement.WebUI/src/components/layout/DashboardLayout.tsx

@ -18,7 +18,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
<Header />
<div className="flex flex-1">
<Sidebar />
<div className="flex-1 flex flex-col transition-all duration-300 ease-in-out pl-3">
<div className="flex-1 flex flex-col transition-all duration-300 ease-in-out">
<Tabs />
<Content>
{children}

2
src/CellularManagement.WebUI/src/components/layout/Tabs.tsx

@ -172,7 +172,7 @@ export function Tabs() {
return (
<div className="flex items-center bg-background py-2 border-b relative w-full">
<div className="flex-1 flex items-center overflow-x-auto">
<div className="flex-1 flex items-center overflow-x-auto w-full">
{tabs.map(tab => (
<div
key={tab.href}

41
src/CellularManagement.WebUI/src/components/ui/button.tsx

@ -0,0 +1,41 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cn } from '@/lib/utils';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
asChild?: boolean;
variant?: 'default' | 'destructive' | 'outline' | 'secondary';
size?: 'default' | 'sm' | 'lg';
}
const buttonVariants = {
default: 'bg-blue-600 text-white hover:bg-blue-700',
destructive: 'bg-red-600 text-white hover:bg-red-700',
outline: 'border border-gray-300 text-gray-700 bg-white hover:bg-gray-50',
secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200',
};
const sizeVariants = {
default: 'px-4 py-2 text-sm',
sm: 'px-3 py-1 text-xs',
lg: 'px-6 py-3 text-base',
};
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(
'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
buttonVariants[variant],
sizeVariants[size],
className
)}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';

26
src/CellularManagement.WebUI/src/components/ui/dialog.tsx

@ -0,0 +1,26 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { cn } from '@/lib/utils';
export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed inset-0 bg-black/30 z-40" />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded shadow-lg focus:outline-none',
className
)}
{...props}
>
{children}
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
));
DialogContent.displayName = 'DialogContent';

2
src/CellularManagement.WebUI/src/constants/menuConfig.ts

@ -44,7 +44,7 @@ export const menuItems: MenuItem[] = [
},
{
title: '角色管理',
href: '/dashboard/users/roles',
href: '/dashboard/user/roles',
permission: 'roles.view',
},
{

6
src/CellularManagement.WebUI/src/pages/dashboard/DashboardHome.tsx

@ -1,13 +1,12 @@
export function DashboardHome() {
return (
<div className="space-y-6">
<div className="w-full p-4 sm:p-6 space-y-6">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground">
使
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<div className="rounded-lg border bg-card p-6">
<div className="flex items-center justify-between">
@ -36,7 +35,6 @@ export function DashboardHome() {
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-6">
<div className="flex items-center justify-between">
<div>
@ -61,7 +59,6 @@ export function DashboardHome() {
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-6">
<div className="flex items-center justify-between">
<div>
@ -87,7 +84,6 @@ export function DashboardHome() {
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-6">
<div className="flex items-center justify-between">
<div>

5
src/CellularManagement.WebUI/src/pages/dashboard/UserManagePage.tsx

@ -0,0 +1,5 @@
import { Outlet } from 'react-router-dom';
export default function UserManagePage() {
return <Outlet />;
}

43
src/CellularManagement.WebUI/src/pages/roles/RoleForm.tsx

@ -0,0 +1,43 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
interface RoleFormProps {
onSubmit: (data: { name: string; description?: string }) => void;
}
export default function RoleForm({ onSubmit }: RoleFormProps) {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ name, description });
setName('');
setDescription('');
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:border-blue-300"
value={name}
onChange={e => setName(e.target.value)}
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:border-blue-300"
value={description}
onChange={e => setDescription(e.target.value)}
/>
</div>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>
</form>
);
}

45
src/CellularManagement.WebUI/src/pages/roles/RoleTable.tsx

@ -0,0 +1,45 @@
import React from 'react';
import { Button } from '@/components/ui/button';
interface RoleTableProps {
roles: any[];
loading: boolean;
onDelete: (roleId: string) => void;
}
export default function RoleTable({ roles, loading, onDelete }: RoleTableProps) {
return (
<div className="bg-white rounded shadow">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
<th className="px-6 py-3"></th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{loading ? (
<tr>
<td colSpan={3} className="text-center py-6">...</td>
</tr>
) : roles.length === 0 ? (
<tr>
<td colSpan={3} className="text-center py-6"></td>
</tr>
) : (
roles.map((role) => (
<tr key={role.id}>
<td className="px-6 py-4 whitespace-nowrap">{role.name}</td>
<td className="px-6 py-4 whitespace-nowrap">{role.description}</td>
<td className="px-6 py-4 whitespace-nowrap text-right">
<Button variant="destructive" size="sm" onClick={() => onDelete(role.id)}></Button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
);
}

53
src/CellularManagement.WebUI/src/pages/roles/RolesView.tsx

@ -0,0 +1,53 @@
import React, { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
import { getAllRoles, createRole, deleteRole } from '@/services/roleService';
import RoleTable from './RoleTable';
import RoleForm from './RoleForm';
export default function RolesView() {
const [roles, setRoles] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const fetchRoles = async () => {
setLoading(true);
const res = await getAllRoles();
setRoles(res.data || []);
setLoading(false);
};
useEffect(() => {
fetchRoles();
}, []);
const handleCreate = async (data: { name: string; description?: string }) => {
await createRole(data);
setOpen(false);
fetchRoles();
};
const handleDelete = async (roleId: string) => {
await deleteRole(roleId);
fetchRoles();
};
return (
<main className="flex-1 p-4 transition-all duration-300 ease-in-out sm:p-6">
<div className="w-full space-y-6">
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold"></h1>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button></Button>
</DialogTrigger>
<DialogContent>
<RoleForm onSubmit={handleCreate} />
</DialogContent>
</Dialog>
</div>
<RoleTable roles={roles} loading={loading} onDelete={handleDelete} />
</div>
</main>
);
}

5
src/CellularManagement.WebUI/src/routes/AppRouter.tsx

@ -7,6 +7,8 @@ import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
const LoginPage = lazy(() => import('@/pages/auth/LoginPage').then(module => ({ default: module.LoginPage })));
const DashboardHome = lazy(() => import('@/pages/dashboard/DashboardHome').then(module => ({ default: module.DashboardHome })));
const ForbiddenPage = lazy(() => import('@/pages/auth/ForbiddenPage'));
const UserManagePage = lazy(() => import('@/pages/dashboard/UserManagePage'));
import RolesView from '@/pages/roles/RolesView';
// 加载中的占位组件
const LoadingFallback = () => (
@ -40,6 +42,9 @@ export function AppRouter() {
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route index element={<DashboardHome />} />
<Route path="user" element={<UserManagePage />}>
<Route path="roles" element={<RolesView />} />
</Route>
{/* 添加更多路由 */}
</Routes>
</Suspense>

6
src/CellularManagement.WebUI/src/services/roleService.ts

@ -0,0 +1,6 @@
import { httpClient } from '@/lib/http-client';
export const getAllRoles = () => httpClient.get('/api/Roles/GetAllRoles');
export const getRole = (roleId: string) => httpClient.get(`/api/Roles/GetRole/${roleId}`);
export const createRole = (data: { name: string; description?: string }) => httpClient.post('/api/Roles/CreateRole', data);
export const deleteRole = (roleId: string) => httpClient.delete(`/api/Roles/DeleteRole/${roleId}`);
Loading…
Cancel
Save