|  |  | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | // ååºå¼ç¶æç®¡ç - ä½¿ç¨è§£æåé»è®¤å¼ | 
 |  |  | const initFormState = () => ({ consumables: false }); | 
 |  |  | const initFormState = () => ({ isConsumables: false }); | 
 |  |  |  | 
 |  |  | const state = reactive({ | 
 |  |  |   form: initFormState(), | 
 |  |  | 
 |  |  |     columns: [ | 
 |  |  |       { prop: "equipmentNo", label: "设å¤ç¼å·", minWidth: 100 }, | 
 |  |  |       { prop: "equipmentName", label: "设å¤åç§°", minWidth: 100 }, | 
 |  |  |       { prop: "consumables", label: "èæ", | 
 |  |  |         formatter: (row) => (row.consumables ? "æ¯" : "å¦"), minWidth: 100 }, | 
 |  |  |       { prop: "isConsumables", label: "èæ", | 
 |  |  |         formatter: (row) => (row.isConsumables ? "æ¯" : "å¦"), minWidth: 100 }, | 
 |  |  |       { prop: "quantity", label: "æ»æ°é", minWidth: 100 }, | 
 |  |  |       { prop: "usedNo", label: "å·²ä½¿ç¨æ°é", minWidth: 100 }, | 
 |  |  |       { prop: "specification", label: "è§æ ¼åå·", minWidth: 100 }, | 
 |  |  | 
 |  |  |  | 
 |  |  | const resetState = () => { | 
 |  |  |   form.value = { | 
 |  |  |     consumables: false, | 
 |  |  |     isConsumables: false, | 
 |  |  |   }; | 
 |  |  |   addOrEdit.value = "add"; | 
 |  |  |   loading.value = true; | 
 |  |  | 
 |  |  | const handleAdd = () => { | 
 |  |  |   addOrEdit.value = "add"; | 
 |  |  |   form.value = { | 
 |  |  |     consumables: false, | 
 |  |  |     isConsumables: false, | 
 |  |  |   }; | 
 |  |  |   title.value = `æ°å¢${currentTabConfig.value.label}`; | 
 |  |  |   // éç¨ç | 
 
 |  |  | 
 |  |  |             </el-form-item> | 
 |  |  |           </el-col> | 
 |  |  |           <el-col :span="11"> | 
 |  |  |             <el-form-item label="æ¯å¦ä¸ºèæ" prop="consumables"> | 
 |  |  |               <el-select v-model="formData.consumables" placeholder="è¯·éæ©æ¯å¦ä¸ºèæç±»å" :disabled="isViewMode"> | 
 |  |  |             <el-form-item label="æ¯å¦ä¸ºèæ" prop="isConsumables"> | 
 |  |  |               <el-select v-model="formData.isConsumables" placeholder="è¯·éæ©æ¯å¦ä¸ºèæç±»å" :disabled="isViewMode"> | 
 |  |  |                 <el-option label="æ¯" :value="true" /> | 
 |  |  |                 <el-option label="å¦" :value="false" /> | 
 |  |  |               </el-select> | 
 |  |  | 
 |  |  |   equipmentName: [ | 
 |  |  |     { required: true, message: "请è¾å
¥ä¾è´§ååç§°", trigger: "blur" }, | 
 |  |  |   ], | 
 |  |  |   consumables: [ | 
 |  |  |   isConsumables: [ | 
 |  |  |     { required: true, message: "è¯·éæ©æ¯å¦ä¸ºèæ", trigger: "change" }, | 
 |  |  |   ], | 
 |  |  | }); | 
 
 |  |  | 
 |  |  |             </template> | 
 |  |  |           </el-input> | 
 |  |  |         </el-form-item> | 
 |  |  |         <el-form-item label="车çå·" prop="licensePlate"> | 
 |  |  |           <!-- é»è®¤ä¸º0 --> | 
 |  |  |           <el-input | 
 |  |  |               :precision="2" | 
 |  |  |               v-model.number="form.licensePlate" | 
 |  |  |               placeholder="请è¾å
¥è½¦çå·" | 
 |  |  |               :disabled="isViewMode" | 
 |  |  |           > | 
 |  |  |           </el-input> | 
 |  |  |         </el-form-item> | 
 |  |  |         <el-form-item label="è¿è´¹" prop="freight"> | 
 |  |  |           <!-- é»è®¤ä¸º0 --> | 
 |  |  |           <el-input | 
 
 |  |  | 
 |  |  |   { prop: "purchaseQuantity", label: "éè´æ°é", minWidth: 100 }, | 
 |  |  |   { prop: "priceIncludingTax", label: "åä»·ï¼å«ç¨ï¼", minWidth: 150 }, | 
 |  |  |   { prop: "totalPriceIncludingTax", label: "æ»ä»·ï¼å«ç¨ï¼", minWidth: 100 }, | 
 |  |  |   { prop: "licensePlate", label: "车çå·", minWidth: 100 }, | 
 |  |  |   { prop: "freight", label: "è¿è´¹", minWidth: 100 }, | 
 |  |  |   { prop: "taxRate", label: "ç¨ç", minWidth: 100 }, | 
 |  |  |   { prop: "priceExcludingTax", label: "ä¸å«ç¨åä»·", minWidth: 100 }, | 
 
 |  |  | 
 |  |  |       </template> | 
 |  |  |     </el-table-column> | 
 |  |  |  | 
 |  |  |     <el-table-column label="éè´åä»·" min-width="120"> | 
 |  |  |     <el-table-column label="éè´æ»ä»·" min-width="120"> | 
 |  |  |       <template #default="{ row, $index }"> | 
 |  |  |         <el-input | 
 |  |  |             v-model="row.purchasePrice" | 
 |  |  |             placeholder="请è¾å
¥éè´åä»·" | 
 |  |  |             placeholder="请è¾å
¥éè´æ»ä»·" | 
 |  |  |             type="number" | 
 |  |  |             @input="handleInput('purchasePrice', $index, $event)" | 
 |  |  |             :disabled="isViewMode" | 
 
| ¶Ô±ÈÐÂÎļþ | 
 |  |  | 
 |  |  | <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> | 
 
 |  |  | 
 |  |  |               <el-select v-model="form.coalId" placeholder="è¯·éæ©ç
¤ç§" @change="setInfo" :disabled="operationType === 'view'"> | 
 |  |  |                 <el-option | 
 |  |  |                     v-for="item in coalOptions" | 
 |  |  |                     :key="item.coalId" | 
 |  |  |                     :key="item.id" | 
 |  |  |                     :label="item.coal" | 
 |  |  |                     :value="item.coalId" | 
 |  |  |                     :value="item.id" | 
 |  |  |                 /> | 
 |  |  |               </el-select> | 
 |  |  |             </el-form-item> |