¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ç¨æ°ç®¡çç³»ç»</h2> |
| | | <div class="header-info"> |
| | | <span class="update-time">æåæ´æ°ï¼{{ lastUpdateTime }}</span> |
| | | <el-button type="primary" size="small" @click="refreshData"> |
| | | <el-icon><Refresh /></el-icon> |
| | | å·æ°æ°æ® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡çåºå --> |
| | | <div class="stats-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon gas-device"> |
| | | <el-icon size="32"><Box /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ totalDevices }}</div> |
| | | <div class="stat-label">å¨ç¨è®¾å¤</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon daily-consumption"> |
| | | <el-icon size="32"><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ dailyConsumption }} m³</div> |
| | | <div class="stat-label">æ¥èé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon monthly-consumption"> |
| | | <el-icon size="32"><DataLine /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ monthlyConsumption }} m³</div> |
| | | <div class="stat-label">æèé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon gas-price"> |
| | | <el-icon size="32"><Money /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">Â¥{{ gasUnitPrice }}</div> |
| | | <div class="stat-label">æ°ä½åä»·</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è´¹ç¨ç»è®¡åºå --> |
| | | <div class="cost-stats"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="cost-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ¥è´¹ç¨ç»è®¡</span> |
| | | <el-tag type="success" size="small">仿¥</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="cost-content"> |
| | | <div class="cost-main"> |
| | | <span class="cost-amount">Â¥{{ dailyTotalCost.toFixed(2) }}</span> |
| | | <span class="cost-unit">å
</span> |
| | | </div> |
| | | <div class="cost-details"> |
| | | <div class="cost-item"> |
| | | <span>æ¶èéï¼</span> |
| | | <span>{{ dailyConsumption }} m³</span> |
| | | </div> |
| | | <div class="cost-item"> |
| | | <span>åä»·ï¼</span> |
| | | <span>¥{{ gasUnitPrice }}/m³</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="cost-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æè´¹ç¨ç»è®¡</span> |
| | | <el-tag type="primary" size="small">æ¬æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="cost-content"> |
| | | <div class="cost-main"> |
| | | <span class="cost-amount">Â¥{{ monthlyTotalCost.toFixed(2) }}</span> |
| | | <span class="cost-unit">å
</span> |
| | | </div> |
| | | <div class="cost-details"> |
| | | <div class="cost-item"> |
| | | <span>æ¶èéï¼</span> |
| | | <span>{{ monthlyConsumption }} m³</span> |
| | | </div> |
| | | <div class="cost-item"> |
| | | <span>å¹³ååä»·ï¼</span> |
| | | <span>¥{{ gasUnitPrice }}/m³</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 设å¤å表åºå --> |
| | | <div class="device-section"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>设å¤çæ§</span> |
| | | <div class="header-actions"> |
| | | <el-button type="primary" size="small" @click="addDevice"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ·»å è®¾å¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-table :data="deviceList" border style="width: 100%" v-loading="tableLoading"> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="设å¤ç¼å·" prop="deviceCode" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="设å¤åç§°" prop="deviceName" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="设å¤ç±»å" prop="deviceType" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="å½ååå(MPa)" prop="currentPressure" width="130" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getPressureClass(scope.row.currentPressure)"> |
| | | {{ scope.row.currentPressure }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å½å温度(â)" prop="currentTemperature" width="130" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getTemperatureClass(scope.row.currentTemperature)"> |
| | | {{ scope.row.currentTemperature }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ°ä½æµåº¦(ppm)" prop="gasConcentration" width="140" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getConcentrationClass(scope.row.gasConcentration)"> |
| | | {{ scope.row.gasConcentration }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è¿è¡ç¶æ" prop="status" width="100" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)" size="small"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æåæ´æ°" prop="lastUpdate" width="160" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" align="center" width="100" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link size="small" @click="editDevice(scope.row)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- æ·»å /ç¼è¾è®¾å¤å¼¹çª --> |
| | | <el-dialog v-model="deviceDialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="deviceForm" :rules="deviceRules" ref="deviceFormRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ç¼å·" prop="deviceCode"> |
| | | <el-input v-model="deviceForm.deviceCode" placeholder="请è¾å
¥è®¾å¤ç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="deviceForm.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ç±»å" prop="deviceType"> |
| | | <el-select v-model="deviceForm.deviceType" placeholder="è¯·éæ©è®¾å¤ç±»å" style="width: 100%"> |
| | | <el-option label="æ¶²åæ°å¨ç½" value="æ¶²åæ°å¨ç½" /> |
| | | <el-option label="å缩æ°å¨ç½" value="å缩æ°å¨ç½" /> |
| | | <el-option label="å¤©ç¶æ°å¨ç½" value="å¤©ç¶æ°å¨ç½" /> |
| | | <el-option label="æ°§æ°å¨ç½" value="æ°§æ°å¨ç½" /> |
| | | <el-option label="å
¶ä»" value="å
¶ä»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·" prop="specification"> |
| | | <el-input v-model="deviceForm.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设计åå(MPa)" prop="designPressure"> |
| | | <el-input-number v-model="deviceForm.designPressure" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="容积(m³)" prop="volume"> |
| | | <el-input-number v-model="deviceForm.volume" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="deviceDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveDevice">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | Refresh, |
| | | Box, |
| | | TrendCharts, |
| | | DataLine, |
| | | Money, |
| | | Plus |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const lastUpdateTime = ref('') |
| | | const totalDevices = ref(0) |
| | | const dailyConsumption = ref(0) |
| | | const monthlyConsumption = ref(0) |
| | | const gasUnitPrice = ref(0) |
| | | const dailyTotalCost = ref(0) |
| | | const monthlyTotalCost = ref(0) |
| | | const deviceList = ref([]) |
| | | const tableLoading = ref(false) |
| | | const deviceDialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const deviceFormRef = ref() |
| | | |
| | | // 设å¤è¡¨åæ°æ® |
| | | const deviceForm = reactive({ |
| | | deviceCode: '', |
| | | deviceName: '', |
| | | deviceType: '', |
| | | specification: '', |
| | | designPressure: 0, |
| | | volume: 0 |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const deviceRules = { |
| | | deviceCode: [{ required: true, message: '请è¾å
¥è®¾å¤ç¼å·', trigger: 'blur' }], |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | deviceType: [{ required: true, message: 'è¯·éæ©è®¾å¤ç±»å', trigger: 'change' }], |
| | | specification: [{ required: true, message: '请è¾å
¥è§æ ¼åå·', trigger: 'blur' }], |
| | | designPressure: [{ required: true, message: '请è¾å
¥è®¾è®¡åå', trigger: 'blur' }], |
| | | volume: [{ required: true, message: '请è¾å
¥å®¹ç§¯', trigger: 'blur' }] |
| | | } |
| | | |
| | | // 宿¶å¨ |
| | | let updateTimer = null |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | // æ´æ°ç»è®¡æ°æ® |
| | | totalDevices.value = Math.floor(Math.random() * 10) + 15 // 15-25å°è®¾å¤ |
| | | dailyConsumption.value = Math.floor(Math.random() * 100) + 200 // 200-300 m³ |
| | | monthlyConsumption.value = Math.floor(Math.random() * 2000) + 5000 // 5000-7000 m³ |
| | | gasUnitPrice.value = (Math.random() * 2 + 3).toFixed(2) // 3-5å
/m³ |
| | | |
| | | // 计ç®è´¹ç¨ |
| | | dailyTotalCost.value = dailyConsumption.value * gasUnitPrice.value |
| | | monthlyTotalCost.value = monthlyConsumption.value * gasUnitPrice.value |
| | | |
| | | // æ´æ°è®¾å¤åè¡¨æ°æ® |
| | | deviceList.value = Array.from({ length: totalDevices.value }, (_, index) => ({ |
| | | id: index + 1, |
| | | deviceCode: `GT${String(index + 1).padStart(3, '0')}`, |
| | | deviceName: `卿°ç½${index + 1}`, |
| | | deviceType: ['æ¶²åæ°å¨ç½', 'å缩æ°å¨ç½', 'å¤©ç¶æ°å¨ç½', 'æ°§æ°å¨ç½'][Math.floor(Math.random() * 4)], |
| | | specification: `${Math.floor(Math.random() * 50) + 50}m³`, |
| | | currentPressure: (Math.random() * 2 + 0.5).toFixed(2), |
| | | currentTemperature: (Math.random() * 20 + 15).toFixed(1), |
| | | gasConcentration: (Math.random() * 10).toFixed(2), |
| | | status: ['running', 'stopped', 'warning', 'error'][Math.floor(Math.random() * 4)], |
| | | lastUpdate: new Date().toLocaleString() |
| | | })) |
| | | |
| | | // æ´æ°æåæ´æ°æ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | } |
| | | |
| | | // è·åååç¶ææ ·å¼ |
| | | const getPressureClass = (pressure) => { |
| | | const p = parseFloat(pressure) |
| | | if (p < 0.8) return 'pressure-low' |
| | | if (p > 1.5) return 'pressure-high' |
| | | return 'pressure-normal' |
| | | } |
| | | |
| | | // è·åæ¸©åº¦ç¶ææ ·å¼ |
| | | const getTemperatureClass = (temperature) => { |
| | | const t = parseFloat(temperature) |
| | | if (t < 10 || t > 35) return 'temperature-warning' |
| | | return 'temperature-normal' |
| | | } |
| | | |
| | | // è·åæµåº¦ç¶ææ ·å¼ |
| | | const getConcentrationClass = (concentration) => { |
| | | const c = parseFloat(concentration) |
| | | if (c > 5) return 'concentration-warning' |
| | | return 'concentration-normal' |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | running: 'success', |
| | | stopped: 'info', |
| | | warning: 'warning', |
| | | error: 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | running: 'è¿è¡ä¸', |
| | | stopped: '已忢', |
| | | warning: 'è¦å', |
| | | error: 'æ
é' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = () => { |
| | | generateMockData() |
| | | ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | // æ·»å è®¾å¤ |
| | | const addDevice = () => { |
| | | dialogTitle.value = 'æ·»å 设å¤' |
| | | Object.keys(deviceForm).forEach(key => { |
| | | deviceForm[key] = key === 'designPressure' || key === 'volume' ? 0 : '' |
| | | }) |
| | | deviceDialogVisible.value = true |
| | | } |
| | | |
| | | // ç¼è¾è®¾å¤ |
| | | const editDevice = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®¾å¤' |
| | | Object.keys(deviceForm).forEach(key => { |
| | | if (row[key] !== undefined) { |
| | | deviceForm[key] = row[key] |
| | | } |
| | | }) |
| | | deviceDialogVisible.value = true |
| | | } |
| | | |
| | | |
| | | |
| | | // ä¿åè®¾å¤ |
| | | const saveDevice = () => { |
| | | deviceFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | ElMessage.success('ä¿åæå') |
| | | deviceDialogVisible.value = false |
| | | refreshData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // å¯å¨å®æ¶æ´æ° |
| | | const startAutoUpdate = () => { |
| | | updateTimer = setInterval(() => { |
| | | generateMockData() |
| | | }, 60000) // æ¯åéæ´æ°ä¸æ¬¡ |
| | | } |
| | | |
| | | // 忢宿¶æ´æ° |
| | | const stopAutoUpdate = () => { |
| | | if (updateTimer) { |
| | | clearInterval(updateTimer) |
| | | updateTimer = null |
| | | } |
| | | } |
| | | |
| | | // ç»ä»¶æè½½ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | startAutoUpdate() |
| | | }) |
| | | |
| | | // ç»ä»¶å¸è½½ |
| | | onUnmounted(() => { |
| | | stopAutoUpdate() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 15px; |
| | | |
| | | .update-time { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .stats-cards { |
| | | margin-bottom: 20px; |
| | | |
| | | .stat-card { |
| | | .stat-content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10px; |
| | | |
| | | .stat-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | |
| | | &.gas-device { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.daily-consumption { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.monthly-consumption { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.gas-price { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | color: white; |
| | | } |
| | | } |
| | | |
| | | .stat-info { |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-top: 5px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .cost-stats { |
| | | margin-bottom: 20px; |
| | | |
| | | .cost-card { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .cost-content { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | |
| | | .cost-main { |
| | | margin-bottom: 15px; |
| | | |
| | | .cost-amount { |
| | | font-size: 36px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .cost-unit { |
| | | font-size: 16px; |
| | | color: #909399; |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | |
| | | .cost-details { |
| | | .cost-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 8px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .device-section { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ç¶ææ ·å¼ |
| | | .pressure-low { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .pressure-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .pressure-high { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .temperature-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .temperature-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .concentration-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .concentration-warning { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |