| | |
| | | <template> |
| | | <div class="home-contianer"> |
| | | <div class="dashboard-container"> |
| | | <!-- æ°æ®æ»è§é¨å --> |
| | | <div class="overview-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="data-overview"> |
| | | <div class="overview-header"> |
| | | <div class="header-left"> |
| | | <h3 class="title"> |
| | | <i class="el-icon-data-analysis header-icon"></i> |
| | | æ°æ®æ»è§ |
| | | </h3> |
| | | </div> |
| | | <div class="header-right"> |
| | | <div class="time-range">æ°æ®æ´æ°äº: {{ currentTime }}</div> |
| | | <div> |
| | | <el-button type="primary" size="small" :icon="Refresh" @click="handleRefresh" :loading="refreshing" |
| | | class="refresh-btn"> |
| | | å·æ°æ°æ® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="metrics-grid"> |
| | | <div class="metric-card" v-for="(item, index) in dataMetrics" :key="`metric-${index}-${refreshKey}`" |
| | | :class="getMetricCardClass(item.name)"> |
| | | <div class="metric-content"> |
| | | <div class="metric-icon-wrapper"> |
| | | <i :class="getMetricIcon(item.name)"></i> |
| | | </div> |
| | | <div class="metric-info"> |
| | | <div class="metric-name">{{ item.name }}</div> |
| | | <div class="metric-value">{{ formatNumber(item.value) }}</div> |
| | | <div class="metric-compare" v-if="item.compare !== undefined"> |
| | | <span v-if="item.name == 'åºåæ»é'"> |
| | | |
| | | </span> |
| | | <span :class="getCompareClass(item.compare)" v-else> |
| | | <i :class="getCompareIcon(item.compare)"></i> |
| | | {{ formatCompareValue(item.compare) }} |
| | | </span> |
| | | <!-- <span class="compare-label">è¾æ¨æ¥</span> --> |
| | | <span class="compare-label">{{ text(item.name) }}</span> |
| | | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="metric-trend" v-if="item.compare !== undefined"> |
| | | <div class="trend-chart"> |
| | | <div class="trend-bar" :style="{ height: getTrendHeight(item) }"></div> |
| | | </div> |
| | | </div> |
| | | <div class="metric-decoration"> |
| | | <div class="decoration-circle"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- ç产任å¡çæ¿ --> |
| | | <div class="overview-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="task-board-container"> |
| | | <div class="board-header"> |
| | | <div class="header-left"> |
| | | <i class="el-icon-s-management board-icon"></i> |
| | | <span class="board-title">å½åç产任å¡</span> |
| | | </div> |
| | | <div class="header-right"> |
| | | <span class="task-count">å
± {{ tableData.length }} 个任å¡</span> |
| | | </div> |
| | | </div> |
| | | <div class="board-body"> |
| | | <div class="scroll-table-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll"> |
| | | <!-- 表头 --> |
| | | <div class="table-header" ref="tableHeader"> |
| | | <table class="header-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>åºå·</th> |
| | | <th>æçç </th> |
| | | <th>èµ·å§å°å</th> |
| | | <th>ç®æ å°å</th> |
| | | <th>ä»»å¡ç±»å</th> |
| | | <th>ä»»å¡ç¶æ</th> |
| | | <th>å建æ¶é´</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="`task-${index}-${refreshKey}`" |
| | | :class="index % 2 === 0 ? 'even-row' : 'odd-row'"> |
| | | <td>{{ index + 1 }}</td> |
| | | <td>{{ row.palletCode || 'æ ' }}</td> |
| | | <td>{{ row.sourceAddress || 'æ ' }}</td> |
| | | <td>{{ row.targetAddress || 'æ ' }}</td> |
| | | <td>{{ row.taskType || 'æ ' }}</td> |
| | | <td> |
| | | <el-tag :type="getStatusType(row.taskState)" size="small" class="status-tag"> |
| | | {{ row.taskState || 'æ ' }} |
| | | </el-tag> |
| | | </td> |
| | | <td>{{ formatDateTime(row.createDate) || 'æ ' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | |
| | | <!-- å¤å¶ä¸ä»½æ°æ®ç¨äºæ ç¼æ»å¨ --> |
| | | <table class="body-table" v-if="tableData.length > rowNum"> |
| | | <tbody> |
| | | <tr v-for="(row, index) in tableData" :key="`task-copy-${index}-${refreshKey}`" |
| | | :class="index % 2 === 0 ? 'even-row' : 'odd-row'"> |
| | | <td>{{ index + tableData.length + 1 }}</td> |
| | | <td>{{ row.palletCode || 'æ ' }}</td> |
| | | <td>{{ row.sourceAddress || 'æ ' }}</td> |
| | | <td>{{ row.targetAddress || 'æ ' }}</td> |
| | | <td>{{ row.taskType || 'æ ' }}</td> |
| | | <td> |
| | | <el-tag :type="getStatusType(row.taskState)" size="small" class="status-tag"> |
| | | {{ row.taskState || 'æ ' }} |
| | | </el-tag> |
| | | </td> |
| | | <td>{{ formatDateTime(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-card"> |
| | | <div class="chart-header"> |
| | | <h3 class="chart-title"> |
| | | <i class="el-icon-trend-charts chart-icon"></i> |
| | | åºåºéè¶å¿ |
| | | </h3> |
| | | <div class="chart-subtitle">è¿7æ¥åºåºç»è®¡</div> |
| | | </div> |
| | | <div class="chart-content"> |
| | | <div v-if="loading" class="chart-loading"> |
| | | <i class="el-icon-loading"></i> |
| | | <span>æ°æ®å è½½ä¸...</span> |
| | | </div> |
| | | <div v-else-if="error" class="chart-error"> |
| | | <i class="el-icon-warning"></i> |
| | | <span>æ°æ®å 载失败</span> |
| | | <el-button type="text" @click="fetchData" class="retry-btn">éè¯</el-button> |
| | | </div> |
| | | <div v-else-if="!chartData.outbound.values.length" class="chart-no-data"> |
| | | <i class="el-icon-data-line"></i> |
| | | <span>ææ åºåºæ°æ®</span> |
| | | </div> |
| | | <div v-else ref="outboundChart" class="chart" :key="`outbound-${refreshKey}`"></div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :lg="12"> |
| | | <div class="chart-card"> |
| | | <div class="chart-header"> |
| | | <h3 class="chart-title"> |
| | | <i class="el-icon-trend-charts chart-icon"></i> |
| | | å
¥åºéè¶å¿ |
| | | </h3> |
| | | <div class="chart-subtitle">è¿7æ¥å
¥åºç»è®¡</div> |
| | | </div> |
| | | <div class="chart-content"> |
| | | <div v-if="loading" class="chart-loading"> |
| | | <i class="el-icon-loading"></i> |
| | | <span>æ°æ®å è½½ä¸...</span> |
| | | </div> |
| | | <div v-else-if="error" class="chart-error"> |
| | | <i class="el-icon-warning"></i> |
| | | <span>æ°æ®å 载失败</span> |
| | | <el-button type="text" @click="fetchData" class="retry-btn">éè¯</el-button> |
| | | </div> |
| | | <div v-else-if="!chartData.inbound.values.length" class="chart-no-data"> |
| | | <i class="el-icon-data-line"></i> |
| | | <span>ææ å
¥åºæ°æ®</span> |
| | | </div> |
| | | <div v-else ref="inboundChart" class="chart" :key="`inbound-${refreshKey}`"></div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- æåº¦æ°æ®å¾è¡¨ --> |
| | | <div class="charts-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :lg="24"> |
| | | <div class="chart-card"> |
| | | <div class="chart-header"> |
| | | <h3 class="chart-title"> |
| | | <i class="el-icon-data-line chart-icon"></i> |
| | | æåº¦åºå
¥åºå¯¹æ¯ |
| | | </h3> |
| | | <div class="chart-subtitle">æ¬ææ¯æ¥åºå
¥åºéç»è®¡</div> |
| | | </div> |
| | | <div class="chart-content"> |
| | | <div v-if="loading" class="chart-loading"> |
| | | <i class="el-icon-loading"></i> |
| | | <span>æ°æ®å è½½ä¸...</span> |
| | | </div> |
| | | <div v-else-if="error" class="chart-error"> |
| | | <i class="el-icon-warning"></i> |
| | | <span>æ°æ®å 载失败</span> |
| | | <el-button type="text" @click="fetchData" class="retry-btn">éè¯</el-button> |
| | | </div> |
| | | <div v-else-if="!chartData.monthData.inValue.length || !chartData.monthData.outValue.length" |
| | | class="chart-no-data"> |
| | | <i class="el-icon-data-line"></i> |
| | | <span>ææ åºå
¥åºæ°æ®</span> |
| | | </div> |
| | | <div v-else ref="monthDataChart" class="chart-large" :key="`month-${refreshKey}`"></div> |
| | | </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'; |
| | | import { Refresh } from '@element-plus/icons-vue'; |
| | | |
| | | // chart2.series[0].data.splice(0, 1); |
| | | // chart2.series[0].data.push(~~(Math.random() * 1000)); |
| | | // ååºå¼æ°æ® |
| | | 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); |
| | | const refreshing = ref(false); |
| | | const refreshKey = ref(0); // ç¨äºå¼ºå¶éæ°æ¸²æ |
| | | |
| | | // chart2.series[1].data.splice(0, 1); |
| | | // chart2.series[1].data.push(~~(Math.random() * 1000)); |
| | | // $chart2.setOption(chart2); |
| | | // }, 2000); |
| | | // æ»å¨ç¸å
³ |
| | | const tableHeader = ref(null); |
| | | const tableBody = ref(null); |
| | | const scrollPosition = ref(0); |
| | | const isScrolling = ref(true); |
| | | let scrollInterval = null; |
| | | const rowNum = 7; // æ¾ç¤ºçè¡æ° |
| | | const rowHeight = 48; // æ¯è¡é«åº¦ |
| | | |
| | | // å½åæ¶é´ |
| | | const currentTime = ref(''); |
| | | |
| | | // å¾è¡¨å¼ç¨åå®ä¾ |
| | | 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 formatNumber = (num) => { |
| | | if (num === undefined || num === null) return '0'; |
| | | return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); |
| | | }; |
| | | |
| | | const text = (str) => { |
| | | if (str == "仿¥è¿åºé" || str == "仿¥åºåºé") { |
| | | return "è¾æ¨æ¥" |
| | | } else if (str == "æ¬æè¿åºé" || str == "æ¬æåºåºé") { |
| | | return "è¾ä¸æ" |
| | | } else { |
| | | return "" |
| | | } |
| | | } |
| | | |
| | | // æ ¼å¼åæ¯è¾å¼ |
| | | const formatCompareValue = (value) => { |
| | | if (value === 0) return '0'; |
| | | return value > 0 ? `+${formatNumber(value)}` : formatNumber(value); |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateString) => { |
| | | if (!dateString) return ''; |
| | | try { |
| | | const date = new Date(dateString); |
| | | return date.toLocaleString('zh-CN', { |
| | | month: '2-digit', |
| | | day: '2-digit', |
| | | hour: '2-digit', |
| | | minute: '2-digit' |
| | | }); |
| | | onUnmounted(() => { |
| | | |
| | | }); |
| | | return { open }; |
| | | }, |
| | | destroyed() { |
| | | } catch { |
| | | return dateString; |
| | | } |
| | | }; |
| | | // window.addEventListener("resize", function () { |
| | | // $chart2.setOption(chart2); |
| | | // }); |
| | | |
| | | // è·åæ¯è¾å¼æ ·å¼ç±» |
| | | const getCompareClass = (compare) => { |
| | | if (compare > 0) return 'compare-positive'; |
| | | if (compare < 0) return 'compare-negative'; |
| | | return 'compare-zero'; |
| | | }; |
| | | |
| | | // è·åæ¯è¾å¼å¾æ |
| | | const getCompareIcon = (compare) => { |
| | | if (compare > 0) return 'el-icon-top'; |
| | | if (compare < 0) return 'el-icon-bottom'; |
| | | return 'el-icon-minus'; |
| | | }; |
| | | |
| | | // è·åè¶å¿é«åº¦ |
| | | const getTrendHeight = (item) => { |
| | | if (item.compare === undefined || item.value === 0) return '0%'; |
| | | |
| | | const maxValue = Math.max(Math.abs(item.value), Math.abs(item.compare)); |
| | | if (maxValue === 0) return '0%'; |
| | | |
| | | const percentage = (Math.abs(item.compare) / maxValue) * 100; |
| | | return `${Math.min(percentage, 100)}%`; |
| | | }; |
| | | |
| | | // è·åææ å¡çæ ·å¼ç±» |
| | | const getMetricCardClass = (name) => { |
| | | const classMap = { |
| | | '仿¥è¿åºé': 'metric-inbound-today', |
| | | '仿¥åºåºé': 'metric-outbound-today', |
| | | 'æ¬æè¿åºé': 'metric-inbound-month', |
| | | 'æ¬æåºåºé': 'metric-outbound-month', |
| | | 'åºåæ»é': 'metric-total' |
| | | }; |
| | | return classMap[name] || ''; |
| | | }; |
| | | |
| | | const getMetricIcon = (type) => { |
| | | const iconMap = { |
| | | '仿¥è¿åºé': 'el-icon-download', |
| | | '仿¥åºåºé': 'el-icon-upload2', |
| | | 'æ¬æè¿åºé': 'el-icon-download', |
| | | 'æ¬æåºåºé': 'el-icon-upload2', |
| | | 'åºåæ»é': 'el-icon-box', |
| | | }; |
| | | return iconMap[type] || 'el-icon-data-board'; |
| | | }; |
| | | |
| | | // ç¶ææ ç¾ç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'è¿è¡ä¸': 'primary', |
| | | '已宿': 'success', |
| | | '已忶': 'info', |
| | | 'å¼å¸¸': 'danger', |
| | | 'å¾
æ§è¡': 'warning' |
| | | }; |
| | | return statusMap[status] || 'primary'; |
| | | }; |
| | | |
| | | // æ´æ°å½åæ¶é´ |
| | | const updateCurrentTime = () => { |
| | | 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 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; |
| | | } |
| | | }, 30); // è°æ´æ»å¨é度 |
| | | }; |
| | | |
| | | // æåæ»å¨ |
| | | const pauseScroll = () => { |
| | | if (scrollInterval) { |
| | | clearInterval(scrollInterval); |
| | | scrollInterval = null; |
| | | } |
| | | isScrolling.value = false; |
| | | }; |
| | | |
| | | // æ¢å¤æ»å¨ |
| | | const resumeScroll = () => { |
| | | if (tableData.value.length > rowNum) { |
| | | startScrolling(); |
| | | } |
| | | }; |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | // æ¸
çæ§çå¾è¡¨å®ä¾ |
| | | if (outboundInstance.value) { |
| | | outboundInstance.value.dispose(); |
| | | } |
| | | if (inboundInstance.value) { |
| | | inboundInstance.value.dispose(); |
| | | } |
| | | if (monthDataInstance.value) { |
| | | monthDataInstance.value.dispose(); |
| | | } |
| | | |
| | | 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: 600; margin-bottom: 8px; color: #303133;">${data.name}</div> |
| | | <div style="display: flex; align-items: center; font-size: 14px;"> |
| | | <span style="display: inline-block; width: 8px; height: 8px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span> |
| | | <span style="color: #606266;">${data.seriesName}: </span> |
| | | <span style="font-weight: 600; margin-left: 8px; color: #303133;">${data.value}</span> |
| | | </div> |
| | | `; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.95)', |
| | | borderColor: '#e4e7ed', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#303133' |
| | | }, |
| | | padding: [8, 12], |
| | | borderRadius: 6, |
| | | shadowColor: 'rgba(0, 0, 0, 0.1)', |
| | | shadowBlur: 8 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.outbound.dates, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266', |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é', |
| | | nameTextStyle: { |
| | | color: '#909399' |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266' |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: '#f0f2f5', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | series: [{ |
| | | name: 'åºåºé', |
| | | data: chartData.outbound.values, |
| | | type: 'bar', |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#ff9f7f' }, |
| | | { offset: 1, color: '#ff6b6b' } |
| | | ]), |
| | | borderRadius: [4, 4, 0, 0] |
| | | }, |
| | | barWidth: '60%', |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#ff6b6b', |
| | | fontWeight: 'bold' |
| | | } |
| | | }] |
| | | }; |
| | | |
| | | // å
¥åºéå¾è¡¨é
ç½®ï¼æçº¿å¾ï¼ |
| | | const inboundOption = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'line' |
| | | }, |
| | | formatter: function (params) { |
| | | const data = params[0]; |
| | | return ` |
| | | <div style="font-weight: 600; margin-bottom: 8px; color: #303133;">${data.name}</div> |
| | | <div style="display: flex; align-items: center; font-size: 14px;"> |
| | | <span style="display: inline-block; width: 8px; height: 8px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span> |
| | | <span style="color: #606266;">${data.seriesName}: </span> |
| | | <span style="font-weight: 600; margin-left: 8px; color: #303133;">${data.value}</span> |
| | | </div> |
| | | `; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.95)', |
| | | borderColor: '#e4e7ed', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#303133' |
| | | }, |
| | | padding: [8, 12], |
| | | borderRadius: 6, |
| | | shadowColor: 'rgba(0, 0, 0, 0.1)', |
| | | shadowBlur: 8 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.inbound.dates, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266', |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é', |
| | | nameTextStyle: { |
| | | color: '#909399' |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266' |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: '#f0f2f5', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | series: [{ |
| | | name: 'å
¥åºé', |
| | | data: chartData.inbound.values, |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | itemStyle: { |
| | | color: '#409eff', |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | lineStyle: { |
| | | width: 3, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
| | | { offset: 0, color: '#409eff' }, |
| | | { offset: 1, color: '#67c23a' } |
| | | ]) |
| | | }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, |
| | | { offset: 1, color: 'rgba(64, 158, 255, 0.1)' } |
| | | ]) |
| | | }, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#409eff', |
| | | fontWeight: 'bold' |
| | | } |
| | | }] |
| | | }; |
| | | |
| | | // æåºå
¥åºéå¾è¡¨é
ç½®ï¼åæçº¿å¾ï¼ |
| | | const monthDataOption = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross', |
| | | crossStyle: { |
| | | color: '#999' |
| | | } |
| | | }, |
| | | formatter: function (params) { |
| | | let html = `<div style="font-weight: 600; margin-bottom: 8px; color: #303133;">${params[0].name}</div>`; |
| | | params.forEach(param => { |
| | | html += ` |
| | | <div style="display: flex; align-items: center; margin: 4px 0; font-size: 14px;"> |
| | | <span style="display: inline-block; width: 8px; height: 8px; background: ${param.color}; border-radius: 50%; margin-right: 8px;"></span> |
| | | <span style="color: #606266;">${param.seriesName}: </span> |
| | | <span style="font-weight: 600; margin-left: 8px; color: #303133;">${param.value}</span> |
| | | </div> |
| | | `; |
| | | }); |
| | | return html; |
| | | }, |
| | | backgroundColor: 'rgba(255, 255, 255, 0.95)', |
| | | borderColor: '#e4e7ed', |
| | | borderWidth: 1, |
| | | textStyle: { |
| | | color: '#303133' |
| | | }, |
| | | padding: [12, 16], |
| | | borderRadius: 6, |
| | | shadowColor: 'rgba(0, 0, 0, 0.1)', |
| | | shadowBlur: 8 |
| | | }, |
| | | legend: { |
| | | data: ['å
¥åºé', 'åºåºé'], |
| | | bottom: 10, |
| | | textStyle: { |
| | | color: '#606266' |
| | | }, |
| | | itemWidth: 12, |
| | | itemHeight: 12 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '12%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.monthData.dates, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266', |
| | | rotate: 45 |
| | | }, |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é', |
| | | nameTextStyle: { |
| | | color: '#909399' |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#e4e7ed' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#606266' |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: '#f0f2f5', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'å
¥åºé', |
| | | data: chartData.monthData.inValue, |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | itemStyle: { |
| | | color: '#409eff' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#409eff', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | { |
| | | name: 'åºåºé', |
| | | data: chartData.monthData.outValue, |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | itemStyle: { |
| | | color: '#ff6b6b' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | animation: true, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}', |
| | | color: '#ff6b6b', |
| | | fontWeight: 'bold' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | outboundInstance.value.setOption(outboundOption); |
| | | inboundInstance.value.setOption(inboundOption); |
| | | monthDataInstance.value.setOption(monthDataOption); |
| | | }; |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | const updateCharts = () => { |
| | | nextTick(() => { |
| | | // å
鿝æ§çå¾è¡¨å®ä¾ |
| | | if (outboundInstance.value) { |
| | | outboundInstance.value.dispose(); |
| | | } |
| | | if (inboundInstance.value) { |
| | | inboundInstance.value.dispose(); |
| | | } |
| | | if (monthDataInstance.value) { |
| | | monthDataInstance.value.dispose(); |
| | | } |
| | | |
| | | // éæ°åå§åå¾è¡¨ |
| | | initCharts(); |
| | | }); |
| | | }; |
| | | |
| | | // ååºå¼çªå£è°æ´ |
| | | const handleResize = () => { |
| | | charts.value.forEach(chart => chart && chart.resize()); |
| | | }; |
| | | |
| | | // æ°æ®å¤ç |
| | | const handleDataUpdate = (data) => { |
| | | console.log('APIååºæ°æ®:', data); |
| | | |
| | | // ä½¿ç¨ Object.assign ç¡®ä¿ååºå¼æ´æ° |
| | | 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, |
| | | compare: item.compare != null ? item.compare : 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(); |
| | | } |
| | | |
| | | // å¼ºå¶æ´æ° refreshKey 触åéæ°æ¸²æ |
| | | refreshKey.value++; |
| | | |
| | | // å»¶è¿åå§åå¾è¡¨ï¼ç¡®ä¿æ°æ®å·²æ´æ° |
| | | nextTick(() => { |
| | | updateCharts(); |
| | | }); |
| | | }; |
| | | |
| | | // æ°æ®è·å |
| | | const fetchData = async () => { |
| | | try { |
| | | loading.value = true; |
| | | error.value = false; |
| | | refreshing.value = true; |
| | | |
| | | const response = await http.post("api/StockInfo/GetStockData", {}); |
| | | console.log('APIååº:', response); |
| | | |
| | | if (response.data && response.data.success !== false) { |
| | | handleDataUpdate(response.data); |
| | | error.value = false; |
| | | } else { |
| | | throw new Error('APIè¿åæ°æ®æ ¼å¼é误'); |
| | | } |
| | | } catch (err) { |
| | | console.error('API请æ±å¤±è´¥:', err); |
| | | error.value = true; |
| | | } finally { |
| | | loading.value = false; |
| | | refreshing.value = false; |
| | | updateCurrentTime(); |
| | | } |
| | | }; |
| | | |
| | | // å·æ°å¤ç |
| | | const handleRefresh = () => { |
| | | fetchData(); |
| | | }; |
| | | |
| | | const intervalId = ref(null); |
| | | const timeIntervalId = ref(null); |
| | | |
| | | const startPolling = () => { |
| | | fetchData(); |
| | | |
| | | // è®¾ç½®å®æ¶æ´æ°å½åæ¶é´ |
| | | timeIntervalId.value = setInterval(updateCurrentTime, 1000); |
| | | |
| | | // è®¾ç½®å®æ¶è·åæ°æ® |
| | | intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5åé轮询 |
| | | }; |
| | | |
| | | const stopPolling = () => { |
| | | if (intervalId.value) { |
| | | clearInterval(intervalId.value); |
| | | intervalId.value = null; |
| | | } |
| | | if (timeIntervalId.value) { |
| | | clearInterval(timeIntervalId.value); |
| | | timeIntervalId.value = null; |
| | | } |
| | | pauseScroll(); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | 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; |
| | | |
| | | <style scoped> |
| | | .retry-btn { |
| | | margin-left: 8px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .chart-error { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .chart-error .el-button { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%); |
| | | min-height: 100vh; |
| | | font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif; |
| | | } |
| | | |
| | | .overview-section { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .data-overview { |
| | | background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
| | | padding: 24px; |
| | | border-radius: 16px; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
| | | border: 1px solid rgba(255, 255, 255, 0.8); |
| | | backdrop-filter: blur(10px); |
| | | } |
| | | |
| | | .overview-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .header-left .title { |
| | | display: flex; |
| | | align-items: center; |
| | | margin: 0 0 8px 0; |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | color: #303133; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .header-icon { |
| | | margin-right: 12px; |
| | | font-size: 24px; |
| | | color: #409eff; |
| | | background: linear-gradient(135deg, #409eff, #79bbff); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .subtitle { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | |
| | | .header-right { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .time-range { |
| | | font-size: 13px; |
| | | color: #909399; |
| | | background: rgba(64, 158, 255, 0.1); |
| | | padding: 6px 12px; |
| | | border-radius: 20px; |
| | | border: 1px solid rgba(64, 158, 255, 0.2); |
| | | } |
| | | |
| | | .refresh-btn { |
| | | border-radius: 20px; |
| | | padding: 8px 16px; |
| | | font-weight: 500; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .refresh-btn:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .metrics-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); |
| | | gap: 20px; |
| | | } |
| | | |
| | | .metric-card { |
| | | background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
| | | border-radius: 12px; |
| | | padding: 24px; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); |
| | | border: 1px solid rgba(255, 255, 255, 0.8); |
| | | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .metric-card:hover { |
| | | transform: translateY(-6px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); |
| | | } |
| | | |
| | | .metric-card::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 3px; |
| | | background: linear-gradient(90deg, transparent 0%, currentColor 50%, transparent 100%); |
| | | opacity: 0; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .metric-card:hover::before { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .metric-inbound-today { |
| | | border-left: 4px solid #67c23a; |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .metric-outbound-today { |
| | | border-left: 4px solid #e6a23c; |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .metric-inbound-month { |
| | | border-left: 4px solid #409eff; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .metric-outbound-month { |
| | | border-left: 4px solid #f56c6c; |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .metric-total { |
| | | border-left: 4px solid #909399; |
| | | color: #909399; |
| | | } |
| | | |
| | | .metric-content { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex: 1; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .metric-icon-wrapper { |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 16px; |
| | | font-size: 28px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .metric-card:hover .metric-icon-wrapper { |
| | | transform: scale(1.1); |
| | | } |
| | | |
| | | .metric-inbound-today .metric-icon-wrapper { |
| | | background: linear-gradient(135deg, rgba(103, 194, 58, 0.15), rgba(103, 194, 58, 0.05)); |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .metric-outbound-today .metric-icon-wrapper { |
| | | background: linear-gradient(135deg, rgba(230, 162, 60, 0.15), rgba(230, 162, 60, 0.05)); |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .metric-inbound-month .metric-icon-wrapper { |
| | | background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(64, 158, 255, 0.05)); |
| | | color: #409eff; |
| | | } |
| | | |
| | | .metric-outbound-month .metric-icon-wrapper { |
| | | background: linear-gradient(135deg, rgba(245, 108, 108, 0.15), rgba(245, 108, 108, 0.05)); |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .metric-total .metric-icon-wrapper { |
| | | background: linear-gradient(135deg, rgba(144, 147, 153, 0.15), rgba(144, 147, 153, 0.05)); |
| | | color: #909399; |
| | | } |
| | | |
| | | .metric-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .metric-name { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .metric-value { |
| | | font-size: 28px; |
| | | font-weight: 800; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | line-height: 1; |
| | | background: linear-gradient(135deg, currentColor, #303133); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .metric-compare { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .compare-positive { |
| | | color: #f56c6c; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .compare-positive i { |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | .compare-negative { |
| | | color: #67c23a; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .compare-negative i { |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | .compare-zero { |
| | | color: #909399; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .compare-hidden { |
| | | display: hidden; |
| | | } |
| | | |
| | | .compare-zero i { |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | .compare-label { |
| | | color: #909399; |
| | | font-size: 11px; |
| | | } |
| | | |
| | | .metric-trend { |
| | | width: 44px; |
| | | height: 44px; |
| | | display: flex; |
| | | align-items: flex-end; |
| | | justify-content: center; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .trend-chart { |
| | | width: 6px; |
| | | height: 100%; |
| | | background: rgba(0, 0, 0, 0.06); |
| | | border-radius: 3px; |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .trend-bar { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | border-radius: 3px; |
| | | transition: height 0.8s cubic-bezier(0.4, 0, 0.2, 1); |
| | | } |
| | | |
| | | .metric-inbound-today .trend-bar { |
| | | background: linear-gradient(to top, #67c23a, #95d475); |
| | | } |
| | | |
| | | .metric-outbound-today .trend-bar { |
| | | background: linear-gradient(to top, #e6a23c, #eebe77); |
| | | } |
| | | |
| | | .metric-inbound-month .trend-bar { |
| | | background: linear-gradient(to top, #409eff, #79bbff); |
| | | } |
| | | |
| | | .metric-outbound-month .trend-bar { |
| | | background: linear-gradient(to top, #f56c6c, #f89898); |
| | | } |
| | | |
| | | .metric-total .trend-bar { |
| | | background: linear-gradient(to top, #909399, #b1b3b8); |
| | | } |
| | | |
| | | .metric-decoration { |
| | | position: absolute; |
| | | top: -20px; |
| | | right: -20px; |
| | | width: 80px; |
| | | height: 80px; |
| | | opacity: 0.1; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .decoration-circle { |
| | | 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; |
| | | } |
| | | border-radius: 50%; |
| | | background: currentColor; |
| | | } |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | /* ä»»å¡çæ¿æ ·å¼ */ |
| | | .task-board-container { |
| | | background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
| | | border-radius: 16px; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
| | | border: 1px solid rgba(255, 255, 255, 0.8); |
| | | overflow: hidden; |
| | | } |
| | | .h-chart { |
| | | height: 340px; |
| | | margin: 6px 0px; |
| | | |
| | | .board-header { |
| | | 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; |
| | | } |
| | | } |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20px 24px; |
| | | background: linear-gradient(135deg, #409eff 0%, #79bbff 100%); |
| | | color: white; |
| | | } |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .board-icon { |
| | | font-size: 20px; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .board-title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .task-count { |
| | | font-size: 14px; |
| | | opacity: 0.9; |
| | | background: rgba(255, 255, 255, 0.2); |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .board-body { |
| | | height: 320px; |
| | | padding: 0; |
| | | } |
| | | |
| | | .scroll-table-container { |
| | | height: 100%; |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .table-header { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 20; |
| | | background: #f8f9fa; |
| | | border-bottom: 2px solid #e4e7ed; |
| | | } |
| | | |
| | | .header-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .header-table th { |
| | | background: #f8f9fa; |
| | | color: #606266; |
| | | font-weight: 600; |
| | | padding: 16px 12px; |
| | | text-align: center; |
| | | border-right: 1px solid #e4e7ed; |
| | | font-size: 14px; |
| | | position: relative; |
| | | } |
| | | |
| | | .header-table th:last-child { |
| | | border-right: none; |
| | | } |
| | | |
| | | .header-table th::after { |
| | | content: ''; |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 2px; |
| | | background: linear-gradient(90deg, #409eff, #79bbff); |
| | | } |
| | | |
| | | .table-body-container { |
| | | position: absolute; |
| | | top: 56px; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .table-body-wrapper { |
| | | transition: transform 0.3s ease; |
| | | } |
| | | |
| | | .body-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .body-table td { |
| | | padding: 14px 12px; |
| | | border-bottom: 1px solid #f0f2f5; |
| | | font-size: 13px; |
| | | height: 48px; |
| | | box-sizing: border-box; |
| | | text-align: center; |
| | | color: #606266; |
| | | } |
| | | |
| | | .even-row { |
| | | background-color: #fafbfc; |
| | | } |
| | | |
| | | .odd-row { |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | .body-table tr:hover { |
| | | background-color: #f0f7ff !important; |
| | | transform: scale(1.01); |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .status-tag { |
| | | border-radius: 12px; |
| | | padding: 4px 12px; |
| | | font-weight: 500; |
| | | border: none; |
| | | } |
| | | |
| | | .no-data-board { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100%; |
| | | width: 100%; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | /* å¾è¡¨å¡çæ ·å¼ */ |
| | | .charts-section { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .chart-card { |
| | | background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
| | | border-radius: 16px; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
| | | border: 1px solid rgba(255, 255, 255, 0.8); |
| | | overflow: hidden; |
| | | height: 400px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .chart-header { |
| | | padding: 20px 24px 0; |
| | | background: transparent; |
| | | } |
| | | |
| | | .chart-title { |
| | | display: flex; |
| | | align-items: center; |
| | | margin: 0 0 8px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .chart-icon { |
| | | margin-right: 8px; |
| | | color: #409eff; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .chart-subtitle { |
| | | font-size: 13px; |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | |
| | | .chart-content { |
| | | flex: 1; |
| | | padding: 0 12px 20px; |
| | | position: relative; |
| | | } |
| | | |
| | | .chart { |
| | | height: 100%; |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart-large { |
| | | height: 100%; |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart-loading, |
| | | .chart-error, |
| | | .chart-no-data { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100%; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .chart-loading i, |
| | | .chart-error i, |
| | | .chart-no-data i { |
| | | font-size: 48px; |
| | | margin-bottom: 16px; |
| | | opacity: 0.6; |
| | | } |
| | | |
| | | .chart-loading { |
| | | color: #409eff; |
| | | } |
| | | |
| | | .chart-error { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | /* Element Plus æ ç¾æ ·å¼è°æ´ */ |
| | | :deep(.el-tag) { |
| | | border: none; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-tag--success) { |
| | | background: linear-gradient(135deg, #f0f9ff, #e1f3ff); |
| | | color: #67c23a; |
| | | } |
| | | |
| | | :deep(.el-tag--primary) { |
| | | background: linear-gradient(135deg, #f0f9ff, #e1f3ff); |
| | | color: #409eff; |
| | | } |
| | | |
| | | :deep(.el-tag--info) { |
| | | background: linear-gradient(135deg, #f4f4f5, #e9e9eb); |
| | | color: #909399; |
| | | } |
| | | |
| | | :deep(.el-tag--warning) { |
| | | background: linear-gradient(135deg, #fdf6ec, #faecd8); |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | :deep(.el-tag--danger) { |
| | | background: linear-gradient(135deg, #fef0f0, #fde2e2); |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 1200px) { |
| | | .metrics-grid { |
| | | grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); |
| | | gap: 16px; |
| | | } |
| | | |
| | | .metric-card { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .metric-value { |
| | | font-size: 24px; |
| | | } |
| | | } |
| | | #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; |
| | | @media (max-width: 768px) { |
| | | .dashboard-container { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .overview-header { |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .header-right { |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | .metrics-grid { |
| | | grid-template-columns: 1fr; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .chart-card { |
| | | height: 350px; |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | /* æ»å¨æ¡æ ·å¼ä¼å */ |
| | | .scroll-table-container::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | .scroll-table-container::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .scroll-table-container::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .scroll-table-container::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | </style> |