| | |
| | | <template> |
| | | <div class="chart_left" style="margin-left: 20px;"> |
| | | <div class="titles"> |
| | | <el-icon class="icons" :size="24" color="#409EFF"> |
| | | <Checked /> |
| | | </el-icon> |
| | | ä»»å¡è¿è¡ä¸ |
| | | <div class="dashboard-container"> |
| | | <div class="overview-section"> |
| | | <div class="data-overview"> |
| | | <h3>æ°æ®æ»è§</h3> |
| | | <div class="metrics"> |
| | | <div class="metric-item" v-for="(item, index) in dataMetrics" :key="index"> |
| | | <div class="metric-name">{{ item.name }}</div> |
| | | <div class="metric-value">{{ item.value }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="charts-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="12"> |
| | | <div class="chart-container"> |
| | | <h3>åºåºé</h3> |
| | | <div v-if="loading" class="loading">å è½½ä¸...</div> |
| | | <div v-else-if="error" class="error">æ°æ®å 载失败</div> |
| | | <div v-else-if="!chartData.outbound.values.length" class="no-data">ææ åºåºæ°æ®</div> |
| | | <div v-else ref="outboundChart" class="chart"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :lg="12"> |
| | | <div class="chart-container"> |
| | | <h3>å
¥åºé</h3> |
| | | <div v-if="loading" class="loading">å è½½ä¸...</div> |
| | | <div v-else-if="error" class="error">æ°æ®å 载失败</div> |
| | | <div v-else-if="!chartData.inbound.values.length" class="no-data">ææ å
¥åºæ°æ®</div> |
| | | <div v-else ref="inboundChart" class="chart"></div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div style="margin-top: 20px;"></div> |
| | | <div class="item_center"> |
| | | <Task-List :data="chartData" :options="chartOptions" /> |
| | | </div> |
| | | </div> |
| | | <div class="chart_left" style="margin-left: 20px;"> |
| | | <div class="titles"> |
| | | <el-icon class="icons" :size="24" color="#409EFF"> |
| | | <Checked /> |
| | | </el-icon> |
| | | ä»»å¡è¿è¡ä¸ |
| | | </div> |
| | | <div style="margin-top: 20px;"></div> |
| | | <div class="item_center"> |
| | | <Task-List :data="chartData" :options="chartOptions" /> |
| | | <div class="charts-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="chart-container"> |
| | | <h3>æåºå
¥åºé</h3> |
| | | <div v-if="loading" class="loading">å è½½ä¸...</div> |
| | | <div v-else-if="error" class="error">æ°æ®å 载失败</div> |
| | | <div v-else-if="!chartData.monthData.inValue.length || !chartData.monthData.outValue.length" class="no-data"> |
| | | ææ åºåºæ°æ®</div> |
| | | <div v-else ref="monthDataChart" class="chart1"></div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import TaskList from '../components/index/TaskList.vue'; |
| | | import http from '../api/http.js'; |
| | | import { ref, onMounted, onUnmounted, watch } from 'vue'; |
| | | const taskListRef = ref(null); |
| | | // ç¤ºä¾æ°æ® |
| | | const chartData = ref([ |
| | | // { value: 103, name: '已忶', itemStyle: { color: '#FF6B6B' } }, |
| | | // { value: 735, name: '已宿', itemStyle: { color: '#4ECDC4' } }, |
| | | ]); |
| | | |
| | | <script setup> |
| | | import http from '../api/http.js'; |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'; |
| | | import * as echarts from 'echarts'; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const dataMetrics = ref([]); |
| | | const chartData = reactive({ |
| | | outbound: { dates: [], values: [] }, |
| | | inbound: { dates: [], values: [] }, |
| | | monthData: { dates: [], inValue: [], outValue: [] } |
| | | }); |
| | | const loading = ref(true); |
| | | const error = ref(false); |
| | | |
| | | // å¾è¡¨å¼ç¨åå®ä¾ |
| | | const outboundChart = ref(null); |
| | | const inboundChart = ref(null); |
| | | const monthDataChart = ref(null); |
| | | const outboundInstance = ref(null); |
| | | const inboundInstance = ref(null); |
| | | const monthDataInstance = ref(null); |
| | | const charts = ref([]); |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | if (!outboundChart.value || !inboundChart.value || !monthDataChart.value) { |
| | | console.log('å¾è¡¨å®¹å¨æªæ¾å°ï¼å»¶è¿åå§å'); |
| | | return; |
| | | } |
| | | |
| | | outboundInstance.value = echarts.init(outboundChart.value); |
| | | inboundInstance.value = echarts.init(inboundChart.value); |
| | | monthDataInstance.value = echarts.init(monthDataChart.value); |
| | | |
| | | charts.value = [outboundInstance.value, inboundInstance.value, monthDataInstance.value]; |
| | | |
| | | // åºåºéå¾è¡¨é
ç½®ï¼æ±ç¶å¾ï¼ |
| | | const outboundOption = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' // é´å½±æç¤ºå¨ |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div style="font-weight: bold; margin-bottom: 5px;">${data.name}</div> |
| | | <div style="display: flex; align-items: center;"> |
| | | <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 5px;"></span> |
| | | <span>${data.seriesName}: </span> |
| | | <span style="font-weight: bold; margin-left: 5px;">${data.value}</span> |
| | | </div> |
| | | `; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | borderColor: '#ddd', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#333' |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.outbound.dates, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é' |
| | | }, |
| | | series: [{ |
| | | name: 'åºåºé', |
| | | data: chartData.outbound.values, |
| | | type: 'bar', |
| | | itemStyle: { |
| | | color: '#e06e6e' |
| | | }, |
| | | barWidth: '60%', |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#e06e6e' |
| | | } |
| | | }] |
| | | }; |
| | | |
| | | // å
¥åºéå¾è¡¨é
ç½®ï¼æçº¿å¾ï¼ |
| | | const inboundOption = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'line' // 线åæç¤ºå¨ |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div style="font-weight: bold; margin-bottom: 5px;">${data.name}</div> |
| | | <div style="display: flex; align-items: center;"> |
| | | <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 5px;"></span> |
| | | <span>${data.seriesName}: </span> |
| | | <span style="font-weight: bold; margin-left: 5px;">${data.value}</span> |
| | | </div> |
| | | `; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | borderColor: '#ddd', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#333' |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.inbound.dates, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é' |
| | | }, |
| | | series: [{ |
| | | name: 'å
¥åºé', |
| | | data: chartData.inbound.values, |
| | | type: 'line', |
| | | itemStyle: { |
| | | color: '#4a7bff' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | smooth: true, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#4a7bff' |
| | | } |
| | | }] |
| | | }; |
| | | |
| | | // æåºå
¥åºéå¾è¡¨é
ç½®ï¼åæçº¿å¾ï¼ |
| | | const monthDataOption = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross', // åååææç¤ºå¨ |
| | | crossStyle: { |
| | | color: '#999' |
| | | } |
| | | }, |
| | | formatter: function (params) { |
| | | let html = `<div style="font-weight: bold; margin-bottom: 5px;">${params[0].name}</div>`; |
| | | params.forEach(param => { |
| | | html += ` |
| | | <div style="display: flex; align-items: center; margin: 2px 0;"> |
| | | <span style="display: inline-block; width: 10px; height: 10px; background: ${param.color}; border-radius: 50%; margin-right: 5px;"></span> |
| | | <span>${param.seriesName}: </span> |
| | | <span style="font-weight: bold; margin-left: 5px;">${param.value}</span> |
| | | </div> |
| | | `; |
| | | }); |
| | | return html; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | borderColor: '#ddd', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#333' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['å
¥åºé', 'åºåºé'], |
| | | bottom: 0 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '10%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.monthData.dates, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | }, |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'å
¥åºé', |
| | | data: chartData.monthData.inValue, |
| | | type: 'line', |
| | | itemStyle: { |
| | | color: '#4a7bff' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | smooth: true, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#4a7bff' |
| | | } |
| | | }, |
| | | { |
| | | name: 'åºåºé', |
| | | data: chartData.monthData.outValue, |
| | | type: 'line', |
| | | itemStyle: { |
| | | color: '#e06e6e' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | smooth: true, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#e06e6e' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | outboundInstance.value.setOption(outboundOption); |
| | | inboundInstance.value.setOption(inboundOption); |
| | | monthDataInstance.value.setOption(monthDataOption); |
| | | }; |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | const updateCharts = () => { |
| | | nextTick(() => { |
| | | if (outboundInstance.value && chartData.outbound.values.length > 0) { |
| | | outboundInstance.value.setOption({ |
| | | xAxis: { data: chartData.outbound.dates }, |
| | | series: [{ data: chartData.outbound.values }] |
| | | }); |
| | | } |
| | | |
| | | if (inboundInstance.value && chartData.inbound.values.length > 0) { |
| | | inboundInstance.value.setOption({ |
| | | xAxis: { data: chartData.inbound.dates }, |
| | | series: [{ data: chartData.inbound.values }] |
| | | }); |
| | | } |
| | | |
| | | if (monthDataInstance.value && chartData.monthData.dates.length > 0) { |
| | | monthDataInstance.value.setOption({ |
| | | xAxis: { data: chartData.monthData.dates }, |
| | | series: [ |
| | | { data: chartData.monthData.inValue }, |
| | | { data: chartData.monthData.outValue } |
| | | ] |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // ååºå¼çªå£è°æ´ |
| | | const handleResize = () => { |
| | | charts.value.forEach(chart => chart && chart.resize()); |
| | | }; |
| | | |
| | | // æ°æ®è·å |
| | | const fetchData = async () => { |
| | | try { |
| | | const response = await http.post("api/Task/GetTaskData", {}); |
| | | chartData.value = response.data; |
| | | loading.value = true; |
| | | error.value = false; |
| | | |
| | | if (taskListRef.value) taskListRef.value.initChart(); |
| | | } catch (error) { |
| | | console.error('API请æ±å¤±è´¥:', error); |
| | | const response = await http.post("api/StockInfo/GetStockData", {}); |
| | | console.log('APIååºæ°æ®:', response.data); |
| | | |
| | | if (response.data && response.data.success !== false) { |
| | | handleDataUpdate(response.data); |
| | | } else { |
| | | throw new Error('APIè¿åæ°æ®æ ¼å¼é误'); |
| | | } |
| | | |
| | | loading.value = false; |
| | | } catch (err) { |
| | | console.error('API请æ±å¤±è´¥:', err); |
| | | loading.value = false; |
| | | error.value = true; |
| | | } |
| | | }; |
| | | const intervalId = ref(null) |
| | | const startPolling = () => { |
| | | fetchData(); // åå§å è½½ |
| | | intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5åé |
| | | |
| | | // æ°æ®å¤ç |
| | | const handleDataUpdate = (data) => { |
| | | console.log('å¤çæ°æ®:', data); |
| | | |
| | | // æ´æ°æ°æ®ææ |
| | | if (data.metrics && Array.isArray(data.metrics)) { |
| | | dataMetrics.value = data.metrics.map(item => ({ |
| | | name: item.name || item.Name || 'æªç¥ææ ', |
| | | value: item.value != null ? item.value : item.Value || 0 |
| | | })); |
| | | } |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | if (data.outbound) { |
| | | chartData.outbound.dates = data.outbound.dates || []; |
| | | chartData.outbound.values = data.outbound.values || []; |
| | | } |
| | | |
| | | if (data.inbound) { |
| | | chartData.inbound.dates = data.inbound.dates || []; |
| | | chartData.inbound.values = data.inbound.values || []; |
| | | } |
| | | |
| | | if (data.monthData) { |
| | | chartData.monthData.dates = data.monthData.dates || []; |
| | | chartData.monthData.inValue = data.monthData.inValue || []; |
| | | chartData.monthData.outValue = data.monthData.outValue || []; |
| | | } |
| | | |
| | | console.log('æ´æ°åçæ°æ®ææ :', dataMetrics.value); |
| | | console.log('æ´æ°åçå¾è¡¨æ°æ®:', chartData); |
| | | |
| | | // å»¶è¿åå§åå¾è¡¨ï¼ç¡®ä¿æ°æ®å·²æ´æ° |
| | | nextTick(() => { |
| | | if (!outboundInstance.value || !inboundInstance.value || !monthDataInstance.value) { |
| | | initCharts(); |
| | | } else { |
| | | updateCharts(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | // 轮询æ§å¶ |
| | | const intervalId = ref(null); |
| | | const startPolling = () => { |
| | | fetchData(); |
| | | intervalId.value = setInterval(fetchData, 5 * 60 * 1000); |
| | | }; |
| | | |
| | | const stopPolling = () => { |
| | | if (intervalId.value) { |
| | | clearInterval(intervalId.value); |
| | | console.log('å·²åæ¢æ°æ®è½®è¯¢'); |
| | | intervalId.value = null; |
| | | } |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | startPolling(); |
| | | window.addEventListener('resize', handleResize); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | stopPolling(); |
| | | }); |
| | | const chartOptions = ref({ |
| | | tooltip: { |
| | | formatter: '{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | itemWidth: 14, |
| | | itemHeight: 14, |
| | | textStyle: { fontSize: 12 } |
| | | } |
| | | charts.value.forEach(chart => chart && chart.dispose()); |
| | | window.removeEventListener('resize', handleResize); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .dashboard { |
| | | width: 100%; |
| | | height: 100vh; |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | background-color: #f5f6fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .m-charts { |
| | | display: grid; |
| | | margin: 10px; |
| | | grid-template-columns: 33% 33% 95%; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .labelContent { |
| | | padding-top: 3vh; |
| | | } |
| | | |
| | | .chart_left { |
| | | position: relative; |
| | | border-radius: 10px; |
| | | background-color: white; |
| | | box-shadow: 0px 0px 10px 0px #ccc; |
| | | height: 100%; |
| | | width: 550px; |
| | | } |
| | | |
| | | .icons { |
| | | margin-right: 5px; |
| | | } |
| | | |
| | | .indexModel .item_center { |
| | | height: 90vh; |
| | | width: 98%; |
| | | margin: 0vh 20%; |
| | | } |
| | | |
| | | .titles { |
| | | width: 200px; |
| | | height: 5vh; |
| | | .overview-section { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .data-overview { |
| | | flex: 1; |
| | | background: white; |
| | | padding: 16px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .metrics { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .metric-item { |
| | | text-align: center; |
| | | flex: 1; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .metric-name { |
| | | font-size: 14px; |
| | | color: #666; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .metric-value { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | margin: 8px 0; |
| | | color: #333; |
| | | } |
| | | |
| | | .charts-section { |
| | | gap: 20px; |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | background: white; |
| | | padding: 16px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .chart { |
| | | height: 550px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart1 { |
| | | height: 550px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .loading, |
| | | .error, |
| | | .no-data { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 1.5rem; |
| | | font-weight: 600; |
| | | position: absolute; |
| | | top: 0px; |
| | | left: 15px; |
| | | }</style> |
| | | height: 500px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .loading { |
| | | color: #999; |
| | | } |
| | | |
| | | .error { |
| | | color: #2d8cf0; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | } |
| | | </style> |