| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = å¼ä¹æ°´æ³¥ç®¡çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åå¥é¤å
|
| | |
|
| | | # å¼åç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'development'
|
| | |
|
| | | # å¼ä¹æ°´æ³¥ç®¡çç³»ç»/å¼åç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åå¥é¤å
/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = å¼ä¹æ°´æ³¥ç®¡çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åå¥é¤å
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'production'
|
| | |
|
| | | # å¼ä¹æ°´æ³¥ç®¡çç³»ç»/ç产ç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åå¥é¤å
/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/prod-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = å¼ä¹æ°´æ³¥ç®¡çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åå¥é¤å
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'staging'
|
| | |
|
| | | # å¼ä¹æ°´æ³¥ç®¡çç³»ç»/ç产ç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åå¥é¤å
/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/stage-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
| | | <meta name="renderer" content="webkit">
|
| | | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
| | | <link rel="icon" href="/HYSNico.ico">
|
| | | <title>å¼ä¹æ°´æ³¥ç®¡çç³»ç»</title>
|
| | | <link rel="icon" href="/favicon.ico">
|
| | | <title>ä¸å°ä¼ä¸æ°åå转åå¥é¤å
</title>
|
| | | <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
| | | <style>
|
| | | html,
|
| | |
| | | { |
| | | "name": "ruoyi", |
| | | "version": "3.8.9", |
| | | "description": "å¼ä¹æ°´æ³¥ç®¡çç³»ç»", |
| | | "description": "ä¸å°ä¼ä¸æ°åå转åå¥é¤å
", |
| | | "author": "è¥ä¾", |
| | | "license": "MIT", |
| | | "type": "module", |
| | |
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/main/MobileChat',
|
| | | component: Layout,
|
| | | redirect: '',
|
| | | hidden: true,
|
| | | children: [
|
| | | {
|
| | | path: '',
|
| | | component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
|
| | | name: 'MobileChat',
|
| | | meta: { title: 'AI对è¯', icon: 'dashboard', affix: true}
|
| | | }
|
| | | ]
|
| | | },
|
| | | // {
|
| | | // path: '/equipment',
|
| | | // component: Layout,
|
| | | // redirect: '/equipment/iot-monitor',
|
| | | // children: [
|
| | | // {
|
| | | // path: 'iot-monitor',
|
| | | // component: () => import('@/views/equipmentManagement/iotMonitor/index.vue'),
|
| | | // name: 'IoTMonitor',
|
| | | // meta: { title: 'IoTçæ§', icon: 'monitor', noCache: true }
|
| | | // }
|
| | | // ]
|
| | | // },
|
| | | // {
|
| | | // path: '/main/MobileChat',
|
| | | // component: Layout,
|
| | | // redirect: '',
|
| | | // hidden: true,
|
| | | // children: [
|
| | | // {
|
| | | // path: '',
|
| | | // component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
|
| | | // name: 'MobileChat',
|
| | | // meta: { title: 'AI对è¯', icon: 'dashboard', affix: true}
|
| | | // }
|
| | | // ]
|
| | | // },
|
| | | {
|
| | | path: '/user',
|
| | | component: Layout,
|
| | |
| | | const { form, rules } = toRefs(data); |
| | | const productOptions = ref([]); |
| | | const currentApproveStatus = ref(0) |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | |
| | | // 审æ¹äººèç¹ç¸å
³ |
| | | const approverNodes = ref([ |
| | |
| | | const submitForm = () => { |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = props.approveType |
| | | // 审æ¹äººå¿
å¡«æ ¡éª |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery"></info-form-dia> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | | </div> |
| | |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | // å®ä¹ç»ä»¶æ¥æ¶çprops |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }); |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | |
| | |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | approveProcessListPage({...page, ...searchForm.value,}).then(res => { |
| | | approveProcessListPage({...page, ...searchForm.value,approveType:props.approveType}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="1" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="2" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="3" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="4" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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="real-time-monitor"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>çµåæ¶è</span> |
| | | <el-tag type="success" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ electricityConsumption }}</span> |
| | | <span class="unit">kW·h</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(electricityTrend)" size="small"> |
| | | {{ electricityTrend > 0 ? 'â' : 'â' }} {{ Math.abs(electricityTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ°´æ¶è</span> |
| | | <el-tag type="primary" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ waterConsumption }}</span> |
| | | <span class="unit">m³</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(waterTrend)" size="small"> |
| | | {{ waterTrend > 0 ? 'â' : 'â' }} {{ Math.abs(waterTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ°ä½æ¶è</span> |
| | | <el-tag type="warning" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ gasConsumption }}</span> |
| | | <span class="unit">m³</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(gasTrend)" size="small"> |
| | | {{ gasTrend > 0 ? 'â' : 'â' }} {{ Math.abs(gasTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è½èè¶å¿åæ --> |
| | | <div class="trend-analysis"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è½èè¶å¿åæ</span> |
| | | <div class="time-selector"> |
| | | <el-radio-group v-model="trendTimeUnit" @change="handleTrendTimeChange"> |
| | | <el-radio value="hour">å°æ¶</el-radio> |
| | | <el-radio value="day">æ¥</el-radio> |
| | | <el-radio value="week">å¨</el-radio> |
| | | <el-radio value="month">æ</el-radio> |
| | | <el-radio value="year">å¹´</el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="trendChart" style="width: 100%; height: 400px;"></div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- è½èç»è®¡ä¸æå --> |
| | | <div class="statistics-ranking"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="statistics-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">è½èç»è®¡æ¥è¡¨</span> |
| | | <div class="header-actions"> |
| | | <el-select v-model="statisticsPeriod" @change="handleStatisticsChange" size="small" style="width: 100px;"> |
| | | <el-option label="æ¥ç»è®¡" value="day" /> |
| | | <el-option label="å¨ç»è®¡" value="week" /> |
| | | <el-option label="æç»è®¡" value="month" /> |
| | | <el-option label="å¹´ç»è®¡" value="year" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="statistics-content"> |
| | | <div class="statistics-item"> |
| | | <span class="label">æ»è½èï¼</span> |
| | | <span class="value">{{ totalEnergyConsumption }} kW·h</span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">忝ï¼</span> |
| | | <span class="value" :class="getComparisonClass(yearOverYear)"> |
| | | {{ yearOverYear > 0 ? '+' : '' }}{{ yearOverYear }}% |
| | | </span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">ç¯æ¯ï¼</span> |
| | | <span class="value" :class="getComparisonClass(monthOverMonth)"> |
| | | {{ monthOverMonth > 0 ? '+' : '' }}{{ monthOverMonth }}% |
| | | </span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">èè½çï¼</span> |
| | | <span class="value success">{{ energySavingRate }}%</span> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="ranking-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">è½èæå</span> |
| | | <el-select v-model="rankingType" @change="handleRankingChange" size="small" style="width: 120px;"> |
| | | <el-option label="é¨é¨æå" value="department" /> |
| | | <el-option label="è½¦é´æå" value="workshop" /> |
| | | <el-option label="è®¾å¤æå" value="equipment" /> |
| | | </el-select> |
| | | </div> |
| | | </template> |
| | | <div class="ranking-list"> |
| | | <div v-for="(item, index) in rankingList" :key="index" class="ranking-item"> |
| | | <div class="ranking-number" :class="getRankingClass(index + 1)">{{ index + 1 }}</div> |
| | | <div class="ranking-info"> |
| | | <div class="ranking-name">{{ item.name }}</div> |
| | | <div class="ranking-value">{{ item.value }} kW·h</div> |
| | | </div> |
| | | <div class="ranking-trend"> |
| | | <el-tag :type="getTrendType(item.trend)" size="small"> |
| | | {{ item.trend > 0 ? 'â' : 'â' }} {{ Math.abs(item.trend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¼å¸¸åæä¸æºè½æ§å¶ --> |
| | | <div class="analysis-control"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="abnormal-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">å¼å¸¸åæ</span> |
| | | <el-tag type="danger" size="small">{{ abnormalCount }}个å¼å¸¸</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="abnormal-list"> |
| | | <div v-for="(item, index) in abnormalList" :key="index" class="abnormal-item"> |
| | | <div class="abnormal-icon"> |
| | | <el-icon :color="getAbnormalColor(item.level)"> |
| | | <Warning v-if="item.level === 'warning'" /> |
| | | <CircleClose v-else /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="abnormal-content"> |
| | | <div class="abnormal-title">{{ item.title }}</div> |
| | | <div class="abnormal-desc">{{ item.description }}</div> |
| | | <div class="abnormal-time">{{ item.time }}</div> |
| | | </div> |
| | | <div class="abnormal-action"> |
| | | <el-button link size="small" @click="handleAbnormal(item)">å¤ç</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="control-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">æºè½æ§å¶ç³»ç»</span> |
| | | <el-switch v-model="autoControlEnabled" @change="handleAutoControlChange" /> |
| | | </div> |
| | | </template> |
| | | <div class="control-content"> |
| | | <div class="control-item"> |
| | | <span class="label">å³°è°·å¹³çµä»·ç®¡çï¼</span> |
| | | <el-tag :type="getPriceType(currentPriceType)" size="small"> |
| | | {{ getPriceTypeText(currentPriceType) }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">è´è·é¢æµï¼</span> |
| | | <span class="value">{{ loadForecast }} kW</span> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">èªå¨å¯åï¼</span> |
| | | <el-tag :type="autoStartStop ? 'success' : 'info'" size="small"> |
| | | {{ autoStartStop ? 'å·²å¯ç¨' : 'å·²ç¦ç¨' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">æºè½è°èï¼</span> |
| | | <el-progress :percentage="intelligentAdjustment" :color="getProgressColor" /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- ç¯ä¿ææ --> |
| | | <div class="environmental-indicators"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç¯ä¿ææ çæ§</span> |
| | | </div> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">ç¢³ææ¾é</div> |
| | | <div class="indicator-value">{{ carbonEmission }} kg</div> |
| | | <div class="indicator-trend"> |
| | | <span>忝ï¼</span> |
| | | <span :class="getComparisonClass(carbonEmissionTrend)"> |
| | | {{ carbonEmissionTrend > 0 ? '+' : '' }}{{ carbonEmissionTrend }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">ç¯ä¿è¾¾æ ç</div> |
| | | <div class="indicator-value">{{ environmentalCompliance }}%</div> |
| | | <div class="indicator-trend"> |
| | | <span>ç®æ ï¼</span> |
| | | <span class="success">95%</span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">绿è²è½æºå æ¯</div> |
| | | <div class="indicator-value">{{ greenEnergyRatio }}%</div> |
| | | <div class="indicator-trend"> |
| | | <span>ç®æ ï¼</span> |
| | | <span class="success">30%</span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- å¤ç»´åº¦æ¥è¡¨ --> |
| | | <div class="multi-dimensional-reports"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>å¤ç»´åº¦æ¥è¡¨</span> |
| | | </div> |
| | | </template> |
| | | <div class="report-filters"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="æ¶é´ç»´åº¦"> |
| | | <el-select v-model="reportTimeDimension" placeholder="éæ©æ¶é´ç»´åº¦"> |
| | | <el-option label="å°æ¶" value="hour" /> |
| | | <el-option label="æ¥" value="day" /> |
| | | <el-option label="å¨" value="week" /> |
| | | <el-option label="æ" value="month" /> |
| | | <el-option label="å¹´" value="year" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="é¨é¨ç»´åº¦"> |
| | | <el-select v-model="reportDepartmentDimension" placeholder="éæ©é¨é¨"> |
| | | <el-option label="å
¨é¨é¨é¨" value="all" /> |
| | | <el-option label="ç产é¨" value="production" /> |
| | | <el-option label="ææ¯é¨" value="technology" /> |
| | | <el-option label="è¡æ¿é¨" value="administration" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="设å¤ç»´åº¦"> |
| | | <el-select v-model="reportEquipmentDimension" placeholder="éæ©è®¾å¤ç±»å"> |
| | | <el-option label="å
¨é¨è®¾å¤" value="all" /> |
| | | <el-option label="çµå设å¤" value="electricity" /> |
| | | <el-option label="æ°´å¤ç设å¤" value="water" /> |
| | | <el-option label="æ°ä½è®¾å¤" value="gas" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateReport">çææ¥è¡¨</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div class="report-preview"> |
| | | <div class="report-data"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">çµåæ¶è</div> |
| | | <div class="data-value">{{ reportData.electricity }} kW·h</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.electricityTrend)"> |
| | | {{ reportData.electricityTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.electricityTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">æ°´æ¶è</div> |
| | | <div class="data-value">{{ reportData.water }} m³</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.waterTrend)"> |
| | | {{ reportData.waterTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.waterTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">æ°ä½æ¶è</div> |
| | | <div class="data-value">{{ reportData.gas }} m³</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.gasTrend)"> |
| | | {{ reportData.gasTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.gasTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <div class="report-chart"> |
| | | <div class="chart-title">è½èè¶å¿å¾</div> |
| | | <div class="chart-bars"> |
| | | <div v-for="(item, index) in reportData.chartData" :key="index" class="chart-bar"> |
| | | <div class="bar-label">{{ item.label }}</div> |
| | | <div class="bar-container"> |
| | | <div class="bar-fill" :style="{ height: item.percentage + '%', backgroundColor: item.color }"></div> |
| | | </div> |
| | | <div class="bar-value">{{ item.value }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="report-summary"> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">æ»è½èï¼</span> |
| | | <span class="summary-value">{{ reportData.totalEnergy }} kW·h</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">å¹³åè½èï¼</span> |
| | | <span class="summary-value">{{ reportData.averageEnergy }} kW·h</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">è½èæçï¼</span> |
| | | <span class="summary-value">{{ reportData.efficiency }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { |
| | | Refresh, |
| | | Download, |
| | | Warning, |
| | | CircleClose, |
| | | Document, |
| | | Edit, |
| | | Bell |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const lastUpdateTime = ref('') |
| | | const electricityConsumption = ref(0) |
| | | const waterConsumption = ref(0) |
| | | const gasConsumption = ref(0) |
| | | const electricityTrend = ref(0) |
| | | const waterTrend = ref(0) |
| | | const gasTrend = ref(0) |
| | | |
| | | // è¶å¿åæ |
| | | const trendTimeUnit = ref('day') |
| | | const trendChart = ref(null) |
| | | let chartInstance = null |
| | | |
| | | // ç»è®¡æ¥è¡¨ |
| | | const statisticsPeriod = ref('month') |
| | | const totalEnergyConsumption = ref(0) |
| | | const yearOverYear = ref(0) |
| | | const monthOverMonth = ref(0) |
| | | const energySavingRate = ref(0) |
| | | |
| | | // è½èæå |
| | | const rankingType = ref('department') |
| | | const rankingList = ref([]) |
| | | |
| | | // å¼å¸¸åæ |
| | | const abnormalCount = ref(0) |
| | | const abnormalList = ref([]) |
| | | |
| | | // æºè½æ§å¶ |
| | | const autoControlEnabled = ref(true) |
| | | const currentPriceType = ref('peak') |
| | | const loadForecast = ref(0) |
| | | const autoStartStop = ref(true) |
| | | const intelligentAdjustment = ref(0) |
| | | |
| | | // ç¯ä¿ææ |
| | | const carbonEmission = ref(0) |
| | | const carbonEmissionTrend = ref(0) |
| | | const environmentalCompliance = ref(0) |
| | | const greenEnergyRatio = ref(0) |
| | | |
| | | // å¤ç»´åº¦æ¥è¡¨ |
| | | const reportTimeDimension = ref('month') |
| | | const reportDepartmentDimension = ref('all') |
| | | const reportEquipmentDimension = ref('all') |
| | | const reportData = ref({ |
| | | electricity: 0, |
| | | water: 0, |
| | | gas: 0, |
| | | electricityTrend: 0, |
| | | waterTrend: 0, |
| | | gasTrend: 0, |
| | | totalEnergy: 0, |
| | | averageEnergy: 0, |
| | | efficiency: 0, |
| | | chartData: [] |
| | | }) |
| | | |
| | | // 宿¶å¨ |
| | | let updateTimer = null |
| | | |
| | | // è·åè¶å¿ç±»åæ ·å¼ |
| | | const getTrendType = (trend) => { |
| | | if (trend > 0) return 'danger' |
| | | if (trend < 0) return 'success' |
| | | return 'info' |
| | | } |
| | | |
| | | // è·å对æ¯ç±»åæ ·å¼ |
| | | const getComparisonClass = (value) => { |
| | | if (value > 0) return 'danger' |
| | | if (value < 0) return 'success' |
| | | return 'info' |
| | | } |
| | | |
| | | // è·åæåæ ·å¼ |
| | | const getRankingClass = (rank) => { |
| | | if (rank === 1) return 'ranking-first' |
| | | if (rank === 2) return 'ranking-second' |
| | | if (rank === 3) return 'ranking-third' |
| | | return 'ranking-normal' |
| | | } |
| | | |
| | | // è·åå¼å¸¸é¢è² |
| | | const getAbnormalColor = (level) => { |
| | | return level === 'warning' ? '#E6A23C' : '#F56C6C' |
| | | } |
| | | |
| | | // è·åçµä»·ç±»åæ ·å¼ |
| | | const getPriceType = (type) => { |
| | | const typeMap = { |
| | | peak: 'danger', |
| | | normal: 'warning', |
| | | valley: 'success' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | | |
| | | // è·åçµä»·ç±»åææ¬ |
| | | const getPriceTypeText = (type) => { |
| | | const typeMap = { |
| | | peak: 'å³°æ¶', |
| | | normal: 'å¹³æ¶', |
| | | valley: 'è°·æ¶' |
| | | } |
| | | return typeMap[type] || 'æªç¥' |
| | | } |
| | | |
| | | // è·åè¿åº¦æ¡é¢è² |
| | | const getProgressColor = (percentage) => { |
| | | if (percentage < 50) return '#67C23A' |
| | | if (percentage < 80) return '#E6A23C' |
| | | return '#F56C6C' |
| | | } |
| | | |
| | | // è·åè¶å¿æ ·å¼ |
| | | const getTrendClass = (trend) => { |
| | | if (trend > 0) return 'trend-up' |
| | | if (trend < 0) return 'trend-down' |
| | | return 'trend-stable' |
| | | } |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | // 宿¶è½èæ°æ® |
| | | electricityConsumption.value = Math.floor(Math.random() * 1000) + 2000 |
| | | waterConsumption.value = Math.floor(Math.random() * 100) + 150 |
| | | gasConsumption.value = Math.floor(Math.random() * 50) + 80 |
| | | |
| | | // è¶å¿æ°æ® |
| | | electricityTrend.value = (Math.random() * 20 - 10).toFixed(1) |
| | | waterTrend.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | gasTrend.value = (Math.random() * 12 - 6).toFixed(1) |
| | | |
| | | // ç»è®¡æ°æ® |
| | | totalEnergyConsumption.value = Math.floor(Math.random() * 50000) + 100000 |
| | | yearOverYear.value = (Math.random() * 20 - 10).toFixed(1) |
| | | monthOverMonth.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | energySavingRate.value = (Math.random() * 10 + 5).toFixed(1) |
| | | |
| | | // æåæ°æ® |
| | | rankingList.value = [ |
| | | { name: 'ç产车é´A', value: Math.floor(Math.random() * 5000) + 10000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'ç产车é´B', value: Math.floor(Math.random() * 4000) + 8000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'ææ¯ç åé¨', value: Math.floor(Math.random() * 3000) + 6000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'è¡æ¿åå
¬åº', value: Math.floor(Math.random() * 2000) + 4000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'åå¤ä¿éåº', value: Math.floor(Math.random() * 1500) + 3000, trend: (Math.random() * 20 - 10).toFixed(1) } |
| | | ].sort((a, b) => b.value - a.value) |
| | | |
| | | // å¼å¸¸æ°æ® |
| | | abnormalCount.value = Math.floor(Math.random() * 5) + 1 |
| | | abnormalList.value = [ |
| | | { level: 'warning', title: 'çµåè´è·è¿é«', description: 'ç产车é´Açµåè´è·è¾¾å°85%ï¼å»ºè®®æ£æ¥è®¾å¤è¿è¡ç¶æ', time: '2åéå' }, |
| | | { level: 'error', title: 'æ°´åå¼å¸¸', description: 'æ°´å¤ç设å¤ååå¼å¸¸ï¼å½ååå0.3MPaï¼ä½äºæ£å¸¸èå´', time: '5åéå' } |
| | | ] |
| | | |
| | | // æºè½æ§å¶æ°æ® |
| | | loadForecast.value = Math.floor(Math.random() * 500) + 1500 |
| | | intelligentAdjustment.value = Math.floor(Math.random() * 30) + 60 |
| | | |
| | | // ç¯ä¿ææ |
| | | carbonEmission.value = Math.floor(Math.random() * 1000) + 5000 |
| | | carbonEmissionTrend.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | environmentalCompliance.value = (Math.random() * 5 + 95).toFixed(1) |
| | | greenEnergyRatio.value = (Math.random() * 10 + 25).toFixed(1) |
| | | |
| | | // æ´æ°æåæ´æ°æ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | |
| | | // åæ¶æ´æ°æ¥è¡¨æ°æ® |
| | | generateReportData() |
| | | } |
| | | |
| | | // åå§åè¶å¿å¾è¡¨ |
| | | const initTrendChart = () => { |
| | | if (chartInstance) { |
| | | chartInstance.dispose() |
| | | } |
| | | |
| | | chartInstance = echarts.init(trendChart.value) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'è½èè¶å¿åæ', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['çµå', 'æ°´', 'æ°ä½'], |
| | | bottom: 10 |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: generateTimeData() |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ¶èé' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'çµå', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | }, |
| | | { |
| | | name: 'æ°´', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | }, |
| | | { |
| | | name: 'æ°ä½', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | } |
| | | ] |
| | | } |
| | | |
| | | chartInstance.setOption(option) |
| | | } |
| | | |
| | | // çææ¶é´æ°æ® |
| | | const generateTimeData = () => { |
| | | const data = [] |
| | | const now = new Date() |
| | | |
| | | switch (trendTimeUnit.value) { |
| | | case 'hour': |
| | | for (let i = 23; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 60 * 60 * 1000) |
| | | data.unshift(time.getHours() + ':00') |
| | | } |
| | | break |
| | | case 'day': |
| | | for (let i = 29; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 24 * 60 * 60 * 1000) |
| | | data.unshift(time.getDate() + 'æ¥') |
| | | } |
| | | break |
| | | case 'week': |
| | | for (let i = 11; i >= 0; i--) { |
| | | data.unshift(`第${12 - i}å¨`) |
| | | } |
| | | break |
| | | case 'month': |
| | | for (let i = 11; i >= 0; i--) { |
| | | const month = (12 - i) % 12 || 12 |
| | | data.unshift(`${month}æ`) |
| | | } |
| | | break |
| | | case 'year': |
| | | for (let i = 4; i >= 0; i--) { |
| | | const year = new Date().getFullYear() - i |
| | | data.unshift(`${year}å¹´`) |
| | | } |
| | | break |
| | | } |
| | | |
| | | return data |
| | | } |
| | | |
| | | // çæç³»åæ°æ® |
| | | const generateSeriesData = () => { |
| | | const data = [] |
| | | const count = trendTimeUnit.value === 'hour' ? 24 : |
| | | trendTimeUnit.value === 'day' ? 30 : |
| | | trendTimeUnit.value === 'week' ? 12 : |
| | | trendTimeUnit.value === 'month' ? 12 : 5 |
| | | |
| | | for (let i = 0; i < count; i++) { |
| | | data.push(Math.floor(Math.random() * 1000) + 500) |
| | | } |
| | | |
| | | return data |
| | | } |
| | | |
| | | // å¤çè¶å¿æ¶é´åå |
| | | const handleTrendTimeChange = () => { |
| | | nextTick(() => { |
| | | initTrendChart() |
| | | }) |
| | | } |
| | | |
| | | // å¤çç»è®¡å¨æåå |
| | | const handleStatisticsChange = () => { |
| | | generateMockData() |
| | | } |
| | | |
| | | // å¤çæåç±»ååå |
| | | const handleRankingChange = () => { |
| | | // æ ¹æ®ç±»åéæ°çææåæ°æ® |
| | | generateMockData() |
| | | } |
| | | |
| | | // å¤çèªå¨æ§å¶åå |
| | | const handleAutoControlChange = (value) => { |
| | | ElMessage.success(`æºè½æ§å¶ç³»ç»å·²${value ? 'å¯ç¨' : 'ç¦ç¨'}`) |
| | | } |
| | | |
| | | // å¤çå¼å¸¸ |
| | | const handleAbnormal = (item) => { |
| | | ElMessage.info(`æ£å¨å¤çå¼å¸¸ï¼${item.title}`) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = () => { |
| | | generateMockData() |
| | | if (chartInstance) { |
| | | initTrendChart() |
| | | } |
| | | ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | // 导åºç»è®¡ |
| | | const exportStatistics = () => { |
| | | ElMessage.success('ç»è®¡æ°æ®å¯¼åºæå') |
| | | } |
| | | |
| | | // 导åºç¯ä¿æ¥å |
| | | const exportEnvironmentalReport = () => { |
| | | ElMessage.success('ç¯ä¿æ¥åå¯¼åºæå') |
| | | } |
| | | |
| | | // çæèªå®ä¹æ¥è¡¨ |
| | | const generateCustomReport = () => { |
| | | ElMessage.info('èªå®ä¹æ¥è¡¨åè½å¼åä¸...') |
| | | } |
| | | |
| | | // 订é
æ¥è¡¨ |
| | | const subscribeReport = () => { |
| | | ElMessage.info('æ¥è¡¨è®¢é
åè½å¼åä¸...') |
| | | } |
| | | |
| | | // çææ¥è¡¨æ°æ® |
| | | const generateReportData = () => { |
| | | // çæåºç¡æ°æ® |
| | | reportData.value.electricity = Math.floor(Math.random() * 5000) + 8000 |
| | | reportData.value.water = Math.floor(Math.random() * 200) + 300 |
| | | reportData.value.gas = Math.floor(Math.random() * 100) + 150 |
| | | |
| | | // çæè¶å¿æ°æ® |
| | | reportData.value.electricityTrend = (Math.random() * 20 - 10).toFixed(1) |
| | | reportData.value.waterTrend = (Math.random() * 15 - 7.5).toFixed(1) |
| | | reportData.value.gasTrend = (Math.random() * 12 - 6).toFixed(1) |
| | | |
| | | // è®¡ç®æ»è½èåå¹³åè½è |
| | | reportData.value.totalEnergy = reportData.value.electricity + reportData.value.water * 0.1 + reportData.value.gas * 0.05 |
| | | reportData.value.averageEnergy = Math.floor(reportData.value.totalEnergy / 3) |
| | | reportData.value.efficiency = (Math.random() * 20 + 80).toFixed(1) |
| | | |
| | | // çæå¾è¡¨æ°æ® |
| | | const labels = ['å¨ä¸', 'å¨äº', 'å¨ä¸', 'å¨å', 'å¨äº', 'å¨å
', '卿¥'] |
| | | const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#9c27b0', '#ff9800'] |
| | | |
| | | reportData.value.chartData = labels.map((label, index) => ({ |
| | | label, |
| | | value: Math.floor(Math.random() * 1000) + 500, |
| | | percentage: Math.floor(Math.random() * 40) + 30, |
| | | color: colors[index] |
| | | })) |
| | | } |
| | | |
| | | // çææ¥è¡¨ |
| | | const generateReport = () => { |
| | | generateReportData() |
| | | ElMessage.success('æ¥è¡¨çææå') |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // å¯å¨å®æ¶æ´æ° |
| | | const startAutoUpdate = () => { |
| | | updateTimer = setInterval(() => { |
| | | generateMockData() |
| | | if (chartInstance) { |
| | | initTrendChart() |
| | | } |
| | | }, 60000) // æ¯åéæ´æ°ä¸æ¬¡ |
| | | } |
| | | |
| | | // 忢宿¶æ´æ° |
| | | const stopAutoUpdate = () => { |
| | | if (updateTimer) { |
| | | clearInterval(updateTimer) |
| | | updateTimer = null |
| | | } |
| | | } |
| | | |
| | | // ç»ä»¶æè½½ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | nextTick(() => { |
| | | initTrendChart() |
| | | }) |
| | | startAutoUpdate() |
| | | }) |
| | | |
| | | // ç»ä»¶å¸è½½ |
| | | onUnmounted(() => { |
| | | stopAutoUpdate() |
| | | if (chartInstance) { |
| | | chartInstance.dispose() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .app-container { |
| | | padding: 12px; |
| | | background: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 16px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 22px; |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | |
| | | .update-time { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .real-time-monitor { |
| | | margin-bottom: 12px; |
| | | |
| | | .monitor-card { |
| | | .monitor-content { |
| | | text-align: center; |
| | | padding: 16px 0; |
| | | |
| | | .monitor-value { |
| | | margin-bottom: 12px; |
| | | |
| | | .value { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .unit { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-left: 4px; |
| | | } |
| | | } |
| | | |
| | | .monitor-trend { |
| | | .trend-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-right: 6px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .trend-analysis { |
| | | margin-bottom: 12px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .time-selector { |
| | | .el-radio-group { |
| | | .el-radio { |
| | | margin-right: 4px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | padding: 16px 0; |
| | | } |
| | | } |
| | | |
| | | .statistics-ranking { |
| | | margin-bottom: 12px; |
| | | |
| | | .statistics-card, .ranking-card { |
| | | height: 100%; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| | | border-radius: 8px; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
| | | } |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #fafafa; |
| | | |
| | | .card-title { |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | .statistics-content { |
| | | padding: 16px; |
| | | |
| | | .statistics-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 10px 12px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | transition: background-color 0.3s ease; |
| | | |
| | | &:hover { |
| | | background: #e9ecef; |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value { |
| | | font-weight: bold; |
| | | font-size: 15px; |
| | | |
| | | &.success { |
| | | color: #67c23a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .ranking-list { |
| | | padding: 16px; |
| | | |
| | | .ranking-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 12px; |
| | | margin-bottom: 6px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | background: #e9ecef; |
| | | transform: translateX(4px); |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .ranking-number { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-weight: bold; |
| | | font-size: 14px; |
| | | margin-right: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | &.ranking-first { |
| | | background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-second { |
| | | background: linear-gradient(135deg, #c0c0c0 0%, #d4d4d4 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-third { |
| | | background: linear-gradient(135deg, #cd7f32 0%, #daa520 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-normal { |
| | | background: linear-gradient(135deg, #f5f5f5 0%, #e9ecef 100%); |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | .ranking-info { |
| | | flex: 1; |
| | | |
| | | .ranking-name { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .ranking-value { |
| | | color: #606266; |
| | | font-size: 13px; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .ranking-trend { |
| | | margin-left: 12px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .analysis-control { |
| | | margin-bottom: 20px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .abnormal-list { |
| | | .abnormal-item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | padding: 15px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .abnormal-icon { |
| | | margin-right: 15px; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .abnormal-content { |
| | | flex: 1; |
| | | |
| | | .abnormal-title { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .abnormal-desc { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .abnormal-time { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | | .abnormal-action { |
| | | margin-left: 15px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .control-content { |
| | | .control-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .environmental-indicators { |
| | | margin-bottom: 20px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .indicator-item { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | |
| | | .indicator-title { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .indicator-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .indicator-trend { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | |
| | | .success { |
| | | color: #67c23a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .multi-dimensional-reports { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .report-filters { |
| | | padding: 20px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .report-preview { |
| | | .report-data { |
| | | padding: 20px 0; |
| | | |
| | | .data-card { |
| | | text-align: center; |
| | | padding: 16px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | margin-bottom: 16px; |
| | | |
| | | .data-title { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .data-trend { |
| | | font-size: 12px; |
| | | |
| | | .trend-up { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .trend-down { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .trend-stable { |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .report-chart { |
| | | margin: 20px 0; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | |
| | | .chart-title { |
| | | text-align: center; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .chart-bars { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: flex-end; |
| | | height: 120px; |
| | | |
| | | .chart-bar { |
| | | text-align: center; |
| | | flex: 1; |
| | | margin: 0 8px; |
| | | |
| | | .bar-label { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .bar-container { |
| | | height: 80px; |
| | | background: #e9ecef; |
| | | border-radius: 4px; |
| | | position: relative; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .bar-fill { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | border-radius: 4px; |
| | | transition: height 0.3s ease; |
| | | } |
| | | |
| | | .bar-value { |
| | | font-size: 12px; |
| | | color: #303133; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .report-summary { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | |
| | | .summary-item { |
| | | text-align: center; |
| | | |
| | | .summary-label { |
| | | display: block; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // éç¨æ ·å¼ |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .success { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .info { |
| | | color: #909399; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container iot-monitor"> |
| | | <div class="header"> |
| | | <div class="title">宿¶å·¥åµçæ§ï¼IoTï¼</div> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="toggleCollecting">{{ collecting ? 'æåéé' : 'å¯å¨éé' }}</el-button> |
| | | <el-button @click="resetAll">éç½®</el-button> |
| | | <span class="ts">䏿¬¡æ´æ°æ¶é´ï¼{{ lastUpdatedDisplay }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <el-alert--> |
| | | <!-- title="è¾¹ç¼é¢è¦è§åï¼è½´æ¿ç£¨æ-æ¯å¨å¼å离åºçº¿Â±5%触ååè¦ï¼æ¸©åº¦/ååè¶ç触åæé"--> |
| | | <!-- type="info"--> |
| | | <!-- :closable="false"--> |
| | | <!-- show-icon--> |
| | | <!-- class="rule-alert"--> |
| | | <!-- />--> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col v-for="dev in devices" :key="dev.id" :span="12"> |
| | | <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <span class="device-name">{{ dev.name }}</span> |
| | | <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? 'åè¦' : 'æ£å¸¸' }}</el-tag> |
| | | </div> |
| | | <div class="meta">ç±»åï¼{{ dev.type }}ï½åºçº¿æ¯å¨ï¼{{ dev.baseline.vibration.toFixed(2) }} mm/s</div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="metrics"> |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }"> |
| | | <div class="metric-head"> |
| | | <span>æ¯å¨(mm/s)</span> |
| | | <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '±5%è¶ç' : 'åºçº¿Â±5%' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'mm/s' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#409EFF']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }"> |
| | | <div class="metric-head"> |
| | | <span>温度(°C)</span> |
| | | <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? 'è¶ç' : '20~80' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: '°C' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#E6A23C']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }"> |
| | | <div class="metric-head"> |
| | | <span>åå(MPa)</span> |
| | | <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? 'è¶ç' : '0.2~1.5' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'MPa' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#67C23A']" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ElNotification } from 'element-plus' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | defineOptions({ name: 'IoTMonitor' }) |
| | | |
| | | const windowSize = 30 |
| | | const collecting = ref(true) |
| | | const lastUpdated = ref(Date.now()) |
| | | const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString()) |
| | | |
| | | const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`)) |
| | | |
| | | function makeSeries(fill, decimals = 2) { |
| | | return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals))) |
| | | } |
| | | |
| | | const devices = reactive([ |
| | | { |
| | | id: 'water-pump', |
| | | name: '注水泵1', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 9 }, |
| | | initial: { temperature: 40, pressure: 0.70 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(9), |
| | | temperature: makeSeries(40, 1), |
| | | pressure: makeSeries(0.7, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'fluid-supply-truck', |
| | | name: '注水泵2', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 7 }, |
| | | initial: { temperature: 30, pressure: 0.60 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(7), |
| | | temperature: makeSeries(30, 1), |
| | | pressure: makeSeries(0.6, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'fracturing-truck', |
| | | name: '注水泵3', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 12 }, |
| | | initial: { temperature: 65, pressure: 1.40 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(12), |
| | | temperature: makeSeries(65, 1), |
| | | pressure: makeSeries(1.4, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'oil-tank-truck', |
| | | name: '注水泵4', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 6 }, |
| | | initial: { temperature: 28, pressure: 0.50 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(6), |
| | | temperature: makeSeries(28, 1), |
| | | pressure: makeSeries(0.5, 2), |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | function currentValue(arr) { |
| | | return arr[arr.length - 1] ?? 0 |
| | | } |
| | | |
| | | function pushWindow(arr, val) { |
| | | if (arr.length >= windowSize) arr.shift() |
| | | arr.push(val) |
| | | } |
| | | |
| | | function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) } |
| | | |
| | | function tickDevice(dev) { |
| | | const vibBase = dev.baseline.vibration |
| | | // æ¯å¨ï¼åºçº¿Â±2%éæºæ³¢å¨ï¼5%æ¦ç触å8%~12%å°å³°æ¨¡æåè¦ |
| | | const spike = Math.random() < 0.05 |
| | | const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04)) |
| | | const vibVal = Number(vibNoise.toFixed(2)) |
| | | pushWindow(dev.series.vibration, vibVal) |
| | | |
| | | // 温度ï¼ç¼æ
¢éæºæ¸¸èµ°ï¼å¹¶æ·»å å¶å髿¸©åç§» |
| | | const tPrev = currentValue(dev.series.temperature) |
| | | const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0) |
| | | const tVal = Number(clamp(tDrift, 15, 95).toFixed(1)) |
| | | pushWindow(dev.series.temperature, tVal) |
| | | |
| | | // ååï¼å°å¹
æ³¢å¨ï¼å¶åä½å/é«å |
| | | const pPrev = currentValue(dev.series.pressure) |
| | | const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0) |
| | | const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2)) |
| | | pushWindow(dev.series.pressure, pVal) |
| | | |
| | | // è¾¹ç¼è®¡ç®éå¼å¤æ |
| | | const vibDelta = Math.abs(vibVal - vibBase) / vibBase |
| | | const vibAlert = vibDelta > 0.05 |
| | | const tAlert = tVal < 20 || tVal > 80 |
| | | const pAlert = pVal < 0.2 || pVal > 1.5 |
| | | |
| | | const prevHasAlert = dev.hasAlert |
| | | dev.alerts.vibration = vibAlert |
| | | dev.alerts.temperature = tAlert |
| | | dev.alerts.pressure = pAlert |
| | | dev.hasAlert = vibAlert || tAlert || pAlert |
| | | |
| | | if (dev.hasAlert && !prevHasAlert) { |
| | | const reasons = [] |
| | | if (vibAlert) reasons.push(`æ¯å¨å离±5% (å½å ${vibVal} / åºçº¿ ${vibBase})`) |
| | | if (tAlert) reasons.push(`温度è¶ç (å½å ${tVal}°C, ææ 20~80°C) `) |
| | | if (pAlert) reasons.push(`ååè¶ç (å½å ${pVal}MPa, ææ 0.2~1.5MPa) `) |
| | | ElNotification({ |
| | | title: `${dev.name} åè¦`, |
| | | message: reasons.join('ï¼'), |
| | | type: vibAlert ? 'error' : 'warning', |
| | | duration: 5000, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | let timer = null |
| | | function start() { |
| | | if (timer) return |
| | | timer = setInterval(() => { |
| | | if (!collecting.value) return |
| | | devices.forEach(tickDevice) |
| | | lastUpdated.value = Date.now() |
| | | }, 10000) |
| | | } |
| | | |
| | | function stop() { |
| | | if (timer) { |
| | | clearInterval(timer) |
| | | timer = null |
| | | } |
| | | } |
| | | |
| | | function toggleCollecting() { collecting.value = !collecting.value } |
| | | |
| | | function resetAll() { |
| | | devices.forEach(dev => { |
| | | dev.series.vibration = makeSeries(dev.baseline.vibration) |
| | | const t0 = dev.initial?.temperature ?? 45 |
| | | const p0 = dev.initial?.pressure ?? 0.8 |
| | | dev.series.temperature = makeSeries(t0, 1) |
| | | dev.series.pressure = makeSeries(p0, 2) |
| | | dev.alerts.vibration = false |
| | | dev.alerts.temperature = false |
| | | dev.alerts.pressure = false |
| | | dev.hasAlert = false |
| | | }) |
| | | lastUpdated.value = Date.now() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | start() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | stop() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .iot-monitor { |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | .title { font-size: 18px; font-weight: 600; } |
| | | .actions { display: flex; align-items: center; gap: 8px; } |
| | | .ts { color: #909399; font-size: 12px; } |
| | | } |
| | | .rule-alert { margin-bottom: 12px; } |
| | | } |
| | | |
| | | .device-card { |
| | | margin-bottom: 16px; |
| | | transition: border-color 0.2s ease, box-shadow 0.2s ease; |
| | | &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; } |
| | | .card-header { |
| | | display: flex; flex-direction: column; gap: 4px; |
| | | .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; } |
| | | .meta { color: #909399; font-size: 12px; } |
| | | } |
| | | .metrics { |
| | | display: grid; |
| | | grid-template-columns: 1fr; |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .metric { |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | padding: 8px 8px 0 8px; |
| | | &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; } |
| | | &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; } |
| | | } |
| | | |
| | | .metric-alert { |
| | | border-color: #F56C6C; |
| | | background: #FFF6F6; |
| | | } |
| | | |
| | | @media (min-width: 1200px) { |
| | | .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; } |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·" prop="deviceModel"> |
| | | <el-input v-model="form.deviceModel" :disabled="form.deviceModel != null ? true : false" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | <el-input v-model="form.deviceModel" :disabled="(form.deviceModel != null && operationType === 'edit')" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | name: "设å¤å°è´¦è¡¨å", |
| | | }); |
| | | const formRef = ref(null); |
| | | const operationType = ref(''); |
| | | const formRules = { |
| | | deviceName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | deviceModel: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | |
| | | }); |
| | | |
| | | const loadForm = async (id) => { |
| | | if (id) { |
| | | operationType.value = 'edit' |
| | | } |
| | | const { code, data } = await getLedgerById(id); |
| | | if (code == 200) { |
| | | form.deviceName = data.deviceName; |
| | |
| | | const { proxy } = getCurrentInstance()
|
| | |
|
| | | const loginForm = ref({
|
| | | username: "admin",
|
| | | password: "admin123",
|
| | | username: "",
|
| | | password: "",
|
| | | rememberMe: false,
|
| | | currentFatoryId:'',
|
| | | })
|
| | |
| | | <style lang='scss' scoped>
|
| | | .login {
|
| | | height: 100%;
|
| | | background-image: url("../assets/indexViews/HYSNView.png");
|
| | | background-image: url("../assets/indexViews/ZQHXView.png");
|
| | | background-size: cover;
|
| | | position: relative;
|
| | | }
|
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <div style="margin-bottom: 10px;text-align: right"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="file" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | style="display: inline;margin-right: 10px" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | > |
| | | </PIMTable> |
| | | <pagination |
| | | style="margin: 10px 0" |
| | | v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { |
| | | fileAdd, |
| | | fileDel, |
| | | fileListPage |
| | | } from "@/api/financialManagement/revenueManagement.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const currentId = ref('') |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref() |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const accountType = ref('') |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (row,type) => { |
| | | accountType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentId.value = row.id; |
| | | getList() |
| | | } |
| | | const paginationSearch = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | // ä¸ä¼ æåå¤ç |
| | | function handleUploadSuccess(res, file) { |
| | | // 妿ä¸ä¼ æå |
| | | if (res.code == 200) { |
| | | const fileRow = {} |
| | | fileRow.name = res.data.originalName |
| | | fileRow.url = res.data.tempPath |
| | | uploadFile(fileRow) |
| | | } else { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | function uploadFile(file) { |
| | | file.accountId = currentId.value; |
| | | file.accountType = accountType.value; |
| | | fileAdd(file).then(res => { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | getList() |
| | | }) |
| | | } |
| | | // ä¸ä¼ 失败å¤ç |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | fileDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <el-button type="info" @click="handleImport">导å
¥</el-button> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</el-button>--> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | </div> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | |
| | | } |
| | | getList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'åå') |
| | | }) |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | min-width="150" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | |
| | | @click="openForm('edit', scope.row)" |
| | | :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName" |
| | | >ç¼è¾</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="success" |
| | | size="small" |
| | | @click="showQRCode(scope.row)" |
| | | >çæäºç»´ç </el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- äºç»´ç æ¾ç¤ºå¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="qrCodeDialogVisible" |
| | | title="éè´ååå·äºç»´ç " |
| | | width="400px" |
| | | center |
| | | > |
| | | <div style="text-align: center;"> |
| | | <img :src="qrCodeUrl" alt="äºç»´ç " style="width:200px;height:200px;" /> |
| | | <div style="margin: 20px;"> |
| | | <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, onMounted } from "vue"; |
| | | import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | |
| | | createPurchaseNo, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | |
| | | import dayjs from "dayjs"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | |
| | | } |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async (row) => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ''; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === '') { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | return; |
| | | } |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | } |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | proxy.$modal.msgWarning("äºç»´ç æªçæ"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement('a'); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="safety-monitoring"> |
| | | <el-row :gutter="20"> |
| | | <!-- 左侧ï¼å®æ¶çæ§åºå --> |
| | | <el-col :span="16"> |
| | | <el-card class="monitoring-card"> |
| | | <div slot="header" class="card-header"> |
| | | <span>宿¶æ°ä½æµåº¦çæ§</span> |
| | | <el-tag :type="systemStatus === 'normal' ? 'success' : 'danger'"> |
| | | {{ systemStatus === 'normal' ? 'ç³»ç»æ£å¸¸' : 'ç³»ç»åè¦' }} |
| | | </el-tag> |
| | | </div> |
| | | |
| | | <!-- å¨ç½åºçæ§ --> |
| | | <div class="monitoring-section"> |
| | | <h3>å¨ç½åºçæ§</h3> |
| | | <div class="sensor-grid"> |
| | | <div class="sensor-item" v-for="sensor in tankSensors" :key="sensor.id"> |
| | | <div class="sensor-header"> |
| | | <span>{{ sensor.name }}</span> |
| | | <el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small"> |
| | | {{ sensor.status === 'normal' ? 'æ£å¸¸' : 'è¶
æ ' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="sensor-data"> |
| | | <div class="data-item"> |
| | | <span>ç²ç·: {{ sensor.methane.toFixed(2) }}%</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)" |
| | | :color="getProgressColor(Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100), 80)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | <div class="data-item"> |
| | | <span>ç¡«åæ°¢: {{ sensor.h2s.toFixed(2) }}ppm</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)" |
| | | :color="getProgressColor(Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100), 80)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºå£å缩æºçæ§ --> |
| | | <div class="monitoring-section"> |
| | | <h3>äºå£å缩æºçæ§</h3> |
| | | <div class="sensor-grid"> |
| | | <div class="sensor-item" v-for="sensor in compressorSensors" :key="sensor.id"> |
| | | <div class="sensor-header"> |
| | | <span>{{ sensor.name }}</span> |
| | | <el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small"> |
| | | {{ sensor.status === 'normal' ? 'æ£å¸¸' : 'è¶
æ ' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="sensor-data"> |
| | | <div class="data-item"> |
| | | <span>ç²ç·: {{ sensor.methane.toFixed(2) }}%</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)" |
| | | :color="getProgressColor(sensor.methane, 2.5)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | <div class="data-item"> |
| | | <span>ç¡«åæ°¢: {{ sensor.h2s.toFixed(2) }}ppm</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)" |
| | | :color="getProgressColor(sensor.h2s, 10)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 宿¶æ²çº¿å¾ --> |
| | | <div class="chart-section"> |
| | | <h3>宿¶æµåº¦æ²çº¿</h3> |
| | | <div class="chart-container"> |
| | | <div ref="chart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§ï¼æ§å¶é¢æ¿ --> |
| | | <el-col :span="8"> |
| | | <el-card class="control-card"> |
| | | <div slot="header" class="card-header"> |
| | | <span>åºæ¥æ§å¶é¢æ¿</span> |
| | | </div> |
| | | |
| | | <!-- å·æ·ç¶æ --> |
| | | <div class="control-section"> |
| | | <h4>å·æ·ç³»ç»ç¶æ</h4> |
| | | <div class="status-grid"> |
| | | <div class="status-item" v-for="sprinkler in sprinklerSystems" :key="sprinkler.id"> |
| | | <div class="status-indicator" :class="sprinkler.status"> |
| | | <i class="el-icon-circle-check" v-if="sprinkler.status === 'active'"></i> |
| | | <i class="el-icon-circle-close" v-else></i> |
| | | </div> |
| | | <span>{{ sprinkler.name }}</span> |
| | | <el-tag :type="sprinkler.status === 'active' ? 'success' : 'info'" size="small"> |
| | | {{ sprinkler.status === 'active' ? 'è¿è¡ä¸' : 'å¾
æº' }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºæ¥è®°å½æé® --> |
| | | <h4>åºæ¥ç®¡ç</h4> |
| | | |
| | | <div class="control-section1"> |
| | | <el-button type="primary" @click="showEmergencyRecords" style="margin-bottom: 10px;"> |
| | | åºæ¥è®°å½ |
| | | </el-button> |
| | | <el-button type="warning" @click="triggerEmergency" :disabled="!hasEmergency"> |
| | | 触ååºæ¥ååº |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- ç³»ç»æ¥å¿ --> |
| | | <div class="control-section"> |
| | | <h4>ç³»ç»æ¥å¿</h4> |
| | | <div class="log-container"> |
| | | <div class="log-item" v-for="log in systemLogs" :key="log.id"> |
| | | <span class="log-time">{{ log.time }}</span> |
| | | <span class="log-content">{{ log.content }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ³æ¼é¢è¦å¼¹çª --> |
| | | <el-dialog |
| | | title="â ï¸ æ³æ¼é¢è¦" |
| | | :visible.sync="leakWarningVisible" |
| | | width="500px" |
| | | :close-on-click-modal="false" |
| | | :close-on-press-escape="false" |
| | | class="leak-warning-dialog" |
| | | > |
| | | <div class="warning-content"> |
| | | <div class="warning-icon"> |
| | | <i class="el-icon-warning"></i> |
| | | </div> |
| | | <div class="warning-text"> |
| | | <h3>æ£æµå°æ°ä½æµåº¦è¶
æ ï¼</h3> |
| | | <p>ä½ç½®ï¼{{ currentWarning.location }}</p> |
| | | <p>è¶
æ æ°ä½ï¼{{ currentWarning.gas }}</p> |
| | | <p>å½åæµåº¦ï¼{{ currentWarning.value }}</p> |
| | | </div> |
| | | </div> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button type="danger" @click="acknowledgeWarning">确认åè¦</el-button> |
| | | <el-button type="primary" @click="viewDetails">æ¥ç详æ
</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- åºæ¥è®°å½å¼¹çª --> |
| | | <el-dialog |
| | | title="åºæ¥è®°å½" |
| | | :visible.sync="emergencyRecordsVisible" |
| | | width="800px" |
| | | > |
| | | <el-table :data="emergencyRecords" style="width: 100%"> |
| | | <el-table-column prop="time" label="æ¶é´" width="180"></el-table-column> |
| | | <el-table-column prop="location" label="ä½ç½®" width="150"></el-table-column> |
| | | <el-table-column prop="type" label="ç±»å" width="120"></el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template slot-scope="scope"> |
| | | <el-tag :type="scope.row.status === 'resolved' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'resolved' ? '已解å³' : 'å¤çä¸' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="description" label="æè¿°"></el-table-column> |
| | | <el-table-column label="æä½" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="viewBlockchainDetails(scope.row)"> |
| | | åºåé¾è¯¦æ
|
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <!-- åºåé¾åè¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog |
| | | title="åºåé¾åè¯è¯¦æ
" |
| | | :visible.sync="blockchainDetailsVisible" |
| | | width="900px" |
| | | > |
| | | <div class="blockchain-details"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="äºä»¶ID">{{ currentEvent.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¶é´æ³">{{ currentEvent.timestamp }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä½ç½®">{{ currentEvent.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="äºä»¶ç±»å">{{ currentEvent.type }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="sensor-data-section"> |
| | | <h4>ä¼ æå¨æ°æ®</h4> |
| | | <el-table :data="currentEvent.sensorData" style="width: 100%"> |
| | | <el-table-column prop="sensor" label="ä¼ æå¨"></el-table-column> |
| | | <el-table-column prop="methane" label="ç²ç·æµåº¦"></el-table-column> |
| | | <el-table-column prop="h2s" label="ç¡«åæ°¢æµåº¦"></el-table-column> |
| | | <el-table-column prop="timestamp" label="è®°å½æ¶é´"></el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="action-log-section"> |
| | | <h4>å¤ç½®å¨ä½è®°å½</h4> |
| | | <el-timeline> |
| | | <el-timeline-item |
| | | v-for="action in currentEvent.actions" |
| | | :key="action.id" |
| | | :timestamp="action.timestamp" |
| | | :type="action.type === 'emergency' ? 'danger' : 'primary'" |
| | | > |
| | | {{ action.description }} |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | |
| | | <div class="blockchain-info"> |
| | | <h4>åºåé¾ä¿¡æ¯</h4> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="åºååå¸">{{ currentEvent.blockHash }}</el-descriptions-item> |
| | | <el-descriptions-item label="交æåå¸">{{ currentEvent.txHash }}</el-descriptions-item> |
| | | <el-descriptions-item label="确认æ°">{{ currentEvent.confirmations }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'SafetyMonitoring', |
| | | data() { |
| | | return { |
| | | systemStatus: 'normal', |
| | | leakWarningVisible: false, |
| | | emergencyRecordsVisible: false, |
| | | blockchainDetailsVisible: false, |
| | | currentWarning: {}, |
| | | currentEvent: {}, |
| | | hasEmergency: false, |
| | | |
| | | // å¨ç½åºä¼ æå¨æ°æ® |
| | | tankSensors: [ |
| | | { id: 1, name: 'å¨ç½T-001', methane: 1.20, h2s: 2.10, status: 'normal' }, |
| | | { id: 2, name: 'å¨ç½T-002', methane: 0.80, h2s: 1.50, status: 'normal' }, |
| | | { id: 3, name: 'å¨ç½T-003', methane: 3.20, h2s: 8.50, status: 'warning' }, |
| | | { id: 4, name: 'å¨ç½T-004', methane: 0.60, h2s: 0.80, status: 'normal' } |
| | | ], |
| | | |
| | | // äºå£å缩æºä¼ æå¨æ°æ® |
| | | compressorSensors: [ |
| | | { id: 5, name: 'å缩æºC-001', methane: 2.10, h2s: 3.20, status: 'normal' }, |
| | | { id: 6, name: 'å缩æºC-002', methane: 4.80, h2s: 12.50, status: 'warning' }, |
| | | { id: 7, name: 'å缩æºC-003', methane: 1.80, h2s: 2.80, status: 'normal' } |
| | | ], |
| | | |
| | | // å·æ·ç³»ç»ç¶æ |
| | | sprinklerSystems: [ |
| | | { id: 1, name: 'å¨ç½åºå·æ·', status: 'active' }, |
| | | { id: 2, name: 'å缩æºåºå·æ·', status: 'standby' }, |
| | | { id: 3, name: 'ç´§æ¥å·æ·', status: 'standby' } |
| | | ], |
| | | |
| | | // ç³»ç»æ¥å¿ |
| | | systemLogs: [ |
| | | { id: 1, time: '14:30:25', content: 'ç³»ç»å¯å¨å®æï¼ææä¼ æå¨æ£å¸¸' }, |
| | | { id: 2, time: '14:35:12', content: 'å¨ç½T-003ç²ç·æµåº¦è¶
æ ï¼è§¦åé¢è¦' }, |
| | | { id: 3, time: '14:35:15', content: 'å¯å¨å¨ç½åºå·æ·ç³»ç»' }, |
| | | { id: 4, time: '14:35:20', content: 'åéç´§æ¥çæ£å¹¿æ' } |
| | | ], |
| | | |
| | | // åºæ¥è®°å½ |
| | | emergencyRecords: [ |
| | | { |
| | | id: 'EM001', |
| | | time: '2024-01-15 14:35:12', |
| | | location: 'å¨ç½T-003', |
| | | type: 'ç²ç·è¶
æ ', |
| | | status: 'resolved', |
| | | description: 'å¨ç½T-003ç²ç·æµåº¦è¾¾å°3.2%ï¼è¶
è¿å®å
¨éå¼2.5%' |
| | | }, |
| | | { |
| | | id: 'EM002', |
| | | time: '2024-01-15 14:35:15', |
| | | location: 'å缩æºC-002', |
| | | type: 'ç¡«åæ°¢è¶
æ ', |
| | | status: 'processing', |
| | | description: 'å缩æºC-002ç¡«åæ°¢æµåº¦è¾¾å°12.5ppmï¼è¶
è¿å®å
¨éå¼10ppm' |
| | | } |
| | | ], |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | chart: null, |
| | | |
| | | // 宿¶å¨ |
| | | timer: null |
| | | } |
| | | }, |
| | | |
| | | mounted() { |
| | | this.initChart() |
| | | this.startDataRefresh() |
| | | this.checkEmergencyStatus() |
| | | }, |
| | | |
| | | beforeDestroy() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer) |
| | | } |
| | | if (this.chart) { |
| | | this.chart.dispose() |
| | | } |
| | | }, |
| | | |
| | | methods: { |
| | | // ç»ä¸è¿åº¦æ¡æ ¼å¼å为两ä½å°æ°ï¼é¿å
æµ®ç¹è¯¯å·®æ¾ç¤º |
| | | formatProgress(percentage) { |
| | | if (percentage == null || isNaN(percentage)) return '0.00%' |
| | | const val = Math.round(Number(percentage) * 100) / 100 |
| | | return `${val.toFixed(2)}%` |
| | | }, |
| | | // åå§åå¾è¡¨ |
| | | initChart() { |
| | | this.chart = echarts.init(this.$refs.chart) |
| | | this.updateChart() |
| | | }, |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | updateChart() { |
| | | const option = { |
| | | title: { |
| | | text: '宿¶æ°ä½æµåº¦çæ§', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['å¨ç½åºç²ç·', 'å¨ç½åºç¡«åæ°¢', 'å缩æºç²ç·', 'å缩æºç¡«åæ°¢'], |
| | | top: 30 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: this.generateTimeData() |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: 'value', |
| | | name: 'ç²ç·æµåº¦(%)', |
| | | position: 'left' |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: 'ç¡«åæ°¢æµåº¦(ppm)', |
| | | position: 'right' |
| | | } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: 'å¨ç½åºç²ç·', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 0.5, 3.5), |
| | | smooth: true, |
| | | yAxisIndex: 0 |
| | | }, |
| | | { |
| | | name: 'å¨ç½åºç¡«åæ°¢', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 0.5, 12), |
| | | smooth: true, |
| | | yAxisIndex: 1 |
| | | }, |
| | | { |
| | | name: 'å缩æºç²ç·', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 1.0, 5.0), |
| | | smooth: true, |
| | | yAxisIndex: 0 |
| | | }, |
| | | { |
| | | name: 'å缩æºç¡«åæ°¢', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 1.0, 15), |
| | | smooth: true, |
| | | yAxisIndex: 1 |
| | | } |
| | | ] |
| | | } |
| | | |
| | | this.chart.setOption(option) |
| | | }, |
| | | |
| | | // çææ¶é´æ°æ® |
| | | generateTimeData() { |
| | | const times = [] |
| | | const now = new Date() |
| | | for (let i = 19; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 5 * 60 * 1000) |
| | | times.push(time.toLocaleTimeString('zh-CN', { hour12: false })) |
| | | } |
| | | return times |
| | | }, |
| | | |
| | | // çæéæºæ°æ® |
| | | generateRandomData(count, min, max) { |
| | | const data = [] |
| | | for (let i = 0; i < count; i++) { |
| | | data.push(+(Math.random() * (max - min) + min).toFixed(2)) |
| | | } |
| | | return data |
| | | }, |
| | | |
| | | // å¼å§æ°æ®å·æ° |
| | | startDataRefresh() { |
| | | this.timer = setInterval(() => { |
| | | this.refreshSensorData() |
| | | this.updateChart() |
| | | this.checkEmergencyStatus() |
| | | }, 5000) // æ¯5ç§å·æ°ä¸æ¬¡ |
| | | }, |
| | | |
| | | // å·æ°ä¼ æå¨æ°æ® |
| | | refreshSensorData() { |
| | | // æ´æ°å¨ç½åºä¼ æå¨æ°æ® |
| | | this.tankSensors.forEach(sensor => { |
| | | sensor.methane = +(Math.random() * 4).toFixed(2) |
| | | sensor.h2s = +(Math.random() * 15).toFixed(2) |
| | | sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s) |
| | | }) |
| | | |
| | | // æ´æ°å缩æºä¼ æå¨æ°æ® |
| | | this.compressorSensors.forEach(sensor => { |
| | | sensor.methane = +(Math.random() * 6).toFixed(2) |
| | | sensor.h2s = +(Math.random() * 20).toFixed(2) |
| | | sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s) |
| | | }) |
| | | |
| | | // æ£æ¥æ¯å¦éè¦è§¦åé¢è¦ |
| | | this.checkLeakWarning() |
| | | }, |
| | | |
| | | // è·åä¼ æå¨ç¶æ |
| | | getSensorStatus(methane, h2s) { |
| | | const methanePct = Math.min(Math.round(methane * 40 * 100) / 100, 100) |
| | | const h2sPct = Math.min(Math.round((h2s / 20) * 100 * 100) / 100, 100) |
| | | if (methanePct >= 80 || h2sPct >= 80) { |
| | | return 'warning' |
| | | } |
| | | return 'normal' |
| | | }, |
| | | |
| | | // æ£æ¥æ³æ¼é¢è¦ |
| | | checkLeakWarning() { |
| | | const allSensors = [...this.tankSensors, ...this.compressorSensors] |
| | | const warningSensor = allSensors.find(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning') |
| | | |
| | | if (warningSensor && !this.leakWarningVisible) { |
| | | this.triggerLeakWarning(warningSensor) |
| | | } |
| | | }, |
| | | |
| | | // è§¦åæ³æ¼é¢è¦ |
| | | triggerLeakWarning(sensor) { |
| | | const methanePct = Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100) |
| | | const h2sPct = Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100) |
| | | const isMethaneMajor = methanePct >= h2sPct |
| | | const overGas = isMethaneMajor ? 'ç²ç·' : 'ç¡«åæ°¢' |
| | | const percent = (isMethaneMajor ? methanePct : h2sPct).toFixed(2) |
| | | this.currentWarning = { |
| | | location: sensor.name, |
| | | gas: overGas, |
| | | value: `${percent}%` |
| | | } |
| | | |
| | | this.leakWarningVisible = true |
| | | this.hasEmergency = true |
| | | |
| | | // èªå¨è§¦ååºæ¥ååº |
| | | this.autoEmergencyResponse(sensor) |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | this.addSystemLog(`æ£æµå°${sensor.name}æ°ä½æµåº¦è¶
æ ï¼è§¦åæ³æ¼é¢è¦`) |
| | | }, |
| | | |
| | | // èªå¨åºæ¥ååº |
| | | autoEmergencyResponse(sensor) { |
| | | // å¯å¨å·æ·ç³»ç» |
| | | if (sensor.name.includes('å¨ç½')) { |
| | | this.sprinklerSystems[0].status = 'active' |
| | | } else if (sensor.name.includes('å缩æº')) { |
| | | this.sprinklerSystems[1].status = 'active' |
| | | } |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | this.addSystemLog(`å¯å¨${sensor.name}åºåå·æ·ç³»ç»`) |
| | | this.addSystemLog(`åéç´§æ¥çæ£å¹¿æ`) |
| | | |
| | | // åå»ºåºæ¥è®°å½ |
| | | this.createEmergencyRecord(sensor) |
| | | }, |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | addSystemLog(content) { |
| | | const now = new Date() |
| | | const time = now.toLocaleTimeString('zh-CN', { hour12: false }) |
| | | |
| | | this.systemLogs.unshift({ |
| | | id: Date.now(), |
| | | time: time, |
| | | content: content |
| | | }) |
| | | |
| | | // ä¿ææå¤20æ¡æ¥å¿ |
| | | if (this.systemLogs.length > 20) { |
| | | this.systemLogs = this.systemLogs.slice(0, 20) |
| | | } |
| | | }, |
| | | |
| | | // åå»ºåºæ¥è®°å½ |
| | | createEmergencyRecord(sensor) { |
| | | const now = new Date() |
| | | const record = { |
| | | id: `EM${Date.now()}`, |
| | | time: now.toLocaleString('zh-CN'), |
| | | location: sensor.name, |
| | | type: sensor.methane > 2.5 ? 'ç²ç·è¶
æ ' : 'ç¡«åæ°¢è¶
æ ', |
| | | status: 'processing', |
| | | description: `${sensor.name}æ£æµå°${sensor.methane > 2.5 ? 'ç²ç·' : 'ç¡«åæ°¢'}æµåº¦è¶
æ ` |
| | | } |
| | | |
| | | this.emergencyRecords.unshift(record) |
| | | }, |
| | | |
| | | // è·åè¿åº¦æ¡é¢è² |
| | | getProgressColor(value, threshold) { |
| | | if (value > threshold) { |
| | | return '#F56C6C' |
| | | } else if (value > threshold * 0.8) { |
| | | return '#E6A23C' |
| | | } |
| | | return '#67C23A' |
| | | }, |
| | | |
| | | // æ£æ¥åºæ¥ç¶æ |
| | | checkEmergencyStatus() { |
| | | const allSensors = [...this.tankSensors, ...this.compressorSensors] |
| | | const has = allSensors.some(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning') |
| | | this.hasEmergency = has |
| | | this.systemStatus = has ? 'warning' : 'normal' |
| | | }, |
| | | |
| | | // 确认åè¦ |
| | | acknowledgeWarning() { |
| | | this.leakWarningVisible = false |
| | | this.addSystemLog('æ³æ¼é¢è¦å·²ç¡®è®¤') |
| | | }, |
| | | |
| | | // æ¥ç详æ
|
| | | viewDetails() { |
| | | this.leakWarningVisible = false |
| | | // è¿éå¯ä»¥è·³è½¬å°è¯¦ç»é¡µé¢ææ¾ç¤ºæ´å¤ä¿¡æ¯ |
| | | }, |
| | | |
| | | // æ¾ç¤ºåºæ¥è®°å½ |
| | | showEmergencyRecords() { |
| | | this.emergencyRecordsVisible = true |
| | | }, |
| | | |
| | | // æ¥çåºåé¾è¯¦æ
|
| | | viewBlockchainDetails(record) { |
| | | this.currentEvent = { |
| | | id: record.id, |
| | | timestamp: record.time, |
| | | location: record.location, |
| | | type: record.type, |
| | | sensorData: [ |
| | | { |
| | | sensor: 'ç²ç·ä¼ æå¨', |
| | | methane: '3.2%', |
| | | h2s: '8.5ppm', |
| | | timestamp: record.time |
| | | }, |
| | | { |
| | | sensor: 'ç¡«åæ°¢ä¼ æå¨', |
| | | methane: '2.8%', |
| | | h2s: '12.5ppm', |
| | | timestamp: record.time |
| | | } |
| | | ], |
| | | actions: [ |
| | | { |
| | | id: 1, |
| | | timestamp: record.time, |
| | | type: 'emergency', |
| | | description: 'æ£æµå°æ°ä½æµåº¦è¶
æ ï¼è§¦åé¢è¦' |
| | | }, |
| | | { |
| | | id: 2, |
| | | timestamp: new Date(new Date(record.time).getTime() + 3000).toLocaleString('zh-CN'), |
| | | type: 'action', |
| | | description: 'å¯å¨å·æ·ç³»ç»é温' |
| | | }, |
| | | { |
| | | id: 3, |
| | | timestamp: new Date(new Date(record.time).getTime() + 5000).toLocaleString('zh-CN'), |
| | | type: 'action', |
| | | description: 'åéç´§æ¥çæ£å¹¿æ' |
| | | } |
| | | ], |
| | | blockHash: '0x1234567890abcdef...', |
| | | txHash: '0xabcdef1234567890...', |
| | | confirmations: 12 |
| | | } |
| | | |
| | | this.emergencyRecordsVisible = false |
| | | this.blockchainDetailsVisible = true |
| | | }, |
| | | |
| | | // 触ååºæ¥ååº |
| | | triggerEmergency() { |
| | | this.$message.success('åºæ¥ååºå·²è§¦å') |
| | | this.addSystemLog('æå¨è§¦ååºæ¥ååº') |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .safety-monitoring { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: calc(100vh - 84px); |
| | | } |
| | | |
| | | .monitoring-card, .control-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .monitoring-section { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .monitoring-section h3 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 2px solid #409EFF; |
| | | } |
| | | |
| | | .sensor-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | | gap: 15px; |
| | | } |
| | | |
| | | .sensor-item { |
| | | background: #fff; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | | } |
| | | |
| | | .sensor-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .sensor-data .data-item { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .sensor-data .data-item span { |
| | | display: block; |
| | | margin-bottom: 5px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .chart-section { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .chart-section h3 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 2px solid #409EFF; |
| | | } |
| | | |
| | | .chart-container { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 400px; |
| | | } |
| | | |
| | | .control-section { |
| | | margin-bottom: 25px; |
| | | } |
| | | .control-section1 { |
| | | display: flex; |
| | | } |
| | | |
| | | .control-section h4 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .status-grid { |
| | | display: grid; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .status-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .status-indicator { |
| | | width: 20px; |
| | | height: 20px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .status-indicator.active { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .status-indicator.standby { |
| | | color: #909399; |
| | | } |
| | | |
| | | .log-container { |
| | | max-height: 200px; |
| | | overflow-y: auto; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .log-item { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 8px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .log-time { |
| | | color: #909399; |
| | | min-width: 60px; |
| | | } |
| | | |
| | | .log-content { |
| | | color: #606266; |
| | | } |
| | | |
| | | /* æ³æ¼é¢è¦å¼¹çªæ ·å¼ */ |
| | | .leak-warning-dialog { |
| | | background: #fff5f5; |
| | | } |
| | | |
| | | .warning-content { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .warning-icon { |
| | | font-size: 60px; |
| | | color: #F56C6C; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .warning-text h3 { |
| | | color: #F56C6C; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .warning-text p { |
| | | margin: 8px 0; |
| | | color: #606266; |
| | | } |
| | | |
| | | /* åºåé¾è¯¦æ
æ ·å¼ */ |
| | | .blockchain-details { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .sensor-data-section, .action-log-section, .blockchain-info { |
| | | margin-top: 25px; |
| | | } |
| | | |
| | | .sensor-data-section h4, .action-log-section h4, .blockchain-info h4 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 1200px) { |
| | | .sensor-grid { |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .safety-monitoring { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .sensor-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .chart { |
| | | height: 300px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState; |
| | | } |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | |
| | | } |
| | | |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({id: row.id}).then(res => { |
| | | // å建 blob 对象 |
| | | const blob = new Blob([res.data], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}) |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | // åå»ºä¸´æ¶ <a> æ ç¾è¿è¡ä¸è½½ |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'æ£éªæ¥å.docx' // è¿éåå端ä¸è´ |
| | | link.download = 'æ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | // æ¸
ç |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | |
| | | const { VITE_APP_ENV } = env;
|
| | | const baseUrl =
|
| | | VITE_APP_ENV == "development"
|
| | | ? "http://114.132.189.42:8092" // å¼åç¯å¢å端æ¥å£
|
| | | : "http://114.132.189.42:8092"; // ç产ç¯å¢å端æ¥å£
|
| | | ? "http://114.132.189.42:8089" // å¼åç¯å¢å端æ¥å£
|
| | | : "http://114.132.189.42:8089"; // ç产ç¯å¢å端æ¥å£
|
| | |
|
| | | return {
|
| | | // é¨ç½²ç产ç¯å¢åå¼åç¯å¢ä¸çURLã
|