| | |
| | | <template> |
| | | <div class="home-contianer"> |
| | | |
| | | <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="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> |
| | | import { ref, onMounted, onUnmounted } from 'vue'; |
| | | export default { |
| | | components: {}, |
| | | data() { |
| | | return { |
| | | |
| | | n: 90, |
| | | value1: '1', |
| | | }; |
| | | }, |
| | | setup() { |
| | | let open = (item) => { |
| | | window.open(item.url, '_blank'); |
| | | }; |
| | | let interval; |
| | | onMounted(() => { |
| | | |
| | | // interval = setInterval(() => { |
| | | // chart2.xAxis[0].data.splice(0, 1); |
| | | // let lastYear = |
| | | // chart2.xAxis[0].data[chart2.xAxis[0].data.length - 1] * 1 + 1; |
| | | // chart2.xAxis[0].data.push(lastYear); |
| | | <script setup> |
| | | import http from '../api/http.js'; |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'; |
| | | import * as echarts from 'echarts'; |
| | | |
| | | // chart2.series[0].data.splice(0, 1); |
| | | // chart2.series[0].data.push(~~(Math.random() * 1000)); |
| | | // ååºå¼æ°æ® |
| | | const dataMetrics = ref([]); |
| | | const chartData = reactive({ |
| | | outbound: { dates: [], values: [] }, |
| | | inbound: { dates: [], values: [] }, |
| | | monthData: { dates: [], inValue: [], outValue: [] } |
| | | }); |
| | | const loading = ref(true); |
| | | const error = ref(false); |
| | | |
| | | // chart2.series[1].data.splice(0, 1); |
| | | // chart2.series[1].data.push(~~(Math.random() * 1000)); |
| | | // $chart2.setOption(chart2); |
| | | // }, 2000); |
| | | }); |
| | | onUnmounted(() => { |
| | | // å¾è¡¨å¼ç¨åå®ä¾ |
| | | 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([]); |
| | | |
| | | }); |
| | | return { open }; |
| | | }, |
| | | destroyed() { |
| | | // åå§åå¾è¡¨ |
| | | 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 { |
| | | loading.value = true; |
| | | error.value = false; |
| | | |
| | | 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; |
| | | } |
| | | }; |
| | | // window.addEventListener("resize", function () { |
| | | // $chart2.setOption(chart2); |
| | | // }); |
| | | |
| | | // æ°æ®å¤ç |
| | | 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); |
| | | intervalId.value = null; |
| | | } |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | startPolling(); |
| | | window.addEventListener('resize', handleResize); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | stopPolling(); |
| | | charts.value.forEach(chart => chart && chart.dispose()); |
| | | window.removeEventListener('resize', handleResize); |
| | | }); |
| | | </script> |
| | | <style lang="less" scoped> |
| | | .home-contianer { |
| | | padding: 6px; |
| | | background: #eee; |
| | | width: 100%; |
| | | height: 100%; |
| | | // max-width: 800px; |
| | | // position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | left: 0; |
| | | margin: 0 auto; |
| | | |
| | | .h-top { |
| | | display: flex; |
| | | .h-top-left { |
| | | height: 100%; |
| | | width: 300px; |
| | | background: white; |
| | | } |
| | | height: 300px; |
| | | } |
| | | .h-top > div { |
| | | border: 1px solid #e8e7e7; |
| | | border-radius: 5px; |
| | | // margin: 6px; |
| | | } |
| | | .h-top-center { |
| | | height: 100%; |
| | | background: white; |
| | | margin: 0 6px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | .item1 .num { |
| | | padding-top: 28px; |
| | | } |
| | | .item2 .num { |
| | | padding-bottom: 20px; |
| | | } |
| | | |
| | | .n-item { |
| | | width: 100%; |
| | | height: 100%; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | // display: flex; |
| | | .item { |
| | | border-right: 1px solid #e5e5e5; |
| | | width: 33.3333333%; |
| | | float: left; |
| | | height: 50%; |
| | | border-bottom: 1px solid #e5e5e5; |
| | | padding: 47px 0; |
| | | font-size: 13px; |
| | | } |
| | | .item:hover { |
| | | background: #f9f9f9; |
| | | cursor: pointer; |
| | | } |
| | | .item:last-child { |
| | | border-right: 0; |
| | | } |
| | | .item3, |
| | | .item6 { |
| | | border-right: 0; |
| | | } |
| | | .num { |
| | | word-break: break-all; |
| | | color: #282727; |
| | | font-size: 30px; |
| | | transition: transform 0.8s; |
| | | } |
| | | .num:hover { |
| | | color: #55ce80; |
| | | transform: scale(1.2); |
| | | } |
| | | .text { |
| | | font-size: 13px; |
| | | color: #777; |
| | | } |
| | | } |
| | | } |
| | | .h-top-right { |
| | | // flex: 1; |
| | | |
| | | width: 400px; |
| | | height: 100%; |
| | | background: white; |
| | | } |
| | | .h3 { |
| | | padding: 7px 15px; |
| | | font-weight: 500; |
| | | background: #fff; |
| | | border-bottom: 1px dotted #d4d4d4; |
| | | } |
| | | <style scoped> |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | background-color: #f5f6fa; |
| | | min-height: 100vh; |
| | | } |
| | | .task-table { |
| | | table { |
| | | width: 100%; |
| | | .thead { |
| | | font-weight: bold; |
| | | } |
| | | tr { |
| | | cursor: pointer; |
| | | td { |
| | | border-bottom: 1px solid #f3f3f3; |
| | | padding: 9px 8px; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | tr:hover { |
| | | background: #eee; |
| | | } |
| | | } |
| | | } |
| | | .h-chart { |
| | | height: 340px; |
| | | margin: 6px 0px; |
| | | |
| | | .overview-section { |
| | | display: flex; |
| | | .h-left-grid { |
| | | width: 300px; |
| | | height: 100%; |
| | | background: white; |
| | | display: inline-block; |
| | | .name { |
| | | margin-left: 7px; |
| | | } |
| | | .item:hover { |
| | | background: #f9f9f9; |
| | | cursor: pointer; |
| | | } |
| | | .item { |
| | | padding: 22px 14px; |
| | | float: left; |
| | | width: 50%; |
| | | height: 33.33333%; |
| | | border-bottom: 1px solid #eee; |
| | | border-right: 1px solid #eee; |
| | | i { |
| | | font-size: 30px; |
| | | } |
| | | .desc { |
| | | font-size: 12px; |
| | | color: #c3c3c3; |
| | | padding: 5px 0 0 4px; |
| | | line-height: 1.5; |
| | | } |
| | | } |
| | | } |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | #h-chart2 { |
| | | border-radius: 3px; |
| | | background: white; |
| | | padding-top: 10px; |
| | | height: 100%; |
| | | width: 0; |
| | | flex: 1; |
| | | margin: 0 7px; |
| | | } |
| | | #h-chart3 { |
| | | border-radius: 3px; |
| | | padding: 10px 10px 0 10px; |
| | | background: white; |
| | | // padding-top: 10px; |
| | | height: 100%; |
| | | |
| | | width: 400px; |
| | | .data-overview { |
| | | flex: 1; |
| | | background: white; |
| | | padding: 16px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | </style> |
| | | |
| | | .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; |
| | | height: 500px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .loading { |
| | | color: #999; |
| | | } |
| | | |
| | | .error { |
| | | color: #2d8cf0; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | } |
| | | </style> |