Files
Environment-Monitoring-System/Design/frontend_Design/DashboardPage_Design.md
ChuXun 4ce487588a 1
2025-10-19 20:31:01 +08:00

8.8 KiB

仪表盘页面设计文档

1. 页面概述

仪表盘页面是用户登录后看到的第一个界面,旨在提供系统关键信息的概览。它主要面向管理员、决策者和主管,展示核心业务指标、任务状态分布、近期活动等,以便用户快速掌握系统整体状况并做出决策。

2. 页面布局

仪表盘页面布局示意图

2.1 布局结构

页面采用响应式网格布局,确保在不同屏幕尺寸下都有良好的可读性。

  • 顶部: 页面标题和一个时间范围选择器,用于筛选仪表盘数据。
  • 中部: 关键指标卡片区域,展示核心数据(如总任务数、待处理反馈等)。
  • 下部:
    • 左侧: 图表区域,通过饼图和柱状图展示任务状态和类型的分布。
    • 右侧: 近期活动或任务列表,展示最新的任务分配或状态变更。

2.2 响应式设计

  • 桌面端: 采用多列网格布局,充分利用屏幕空间。
  • 平板端: 网格列数减少,卡片和图表垂直堆叠。
  • 移动端: 单列布局,所有模块垂直排列,保证可读性。

3. 组件结构

<template>
  <div class="dashboard-page">
    <!-- 页面头部 -->
    <el-page-header title="仪表盘" content="系统概览" class="page-header" />
    
    <!-- 时间范围选择器 -->
    <div class="dashboard-filters">
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        range-separator=""
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        @change="handleDateChange"
      />
    </div>

    <!-- 加载与错误状态 -->
    <div v-if="loading" class="loading-state">
      <el-skeleton :rows="5" animated />
    </div>
    <div v-else-if="error" class="error-state">
      <el-alert type="error" :title="error" show-icon :closable="false" />
    </div>
    
    <!-- 数据内容 -->
    <div v-else class="dashboard-content">
      <!-- 关键指标卡片 -->
      <el-row :gutter="20" class="summary-cards">
        <el-col :span="6" :xs="24" :sm="12" :md="6">
          <StatisticCard icon="list" title="总任务数" :value="stats.totalTasks" color="#409eff" />
        </el-col>
        <el-col :span="6" :xs="24" :sm="12" :md="6">
          <StatisticCard icon="clock" title="待处理任务" :value="stats.pendingTasks" color="#e6a23c" />
        </el-col>
        <el-col :span="6" :xs="24" :sm="12" :md="6">
          <StatisticCard icon="chat-dot-round" title="待审核反馈" :value="stats.pendingFeedback" color="#f56c6c" />
        </el-col>
        <el-col :span="6" :xs="24" :sm="12" :md="6">
          <StatisticCard icon="user" title="活跃用户" :value="stats.activeUsers" color="#67c23a" />
        </el-col>
      </el-row>
      
      <!-- 图表区域 -->
      <el-row :gutter="20" class="charts-section">
        <el-col :span="12" :xs="24" :sm="24" :md="12">
          <el-card shadow="never">
            <template #header>任务状态分布</template>
            <PieChart :chart-data="taskStatusChartData" />
          </el-card>
        </el-col>
        <el-col :span="12" :xs="24" :sm="24" :md="12">
          <el-card shadow="never">
            <template #header>任务类型分布</template>
            <BarChart :chart-data="taskTypeChartData" />
          </el-card>
        </el-col>
      </el-row>
      
      <!-- 近期任务列表 -->
      <el-card shadow="never" class="recent-tasks-section">
        <template #header>近期任务动态</template>
        <el-table :data="recentTasks" stripe>
          <el-table-column prop="title" label="任务标题" />
          <el-table-column prop="assignee" label="执行人" />
          <el-table-column prop="status" label="状态">
            <template #default="{ row }">
              <el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="updatedAt" label="更新时间" />
        </el-table>
      </el-card>
    </div>
  </div>
</template>

4. 数据结构

// 仪表盘统计数据
interface DashboardStats {
  totalTasks: number;
  pendingTasks: number;
  pendingFeedback: number;
  activeUsers: number;
}

// 图表数据项
interface ChartDataItem {
  name: string;
  value: number;
}

// 近期任务项
interface RecentTask {
  id: number;
  title: string;
  assignee: string;
  status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED';
  updatedAt: string;
}

