|
|
@@ -0,0 +1,376 @@
|
|
|
+---
|
|
|
+description: "Shadcn-ui 数据大屏开发指令"
|
|
|
+---
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+基于 `src/client/admin-shadcn/pages/Dashboard.tsx` 中数据大屏的实现,提取可复用的数据可视化开发模式和最佳实践,适用于基于 Shadcn-ui 的数据大屏和仪表板开发。
|
|
|
+
|
|
|
+## 核心特性
|
|
|
+
|
|
|
+### 1. 响应式布局系统
|
|
|
+- **网格布局**:使用 CSS Grid 和 Flexbox 实现响应式卡片布局
|
|
|
+- **断点设计**:支持 sm, md, lg, xl, 2xl 五个断点
|
|
|
+- **自适应卡片**:卡片宽度根据屏幕尺寸自动调整
|
|
|
+
|
|
|
+### 2. 数据可视化组件
|
|
|
+- **图表集成**:基于 Recharts 的图表组件封装
|
|
|
+- **统计卡片**:标准化的 KPI 展示卡片
|
|
|
+- **实时数据**:支持数据实时更新和刷新
|
|
|
+
|
|
|
+### 3. 主题一致性
|
|
|
+- **深色/浅色主题**:自动适配系统主题
|
|
|
+- **色彩规范**:使用 Tailwind CSS 色彩系统
|
|
|
+- **动画效果**:平滑的过渡和加载动画
|
|
|
+
|
|
|
+## 开发模板
|
|
|
+
|
|
|
+### 基础结构模板
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 1. 核心导入
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
+import { Button } from '@/client/components/ui/button';
|
|
|
+import { RefreshCw, TrendingUp, TrendingDown } from 'lucide-react';
|
|
|
+import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
|
|
+
|
|
|
+// 2. 数据类型定义
|
|
|
+interface DashboardData {
|
|
|
+ totalUsers: number;
|
|
|
+ totalRevenue: number;
|
|
|
+ monthlyGrowth: number;
|
|
|
+ chartData: Array<{
|
|
|
+ name: string;
|
|
|
+ value: number;
|
|
|
+ growth: number;
|
|
|
+ }>;
|
|
|
+}
|
|
|
+
|
|
|
+// 3. 组件状态
|
|
|
+const [data, setData] = useState<DashboardData | null>(null);
|
|
|
+const [loading, setLoading] = useState(true);
|
|
|
+const [lastUpdated, setLastUpdated] = useState<Date>(new Date());
|
|
|
+```
|
|
|
+
|
|
|
+### 统计卡片模板
|
|
|
+
|
|
|
+#### 基础统计卡片
|
|
|
+```typescript
|
|
|
+<Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">总用户数</CardTitle>
|
|
|
+ <Users className="h-4 w-4 text-muted-foreground" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold">{data?.totalUsers.toLocaleString()}</div>
|
|
|
+ <p className="text-xs text-muted-foreground">
|
|
|
+ {data?.monthlyGrowth > 0 ? '+' : ''}{data?.monthlyGrowth}% 较上月
|
|
|
+ </p>
|
|
|
+ </CardContent>
|
|
|
+</Card>
|
|
|
+```
|
|
|
+
|
|
|
+#### 带趋势指标卡片
|
|
|
+```typescript
|
|
|
+<Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">总收入</CardTitle>
|
|
|
+ <DollarSign className="h-4 w-4 text-muted-foreground" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold">${data?.totalRevenue.toLocaleString()}</div>
|
|
|
+ <div className="flex items-center mt-1">
|
|
|
+ {data?.monthlyGrowth > 0 ? (
|
|
|
+ <TrendingUp className="h-4 w-4 text-green-500 mr-1" />
|
|
|
+ ) : (
|
|
|
+ <TrendingDown className="h-4 w-4 text-red-500 mr-1" />
|
|
|
+ )}
|
|
|
+ <p className={`text-xs ${data?.monthlyGrowth > 0 ? 'text-green-500' : 'text-red-500'}`}>
|
|
|
+ {Math.abs(data?.monthlyGrowth || 0)}% 较上月
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+</Card>
|
|
|
+```
|
|
|
+
|
|
|
+### 图表组件模板
|
|
|
+
|
|
|
+#### 柱状图
|
|
|
+```typescript
|
|
|
+<Card className="col-span-4">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle>月度数据概览</CardTitle>
|
|
|
+ <CardDescription>过去12个月的数据趋势</CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="pl-2">
|
|
|
+ <ResponsiveContainer width="100%" height={350}>
|
|
|
+ <BarChart data={chartData}>
|
|
|
+ <CartesianGrid strokeDasharray="3 3" />
|
|
|
+ <XAxis dataKey="name" />
|
|
|
+ <YAxis />
|
|
|
+ <Tooltip />
|
|
|
+ <Legend />
|
|
|
+ <Bar dataKey="value" fill="#8884d8" />
|
|
|
+ </BarChart>
|
|
|
+ </ResponsiveContainer>
|
|
|
+ </CardContent>
|
|
|
+</Card>
|
|
|
+```
|
|
|
+
|
|
|
+#### 折线图
|
|
|
+```typescript
|
|
|
+<Card className="col-span-4">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle>趋势分析</CardTitle>
|
|
|
+ <CardDescription>数据变化趋势</CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <ResponsiveContainer width="100%" height={300}>
|
|
|
+ <LineChart data={lineChartData}>
|
|
|
+ <CartesianGrid strokeDasharray="3 3" />
|
|
|
+ <XAxis dataKey="name" />
|
|
|
+ <YAxis />
|
|
|
+ <Tooltip />
|
|
|
+ <Legend />
|
|
|
+ <Line type="monotone" dataKey="users" stroke="#8884d8" strokeWidth={2} />
|
|
|
+ <Line type="monotone" dataKey="revenue" stroke="#82ca9d" strokeWidth={2} />
|
|
|
+ </LineChart>
|
|
|
+ </ResponsiveContainer>
|
|
|
+ </CardContent>
|
|
|
+</Card>
|
|
|
+```
|
|
|
+
|
|
|
+#### 饼图
|
|
|
+```typescript
|
|
|
+<Card className="col-span-3">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle>数据分布</CardTitle>
|
|
|
+ <CardDescription>按类别分布</CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <ResponsiveContainer width="100%" height={300}>
|
|
|
+ <PieChart>
|
|
|
+ <Pie
|
|
|
+ data={pieData}
|
|
|
+ cx="50%"
|
|
|
+ cy="50%"
|
|
|
+ labelLine={false}
|
|
|
+ label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
|
|
+ outerRadius={80}
|
|
|
+ fill="#8884d8"
|
|
|
+ dataKey="value"
|
|
|
+ >
|
|
|
+ {pieData.map((entry, index) => (
|
|
|
+ <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
|
+ ))}
|
|
|
+ </Pie>
|
|
|
+ <Tooltip />
|
|
|
+ </PieChart>
|
|
|
+ </ResponsiveContainer>
|
|
|
+ </CardContent>
|
|
|
+</Card>
|
|
|
+```
|
|
|
+
|
|
|
+### 响应式网格布局
|
|
|
+
|
|
|
+#### 基础网格
|
|
|
+```typescript
|
|
|
+<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
|
+ {/* 统计卡片 */}
|
|
|
+ <Card>...</Card>
|
|
|
+ <Card>...</Card>
|
|
|
+ <Card>...</Card>
|
|
|
+ <Card>...</Card>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+#### 复杂网格
|
|
|
+```typescript
|
|
|
+<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
|
|
+ <Card className="col-span-4">...</Card>
|
|
|
+ <Card className="col-span-3">...</Card>
|
|
|
+</div>
|
|
|
+```
|
|
|
+
|
|
|
+### 数据加载状态
|
|
|
+
|
|
|
+#### 骨架屏
|
|
|
+```typescript
|
|
|
+import { Skeleton } from '@/client/components/ui/skeleton';
|
|
|
+
|
|
|
+const LoadingCard = () => (
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <Skeleton className="h-4 w-[100px]" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <Skeleton className="h-8 w-[150px] mb-2" />
|
|
|
+ <Skeleton className="h-3 w-[100px]" />
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+#### 加载动画
|
|
|
+```typescript
|
|
|
+import { Loader2 } from 'lucide-react';
|
|
|
+
|
|
|
+const LoadingSpinner = () => (
|
|
|
+ <div className="flex items-center justify-center h-[400px]">
|
|
|
+ <Loader2 className="h-8 w-8 animate-spin" />
|
|
|
+ </div>
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 实时数据更新
|
|
|
+
|
|
|
+#### 自动刷新
|
|
|
+```typescript
|
|
|
+const Dashboard = () => {
|
|
|
+ const [data, setData] = useState<DashboardData | null>(null);
|
|
|
+ const [autoRefresh, setAutoRefresh] = useState(true);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchData();
|
|
|
+
|
|
|
+ if (autoRefresh) {
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ fetchData();
|
|
|
+ }, 30000); // 30秒刷新一次
|
|
|
+
|
|
|
+ return () => clearInterval(interval);
|
|
|
+ }
|
|
|
+ }, [autoRefresh]);
|
|
|
+
|
|
|
+ const fetchData = async () => {
|
|
|
+ try {
|
|
|
+ const response = await apiClient.$get();
|
|
|
+ setData(response.data);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to fetch dashboard data:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <div className="flex justify-between items-center mb-4">
|
|
|
+ <h1 className="text-3xl font-bold">数据大屏</h1>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => fetchData()}
|
|
|
+ >
|
|
|
+ <RefreshCw className="h-4 w-4 mr-2" />
|
|
|
+ 刷新数据
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ {/* 数据展示 */}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 主题配置
|
|
|
+
|
|
|
+### 颜色配置
|
|
|
+```typescript
|
|
|
+const COLORS = {
|
|
|
+ primary: '#8884d8',
|
|
|
+ secondary: '#82ca9d',
|
|
|
+ accent: '#ffc658',
|
|
|
+ danger: '#ff7c7c',
|
|
|
+ warning: '#ffb347',
|
|
|
+ success: '#00c49f',
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 图表主题
|
|
|
+```typescript
|
|
|
+const chartConfig = {
|
|
|
+ users: {
|
|
|
+ label: "用户数",
|
|
|
+ color: "#2563eb",
|
|
|
+ },
|
|
|
+ revenue: {
|
|
|
+ label: "收入",
|
|
|
+ color: "#16a34a",
|
|
|
+ },
|
|
|
+ orders: {
|
|
|
+ label: "订单数",
|
|
|
+ color: "#9333ea",
|
|
|
+ },
|
|
|
+} satisfies ChartConfig;
|
|
|
+```
|
|
|
+
|
|
|
+## 最佳实践
|
|
|
+
|
|
|
+### 1. 性能优化
|
|
|
+- 使用 `useMemo` 缓存计算数据
|
|
|
+- 实现数据分页加载
|
|
|
+- 使用虚拟滚动处理大量数据
|
|
|
+
|
|
|
+### 2. 用户体验
|
|
|
+- 提供数据刷新功能
|
|
|
+- 显示最后更新时间
|
|
|
+- 添加数据加载状态
|
|
|
+
|
|
|
+### 3. 无障碍设计
|
|
|
+- 为图表添加描述性标签
|
|
|
+- 提供键盘导航支持
|
|
|
+- 使用高对比度颜色
|
|
|
+
|
|
|
+### 4. 响应式设计
|
|
|
+```typescript
|
|
|
+// 响应式断点
|
|
|
+const breakpoints = {
|
|
|
+ sm: 640,
|
|
|
+ md: 768,
|
|
|
+ lg: 1024,
|
|
|
+ xl: 1280,
|
|
|
+ 2xl: 1536,
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 完整示例
|
|
|
+
|
|
|
+### 综合数据大屏
|
|
|
+```typescript
|
|
|
+import { Dashboard } from '@/client/admin-shadcn/pages/Dashboard';
|
|
|
+
|
|
|
+const DashboardPage = () => {
|
|
|
+ return (
|
|
|
+ <div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
|
|
|
+ <div className="flex items-center justify-between space-y-2">
|
|
|
+ <h2 className="text-3xl font-bold tracking-tight">数据大屏</h2>
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <Button variant="outline" size="sm">
|
|
|
+ <RefreshCw className="h-4 w-4 mr-2" />
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Dashboard />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 组件导入清单
|
|
|
+
|
|
|
+```typescript
|
|
|
+// UI 组件
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
+import { Button } from '@/client/components/ui/button';
|
|
|
+import { Skeleton } from '@/client/components/ui/skeleton';
|
|
|
+
|
|
|
+// 图表组件
|
|
|
+import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
|
|
+
|
|
|
+// 图标
|
|
|
+import { Users, DollarSign, TrendingUp, TrendingDown, RefreshCw, Activity } from 'lucide-react';
|
|
|
+
|
|
|
+// 工具
|
|
|
+import { useState, useEffect, useMemo } from 'react';
|
|
|
+import { format } from 'date-fns';
|