Browse Source

✨ feat(dashboard): 重构仪表盘页面UI与功能

- 采用新的卡片式布局,展示活跃用户、系统消息和在线用户三大核心指标
- 添加系统性能监控模块,显示CPU、内存、磁盘和网络使用率
- 增加最近活动记录列表,展示系统最新操作日志
- 添加快捷操作区域,提供用户管理、系统设置等常用功能入口

✨ feat(login): 优化登录页面UI与交互体验

- 重构登录表单,使用react-hook-form和zod进行表单验证
- 添加密码显示/隐藏切换功能
- 优化页面布局和视觉效果,使用渐变背景和阴影提升层次感
- 增加加载状态动画和成功/失败提示
- 添加地理位置获取功能,增强登录审计能力
yourname 6 months ago
parent
commit
349c6b75ec
2 changed files with 359 additions and 148 deletions
  1. 226 64
      src/client/admin/pages/Dashboard.tsx
  2. 133 84
      src/client/admin/pages/Login.tsx

+ 226 - 64
src/client/admin/pages/Dashboard.tsx

@@ -1,75 +1,237 @@
 import React from 'react';
 import React from 'react';
-import {
-  Card, Row, Col, Typography, Statistic, Space
-} from 'antd';
-import {
-  UserOutlined, BellOutlined, EyeOutlined
-} from '@ant-design/icons';
-
-const { Title } = Typography;
+import { Users, Bell, Eye, TrendingUp, TrendingDown, Activity } from 'lucide-react';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Badge } from '@/client/components/ui/badge';
+import { Progress } from '@/client/components/ui/progress';
 
 
 // 仪表盘页面
 // 仪表盘页面
 export const DashboardPage = () => {
 export const DashboardPage = () => {
+  const stats = [
+    {
+      title: '活跃用户',
+      value: '112,893',
+      icon: Users,
+      color: 'text-blue-500',
+      bgColor: 'bg-blue-50',
+      trend: 12.5,
+      trendDirection: 'up',
+      description: '较昨日增长 12.5%',
+    },
+    {
+      title: '系统消息',
+      value: '93',
+      icon: Bell,
+      color: 'text-yellow-500',
+      bgColor: 'bg-yellow-50',
+      trend: 0,
+      trendDirection: 'neutral',
+      description: '其中 5 条未读',
+    },
+    {
+      title: '在线用户',
+      value: '1,128',
+      icon: Eye,
+      color: 'text-purple-500',
+      bgColor: 'bg-purple-50',
+      trend: 32.1,
+      trendDirection: 'up',
+      description: '当前在线率 32.1%',
+    },
+  ];
+
+  const recentActivities = [
+    {
+      id: 1,
+      user: '张三',
+      action: '登录系统',
+      time: '2分钟前',
+      status: 'success',
+    },
+    {
+      id: 2,
+      user: '李四',
+      action: '创建了新用户',
+      time: '5分钟前',
+      status: 'info',
+    },
+    {
+      id: 3,
+      user: '王五',
+      action: '删除了用户',
+      time: '10分钟前',
+      status: 'warning',
+    },
+    {
+      id: 4,
+      user: '赵六',
+      action: '修改了配置',
+      time: '15分钟前',
+      status: 'info',
+    },
+  ];
+
+  const systemMetrics = [
+    {
+      name: 'CPU使用率',
+      value: 65,
+      color: 'bg-green-500',
+    },
+    {
+      name: '内存使用率',
+      value: 78,
+      color: 'bg-blue-500',
+    },
+    {
+      name: '磁盘使用率',
+      value: 45,
+      color: 'bg-purple-500',
+    },
+    {
+      name: '网络使用率',
+      value: 32,
+      color: 'bg-orange-500',
+    },
+  ];
+
   return (
   return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>仪表盘</Title>
+    <div className="space-y-6">
+      <div>
+        <h1 className="text-3xl font-bold tracking-tight">仪表盘</h1>
+        <p className="text-muted-foreground">
+          欢迎回来!这里是系统概览和关键指标。
+        </p>
       </div>
       </div>
-      <Row gutter={[16, 16]}>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>活跃用户</Typography.Title>
-              <UserOutlined style={{ fontSize: 24, color: '#1890ff' }} />
-            </div>
-            <Statistic
-              value={112893}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              prefix={<span style={{ color: '#52c41a' }}>↑</span>}
-              suffix="人"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              较昨日增长 12.5%
-            </div>
-          </Card>
-        </Col>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>系统消息</Typography.Title>
-              <BellOutlined style={{ fontSize: 24, color: '#faad14' }} />
-            </div>
-            <Statistic
-              value={93}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              prefix={<span style={{ color: '#faad14' }}>●</span>}
-              suffix="条"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              其中 5 条未读
-            </div>
-          </Card>
-        </Col>
-        <Col xs={24} sm={12} lg={8}>
-          <Card className="shadow-sm transition-all duration-300 hover:shadow-md">
-            <div className="flex items-center justify-between mb-2">
-              <Typography.Title level={5}>在线用户</Typography.Title>
-              <EyeOutlined style={{ fontSize: 24, color: '#722ed1' }} />
+
+      {/* 统计卡片 */}
+      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
+        {stats.map((stat, index) => {
+          const Icon = stat.icon;
+          return (
+            <Card key={index} className="transition-all duration-300 hover:shadow-lg">
+              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+                <CardTitle className="text-sm font-medium">
+                  {stat.title}
+                </CardTitle>
+                <div className={`p-2 rounded-full ${stat.bgColor}`}>
+                  <Icon className={`h-4 w-4 ${stat.color}`} />
+                </div>
+              </CardHeader>
+              <CardContent>
+                <div className="text-2xl font-bold">{stat.value}</div>
+                <div className="flex items-center space-x-2">
+                  {stat.trendDirection === 'up' && (
+                    <TrendingUp className="h-4 w-4 text-green-500" />
+                  )}
+                  {stat.trendDirection === 'down' && (
+                    <TrendingDown className="h-4 w-4 text-red-500" />
+                  )}
+                  <p className="text-xs text-muted-foreground">{stat.description}</p>
+                </div>
+              </CardContent>
+            </Card>
+          );
+        })}
+      </div>
+
+      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
+        {/* 系统性能 */}
+        <Card className="lg:col-span-4">
+          <CardHeader>
+            <CardTitle>系统性能</CardTitle>
+            <CardDescription>
+              当前系统各项资源的使用情况
+            </CardDescription>
+          </CardHeader>
+          <CardContent>
+            <div className="space-y-4">
+              {systemMetrics.map((metric, index) => (
+                <div key={index} className="space-y-2">
+                  <div className="flex justify-between text-sm">
+                    <span className="font-medium">{metric.name}</span>
+                    <span className="text-muted-foreground">{metric.value}%</span>
+                  </div>
+                  <Progress value={metric.value} className="h-2" />
+                </div>
+              ))}
             </div>
             </div>
-            <Statistic
-              value={1128}
-              loading={false}
-              valueStyle={{ fontSize: 28 }}
-              suffix="人"
-            />
-            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
-              当前在线率 32.1%
+          </CardContent>
+        </Card>
+
+        {/* 最近活动 */}
+        <Card className="lg:col-span-3">
+          <CardHeader>
+            <CardTitle>最近活动</CardTitle>
+            <CardDescription>
+              系统最新操作记录
+            </CardDescription>
+          </CardHeader>
+          <CardContent>
+            <div className="space-y-4">
+              {recentActivities.map((activity) => (
+                <div key={activity.id} className="flex items-center space-x-4">
+                  <div className="flex-shrink-0">
+                    <div className={`p-2 rounded-full ${
+                      activity.status === 'success' ? 'bg-green-100' :
+                      activity.status === 'warning' ? 'bg-yellow-100' : 'bg-blue-100'
+                    }`}>
+                      <Activity className={`h-4 w-4 ${
+                        activity.status === 'success' ? 'text-green-600' :
+                        activity.status === 'warning' ? 'text-yellow-600' : 'text-blue-600'
+                      }`} />
+                    </div>
+                  </div>
+                  <div className="flex-1 space-y-1">
+                    <p className="text-sm font-medium">
+                      {activity.user} {activity.action}
+                    </p>
+                    <p className="text-sm text-muted-foreground">
+                      {activity.time}
+                    </p>
+                  </div>
+                </div>
+              ))}
             </div>
             </div>
-          </Card>
-        </Col>
-      </Row>
+          </CardContent>
+        </Card>
+      </div>
+
+      {/* 快捷操作 */}
+      <Card>
+        <CardHeader>
+          <CardTitle>快捷操作</CardTitle>
+          <CardDescription>
+            常用的管理功能
+          </CardDescription>
+        </CardHeader>
+        <CardContent>
+          <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
+            <Card className="hover:shadow-md transition-all cursor-pointer">
+              <CardHeader className="pb-3">
+                <CardTitle className="text-base">用户管理</CardTitle>
+                <CardDescription>查看和管理所有用户</CardDescription>
+              </CardHeader>
+            </Card>
+            <Card className="hover:shadow-md transition-all cursor-pointer">
+              <CardHeader className="pb-3">
+                <CardTitle className="text-base">系统设置</CardTitle>
+                <CardDescription>配置系统参数</CardDescription>
+              </CardHeader>
+            </Card>
+            <Card className="hover:shadow-md transition-all cursor-pointer">
+              <CardHeader className="pb-3">
+                <CardTitle className="text-base">数据备份</CardTitle>
+                <CardDescription>执行数据备份操作</CardDescription>
+              </CardHeader>
+            </Card>
+            <Card className="hover:shadow-md transition-all cursor-pointer">
+              <CardHeader className="pb-3">
+                <CardTitle className="text-base">日志查看</CardTitle>
+                <CardDescription>查看系统日志</CardDescription>
+              </CardHeader>
+            </Card>
+          </div>
+        </CardContent>
+      </Card>
     </div>
     </div>
   );
   );
 };
 };

