From 973574fd62a1a130a1c229f812e70cfe3763b48e Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 14 十月 2026 09:54:10 +0800
Subject: [PATCH] feat(bi): 增加CRM数据展示功能并优化图表组件

---
 src/views/bi/dashboard/index.vue |   38 +++++++++++++++++++++++++++++---------
 1 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/src/views/bi/dashboard/index.vue b/src/views/bi/dashboard/index.vue
index c1bbf5c..70b9437 100644
--- a/src/views/bi/dashboard/index.vue
+++ b/src/views/bi/dashboard/index.vue
@@ -23,27 +23,41 @@
 const charts = ref<BiDashboardApi.ChartItem[]>([]);
 const loading = ref(true);
 const lastUpdateTime = ref('');
+let dataRefreshTimer: ReturnType<typeof setInterval> | undefined;
 
-async function loadData() {
+async function loadData(background = false) {
   if (!meta.value) return;
+  if (!background && dataRefreshTimer) clearInterval(dataRefreshTimer);
   try {
-    loading.value = true;
+    if (!background) loading.value = true;
     const data = await getDashboardData(meta.value.code);
     charts.value = data.length > 0 ? data : getDefaultCharts();
     lastUpdateTime.value = new Date().toLocaleTimeString('zh-CN', { hour12: false });
   } catch {
-    charts.value = getDefaultCharts();
+    if (!background) charts.value = getDefaultCharts();
   } finally {
     loading.value = false;
     await nextTick();
     resizeAllCharts();
+    if (!background && dashboardCode.value === 'purchase-sales') {
+      const intervals = charts.value.map((chart) => chart.refreshInterval).filter((interval) => interval > 0);
+      if (intervals.length) dataRefreshTimer = setInterval(() => {
+        if (!loading.value && document.visibilityState === 'visible') loadData(true);
+      }, Math.max(15, Math.min(...intervals)) * 1000);
+    }
   }
 }
 
 // ======== 鍒嗙被 ========
-const numberCards = computed(() => charts.value.filter((c) => c.chartType === 'number'));
-const tableCharts = computed(() => charts.value.filter((c) => c.chartType === 'table'));
-const echartsCharts = computed(() => charts.value.filter((c) => !['number', 'table'].includes(c.chartType)));
+const displayCharts = computed(() => {
+  if (dashboardCode.value !== 'purchase-sales') return charts.value;
+  return [...charts.value].sort((a, b) =>
+    Number(b.chartOptions?.dashboardSection === 'crm') - Number(a.chartOptions?.dashboardSection === 'crm'),
+  );
+});
+const numberCards = computed(() => displayCharts.value.filter((c) => c.chartType === 'number'));
+const tableCharts = computed(() => displayCharts.value.filter((c) => c.chartType === 'table'));
+const echartsCharts = computed(() => displayCharts.value.filter((c) => !['number', 'table'].includes(c.chartType)));
 
 // ======== 鍥捐〃 Grid 鍒楄法搴� ========
 function isFullWidth(chart: BiDashboardApi.ChartItem): boolean {
@@ -102,6 +116,7 @@
     const progress = Math.min((currentTime - startTime) / duration, 1);
     const eased = 1 - (1 - progress) ** 3;
     el.textContent = (end * eased).toFixed(decimals);
+    if (progress === 1) el.textContent = formatNumber(end);
     if (progress < 1) requestAnimationFrame(update);
   }
   requestAnimationFrame(update);
@@ -110,7 +125,7 @@
 function maybeAnimate(el: HTMLElement, cardId: number, value: number) {
   if (animatedIds.has(cardId)) return;
   animatedIds.add(cardId);
-  animateValue(el, value, 0);
+  animateValue(el, value, Number.isInteger(value) ? 0 : 2);
 }
 
 watch(() => charts.value, () => {
@@ -157,6 +172,10 @@
 
 // ======== KPI 鍥炬爣/棰滆壊 ========
 function getKpiIcon(name: string): string {
+  if (name.includes('瀹㈡埛')) return 'lucide:users';
+  if (name.includes('鍟嗘満')) return 'lucide:target';
+  if (name.includes('鍥炴')) return 'lucide:wallet';
+  if (name.includes('鎶ヤ环')) return 'lucide:receipt-text';
   if (name.includes('閲囪喘')) return 'lucide:shopping-cart';
   if (name.includes('閿�鍞�')) return 'lucide:trending-up';
   if (name.includes('鍞悗')) return 'lucide:headphones';
@@ -181,6 +200,7 @@
   loadData();
 });
 onBeforeUnmount(() => {
+  if (dataRefreshTimer) clearInterval(dataRefreshTimer);
   document.removeEventListener('fullscreenchange', onFullscreenChange);
   resizeObserver?.disconnect();
 });
@@ -249,7 +269,7 @@
             <IconifyIcon :icon="getKpiIcon(card.name)" />
           </div>
           <div class="kpi-body">
-            <span class="kpi-label">{{ card.name }}</span>
+            <span class="kpi-label" :title="card.name">{{ card.name }}</span>
             <span
               class="kpi-value"
               :ref="(el: unknown) => {
@@ -514,7 +534,7 @@
 
 .kpi-label {
   font-size: 11px; color: var(--text-secondary); letter-spacing: 0.3px;
-  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+  white-space: normal; overflow-wrap: anywhere; line-height: 1.4;
 }
 
 .kpi-value {

--
Gitblit v1.9.3