| | |
| | | <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"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="data-overview"> |
| | | <h3 class="title">æ°æ®æ»è§</h3> |
| | | <div class="metrics"> |
| | | <div class="metric-item" v-for="(item, index) in dataMetrics" :key="index"> |
| | | <div style="display: flex;align-items: center; margin-left: 80px;"> |
| | | <div class="metric-icon"> |
| | | <i :class="getMetricIcon(item.name)"></i> |
| | | </div> |
| | | <div class="metric-name">{{ item.name }}</div> |
| | | </div> |
| | | <div class="metric-value">{{ item.value }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div class="overview-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="bg-color-black task-board-container"> |
| | | <div class="d-flex pt-2 pl-2"> |
| | | <div class="d-flex"> |
| | | <span class="fs-xl text mx-2 title">å½åç产任å¡</span> |
| | | </div> |
| | | </div> |
| | | <div class="body-box"> |
| | | <div class="scroll-table-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll"> |
| | | <!-- 表头 --> |
| | | <div class="table-header" ref="tableHeader"> |
| | | <table class="header-table"> |
| | | <thead> |
| | | <tr> |
| | | <th style="width: 60px;font-size: 18px;">åºå·</th> |
| | | <th style="width: 100px;font-size: 18px;">æçç </th> |
| | | <th style="width: 120px;font-size: 18px;">èµ·å§å°å</th> |
| | | <th style="width: 120px;font-size: 18px;">ç®æ å°å</th> |
| | | <th style="width: 120px;font-size: 18px;">ä»»å¡ç±»å</th> |
| | | <th style="width: 120px;font-size: 18px;">ä»»å¡ç¶æ</th> |
| | | <th style="width: 160px;font-size: 18px;">å建æ¶é´</th> |
| | | </tr> |
| | | </thead> |
| | | </table> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼å
容åºå --> |
| | | <div class="table-body-container" ref="tableBody"> |
| | | <div class="table-body-wrapper" :style="{ transform: `translateY(-${scrollPosition}px)` }"> |
| | | <!-- åå§æ°æ® --> |
| | | <table class="body-table"> |
| | | <tbody> |
| | | <tr v-for="(row, index) in tableData" :key="index" |
| | | :class="index % 2 === 0 ? 'even-row' : 'odd-row'"> |
| | | <td style="width: 60px; text-align: center;font-size: 16px;">{{ index + 1 }}</td> |
| | | <td style="width: 100px; text-align: center;font-size: 16px;">{{ row.palletCode || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.sourceAddress || 'æ ' }} |
| | | </td> |
| | | <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.targetAddress || 'æ ' }} |
| | | </td> |
| | | <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.taskType || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;font-size: 16px;"> |
| | | <el-tag :type="getStatusType(row.taskState)" size="small"> |
| | | {{ row.taskState || 'æ ' }} |
| | | </el-tag> |
| | | </td> |
| | | <td style="width: 160px; text-align: center;font-size: 18px;">{{ row.createDate || 'æ ' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | |
| | | <!-- å¤å¶ä¸ä»½æ°æ®ç¨äºæ ç¼æ»å¨ --> |
| | | <table class="body-table" v-if="tableData.length > rowNum"> |
| | | <tbody> |
| | | <tr v-for="(row, index) in tableData" :key="`copy-${index}`" |
| | | :class="index % 2 === 0 ? 'even-row' : 'odd-row'"> |
| | | <td style="width: 60px; text-align: center;">{{ index + tableData.length + 1 }}</td> |
| | | <td style="width: 100px; text-align: center;">{{ row.palletCode || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;">{{ row.sourceAddress || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;">{{ row.targetAddress || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;">{{ row.taskType || 'æ ' }}</td> |
| | | <td style="width: 120px; text-align: center;"> |
| | | <el-tag :type="getStatusType(row.taskState)" size="small"> |
| | | {{ row.taskState || 'æ ' }} |
| | | </el-tag> |
| | | </td> |
| | | <td style="width: 160px; text-align: center;">{{ row.createDate || 'æ ' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-if="tableData.length === 0" class="no-data-board"> |
| | | <el-empty description="ææ çäº§ä»»å¡æ°æ®" :image-size="80" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div class="charts-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="12"> |
| | | <div class="chart-container"> |
| | | <h3 class="title">åºåºé</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 class="title">å
¥åºé</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 class="title">æåºå
¥åºé</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' } }, |
| | | ]); |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick, computed } from 'vue'; |
| | | import * as echarts from 'echarts'; |
| | | // import { TrendCharts } from '@element-plus/icons-vue'; |
| | | |
| | | const fetchData = async () => { |
| | | try { |
| | | const response = await http.post("api/Task/GetTaskData", {}); |
| | | chartData.value = response.data; |
| | | // ååºå¼æ°æ® |
| | | const dataMetrics = ref([]); |
| | | const tableData = ref([]); |
| | | const chartData = reactive({ |
| | | outbound: { dates: [], values: [] }, |
| | | inbound: { dates: [], values: [] }, |
| | | monthData: { dates: [], inValue: [], outValue: [] } |
| | | }); |
| | | const loading = ref(true); |
| | | const error = ref(false); |
| | | |
| | | if (taskListRef.value) taskListRef.value.initChart(); |
| | | } catch (error) { |
| | | console.error('API请æ±å¤±è´¥:', error); |
| | | // æ»å¨ç¸å
³ |
| | | const tableHeader = ref(null); |
| | | const tableBody = ref(null); |
| | | const scrollPosition = ref(0); |
| | | const isScrolling = ref(true); |
| | | let scrollInterval = null; |
| | | const rowNum = 7; // æ¾ç¤ºçè¡æ° |
| | | const rowHeight = 40; // æ¯è¡é«åº¦ |
| | | |
| | | |
| | | const getMetricIcon = (type) => { |
| | | const iconMap = { |
| | | '仿¥è¿åºé': 'iconfont icon-cangpeitubiao_huanhuorukutuihuoruku', |
| | | '仿¥åºåºé': 'el-icon-takeaway-box', |
| | | 'æ¬æè¿åºé': 'iconfont icon-cangpeitubiao_huanhuorukutuihuoruku', |
| | | 'æ¬æåºåºé': 'el-icon-takeaway-box', |
| | | 'åºåæ»é': 'el-icon-goods', |
| | | }; |
| | | return iconMap[type] || 'el-icon-data-board'; |
| | | }; |
| | | // å¾è¡¨å¼ç¨åå®ä¾ |
| | | 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 startScrolling = () => { |
| | | if (tableData.value.length <= rowNum) { |
| | | isScrolling.value = false; |
| | | return; // æ°æ®å°æ¶ä¸æ»å¨ |
| | | } |
| | | |
| | | isScrolling.value = true; |
| | | const totalHeight = tableData.value.length * rowHeight; |
| | | |
| | | scrollInterval = setInterval(() => { |
| | | scrollPosition.value += 1; |
| | | |
| | | // 彿»å¨å°ç¬¬ä¸ä»½æ°æ®çæ«å°¾æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | if (scrollPosition.value >= totalHeight) { |
| | | scrollPosition.value = 0; |
| | | } |
| | | }, 20); // è°æ´æ»å¨é度 |
| | | }; |
| | | |
| | | // æåæ»å¨ |
| | | const pauseScroll = () => { |
| | | if (scrollInterval) { |
| | | clearInterval(scrollInterval); |
| | | scrollInterval = null; |
| | | } |
| | | isScrolling.value = false; |
| | | }; |
| | | |
| | | // æ¢å¤æ»å¨ |
| | | const resumeScroll = () => { |
| | | if (tableData.value.length > rowNum) { |
| | | startScrolling(); |
| | | } |
| | | }; |
| | | const intervalId = ref(null) |
| | | const startPolling = () => { |
| | | fetchData(); // åå§å è½½ |
| | | intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5åé |
| | | |
| | | // ç¶ææ ç¾ç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | }; |
| | | return statusMap[status] || 'primary'; |
| | | }; |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | // åå§åå¾è¡¨ |
| | | 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 handleDataUpdate = (data) => { |
| | | console.log('APIååºæ°æ®:', 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 || []; |
| | | } |
| | | |
| | | // æ´æ°è¡¨æ ¼æ°æ® |
| | | if (data && data.newTask && Array.isArray(data.newTask)) { |
| | | tableData.value = data.newTask.map(task => ({ |
| | | palletCode: task.palletCode || 'æ ', |
| | | roadway: task.roadway || 'æ ', |
| | | sourceAddress: task.sourceAddress || 'æ ', |
| | | targetAddress: task.targetAddress || 'æ ', |
| | | taskType: task.taskType || 'æ ', |
| | | taskState: task.taskState || 'æ ', |
| | | errorMessage: task.errorMessage || 'æ ', |
| | | createDate: task.createDate || 'æ ', |
| | | })); |
| | | |
| | | // æ°æ®æ´æ°åéæ°å¯å¨æ»å¨ |
| | | nextTick(() => { |
| | | pauseScroll(); |
| | | startScrolling(); |
| | | }); |
| | | } else { |
| | | tableData.value = []; |
| | | pauseScroll(); |
| | | } |
| | | |
| | | // å»¶è¿åå§åå¾è¡¨ï¼ç¡®ä¿æ°æ®å·²æ´æ° |
| | | nextTick(() => { |
| | | if (!outboundInstance.value || !inboundInstance.value || !monthDataInstance.value) { |
| | | initCharts(); |
| | | } else { |
| | | updateCharts(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ°æ®è·å |
| | | const fetchData = async () => { |
| | | try { |
| | | loading.value = true; |
| | | error.value = false; |
| | | |
| | | const response = await http.post("api/StockInfo/GetStockData", {}); |
| | | console.log('APIååº:', response); |
| | | |
| | | 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 stopPolling = () => { |
| | | if (intervalId.value) { |
| | | clearInterval(intervalId.value); |
| | | console.log('å·²åæ¢æ°æ®è½®è¯¢'); |
| | | intervalId.value = null; |
| | | } |
| | | pauseScroll(); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | 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; |
| | | |
| | | <style scoped lang="less"> |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | background-color: #f5f6fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .overview-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .data-overview { |
| | | background: white; |
| | | padding: 16px; |
| | | border-radius: 8px; |
| | | height: 200px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | .title { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &::before { |
| | | display: block; |
| | | content: ""; |
| | | width: 6px; |
| | | height: 20px; |
| | | background-color: #409eff; |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .task-board-container { |
| | | height: 300px; |
| | | border-radius: 8px; |
| | | background-color: white; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .d-flex { |
| | | display: flex; |
| | | |
| | | .title { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &::before { |
| | | display: block; |
| | | content: ""; |
| | | width: 6px; |
| | | height: 20px; |
| | | background-color: #409eff; |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pt-2 { |
| | | padding-top: 0.5rem; |
| | | } |
| | | |
| | | .pl-2 { |
| | | padding-left: 0.5rem; |
| | | } |
| | | |
| | | .text-icon { |
| | | color: #409eff; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .fs-xl { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text { |
| | | color: #333; |
| | | } |
| | | |
| | | .mx-2 { |
| | | margin-left: 0.5rem; |
| | | margin-right: 0.5rem; |
| | | } |
| | | |
| | | .body-box { |
| | | flex: 1; |
| | | padding: 10px; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .scroll-table-container { |
| | | height: 250px; |
| | | position: relative; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .table-header { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 10; |
| | | background: #0066cc; |
| | | } |
| | | |
| | | .header-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .header-table th { |
| | | background: #0066cc; |
| | | color: white; |
| | | font-weight: bold; |
| | | padding: 8px 4px; |
| | | text-align: center; |
| | | border-right: 1px solid #0055aa; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .header-table th:last-child { |
| | | border-right: none; |
| | | } |
| | | |
| | | .table-body-container { |
| | | position: absolute; |
| | | top: 40px; |
| | | /* 表头é«åº¦ */ |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .table-body-wrapper { |
| | | transition: transform 0.1s linear; |
| | | } |
| | | |
| | | .body-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .body-table td { |
| | | padding: 8px 4px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | font-size: 12px; |
| | | height: 40px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .m-charts { |
| | | display: grid; |
| | | margin: 10px; |
| | | grid-template-columns: 33% 33% 95%; |
| | | justify-content: space-between; |
| | | .even-row { |
| | | background-color: #f8f9fa; |
| | | } |
| | | |
| | | .labelContent { |
| | | padding-top: 3vh; |
| | | .odd-row { |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | .chart_left { |
| | | position: relative; |
| | | border-radius: 10px; |
| | | background-color: white; |
| | | box-shadow: 0px 0px 10px 0px #ccc; |
| | | .body-table tr:hover { |
| | | background-color: #e6f7ff !important; |
| | | } |
| | | |
| | | .no-data-board { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100%; |
| | | width: 550px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .icons { |
| | | margin-right: 5px; |
| | | .metrics { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-top: 15px; |
| | | } |
| | | |
| | | .indexModel .item_center { |
| | | height: 90vh; |
| | | width: 98%; |
| | | margin: 0vh 20%; |
| | | .metric-item { |
| | | text-align: center; |
| | | flex: 1; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .titles { |
| | | width: 200px; |
| | | height: 5vh; |
| | | .metric-name { |
| | | font-size: 24px; |
| | | color: #666; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .metric-value { |
| | | font-size: 22px; |
| | | font-weight: bold; |
| | | margin: 20px 0; |
| | | color: #333; |
| | | } |
| | | |
| | | .charts-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-container { |
| | | background: white; |
| | | padding: 16px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | height: 400px; |
| | | |
| | | .title { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &::before { |
| | | display: block; |
| | | content: ""; |
| | | width: 6px; |
| | | height: 20px; |
| | | background-color: #409eff; |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart { |
| | | height: 350px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart1 { |
| | | height: 350px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .loading, |
| | | .error, |
| | | .no-data { |
| | | margin-top: 50px; |
| | | height: 500px; |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .loading { |
| | | color: #999; |
| | | } |
| | | |
| | | .error { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .metric-icon { |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 1.5rem; |
| | | font-weight: 600; |
| | | position: absolute; |
| | | top: 0px; |
| | | left: 15px; |
| | | }</style> |
| | | justify-content: center; |
| | | margin-right: 12px; |
| | | margin-bottom: 6px; |
| | | font-size: 24px; |
| | | background: rgba(255, 255, 255, 0.8); |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | } |
| | | |
| | | /* Element Plus æ ç¾æ ·å¼è°æ´ */ |
| | | :deep(.el-tag) { |
| | | border: none; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | :deep(.el-tag--success) { |
| | | background-color: #f0f9ff; |
| | | color: #67c23a; |
| | | } |
| | | |
| | | :deep(.el-tag--primary) { |
| | | background-color: #f0f9ff; |
| | | color: #409eff; |
| | | } |
| | | |
| | | :deep(.el-tag--info) { |
| | | background-color: #f4f4f5; |
| | | color: #909399; |
| | | } |
| | | |
| | | :deep(.el-tag--danger) { |
| | | background-color: #fef0f0; |
| | | color: #f56c6c; |
| | | } |
| | | </style> |