+ 133 - 84
src/client/admin/pages/Login.tsx

@@ -1,34 +1,42 @@
-import React, { useState } from 'react';
-import {
-  Form,
-  Input,
-  Button,
-  Card,
-  App,
-} from 'antd';
-import {
-  UserOutlined,
-  LockOutlined,
-  EyeOutlined,
-  EyeInvisibleOutlined
-} from '@ant-design/icons';
+import { useState } from 'react';
 import { useNavigate } from 'react-router';
 import { useNavigate } from 'react-router';
-import {
-  useAuth,
-} from '../hooks/AuthProvider';
+import { useAuth } from '../hooks/AuthProvider';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import { toast } from 'sonner';
+import { Eye, EyeOff, User, Lock } from 'lucide-react';
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
+import { Input } from '@/client/components/ui/input';
 
 
+// 表单验证Schema
+const loginSchema = z.object({
+  username: z.string().min(1, '请输入用户名'),
+  password: z.string().min(1, '请输入密码'),
+});
+
+type LoginFormData = z.infer<typeof loginSchema>;
 
 
 // 登录页面
 // 登录页面
 export const LoginPage = () => {
 export const LoginPage = () => {
-  const { message } = App.useApp();
   const { login } = useAuth();
   const { login } = useAuth();
-  const [form] = Form.useForm();
-  const [loading, setLoading] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [showPassword, setShowPassword] = useState(false);
   const navigate = useNavigate();
   const navigate = useNavigate();
   
   
-  const handleSubmit = async (values: { username: string; password: string }) => {
+  const form = useForm<LoginFormData>({
+    resolver: zodResolver(loginSchema),
+    defaultValues: {
+      username: '',
+      password: '',
+    },
+  });
+
+  const handleSubmit = async (data: LoginFormData) => {
     try {
     try {
-      setLoading(true);
+      setIsLoading(true);
       
       
       // 获取地理位置
       // 获取地理位置
       let latitude: number | undefined;
       let latitude: number | undefined;
@@ -46,84 +54,125 @@ export const LoginPage = () => {
         console.warn('获取地理位置失败:', geoError);
         console.warn('获取地理位置失败:', geoError);
       }
       }
       
       
-      await login(values.username, values.password, latitude, longitude);
+      await login(data.username, data.password, latitude, longitude);
       // 登录成功后跳转到管理后台首页
       // 登录成功后跳转到管理后台首页
       navigate('/admin/dashboard');
       navigate('/admin/dashboard');
+      toast.success('登录成功!欢迎回来');
     } catch (error: any) {
     } catch (error: any) {
-      message.error(error instanceof Error ? error.message : '登录失败');
+      toast.error(error instanceof Error ? error.message : '登录失败');
     } finally {
     } finally {
-      setLoading(false);
+      setIsLoading(false);
     }
     }
   };
   };
   
   
   return (
   return (
-    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4 sm:px-6 lg:px-8">
+    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 py-12 px-4 sm:px-6 lg:px-8">
       <div className="max-w-md w-full space-y-8">
       <div className="max-w-md w-full space-y-8">
         <div className="text-center">
         <div className="text-center">
-          <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
-            <UserOutlined style={{ fontSize: 32, color: '#1890ff' }} />
+          <div className="mx-auto w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center shadow-lg">
+            <User className="h-8 w-8 text-white" />
           </div>
           </div>
-          <h2 className="mt-2 text-center text-3xl font-extrabold text-gray-900">
+          <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
             管理后台登录
             管理后台登录
           </h2>
           </h2>
-          <p className="mt-2 text-gray-500">请输入您的账号和密码</p>
+          <p className="mt-2 text-center text-sm text-gray-600">
+            请输入您的账号和密码继续操作
+          </p>
         </div>
         </div>
         
         
-        <Card className="shadow-lg border-none transition-all duration-300 hover:shadow-xl">
-          <Form
-            form={form}
-            name="login"
-            onFinish={handleSubmit}
-            autoComplete="off"
-            layout="vertical"
-          >
-            <Form.Item
-              name="username"
-              rules={[{ required: true, message: '请输入用户名' }]}
-              label="用户名"
-            >
-              <Input
-                prefix={<UserOutlined className="text-primary" />}
-                placeholder="请输入用户名"
-                size="large"
-                className="transition-all duration-200 focus:border-primary focus:ring-1 focus:ring-primary"
-              />
-            </Form.Item>
-            
-            <Form.Item
-              name="password"
-              rules={[{ required: true, message: '请输入密码' }]}
-              label="密码"
-            >
-              <Input.Password
-                prefix={<LockOutlined className="text-primary" />}
-                placeholder="请输入密码"
-                size="large"
-                iconRender={(visible) => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
-                className="transition-all duration-200 focus:border-primary focus:ring-1 focus:ring-primary"
-              />
-            </Form.Item>
-            
-            <Form.Item>
-              <Button
-                type="primary"
-                htmlType="submit"
-                size="large"
-                block
-                loading={loading}
-                className="h-12 text-lg transition-all duration-200 hover:shadow-lg"
-              >
-                登录
-              </Button>
-            </Form.Item>
-          </Form>
-          
-          <div className="mt-6 text-center text-gray-500 text-sm">
-            <p>测试账号: <span className="font-medium">admin</span> / <span className="font-medium">admin123</span></p>
-            <p className="mt-1">© {new Date().getFullYear()} 管理系统. 保留所有权利.</p>
-          </div>
+        <Card className="shadow-xl border-0">
+          <CardHeader className="text-center">
+            <CardTitle className="text-2xl">欢迎登录</CardTitle>
+            <CardDescription>
+              使用您的账户信息登录系统
+            </CardDescription>
+          </CardHeader>
+          <CardContent>
+            <Form {...form}>
+              <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
+                <FormField
+                  control={form.control}
+                  name="username"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>用户名</FormLabel>
+                      <FormControl>
+                        <div className="relative">
+                          <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
+                          <Input
+                            placeholder="请输入用户名"
+                            className="pl-10"
+                            {...field}
+                          />
+                        </div>
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+                
+                <FormField
+                  control={form.control}
+                  name="password"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>密码</FormLabel>
+                      <FormControl>
+                        <div className="relative">
+                          <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
+                          <Input
+                            type={showPassword ? 'text' : 'password'}
+                            placeholder="请输入密码"
+                            className="pl-10 pr-10"
+                            {...field}
+                          />
+                          <button
+                            type="button"
+                            className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
+                            onClick={() => setShowPassword(!showPassword)}
+                          >
+                            {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
+                          </button>
+                        </div>
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+                
+                <Button
+                  type="submit"
+                  className="w-full"
+                  disabled={isLoading}
+                >
+                  {isLoading ? (
+                    <>
+                      <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
+                      登录中...
+                    </>
+                  ) : (
+                    '登录'
+                  )}
+                </Button>
+              </form>
+            </Form>
+          </CardContent>
+          <CardFooter className="flex flex-col items-center space-y-2">
+            <div className="text-sm text-gray-500">
+              测试账号: <span className="font-medium text-gray-700">admin</span> / <span className="font-medium text-gray-700">admin123</span>
+            </div>
+            <div className="text-xs text-gray-400">
+              © {new Date().getFullYear()} 管理系统. 保留所有权利.
+            </div>
+          </CardFooter>
         </Card>
         </Card>
+        
+        <div className="text-center">
+          <p className="text-sm text-gray-500">
+            遇到问题?<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">联系管理员</a>
+          </p>
+        </div>
       </div>
       </div>
     </div>
     </div>
   );
   );
-};
+};