From c2e8526f8f857be5e3a8c6f2b39c5efd424de33e Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 15 十月 2026 11:50:24 +0800
Subject: [PATCH] refactor(auth): 优化认证逻辑和界面组件结构
---
src/views/bi/dashboard/index.vue | 136 ++++++++++-----------------------------------
1 files changed, 31 insertions(+), 105 deletions(-)
diff --git a/src/views/bi/dashboard/index.vue b/src/views/bi/dashboard/index.vue
index 98072c2..87299c4 100644
--- a/src/views/bi/dashboard/index.vue
+++ b/src/views/bi/dashboard/index.vue
@@ -12,6 +12,7 @@
import ChartCard from './modules/ChartCard.vue';
import ClockWidget from './modules/ClockWidget.vue';
+import RealtimeTable from './modules/RealtimeTable.vue';
import { DASHBOARD_META, getDashboardCode, getDefaultCharts } from './data';
defineOptions({ name: 'BiDashboard' });
@@ -24,26 +25,38 @@
const loading = ref(true);
const lastUpdateTime = ref('');
let dataRefreshTimer: ReturnType<typeof setInterval> | undefined;
+let requestSequence = 0;
+let fetching = false;
+let disposed = false;
async function loadData(background = false) {
- if (!meta.value) return;
+ if (!meta.value || disposed || (background && fetching)) return;
+ const sequence = ++requestSequence;
+ const code = meta.value.code;
+ fetching = true;
if (!background && dataRefreshTimer) clearInterval(dataRefreshTimer);
try {
if (!background) loading.value = true;
- const data = await getDashboardData(meta.value.code);
+ const data = await getDashboardData(code);
+ if (disposed || sequence !== requestSequence) return;
charts.value = data.length > 0 ? data : getDefaultCharts();
lastUpdateTime.value = new Date().toLocaleTimeString('zh-CN', { hour12: false });
} catch {
- if (!background) charts.value = getDefaultCharts();
+ if (!background && !disposed && sequence === requestSequence) charts.value = getDefaultCharts();
} finally {
- loading.value = false;
- await nextTick();
- resizeAllCharts();
- if (!background) {
- 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);
+ if (!disposed && sequence === requestSequence) {
+ fetching = false;
+ loading.value = false;
+ await nextTick();
+ if (!disposed && sequence === requestSequence) {
+ resizeAllCharts();
+ if (!background) {
+ 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);
+ }
+ }
}
}
}
@@ -161,11 +174,6 @@
return val;
}
-// 澶у睆琛ㄦ牸銆屽疄鏃舵粴鍔ㄣ�嶏細鏁版嵁澶嶅埗涓�浠斤紝閰嶅悎 CSS marquee 鍋氭棤缂濈旱鍚戞粴鍔�
-function duplicateRows(rows?: Record<string, unknown>[]): Record<string, unknown>[] {
- return rows ? [...rows, ...rows] : [];
-}
-
// ======== 鐪嬫澘涓婚鑹� ========
const dashboardAccentColors: Record<string, string> = {
'purchase-sales': '#6366f1',
@@ -205,6 +213,8 @@
loadData();
});
onBeforeUnmount(() => {
+ disposed = true;
+ requestSequence++;
if (dataRefreshTimer) clearInterval(dataRefreshTimer);
document.removeEventListener('fullscreenchange', onFullscreenChange);
resizeObserver?.disconnect();
@@ -311,30 +321,11 @@
<span class="table-header-title">{{ chart.name }}</span>
<span class="table-badge">瀹炴椂婊氬姩</span>
</div>
- <div v-if="chart.data && chart.data.length > 0" class="table-wrap">
- <table class="data-table">
- <thead>
- <tr>
- <th
- v-for="key in Object.keys(chart.data[0] || {})"
- :key="key"
- >{{ key }}</th>
- </tr>
- </thead>
- <tbody class="marquee-tbody">
- <tr
- v-for="(row, ri) in duplicateRows(chart.data)"
- :key="ri"
- :style="{ animationDelay: `${ri * 50}ms` }"
- >
- <td
- v-for="key in Object.keys(chart.data[0] || {})"
- :key="key"
- >{{ getCellValue(row, key) }}</td>
- </tr>
- </tbody>
- </table>
- </div>
+ <RealtimeTable
+ v-if="chart.data && chart.data.length > 0"
+ :rows="chart.data"
+ :format-cell="getCellValue"
+ />
<Empty v-else description="鏆傛棤鏁版嵁" />
</div>
</template>
@@ -612,71 +603,6 @@
padding: 2px 8px; border-radius: 10px;
background: rgba(0,255,136,0.08); color: var(--green);
border: 1px solid rgba(0,255,136,0.15);
-}
-
-.table-wrap {
- overflow-x: auto;
- overflow-y: hidden;
- max-height: 320px;
- border-radius: 8px;
- background: rgba(4, 14, 36, 0.55);
- backdrop-filter: blur(6px);
- -webkit-backdrop-filter: blur(6px);
- border: 1px solid rgba(0, 229, 255, 0.08);
-}
-
-.data-table {
- width: 100%; border-collapse: collapse; font-size: 12px;
-}
-
-.data-table thead {
- position: sticky;
- top: 0;
- z-index: 2;
-}
-
-.data-table thead th {
- background: rgba(6, 18, 42, 0.8);
- color: var(--text-secondary);
- font-weight: 600; font-size: 10px;
- text-transform: uppercase; letter-spacing: 0.8px;
- padding: 10px 14px; text-align: left;
- border-bottom: 1px solid rgba(0, 229, 255, 0.12);
- white-space: nowrap;
-}
-
-.data-table tbody td {
- padding: 9px 14px; color: var(--text-primary);
- border-bottom: 1px solid rgba(0, 229, 255, 0.04);
- white-space: nowrap;
-}
-
-.data-table tbody tr {
- transition: all 0.25s;
- opacity: 0;
- animation: row-fade-in 0.45s ease forwards;
-}
-
-.data-table tbody tr:hover {
- background: rgba(0, 229, 255, 0.06);
- box-shadow: inset 0 0 20px rgba(0, 229, 255, 0.03);
-}
-
-@keyframes row-fade-in {
- from { opacity: 0; transform: translateY(6px); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-/* 澶у睆琛ㄦ牸銆屽疄鏃舵粴鍔ㄣ�嶏細tbody 澶嶅埗涓�浠藉悗 translateY(-50%) 鏃犵紳寰幆 */
-.marquee-tbody {
- animation: table-marquee 15s linear infinite;
-}
-.marquee-tbody:hover {
- animation-play-state: paused;
-}
-@keyframes table-marquee {
- from { transform: translateY(0); }
- to { transform: translateY(-50%); }
}
/* ======== 绌虹姸鎬� ======== */
--
Gitblit v1.9.3