// 仪表盘完整数据
interface DashboardData {
  stats: DashboardStats;
  taskStatusDistribution: ChartDataItem[];
  taskTypeDistribution: ChartDataItem[];
  recentTasks: RecentTask[];
}

5. 状态管理

// 组件内状态
const dateRange = ref<[Date, Date]>();
const loading = ref<boolean>(true);
const error = ref<string | null>(null);

// 全局状态 (Pinia Store)
const dashboardStore = useDashboardStore();
const { stats, taskStatusChartData, taskTypeChartData, recentTasks } = storeToRefs(dashboardStore);

6. 交互逻辑

6.1 数据加载

// 在组件挂载时加载数据
onMounted(() => {
  fetchDashboardData();
});

const fetchDashboardData = async () => {
  try {
    loading.value = true;
    error.value = null;
    
    // 从 store 中调用 action 加载数据
    await dashboardStore.fetchDashboardData({
      startDate: dateRange.value?.[0],
      endDate: dateRange.value?.[1],
    });
    
  } catch (err) {
    error.value = '仪表盘数据加载失败,请稍后重试。';
  } finally {
    loading.value = false;
  }
};

// 当日期范围变化时重新加载数据
const handleDateChange = () => {
  fetchDashboardData();
};

6.2 辅助函数

// 根据任务状态返回不同的标签类型
const getStatusTagType = (status: RecentTask['status']) => {
  switch (status) {
    case 'PENDING': return 'warning';
    case 'IN_PROGRESS': return 'primary';
    case 'COMPLETED': return 'success';
    case 'REVIEWED': return 'info';
    default: return 'info';
  }
};

7. API 调用

7.1 仪表盘 API

// api/dashboard.ts
export const dashboardApi = {
  getDashboardData: (params: { startDate?: Date; endDate?: Date }) => 
    apiClient.get<DashboardData>('/dashboard', { params })
};

// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', {
  state: (): DashboardData => ({
    stats: { totalTasks: 0, pendingTasks: 0, pendingFeedback: 0, activeUsers: 0 },
    taskStatusDistribution: [],
    taskTypeDistribution: [],
    recentTasks: [],
  }),
  getters: {
    // 将后端数据转换为 ECharts 需要的格式
    taskStatusChartData: (state) => ({
      legend: state.taskStatusDistribution.map(item => item.name),
      series: [{ data: state.taskStatusDistribution }]
    }),
    taskTypeChartData: (state) => ({
      xAxis: state.taskTypeDistribution.map(item => item.name),
      series: [{ data: state.taskTypeDistribution.map(item => item.value) }]
    })
  },
  actions: {
    async fetchDashboardData(params) {
      try {
        const { data } = await dashboardApi.getDashboardData(params);
        // 更新整个 state
        this.$patch(data);
        return data;
      } catch (error) {
        console.error('获取仪表盘数据失败:', error);
        throw error;
      }
    }
  }
});

8. 样式设计

.dashboard-page {
  padding: 24px;
  
  .page-header {
    margin-bottom: 24px;
  }
  
  .dashboard-filters {
    margin-bottom: 24px;
  }
  
  .summary-cards {
    margin-bottom: 20px;
  }
  
  .charts-section {
    margin-bottom: 20px;
  }

  .el-card {
    border: none;
    border-radius: 8px;
  }

  .loading-state,
  .error-state {
    padding: 40px;
  }
}

// 响应式样式
@media screen and (max-width: 768px) {
  .dashboard-page {
    padding: 16px;
    
    .summary-cards .el-col,
    .charts-section .el-col {
      margin-bottom: 20px;
    }
  }
}

9. 测试用例

  1. 单元测试

    • 测试 getStatusTagType 辅助函数的正确性。
    • 测试 Pinia store中getters的数据转换逻辑。
    • 测试 StatisticCardPieChartBarChart 组件的渲染是否正确。
  2. 集成测试

    • 测试页面在加载、成功、失败状态下的显示。
    • 测试日期选择器筛选功能是否能正确触发数据重新加载。
    • 模拟 API 调用,验证数据是否正确渲染到页面上。

10. 性能优化

  1. 懒加载图表: 使用 v-if 或动态导入,确保图表库只在需要时加载和渲染。
  2. 骨架屏: 在数据加载时使用骨架屏,提升用户体验。
  3. 数据缓存: 对不经常变化的数据(如任务类型),可以在 Pinia store 中设置缓存策略,避免重复请求。
  4. 节流/防抖: 对日期选择器的 change 事件使用节流或防抖,防止用户快速操作导致频繁的 API 请求。