You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
10 KiB
438 lines
10 KiB
<template>
|
|
<div class="dashboard-container">
|
|
<!-- 标题区域 -->
|
|
<div class="header-section">
|
|
<div class="header-info left">
|
|
<!-- <div class="info-item">
|
|
<span class="status-indicator" :class="isLayoutReady ? 'online' : 'loading'"></span>
|
|
<span>{{ isLayoutReady ? '系统运行正常' : '系统初始化中' }}</span>
|
|
</div> -->
|
|
</div>
|
|
<h1 class="main-title">{{ config?.screen?.title || '智慧活畜交易大数据中心' }}</h1>
|
|
<div class="header-info right">
|
|
<div class="info-item">
|
|
<span class="current-time">{{ currentTime || '--:--:--' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主要内容区域 -->
|
|
<div v-if="isLayoutReady" class="main-content">
|
|
<!-- 左侧区域 - 分为左和左中两列 -->
|
|
<div class="left-section">
|
|
<!-- 左列 -->
|
|
<div class="left-column">
|
|
<div class="left-top">
|
|
<RealTimeStats />
|
|
</div>
|
|
<div class="left-bottom">
|
|
<YakTradingData />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 左中列 -->
|
|
<div class="left-center-column">
|
|
<div class="left-center-top">
|
|
<ComprehensiveSalesStats />
|
|
</div>
|
|
<div class="left-center-bottom">
|
|
<YakPriceTrend />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 中央区域 -->
|
|
<div class="center-section">
|
|
<div class="map-container">
|
|
<ChinaMap />
|
|
<!-- <ScrollingAnnouncement /> -->
|
|
<!-- <div class="floating-table">
|
|
<TransactionDetails />
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧区域 - 分为右中和右两列 -->
|
|
<div class="right-section">
|
|
<!-- 当供应信息展开时,整个右侧区域显示供应信息 -->
|
|
<div v-if="isSupplyExpanded" class="expanded-supply-container">
|
|
<SupplyDemandData :force-expanded="true" @expand-change="handleSupplyExpand" />
|
|
</div>
|
|
|
|
<!-- 正常状态下的右侧布局 -->
|
|
<template v-else>
|
|
<!-- 右中列 -->
|
|
<div class="right-center-column">
|
|
<div class="right-center-top">
|
|
<ExchangeMonitor />
|
|
</div>
|
|
<div class="right-center-middle">
|
|
<YakSalesTypeStats />
|
|
</div>
|
|
<div class="right-center-bottom">
|
|
<PurchaserAnalysis />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右列 -->
|
|
<div class="right-column">
|
|
<div class="right-top">
|
|
<MarketEnvironmentMonitor />
|
|
</div>
|
|
<div class="right-middle">
|
|
<MarketRealtimeMonitor />
|
|
</div>
|
|
<div class="right-bottom">
|
|
<SupplyDemandData :force-expanded="false" @expand-change="handleSupplyExpand" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 加载状态 -->
|
|
<div v-else class="loading-container">
|
|
<div class="loading-content">
|
|
<div class="loading-spinner"></div>
|
|
<div class="loading-text">正在初始化布局...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
import { configManager } from './utils/config.js'
|
|
import { dataManager } from './utils/dataManager.js'
|
|
import { dataUpdater } from './utils/dataUpdater.js'
|
|
|
|
// 导入组件
|
|
import RealTimeStats from './components/RealTimeStats.vue'
|
|
import YakTradingData from './components/YakTradingData.vue'
|
|
import ComprehensiveSalesStats from './components/ComprehensiveSalesStats.vue'
|
|
import YakPriceTrend from './components/YakPriceTrend.vue'
|
|
import ChinaMap from './components/ChinaMap.vue'
|
|
import ScrollingAnnouncement from './components/ScrollingAnnouncement.vue'
|
|
import TransactionDetails from './components/TransactionDetails.vue'
|
|
import ExchangeMonitor from './components/ExchangeMonitor.vue'
|
|
import YakSalesTypeStats from './components/YakSalesTypeStats.vue'
|
|
import PurchaserAnalysis from './components/PurchaserAnalysis.vue'
|
|
import MarketEnvironmentMonitor from './components/MarketEnvironmentMonitor.vue'
|
|
import MarketRealtimeMonitor from './components/MarketRealtimeMonitor.vue'
|
|
import SupplyDemandData from './components/SupplyDemandData.vue'
|
|
|
|
// 响应式数据
|
|
const config = ref(null)
|
|
const currentTime = ref('')
|
|
const isLayoutReady = ref(false) // 控制页面渲染时机
|
|
const isSupplyExpanded = ref(false) // 控制供应信息组件是否展开
|
|
|
|
// 更新时间
|
|
const updateTime = () => {
|
|
const now = new Date()
|
|
currentTime.value = now.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
})
|
|
}
|
|
|
|
// 处理供应信息组件展开/收缩
|
|
const handleSupplyExpand = (expanded) => {
|
|
isSupplyExpanded.value = expanded
|
|
}
|
|
|
|
// 初始化配置和时间
|
|
let timeTimer = null
|
|
|
|
onMounted(async () => {
|
|
// 立即开始更新时间
|
|
updateTime()
|
|
timeTimer = setInterval(updateTime, 1000)
|
|
|
|
try {
|
|
// 加载配置
|
|
console.log('App.vue: 开始加载配置')
|
|
config.value = await configManager.loadConfig()
|
|
console.log('App.vue: 配置加载完成', config.value)
|
|
|
|
// 设置CSS变量
|
|
console.log('App.vue: 设置CSS变量')
|
|
configManager.setCSSVariables()
|
|
|
|
// 等待CSS变量生效和DOM更新
|
|
await nextTick()
|
|
|
|
// 再等待一帧确保样式完全应用
|
|
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
console.log('App.vue: CSS变量已生效')
|
|
|
|
// 加载数据配置
|
|
console.log('App.vue: 开始加载数据配置')
|
|
const loadedData = await dataManager.loadData()
|
|
console.log('App.vue: 数据配置加载完成', loadedData)
|
|
|
|
// 标记布局准备完成,开始渲染组件
|
|
console.log('App.vue: 布局准备完成,开始渲染组件')
|
|
isLayoutReady.value = true
|
|
|
|
console.log('Dashboard 完全初始化完成')
|
|
} catch (error) {
|
|
console.error('Failed to initialize dashboard:', error)
|
|
// 使用默认配置
|
|
config.value = configManager.getDefaultConfig()
|
|
configManager.setCSSVariables()
|
|
|
|
// 等待DOM更新后显示页面
|
|
await nextTick()
|
|
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
isLayoutReady.value = true
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timeTimer) {
|
|
clearInterval(timeTimer)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 标题区域 */
|
|
.header-section {
|
|
height: 84px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0 40px;
|
|
background: url('./images/大标题背景.png') center/contain no-repeat;
|
|
background-size: auto 84px;
|
|
border: 1px solid rgba(64, 158, 255, 0.3);
|
|
border-radius: 12px;
|
|
backdrop-filter: blur(10px);
|
|
position: relative;
|
|
z-index: 10;
|
|
flex-shrink: 0;
|
|
background-size: 100% 84px;
|
|
}
|
|
|
|
.main-title {
|
|
font-size: 36px;
|
|
font-weight: 700;
|
|
background: linear-gradient(180deg, #fff 50%, #44c1ff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
text-align: center;
|
|
letter-spacing: 3px;
|
|
text-shadow: 0 0 20px rgba(64, 158, 255, 0.5);
|
|
flex: 2;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
|
|
}
|
|
|
|
.header-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
flex: 1;
|
|
|
|
&.left {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
&.right {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
color: #a0a8b8;
|
|
}
|
|
|
|
.current-time {
|
|
font-family: 'Courier New', monospace;
|
|
font-weight: bold;
|
|
color: #409EFF;
|
|
}
|
|
|
|
/* 主要内容区域 */
|
|
.main-content {
|
|
flex: 1;
|
|
display: flex;
|
|
gap: 16px;
|
|
min-height: 0;
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
/* 左侧区域布局 */
|
|
.left-section {
|
|
flex: 1;
|
|
display: flex;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.left-column {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.left-center-column {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.left-top,
|
|
.left-bottom,
|
|
.left-center-top,
|
|
.left-center-bottom {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* 中央区域布局 */
|
|
.center-section {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
|
|
.map-container {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.floating-table {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
right: 20px;
|
|
height: 200px;
|
|
background: rgba(26, 31, 46, 0.95);
|
|
border: 1px solid rgba(64, 158, 255, 0.4);
|
|
border-radius: 8px;
|
|
backdrop-filter: blur(10px);
|
|
z-index: 100;
|
|
}
|
|
|
|
/* 右侧区域布局 */
|
|
.right-section {
|
|
flex: 1;
|
|
display: flex;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.right-center-column {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.right-column {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.right-center-top,
|
|
.right-center-middle,
|
|
.right-center-bottom,
|
|
.right-top,
|
|
.right-middle,
|
|
.right-bottom {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* 展开的供应信息容器 */
|
|
.expanded-supply-container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* 状态指示器动画 */
|
|
@keyframes pulse {
|
|
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
50% {
|
|
opacity: 0.7;
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
|
|
.status-indicator.online {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.status-indicator.loading {
|
|
background: #E6A23C;
|
|
box-shadow: 0 0 8px rgba(230, 162, 60, 0.6);
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
/* 加载状态样式 */
|
|
.loading-container {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 400px;
|
|
}
|
|
|
|
.loading-content {
|
|
text-align: center;
|
|
color: #409EFF;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid rgba(64, 158, 255, 0.3);
|
|
border-top: 3px solid #409EFF;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 16px;
|
|
}
|
|
|
|
.loading-text {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: #a0a8b8;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
</style> |