Parcourir la source

重构DashboardView中的图表渲染逻辑,添加图表销毁和新画布创建功能,确保数据完整性并优化错误处理,提升用户体验。

wuyi il y a 2 mois
Parent
commit
4592a3a10f
1 fichiers modifiés avec 130 ajouts et 125 suppressions
  1. 130 125
      src/views/DashboardView.vue

+ 130 - 125
src/views/DashboardView.vue

@@ -178,6 +178,66 @@ const loadTeamStats = async () => {
   }
 }
 
+// 销毁图表实例
+const destroyChart = () => {
+  if (chartInstance.value) {
+    try {
+      chartInstance.value.destroy()
+    } catch (e) {
+      console.warn('销毁图表实例失败:', e)
+    }
+    chartInstance.value = null
+  }
+}
+
+// 创建图表配置
+const createChartConfig = (labels, datasets) => {
+  return {
+    type: 'line',
+    data: {
+      labels,
+      datasets
+    },
+    options: {
+      responsive: true,
+      maintainAspectRatio: false,
+      plugins: {
+        legend: {
+          position: 'top',
+          labels: {
+            usePointStyle: true,
+            boxWidth: 10
+          }
+        },
+        tooltip: {
+          mode: 'index',
+          intersect: false,
+          callbacks: {
+            label: function(context) {
+              let label = context.dataset.label || '';
+              if (label) {
+                label += ': ';
+              }
+              if (context.parsed.y !== null) {
+                label += context.parsed.y.toFixed(2);
+              }
+              return label;
+            }
+          }
+        }
+      },
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      },
+      animation: {
+        duration: 300
+      }
+    }
+  }
+}
+
 // 加载收入统计数据
 const loadIncomeStats = async () => {
   try {
@@ -200,24 +260,16 @@ const loadIncomeStats = async () => {
     }
 
     // 获取数据前先销毁旧图表
-    if (chartInstance.value) {
-      try {
-        chartInstance.value.destroy()
-        chartInstance.value = null
-      } catch (e) {
-        console.warn('销毁旧图表实例失败:', e)
-      }
-    }
+    destroyChart()
 
     const data = await getIncomeStatistics(startDate, endDate, userId)
     
-    // 确保数据结构完整
+    // 确保数据结构完整并转换为数字类型
     incomeStats.value = {
       dates: data?.dates || [],
-      total: data?.total || [],
-      totalTip: data?.totalTip || [],
-      totalCommission: data?.totalCommission || [],
-      agents: data?.agents || []
+      total: data?.total?.map(v => Number(v) || 0) || [],
+      totalTip: data?.totalTip?.map(v => Number(v) || 0) || [],
+      totalCommission: data?.totalCommission?.map(v => Number(v) || 0) || []
     }
 
     // 渲染图表
@@ -229,141 +281,93 @@ const loadIncomeStats = async () => {
   }
 }
 
-// 重置画布
-const resetCanvas = () => {
+// 创建新画布
+const createCanvas = () => {
   const chartContainer = document.querySelector('.chart-wrapper')
-  if (!chartContainer) return null
-
-  // 移除旧画布
-  const oldCanvas = document.getElementById('incomeChart')
-  if (oldCanvas) {
-    oldCanvas.remove()
+  if (!chartContainer) {
+    console.error('找不到图表容器')
+    return null
   }
-
+  
+  // 清空容器
+  chartContainer.innerHTML = ''
+  
   // 创建新画布
-  const newCanvas = document.createElement('canvas')
-  newCanvas.id = 'incomeChart'
-  newCanvas.height = 300
-  chartContainer.appendChild(newCanvas)
-
-  return newCanvas
+  const canvas = document.createElement('canvas')
+  canvas.id = 'incomeChart'
+  canvas.height = 300
+  chartContainer.appendChild(canvas)
+  
+  return canvas
 }
 
 // 渲染图表
 const renderChart = () => {
-  if (!incomeStats.value) {
-    console.warn('没有收入统计数据,跳过图表渲染')
-    return
-  }
-
-  // 重置画布
-  const canvas = resetCanvas()
-  if (!canvas) {
-    console.error('无法创建图表画布')
+  // 检查数据有效性
+  if (!incomeStats.value || !incomeStats.value.dates || !incomeStats.value.dates.length) {
+    console.warn('没有有效的收入统计数据')
     return
   }
-
-  // 获取上下文
+  
+  // 创建新画布
+  const canvas = createCanvas()
+  if (!canvas) return
+  
+  // 获取绘图上下文
   const ctx = canvas.getContext('2d')
   if (!ctx) {
-    console.error('无法获取图表上下文')
+    console.error('无法获取图上下文')
     return
   }
-
-  // 准备数据并验证
-  const labels = incomeStats.value.dates || []
   
-  // 验证数据完整性
-  if (!labels.length) {
-    console.warn('图表日期数据不完整,跳过渲染')
-    return
-  }
-
-  // 准备数据集
-  const datasets = []
+  // 准备数据
+  const { dates, total, totalTip, totalCommission } = incomeStats.value
   
-  // 添加总收入数据集
-  const totalData = incomeStats.value.total || []
-  if (totalData.length > 0) {
-    datasets.push({
+  // 创建数据集
+  const datasets = [
+    {
       label: '总收入',
-      data: [...totalData],
+      data: total,
       borderColor: '#2563eb',
-      backgroundColor: 'rgba(37, 99, 235, 0.1)',
+      backgroundColor: 'rgba(37, 99, 235, 0.05)',
       fill: true,
-      tension: 0.4
-    })
-  }
-
-  // 添加打赏收入数据集
-  const tipData = incomeStats.value.totalTip || []
-  datasets.push({
-    label: '打赏收入',
-    data: [...tipData],
-    borderColor: '#10b981',
-    backgroundColor: 'rgba(16, 185, 129, 0.1)',
-    fill: true,
-    tension: 0.4
-  })
-
-  // 添加返佣收入数据集
-  const commissionData = incomeStats.value.totalCommission || []
-  datasets.push({
-    label: '返佣收入',
-    data: [...commissionData],
-    borderColor: '#f59e0b',
-    backgroundColor: 'rgba(245, 158, 11, 0.1)',
-    fill: true,
-    tension: 0.4
-  })
-
-  // 如果没有有效数据集,跳过渲染
-  if (datasets.length === 0) {
-    console.warn('没有有效的数据集,跳过图表渲染')
-    return
-  }
-
-  // 使用requestAnimationFrame确保DOM已更新
-  requestAnimationFrame(() => {
+      tension: 0.3
+    },
+    {
+      label: '打赏收入',
+      data: totalTip,
+      borderColor: '#10b981',
+      backgroundColor: 'rgba(16, 185, 129, 0.05)',
+      fill: true,
+      tension: 0.3
+    },
+    {
+      label: '返佣收入',
+      data: totalCommission,
+      borderColor: '#f59e0b',
+      backgroundColor: 'rgba(245, 158, 11, 0.05)',
+      fill: true,
+      tension: 0.3
+    }
+  ]
+  
+  // 使用setTimeout确保DOM已更新
+  setTimeout(() => {
     try {
-      // 创建图表配置
-      const config = {
-        type: 'line',
-        data: {
-          labels: [...labels],
-          datasets: datasets
-        },
-        options: {
-          responsive: true,
-          maintainAspectRatio: false,
-          plugins: {
-            legend: {
-              position: 'top'
-            },
-            tooltip: {
-              mode: 'index',
-              intersect: false
-            }
-          },
-          scales: {
-            y: {
-              beginAtZero: true
-            }
-          }
-        }
-      }
-
-      // 创建图表实例
+      // 创建图表
+      const config = createChartConfig(dates, datasets)
       chartInstance.value = new Chart(ctx, config)
     } catch (error) {
       console.error('图表渲染失败:', error)
       chartInstance.value = null
     }
-  })
+  }, 0)
 }
 
 // 监听团队选择变化
 watch(selectedChartTeamId, () => {
+  // 先销毁当前图表,再加载新数据
+  destroyChart()
   loadIncomeStats()
 })
 
@@ -373,15 +377,16 @@ onMounted(async () => {
     await teamStore.loadTeams()
   }
   await loadTeamStats()
-  await loadIncomeStats()
+  
+  // 延迟加载图表数据,确保DOM已渲染
+  setTimeout(() => {
+    loadIncomeStats()
+  }, 100)
 })
 
 // 组件卸载时清理图表实例
 onUnmounted(() => {
-  if (chartInstance.value) {
-    chartInstance.value.destroy()
-    chartInstance.value = null
-  }
+  destroyChart()
 })
 </script>