生产管控-生产报表与绩效分析、库存与物料看板、智能排产三个前端页面开发
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="intelligent-scheduling"> |
| | | <!-- æç´¢è¡¨å --> |
| | | <el-card class="search-form-card" shadow="never"> |
| | | <el-form :inline="true" :model="searchParams" class="search-form"> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="searchParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | :shortcuts="dateShortcuts" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç
¤ç§"> |
| | | <el-input v-model="searchParams.coalType" placeholder="请è¾å
¥ç
¤ç§" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="æäº§ç¶æ"> |
| | | <el-select v-model="searchParams.status" placeholder="è¯·éæ©æäº§ç¶æ" clearable style="width: 200px"> |
| | | <el-option label="å
¨é¨" value="" /> |
| | | <el-option label="å¾
æäº§" value="pending" /> |
| | | <el-option label="æäº§ä¸" value="processing" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch" :loading="loading">æ¥è¯¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | <el-button type="success" @click="handleRandomScheduling">éæºæäº§</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-s-order" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">æ»æäº§é</div> |
| | | <div class="card-value">{{ totalScheduledQuantity.toFixed(2) }} å¨</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-check" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">已宿æäº§</div> |
| | | <div class="card-value">{{ completedScheduledQuantity.toFixed(2) }} å¨</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-time" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">å¾
æäº§</div> |
| | | <div class="card-value">{{ pendingScheduledQuantity.toFixed(2) }} å¨</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-warning" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">åºåé¢è¦</div> |
| | | <div class="card-value">{{ stockWarningCount }} 项</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºååæå表 --> |
| | | <el-card class="stock-materials-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºååæ</span> |
| | | </div> |
| | | </template> |
| | | <el-table v-loading="loading" :data="stockMaterials" style="width: 100%"> |
| | | <el-table-column prop="id" label="åºå·" width="80" type="index" /> |
| | | <el-table-column prop="coalType" label="ç
¤ç§" width="120" /> |
| | | <el-table-column prop="origin" label="产å°" width="120" /> |
| | | <el-table-column prop="calorificValue" label="çå¼" width="120" /> |
| | | <el-table-column prop="currentStock" label="å½ååºå(å¨)" width="150" align="right"> |
| | | <template #default="scope"> |
| | | <span :class="{ 'stock-warning': scope.row.currentStock < scope.row.minStock }"> |
| | | {{ scope.row.currentStock.toFixed(2) }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="minStock" label="æä½åºå(å¨)" width="150" align="right"> |
| | | <template #default="scope">{{ scope.row.minStock.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½" width="80" /> |
| | | <el-table-column prop="updateTime" label="æ´æ°æ¶é´" width="180" /> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- æäº§ç»æå表 --> |
| | | <el-card class="scheduling-results-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æäº§ç»æ</span> |
| | | </div> |
| | | </template> |
| | | <el-table v-loading="loading" :data="schedulingResults" style="width: 100%"> |
| | | <el-table-column prop="code" label="æäº§ç¼ç " width="180" /> |
| | | <el-table-column prop="productionLine" label="ç产线" width="120" /> |
| | | <el-table-column prop="coalType" label="ç
¤ç§" width="120" /> |
| | | <el-table-column prop="quantity" label="æäº§æ°é(å¨)" width="150" align="right"> |
| | | <template #default="scope">{{ scope.row.quantity.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="scheduleTime" label="æäº§æ¶é´" width="180" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag |
| | | :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'" |
| | | size="small" |
| | | > |
| | | {{ scope.row.status === 'completed' ? '已宿' : scope.row.status === 'processing' ? 'æäº§ä¸' : 'å¾
æäº§' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" width="200" /> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | // æç´¢åæ° |
| | | const searchParams = ref({ |
| | | dateRange: [], |
| | | coalType: '', |
| | | status: '' |
| | | }) |
| | | |
| | | // æ¥æå¿«æ·é项 |
| | | const dateShortcuts = [ |
| | | { |
| | | text: 'ä»å¤©', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'æ¨å¤©', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'è¿7天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'è¿30天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'æ¬æ', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date(end.getFullYear(), end.getMonth(), 1) |
| | | return [start, end] |
| | | } |
| | | } |
| | | ] |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const totalScheduledQuantity = ref(0) |
| | | const completedScheduledQuantity = ref(0) |
| | | const pendingScheduledQuantity = ref(0) |
| | | const stockWarningCount = ref(0) |
| | | |
| | | // åºååææ°æ® |
| | | const stockMaterials = ref([ |
| | | { id: 1, coalType: 'çç
¤', origin: '山西', calorificValue: '5300大å¡', currentStock: 1200.5, minStock: 500, unit: 'å¨', updateTime: '2023-05-07 09:30:15' }, |
| | | { id: 2, coalType: 'æ çç
¤', origin: 'å
èå¤', calorificValue: '5800大å¡', currentStock: 800.2, minStock: 400, unit: 'å¨', updateTime: '2023-05-07 10:15:30' }, |
| | | { id: 3, coalType: 'è¤ç
¤', origin: 'æ°ç', calorificValue: '4200大å¡', currentStock: 350.8, minStock: 400, unit: 'å¨', updateTime: '2023-05-07 11:05:45' }, |
| | | { id: 4, coalType: 'è´«ç
¤', origin: 'é西', calorificValue: '5100大å¡', currentStock: 900.0, minStock: 300, unit: 'å¨', updateTime: '2023-05-07 13:20:00' }, |
| | | { id: 5, coalType: 'ç¦ç
¤', origin: 'è´µå·', calorificValue: '5400大å¡', currentStock: 650.5, minStock: 350, unit: 'å¨', updateTime: '2023-05-07 14:45:15' } |
| | | ]) |
| | | |
| | | // æäº§ç»ææ°æ® |
| | | const schedulingResults = ref([ |
| | | { code: 'PS20230507001', productionLine: 'ç产线1', coalType: 'çç
¤', quantity: 200.5, scheduleTime: '2023-05-07 09:30:15', status: 'completed', remark: 'æè®¡å宿' }, |
| | | { code: 'PS20230507002', productionLine: 'ç产线2', coalType: 'æ çç
¤', quantity: 150.2, scheduleTime: '2023-05-07 10:15:30', status: 'processing', remark: 'æ£å¨è¿è¡ä¸' }, |
| | | { code: 'PS20230507003', productionLine: 'ç产线3', coalType: 'è´«ç
¤', quantity: 180.0, scheduleTime: '2023-05-07 11:05:45', status: 'pending', remark: 'çå¾
æäº§' } |
| | | ]) |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // 计ç®ç»è®¡æ°æ® |
| | | const calculateStats = () => { |
| | | // è®¡ç®æ»æäº§é |
| | | totalScheduledQuantity.value = schedulingResults.value.reduce((sum, item) => sum + item.quantity, 0) |
| | | |
| | | // 计ç®å·²å®ææäº§ |
| | | completedScheduledQuantity.value = schedulingResults.value |
| | | .filter(item => item.status === 'completed') |
| | | .reduce((sum, item) => sum + item.quantity, 0) |
| | | |
| | | // 计ç®å¾
æäº§ |
| | | pendingScheduledQuantity.value = schedulingResults.value |
| | | .filter(item => item.status === 'pending') |
| | | .reduce((sum, item) => sum + item.quantity, 0) |
| | | |
| | | // 计ç®åºåé¢è¦æ°é |
| | | stockWarningCount.value = stockMaterials.value.filter(item => item.currentStock < item.minStock).length |
| | | } |
| | | |
| | | // çææäº§ç¼ç |
| | | const generateSchedulingCode = () => { |
| | | const date = new Date() |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0') |
| | | return `PS${year}${month}${day}${random}` |
| | | } |
| | | |
| | | // éæºæäº§ |
| | | const handleRandomScheduling = async () => { |
| | | try { |
| | | loading.value = true |
| | | |
| | | // 模æAPI请æ±å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 1000)) |
| | | |
| | | // è·åæåºåçåæ |
| | | const availableMaterials = stockMaterials.value.filter(item => item.currentStock > 0) |
| | | |
| | | if (availableMaterials.length === 0) { |
| | | ElMessage.warning('没æå¯ç¨çåºååæ') |
| | | return |
| | | } |
| | | |
| | | // éæºéæ©åæ |
| | | const randomMaterial = availableMaterials[Math.floor(Math.random() * availableMaterials.length)] |
| | | |
| | | // éæºçææäº§æ°éï¼ä¸è¶
è¿åºåç50%ï¼ |
| | | const maxQuantity = randomMaterial.currentStock * 0.5 |
| | | const quantity = Math.max(50, Math.random() * maxQuantity) // è³å°æäº§50å¨ |
| | | |
| | | // éæºéæ©ç产线 |
| | | const productionLines = ['ç产线1', 'ç产线2', 'ç产线3'] |
| | | const randomLine = productionLines[Math.floor(Math.random() * productionLines.length)] |
| | | |
| | | // çææ°çæäº§è®¡å |
| | | const newScheduling = { |
| | | code: generateSchedulingCode(), |
| | | productionLine: randomLine, |
| | | coalType: randomMaterial.coalType, |
| | | quantity: quantity, |
| | | scheduleTime: new Date().toLocaleString('zh-CN'), |
| | | status: 'pending', |
| | | remark: 'éæºæäº§çæ' |
| | | } |
| | | |
| | | // æ·»å å°æäº§ç»æå表 |
| | | schedulingResults.value.unshift(newScheduling) |
| | | |
| | | // æ´æ°åºåæ°é |
| | | randomMaterial.currentStock -= quantity |
| | | |
| | | // æ´æ°ç»è®¡æ°æ® |
| | | calculateStats() |
| | | |
| | | ElMessage.success('éæºæäº§æå') |
| | | } catch (error) { |
| | | console.error('éæºæäº§å¤±è´¥:', error) |
| | | ElMessage.error('éæºæäº§å¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleSearch = async () => { |
| | | try { |
| | | loading.value = true |
| | | |
| | | // 模æAPI请æ±å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 800)) |
| | | |
| | | // è¿éå¯ä»¥æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® |
| | | // å®é
åºç¨ä¸åºè¯¥è°ç¨APIè·åæ°æ® |
| | | |
| | | ElMessage.success('æ¥è¯¢æå') |
| | | } catch (error) { |
| | | console.error('æ¥è¯¢å¤±è´¥:', error) |
| | | ElMessage.error('æ¥è¯¢å¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // éç½®æç´¢åæ° |
| | | const handleReset = () => { |
| | | searchParams.value = { |
| | | dateRange: [], |
| | | coalType: '', |
| | | status: '' |
| | | } |
| | | |
| | | // éç½®åéæ°æ¥è¯¢ |
| | | handleSearch() |
| | | } |
| | | |
| | | // åå§åé¡µé¢ |
| | | onMounted(() => { |
| | | // é»è®¤æ¥è¯¢è¿7å¤©æ°æ® |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | searchParams.value.dateRange = [start, end] |
| | | |
| | | // 计ç®ç»è®¡æ°æ® |
| | | calculateStats() |
| | | |
| | | // åå§å è½½æ°æ® |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .intelligent-scheduling { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-form-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .search-form { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | | gap: 16px; |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .stat-card { |
| | | display: flex; |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | transition: transform 0.3s, box-shadow 0.3s; |
| | | } |
| | | |
| | | .stat-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 48px; |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 50%; |
| | | font-size: 24px; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) .card-icon { |
| | | background: #ECF5FF; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .stat-card:nth-child(2) .card-icon { |
| | | background: #F6FFED; |
| | | color: #52C41A; |
| | | } |
| | | |
| | | .stat-card:nth-child(3) .card-icon { |
| | | background: #FFF7E6; |
| | | color: #FAAD14; |
| | | } |
| | | |
| | | .stat-card:nth-child(4) .card-icon { |
| | | background: #FFF1F0; |
| | | color: #F5222D; |
| | | } |
| | | |
| | | .card-content { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stock-materials-card, |
| | | .scheduling-results-card { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 16px; |
| | | border-bottom: 1px solid #EBEEF5; |
| | | } |
| | | |
| | | .card-header span { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stock-warning { |
| | | color: #F5222D; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* ååºå¼å¸å± */ |
| | | @media (max-width: 768px) { |
| | | .intelligent-scheduling { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .stats-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .search-form { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-form .el-form-item { |
| | | margin-right: 0; |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="coal-blending-efficiency"> |
| | | <div class="page-header"> |
| | | <div class="search-form"> |
| | | <el-form :inline="true" :model="searchParams" class="demo-form-inline"> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="searchParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | :shortcuts="dateShortcuts" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch" :loading="loading">æ¥è¯¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡çåºå --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-s-operation"></i> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">åä½çå¼èç
¤é</div> |
| | | <div class="card-value">{{ unitHeatCoalConsumption.toFixed(2) }} kg/GJ</div> |
| | | <div class="card-desc">è¶ä½è¶å¥½</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-check-circle"></i> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">æ´ä½è¾¾æ ç</div> |
| | | <div class="card-value">{{ overallComplianceRate.toFixed(2) }}%</div> |
| | | <div class="card-desc">åçéâ¥5000大å¡</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-data-line"></i> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">å¹³ååçé</div> |
| | | <div class="card-value">{{ averageCalorificValue.toFixed(0) }} 大å¡</div> |
| | | <div class="card-desc">è¶é«è¶å¥½</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-files"></i> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">é
æ¹ä½¿ç¨é¢æ¬¡</div> |
| | | <div class="card-value">{{ totalBatches }} 次</div> |
| | | <div class="card-desc">æ¶é´èå´å
</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-s-data"></i> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">æ»æ¹æ¬¡</div> |
| | | <div class="card-value">{{ totalBatches }}</div> |
| | | <div class="card-desc">æ¶é´èå´å
</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="charts-container"> |
| | | <!-- è¾¾æ çè¶å¿å¾ --> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è¾¾æ çè¶å¿</span> |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="complianceChartRef" |
| | | :options="complianceTrendOptions" |
| | | :chartStyle="{ height: '300px', width: '100%' }" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ç
¤ç§åçéå¯¹æ¯ --> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç
¤ç§åçé对æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="calorificChartRef" |
| | | :options="calorificValueOptions" |
| | | :chartStyle="{ height: '300px', width: '100%' }" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- é
æ¹ä½¿ç¨é¢æ¬¡ --> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é
æ¹ä½¿ç¨é¢æ¬¡</span> |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="recipeChartRef" |
| | | :options="recipeFrequencyOptions" |
| | | :chartStyle="{ height: '300px', width: '100%' }" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- å å·¥å¾çåæ --> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>å å·¥å¾çåæ</span> |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="yieldChartRef" |
| | | :options="processingYieldOptions" |
| | | :chartStyle="{ height: '300px', width: '100%' }" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ææ¬ç»æå¾è°± --> |
| | | <el-card class="chart-card cost-structure"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ææ¬ç»æå¾è°±</span> |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="costChartRef" |
| | | :options="costStructureOptions" |
| | | :chartStyle="{ height: '300px', width: '100%' }" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | |
| | | // æç´¢åæ° |
| | | const searchParams = ref({ |
| | | dateRange: [] |
| | | }) |
| | | |
| | | // æ¥æå¿«æ·é项 |
| | | const dateShortcuts = [ |
| | | { |
| | | text: 'è¿7天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'è¿30天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'æ¬æ', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date(end.getFullYear(), end.getMonth(), 1) |
| | | return [start, end] |
| | | } |
| | | } |
| | | ] |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const unitHeatCoalConsumption = ref(32.5) |
| | | const overallComplianceRate = ref(85.7) |
| | | const averageCalorificValue = ref(5260) |
| | | const totalBatches = ref(48) |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const mockComplianceData = [ |
| | | { date: '2023-06-01', rate: 82 }, |
| | | { date: '2023-06-02', rate: 85 }, |
| | | { date: '2023-06-03', rate: 88 }, |
| | | { date: '2023-06-04', rate: 84 }, |
| | | { date: '2023-06-05', rate: 87 }, |
| | | { date: '2023-06-06', rate: 90 }, |
| | | { date: '2023-06-07', rate: 86 }, |
| | | { date: '2023-06-08', rate: 89 }, |
| | | { date: '2023-06-09', rate: 92 }, |
| | | { date: '2023-06-10', rate: 88 } |
| | | ] |
| | | |
| | | const mockCalorificData = [ |
| | | { name: 'çç
¤', value: 5800 }, |
| | | { name: 'æ çç
¤', value: 6200 }, |
| | | { name: 'è¤ç
¤', value: 4500 }, |
| | | { name: 'è´«ç
¤', value: 5300 }, |
| | | { name: 'ç¦ç
¤', value: 5500 } |
| | | ] |
| | | |
| | | const mockRecipeData = [ |
| | | { name: 'é
æ¹A (çç
¤60%+æ çç
¤40%)', value: 18 }, |
| | | { name: 'é
æ¹B (çç
¤70%+è¤ç
¤30%)', value: 12 }, |
| | | { name: 'é
æ¹C (æ çç
¤50%+è´«ç
¤50%)', value: 8 }, |
| | | { name: 'é
æ¹D (çç
¤50%+ç¦ç
¤50%)', value: 6 }, |
| | | { name: 'å
¶ä»é
æ¹', value: 4 } |
| | | ] |
| | | |
| | | const mockYieldData = [ |
| | | { name: 'çç
¤', yield: 85, efficiency: 92 }, |
| | | { name: 'æ çç
¤', yield: 88, efficiency: 89 }, |
| | | { name: 'è¤ç
¤', yield: 75, efficiency: 78 }, |
| | | { name: 'è´«ç
¤', yield: 82, efficiency: 85 }, |
| | | { name: 'ç¦ç
¤', yield: 80, efficiency: 87 } |
| | | ] |
| | | |
| | | const mockCostData = [ |
| | | { name: 'èæ', value: 45 }, |
| | | { name: 'è½è', value: 30 }, |
| | | { name: '人å', value: 25 } |
| | | ] |
| | | |
| | | // è¾¾æ çè¶å¿å¾é
ç½® |
| | | const complianceTrendOptions = computed(() => ({ |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: '{b}<br/>è¾¾æ ç: {c}%' |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: mockComplianceData.map(item => item.date), |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'è¾¾æ ç (%)', |
| | | min: 70, |
| | | max: 100 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'è¾¾æ ç', |
| | | type: 'line', |
| | | smooth: true, |
| | | data: mockComplianceData.map(item => item.rate), |
| | | itemStyle: { |
| | | color: '#409EFF' |
| | | }, |
| | | lineStyle: { |
| | | width: 3 |
| | | }, |
| | | areaStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: 'rgba(64, 158, 255, 0.3)' |
| | | }, { |
| | | offset: 1, |
| | | color: 'rgba(64, 158, 255, 0.05)' |
| | | }] |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | // ç
¤ç§åçé对æ¯å¾é
ç½® |
| | | const calorificValueOptions = computed(() => ({ |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: '{b}<br/>åçé: {c} 大å¡' |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: mockCalorificData.map(item => item.name) |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'åçé (大å¡)' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åçé', |
| | | type: 'bar', |
| | | data: mockCalorificData.map(item => item.value), |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#FF6B81' }, |
| | | { offset: 1, color: '#FF8E53' } |
| | | ]) |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}大å¡' |
| | | } |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | // é
æ¹ä½¿ç¨é¢æ¬¡å¾é
ç½® |
| | | const recipeFrequencyOptions = computed(() => ({ |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{b}: {c}次 ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left', |
| | | textStyle: { |
| | | fontSize: 12 |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'é
æ¹ä½¿ç¨é¢æ¬¡', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['60%', '50%'], |
| | | data: mockRecipeData, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | }, |
| | | itemStyle: { |
| | | borderRadius: 8, |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}\n{c}次' |
| | | } |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | // å å·¥å¾çåæå¾é
ç½® |
| | | const processingYieldOptions = computed(() => ({ |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['åºæ´ç', 'å å·¥è½æ'] |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: mockYieldData.map(item => item.name) |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'ç¾åæ¯ (%)', |
| | | min: 60, |
| | | max: 100 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åºæ´ç', |
| | | type: 'bar', |
| | | data: mockYieldData.map(item => item.yield), |
| | | itemStyle: { |
| | | color: '#52C41A' |
| | | } |
| | | }, |
| | | { |
| | | name: 'å å·¥è½æ', |
| | | type: 'bar', |
| | | data: mockYieldData.map(item => item.efficiency), |
| | | itemStyle: { |
| | | color: '#1890FF' |
| | | } |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | // ææ¬ç»æå¾è°±é
ç½® |
| | | const costStructureOptions = computed(() => ({ |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{b}: {c}%' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'ææ¬ç»æ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['60%', '50%'], |
| | | data: mockCostData, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | }, |
| | | roseType: 'radius', |
| | | itemStyle: { |
| | | borderRadius: 8, |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | } |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const loading = ref(false) |
| | | |
| | | const handleSearch = async () => { |
| | | try { |
| | | loading.value = true |
| | | // å¨å®é
åºç¨ä¸ï¼è¿éä¼è°ç¨APIè·åç宿°æ® |
| | | console.log('æç´¢åæ°:', searchParams.value) |
| | | |
| | | // è·åé
ç
¤æ°æ® |
| | | // const blendingRes = await getCoalBlendingList({ |
| | | // startTime: searchParams.value.dateRange[0] ? parseTime(searchParams.value.dateRange[0]) : '', |
| | | // endTime: searchParams.value.dateRange[1] ? parseTime(searchParams.value.dateRange[1]) : '' |
| | | // }) |
| | | |
| | | // 模æAPI请æ±å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 800)) |
| | | |
| | | // æ¨¡ææ°æ®æ´æ°ï¼å®é
åºç¨ä¸åºæ ¹æ®APIè¿åç»ææ´æ°ï¼ |
| | | unitHeatCoalConsumption.value = 31.8 + Math.random() * 5 |
| | | overallComplianceRate.value = 80 + Math.random() * 15 |
| | | averageCalorificValue.value = 5000 + Math.random() * 500 |
| | | totalBatches.value = 40 + Math.floor(Math.random() * 20) |
| | | |
| | | // éæºæ´æ°é¨åå¾è¡¨æ°æ® |
| | | mockComplianceData.forEach(item => { |
| | | item.rate = 80 + Math.random() * 15 |
| | | }) |
| | | |
| | | ElMessage.success('æ¥è¯¢æå') |
| | | } catch (error) { |
| | | console.error('æ¥è¯¢å¤±è´¥:', error) |
| | | ElMessage.error('æ¥è¯¢å¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const handleRefresh = () => { |
| | | ElMessage.info('æ£å¨å·æ°æ°æ®...') |
| | | handleSearch() |
| | | } |
| | | |
| | | // å¯¼åºæ°æ® |
| | | const handleExport = async () => { |
| | | try { |
| | | loading.value = true |
| | | // 模æå¯¼åºæä½ |
| | | await new Promise(resolve => setTimeout(resolve, 1000)) |
| | | |
| | | ElMessage.success('æ°æ®å¯¼åºæå') |
| | | } catch (error) { |
| | | console.error('导åºå¤±è´¥:', error) |
| | | ElMessage.error('导åºå¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // éç½®æç´¢åæ° |
| | | const handleReset = () => { |
| | | searchParams.value = { |
| | | dateRange: [] |
| | | } |
| | | } |
| | | |
| | | // åå§åé¡µé¢ |
| | | onMounted(() => { |
| | | // é»è®¤æ¥è¯¢è¿7å¤©æ°æ® |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | searchParams.value.dateRange = [start, end] |
| | | |
| | | // åå§å è½½æ°æ® |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .coal-blending-efficiency { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin-bottom: 15px; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | | gap: 16px; |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .stat-card { |
| | | display: flex; |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | transition: transform 0.3s, box-shadow 0.3s; |
| | | } |
| | | |
| | | .stat-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 48px; |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 50%; |
| | | font-size: 24px; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) .card-icon { |
| | | background: #ECF5FF; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .stat-card:nth-child(2) .card-icon { |
| | | background: #F0F9FF; |
| | | color: #69B1FF; |
| | | } |
| | | |
| | | .stat-card:nth-child(3) .card-icon { |
| | | background: #F6FFED; |
| | | color: #52C41A; |
| | | } |
| | | |
| | | .stat-card:nth-child(4) .card-icon { |
| | | background: #FFF7E6; |
| | | color: #FAAD14; |
| | | } |
| | | |
| | | .card-content { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .card-desc { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .charts-container { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); |
| | | gap: 24px; |
| | | } |
| | | |
| | | .chart-card { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 16px; |
| | | border-bottom: 1px solid #EBEEF5; |
| | | } |
| | | |
| | | .card-header span { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .chart-wrapper { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .cost-structure { |
| | | grid-column: 1 / -1; |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .charts-container { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="stock-material-board"> |
| | | <div class="page-header"> |
| | | <h2>åºåä¸ç©æçæ¿</h2> |
| | | </div> |
| | | |
| | | <!-- æç´¢è¡¨å --> |
| | | <el-card class="search-form-card" shadow="never"> |
| | | <el-form :inline="true" :model="searchParams" class="search-form"> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="searchParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | :shortcuts="dateShortcuts" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç
¤ç§"> |
| | | <el-input v-model="searchParams.coalType" placeholder="请è¾å
¥ç
¤ç§" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="车次ç¼ç "> |
| | | <el-input v-model="searchParams.trainCode" placeholder="请è¾å
¥è½¦æ¬¡ç¼ç " clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch" :loading="loading">æ¥è¯¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | <el-button @click="handleRefresh">å·æ°</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-download" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">仿¥å
¥åºæ»é</div> |
| | | <div class="card-value">{{ todayInStock.toFixed(2) }} å¨</div> |
| | | <div class="card-percentage" :class="{ positive: todayInStockPercentage > 0, negative: todayInStockPercentage < 0 }"> |
| | | <i v-if="todayInStockPercentage > 0" class="el-icon-caret-top" /> |
| | | <i v-else-if="todayInStockPercentage < 0" class="el-icon-caret-bottom" /> |
| | | {{ Math.abs(todayInStockPercentage).toFixed(1) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-upload" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">仿¥åºåºæ»é</div> |
| | | <div class="card-value">{{ todayOutStock.toFixed(2) }} å¨</div> |
| | | <div class="card-percentage" :class="{ positive: todayOutStockPercentage > 0, negative: todayOutStockPercentage < 0 }"> |
| | | <i v-if="todayOutStockPercentage > 0" class="el-icon-caret-top" /> |
| | | <i v-else-if="todayOutStockPercentage < 0" class="el-icon-caret-bottom" /> |
| | | {{ Math.abs(todayOutStockPercentage).toFixed(1) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-takeaway-box" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">仿¥é¢ææ»é</div> |
| | | <div class="card-value">{{ todayMaterialPick.toFixed(2) }} å¨</div> |
| | | <div class="card-percentage" :class="{ positive: todayMaterialPickPercentage > 0, negative: todayMaterialPickPercentage < 0 }"> |
| | | <i v-if="todayMaterialPickPercentage > 0" class="el-icon-caret-top" /> |
| | | <i v-else-if="todayMaterialPickPercentage < 0" class="el-icon-caret-bottom" /> |
| | | {{ Math.abs(todayMaterialPickPercentage).toFixed(1) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="stat-card"> |
| | | <div class="card-icon"> |
| | | <i class="el-icon-shopping-cart-full" /> |
| | | </div> |
| | | <div class="card-content"> |
| | | <div class="card-title">å½ååºåæ»é</div> |
| | | <div class="card-value">{{ currentTotalStock.toFixed(2) }} å¨</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åç±»æ°æ®å±ç¤º --> |
| | | <el-card class="category-cards" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åç±»æ°æ®ç»è®¡</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- ç
¤ç§åç±» --> |
| | | <div class="category-section"> |
| | | <h3 class="category-title"><i class="el-icon-menu"></i> ç
¤ç§åå¸</h3> |
| | | <div class="category-items"> |
| | | <div v-for="item in coalTypeData" :key="item.name" class="category-item"> |
| | | <span class="item-name">{{ item.name }}</span> |
| | | <div class="item-bar"> |
| | | <div class="item-progress" :style="{ width: item.percentage + '%' }"></div> |
| | | </div> |
| | | <span class="item-value">{{ item.value.toFixed(2) }} å¨ ({{ item.percentage.toFixed(1) }}%)</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 产å°åç±» --> |
| | | <div class="category-section"> |
| | | <h3 class="category-title"><i class="el-icon-location"></i> 产å°åå¸</h3> |
| | | <div class="category-items"> |
| | | <div v-for="item in originData" :key="item.name" class="category-item"> |
| | | <span class="item-name">{{ item.name }}</span> |
| | | <div class="item-bar"> |
| | | <div class="item-progress" :style="{ width: item.percentage + '%' }"></div> |
| | | </div> |
| | | <span class="item-value">{{ item.value.toFixed(2) }} å¨ ({{ item.percentage.toFixed(1) }}%)</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- çå¼åç±» --> |
| | | <div class="category-section"> |
| | | <h3 class="category-title"><i class="el-icon-fire"></i> çå¼åå¸</h3> |
| | | <div class="category-items"> |
| | | <div v-for="item in calorificData" :key="item.name" class="category-item"> |
| | | <span class="item-name">{{ item.name }}</span> |
| | | <div class="item-bar"> |
| | | <div class="item-progress" :style="{ width: item.percentage + '%' }"></div> |
| | | </div> |
| | | <span class="item-value">{{ item.value.toFixed(2) }} å¨ ({{ item.percentage.toFixed(1) }}%)</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 车次ç¼ç åç±» --> |
| | | <div class="category-section"> |
| | | <h3 class="category-title"><i class="el-icon-train"></i> 车次ç¼ç ç»è®¡</h3> |
| | | <el-table :data="trainCodeData" style="width: 100%"> |
| | | <el-table-column prop="trainCode" label="车次ç¼ç " width="160" /> |
| | | <el-table-column prop="count" label="次æ°" width="100" align="right" /> |
| | | <el-table-column prop="totalQuantity" label="æ»é(å¨)" width="120" align="right"> |
| | | <template #default="scope">{{ scope.row.totalQuantity.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- æè¿äº¤æè®°å½ --> |
| | | <el-card class="transactions-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æè¿äº¤æè®°å½</span> |
| | | </div> |
| | | </template> |
| | | <el-table v-loading="loading" :data="recentTransactions" style="width: 100%"> |
| | | <el-table-column prop="code" label="ç¼ç " width="160" /> |
| | | <el-table-column prop="type" label="ç±»å" width="80"> |
| | | <template #default="scope"> |
| | | <span class="type-tag" |
| | | :class="{ |
| | | 'type-in': scope.row.type === 'å
¥åº', |
| | | 'type-out': scope.row.type === 'åºåº', |
| | | 'type-pick': scope.row.type === '颿' |
| | | }"> |
| | | {{ scope.row.type }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="coalType" label="ç
¤ç§" width="100" /> |
| | | <el-table-column prop="origin" label="产å°" width="100" /> |
| | | <el-table-column prop="calorificValue" label="çå¼" width="120" /> |
| | | <el-table-column prop="quantity" label="æ°é(å¨)" width="120" /> |
| | | <el-table-column prop="trainCode" label="车次ç¼ç " width="120" /> |
| | | <el-table-column prop="createTime" label="å建æ¶é´" width="180" /> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | // æç´¢åæ° |
| | | const searchParams = ref({ |
| | | dateRange: [], |
| | | coalType: '', |
| | | trainCode: '' |
| | | }) |
| | | |
| | | // æ¥æå¿«æ·é项 |
| | | const dateShortcuts = [ |
| | | { |
| | | text: 'ä»å¤©', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'æ¨å¤©', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'è¿7天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'è¿30天', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) |
| | | return [start, end] |
| | | } |
| | | }, |
| | | { |
| | | text: 'æ¬æ', |
| | | value: () => { |
| | | const end = new Date() |
| | | const start = new Date(end.getFullYear(), end.getMonth(), 1) |
| | | return [start, end] |
| | | } |
| | | } |
| | | ] |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const todayInStock = ref(1250.75) |
| | | const todayInStockPercentage = ref(5.2) |
| | | const todayOutStock = ref(890.30) |
| | | const todayOutStockPercentage = ref(-2.1) |
| | | const todayMaterialPick = ref(320.45) |
| | | const todayMaterialPickPercentage = ref(10.3) |
| | | const currentTotalStock = ref(15680.95) |
| | | |
| | | // åç±»æ°æ® - ç
¤ç§åå¸ |
| | | const coalTypeData = ref([ |
| | | { name: 'çç
¤', value: 4500, percentage: 30.0 }, |
| | | { name: 'æ çç
¤', value: 3200, percentage: 21.3 }, |
| | | { name: 'è¤ç
¤', value: 2800, percentage: 18.7 }, |
| | | { name: 'è´«ç
¤', value: 2500, percentage: 16.7 }, |
| | | { name: 'ç¦ç
¤', value: 2680, percentage: 17.9 } |
| | | ]) |
| | | |
| | | // åç±»æ°æ® - 产å°åå¸ |
| | | const originData = ref([ |
| | | { name: '山西', value: 5200, percentage: 33.1 }, |
| | | { name: 'å
èå¤', value: 3800, percentage: 24.2 }, |
| | | { name: 'é西', value: 2900, percentage: 18.5 }, |
| | | { name: 'æ°ç', value: 2100, percentage: 13.4 }, |
| | | { name: 'è´µå·', value: 1680, percentage: 10.7 } |
| | | ]) |
| | | |
| | | // åç±»æ°æ® - çå¼åå¸ |
| | | const calorificData = ref([ |
| | | { name: '4000-4500', value: 2800, percentage: 17.9 }, |
| | | { name: '4500-5000', value: 4200, percentage: 26.8 }, |
| | | { name: '5000-5500', value: 5600, percentage: 35.7 }, |
| | | { name: '5500-6000', value: 2500, percentage: 16.0 }, |
| | | { name: '6000+', value: 580, percentage: 3.7 } |
| | | ]) |
| | | |
| | | // åç±»æ°æ® - 车次ç¼ç ç»è®¡ |
| | | const trainCodeData = ref([ |
| | | { trainCode: 'C12345', count: 12, totalQuantity: 4200.5 }, |
| | | { trainCode: 'C12346', count: 8, totalQuantity: 2800.2 }, |
| | | { trainCode: 'C12347', count: 10, totalQuantity: 3500.0 }, |
| | | { trainCode: 'C12348', count: 7, totalQuantity: 2450.8 }, |
| | | { trainCode: 'C12349', count: 5, totalQuantity: 1800.3 } |
| | | ]) |
| | | |
| | | // æ¨¡ææ°æ® - æè¿äº¤æè®°å½ |
| | | const recentTransactions = ref([ |
| | | { code: 'RK20230507001', type: 'å
¥åº', coalType: 'çç
¤', origin: '山西', calorificValue: '5300大å¡', quantity: 350.5, trainCode: 'C12345', createTime: '2023-05-07 09:30:15' }, |
| | | { code: 'CK20230507001', type: 'åºåº', coalType: 'æ çç
¤', origin: 'å
èå¤', calorificValue: '5800大å¡', quantity: 280.2, trainCode: 'C12346', createTime: '2023-05-07 10:15:30' }, |
| | | { code: 'LL20230507001', type: '颿', coalType: 'è¤ç
¤', origin: 'æ°ç', calorificValue: '4200大å¡', quantity: 120.8, trainCode: '', createTime: '2023-05-07 11:05:45' }, |
| | | { code: 'RK20230507002', type: 'å
¥åº', coalType: 'è´«ç
¤', origin: 'é西', calorificValue: '5100大å¡', quantity: 400.0, trainCode: 'C12347', createTime: '2023-05-07 13:20:00' }, |
| | | { code: 'CK20230507002', type: 'åºåº', coalType: 'ç¦ç
¤', origin: 'è´µå·', calorificValue: '5400大å¡', quantity: 310.5, trainCode: 'C12348', createTime: '2023-05-07 14:45:15' }, |
| | | { code: 'LL20230507002', type: '颿', coalType: 'çç
¤', origin: '山西', calorificValue: '5300大å¡', quantity: 200.0, trainCode: '', createTime: '2023-05-07 15:30:30' } |
| | | ]) |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleSearch = async () => { |
| | | try { |
| | | loading.value = true |
| | | console.log('æç´¢åæ°:', searchParams.value) |
| | | |
| | | // 模æAPI请æ±å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 800)) |
| | | |
| | | // æ¨¡ææ°æ®æ´æ° |
| | | todayInStock.value = 1000 + Math.random() * 500 |
| | | todayInStockPercentage.value = -10 + Math.random() * 20 |
| | | todayOutStock.value = 800 + Math.random() * 400 |
| | | todayOutStockPercentage.value = -10 + Math.random() * 20 |
| | | todayMaterialPick.value = 200 + Math.random() * 200 |
| | | todayMaterialPickPercentage.value = -10 + Math.random() * 20 |
| | | currentTotalStock.value = 15000 + Math.random() * 3000 |
| | | |
| | | // æ´æ°åç±»æ°æ® |
| | | coalTypeData.value.forEach(item => { |
| | | item.value = 2000 + Math.random() * 3000 |
| | | }) |
| | | |
| | | originData.value.forEach(item => { |
| | | item.value = 1500 + Math.random() * 4000 |
| | | }) |
| | | |
| | | calorificData.value.forEach(item => { |
| | | item.value = 500 + Math.random() * 5000 |
| | | }) |
| | | |
| | | trainCodeData.value.forEach(item => { |
| | | item.count = 3 + Math.floor(Math.random() * 15) |
| | | item.totalQuantity = 1000 + Math.random() * 4000 |
| | | }) |
| | | |
| | | // éæ°è®¡ç®ç¾åæ¯ |
| | | const calculatePercentages = (data) => { |
| | | const total = data.reduce((sum, item) => sum + item.value, 0) |
| | | data.forEach(item => { |
| | | item.percentage = total > 0 ? (item.value / total) * 100 : 0 |
| | | }) |
| | | } |
| | | |
| | | calculatePercentages(coalTypeData.value) |
| | | calculatePercentages(originData.value) |
| | | calculatePercentages(calorificData.value) |
| | | |
| | | ElMessage.success('æ¥è¯¢æå') |
| | | } catch (error) { |
| | | console.error('æ¥è¯¢å¤±è´¥:', error) |
| | | ElMessage.error('æ¥è¯¢å¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // éç½®æç´¢åæ° |
| | | const handleReset = () => { |
| | | searchParams.value = { |
| | | dateRange: [], |
| | | coalType: '', |
| | | trainCode: '' |
| | | } |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const handleRefresh = () => { |
| | | ElMessage.info('æ£å¨å·æ°æ°æ®...') |
| | | handleSearch() |
| | | } |
| | | |
| | | // èªå¨çæç¼ç 彿° |
| | | const generateCode = (type) => { |
| | | const prefix = type === 'å
¥åº' ? 'RK' : type === 'åºåº' ? 'CK' : 'LL' |
| | | const date = new Date().toISOString().slice(0, 10).replace(/-/g, '') |
| | | const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0') |
| | | return `${prefix}${date}${random}` |
| | | } |
| | | |
| | | // åå§åé¡µé¢ |
| | | onMounted(() => { |
| | | // é»è®¤æ¥è¯¢è¿7å¤©æ°æ® |
| | | const end = new Date() |
| | | const start = new Date() |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) |
| | | searchParams.value.dateRange = [start, end] |
| | | |
| | | // åå§å è½½æ°æ® |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .stock-material-board { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin-bottom: 15px; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-form-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .search-form { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | | gap: 16px; |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .stat-card { |
| | | display: flex; |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | transition: transform 0.3s, box-shadow 0.3s; |
| | | } |
| | | |
| | | .stat-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 48px; |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 50%; |
| | | font-size: 24px; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) .card-icon { |
| | | background: #F6FFED; |
| | | color: #52C41A; |
| | | } |
| | | |
| | | .stat-card:nth-child(2) .card-icon { |
| | | background: #FFF7E6; |
| | | color: #FA8C16; |
| | | } |
| | | |
| | | .stat-card:nth-child(3) .card-icon { |
| | | background: #FFF2F0; |
| | | color: #FF4D4F; |
| | | } |
| | | |
| | | .stat-card:nth-child(4) .card-icon { |
| | | background: #E6F7FF; |
| | | color: #1890FF; |
| | | } |
| | | |
| | | .card-content { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .card-percentage { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .card-percentage.positive { |
| | | color: #52C41A; |
| | | } |
| | | |
| | | .card-percentage.negative { |
| | | color: #FF4D4F; |
| | | } |
| | | |
| | | .category-cards { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .category-section { |
| | | margin-bottom: 24px; |
| | | padding-bottom: 20px; |
| | | border-bottom: 1px solid #EBEEF5; |
| | | } |
| | | |
| | | .category-section:last-child { |
| | | margin-bottom: 0; |
| | | padding-bottom: 0; |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .category-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 16px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .category-items { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .category-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .item-name { |
| | | width: 100px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .item-bar { |
| | | flex: 1; |
| | | height: 8px; |
| | | background-color: #F5F7FA; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .item-progress { |
| | | height: 100%; |
| | | background-color: #409EFF; |
| | | border-radius: 4px; |
| | | transition: width 0.3s ease; |
| | | } |
| | | |
| | | .item-value { |
| | | width: 140px; |
| | | text-align: right; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .transactions-card { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #EBEEF5; |
| | | } |
| | | |
| | | .card-header span { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .type-tag { |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .type-in { |
| | | background: #F6FFED; |
| | | color: #52C41A; |
| | | border: 1px solid #B7EB8F; |
| | | } |
| | | |
| | | .type-out { |
| | | background: #FFF7E6; |
| | | color: #FA8C16; |
| | | border: 1px solid #FFD591; |
| | | } |
| | | |
| | | .type-pick { |
| | | background: #FFF2F0; |
| | | color: #FF4D4F; |
| | | border: 1px solid #FFCCC7; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media screen and (max-width: 1200px) { |
| | | .stats-cards { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | } |
| | | |
| | | @media screen and (max-width: 768px) { |
| | | .stats-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .search-form { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-form .el-form-item { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |