| | |
| | | } from '#/api/bi/warehouseCoordinate'; |
| | | |
| | | import ClockWidget from '../dashboard/modules/ClockWidget.vue'; |
| | | import RealtimeTable from '../dashboard/modules/RealtimeTable.vue'; |
| | | import { |
| | | getChinaMapOptions, |
| | | getWarehouseAreaLineOptions, |
| | |
| | | const charts = ref<BiDashboardApi.ChartItem[]>([]); |
| | | const loading = ref(true); |
| | | const lastUpdateTime = ref(''); |
| | | let fetching = false; |
| | | let disposed = false; |
| | | |
| | | async function loadData() { |
| | | async function loadData(background = false) { |
| | | if (fetching || disposed) return false; |
| | | fetching = true; |
| | | try { |
| | | loading.value = true; |
| | | if (!background) loading.value = true; |
| | | const data = await getDashboardData('warehouse'); |
| | | if (disposed) return false; |
| | | charts.value = data.length > 0 ? data : []; |
| | | lastUpdateTime.value = new Date().toLocaleTimeString('zh-CN', { hour12: false }); |
| | | return true; |
| | | } catch { |
| | | charts.value = []; |
| | | if (!background && !disposed) charts.value = []; |
| | | return false; |
| | | } finally { |
| | | loading.value = false; |
| | | await nextTick(); |
| | | resizeAllCharts(); |
| | | fetching = false; |
| | | if (!disposed) { |
| | | loading.value = false; |
| | | await nextTick(); |
| | | if (!disposed) resizeAllCharts(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // ======== 表格数据 ======== |
| | | const tableData = computed(() => tableChart.value?.data || []); |
| | | const tableColumns = computed(() => { |
| | | if (tableData.value.length === 0) return []; |
| | | return Object.keys(tableData.value[0] || {}); |
| | | }); |
| | | |
| | | |
| | | // ======== 库存预警 ======== |
| | | const warningList = computed(() => { |
| | |
| | | function setupTimer() { |
| | | clearTimer(); |
| | | refreshTimer = setInterval(async () => { |
| | | await loadData(); |
| | | if (document.hidden || editMode.value || disposed) return; |
| | | if (!(await loadData(true))) return; |
| | | await nextTick(); |
| | | await renderAllCharts(); |
| | | if (editMode.value) await nextTick().then(() => attachMapListeners()); |
| | |
| | | resizeObserver = new ResizeObserver(() => resizeAllCharts()); |
| | | if (dashboardRef.value) resizeObserver.observe(dashboardRef.value); |
| | | await loadCoordinates(); |
| | | if (disposed) return; |
| | | await loadData(); |
| | | if (disposed) return; |
| | | await nextTick(); |
| | | await renderAllCharts(); |
| | | setupTimer(); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | disposed = true; |
| | | document.removeEventListener('fullscreenchange', onFullscreenChange); |
| | | resizeObserver?.disconnect(); |
| | | clearTimer(); |
| | |
| | | 最近入库记录 |
| | | <span class="wh-table-badge">实时滚动</span> |
| | | </div> |
| | | <div v-if="tableData.length > 0" class="wh-table-wrap"> |
| | | <table class="wh-table"> |
| | | <thead> |
| | | <tr> |
| | | <th v-for="col in tableColumns" :key="col">{{ col }}</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(row, ri) in tableData" |
| | | :key="ri" |
| | | :style="{ animationDelay: `${ri * 50}ms` }" |
| | | > |
| | | <td v-for="col in tableColumns" :key="col"> |
| | | {{ getCellValue(row, col) }} |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | <RealtimeTable |
| | | v-if="tableData.length > 0" |
| | | :rows="tableData" |
| | | :format-cell="getCellValue" |
| | | /> |
| | | <Empty v-else description="暂无数据" /> |
| | | </div> |
| | | </template> |
| | |
| | | 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); |
| | | } |
| | | |
| | | .wh-table-wrap { |
| | | overflow-x: auto; 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); |
| | | } |
| | | |
| | | .wh-table { |
| | | width: 100%; border-collapse: collapse; font-size: 12px; |
| | | } |
| | | |
| | | .wh-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; |
| | | } |
| | | |
| | | .wh-table tbody td { |
| | | padding: 9px 14px; color: var(--text-primary); |
| | | border-bottom: 1px solid rgba(0, 229, 255, 0.04); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .wh-table tbody tr { |
| | | transition: all 0.25s; |
| | | opacity: 0; |
| | | animation: row-fade-in 0.45s ease forwards; |
| | | } |
| | | |
| | | .wh-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); } |
| | | } |
| | | |
| | | /* ======== 全屏 ======== */ |