src/views/equipmentManagement/iotMonitor/indexWD.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/views/equipmentManagement/iotMonitor/indexWD.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,317 @@ <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: 'hydrocyclone-desander', name: 'ææµé¤ç å¨', type: 'å离设å¤', baseline: { vibration: 8 }, initial: { temperature: 35, pressure: 0.85 }, alerts: { vibration: false, temperature: false, pressure: false }, hasAlert: false, series: { vibration: makeSeries(8), temperature: makeSeries(35, 1), pressure: makeSeries(0.85, 2), }, }, { id: 'high-pressure-separator', name: 'é«ååç¦»å¨æ¬', type: 'å离设å¤', baseline: { vibration: 6 }, initial: { temperature: 45, pressure: 1.20 }, alerts: { vibration: false, temperature: false, pressure: false }, hasAlert: false, series: { vibration: makeSeries(6), temperature: makeSeries(45, 1), pressure: makeSeries(1.2, 2), }, }, { id: 'heating-throttle-pressure', name: 'ç»åå¼å çèæµè°å', type: 'è°å设å¤', baseline: { vibration: 10 }, initial: { temperature: 75, pressure: 1.80 }, alerts: { vibration: false, temperature: false, pressure: false }, hasAlert: false, series: { vibration: makeSeries(10), temperature: makeSeries(75, 1), pressure: makeSeries(1.8, 2), }, }, { id: 'three-phase-separator', name: 'ä¸ç¸å离å¨', type: 'å离设å¤', baseline: { vibration: 7 }, initial: { temperature: 38, pressure: 0.95 }, alerts: { vibration: false, temperature: false, pressure: false }, hasAlert: false, series: { vibration: makeSeries(7), temperature: makeSeries(38, 1), pressure: makeSeries(0.95, 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> src/views/procurementManagement/procurementLedger/index.vue
@@ -39,6 +39,7 @@ <div class="table_list"> <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> <el-button type="success" @click="openScanAddDialog">æ«ç æ°å¢</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> </div> @@ -164,6 +165,7 @@ @click="showQRCode(scope.row)" >çæäºç»´ç </el-button > </template> </el-table-column> </el-table> @@ -560,6 +562,206 @@ <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> </div> </div> </el-dialog> <!-- æ«ç æ°å¢å¯¹è¯æ¡ --> <el-dialog v-model="scanAddDialogVisible" title="æ«ç æ°å¢éè´å°è´¦" width="70%" @close="closeScanAddDialog" > <el-form :model="scanAddForm" label-width="140px" label-position="top" :rules="scanAddRules" ref="scanAddFormRef" > <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç å 容ï¼"> <el-input v-model="scanAddForm.scanContent" type="textarea" :rows="3" placeholder="è¯·æ«æäºç»´ç ææå¨è¾å ¥éè´ååä¿¡æ¯" @input="parseScanContent" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éè´ååå·ï¼" prop="purchaseContractNumber"> <el-input v-model="scanAddForm.purchaseContractNumber" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> <el-input v-model="scanAddForm.supplierName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="项ç®åç§°ï¼" prop="projectName"> <el-input v-model="scanAddForm.projectName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ååéé¢(å )ï¼" prop="contractAmount"> <el-input-number v-model="scanAddForm.contractAmount" :precision="2" :step="0.1" clearable style="width: 100%" placeholder="请è¾å ¥" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼ï¼"> <el-input v-model="scanAddForm.paymentMethod" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½å ¥äººï¼"> <el-input v-model="scanAddForm.recorderName" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="夿³¨ï¼"> <el-input v-model="scanAddForm.remark" type="textarea" :rows="2" placeholder="请è¾å ¥å¤æ³¨ä¿¡æ¯" clearable /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitScanAdd">确认æ°å¢</el-button> <el-button @click="closeScanAddDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- æ«ç ç»è®°å¯¹è¯æ¡ --> <el-dialog v-model="scanDialogVisible" title="æ«ç ç»è®°" width="60%" @close="closeScanDialog" > <el-form :model="scanForm" label-width="120px" label-position="left" :rules="scanRules" ref="scanFormRef" > <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éè´ååå·ï¼"> <el-input v-model="scanForm.purchaseContractNumber" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºååç§°ï¼"> <el-input v-model="scanForm.supplierName" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="项ç®åç§°ï¼"> <el-input v-model="scanForm.projectName" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ«ç æ¶é´ï¼"> <el-input v-model="scanForm.scanTime" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="æ«ç 人ï¼"> <el-input v-model="scanForm.scannerName" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ«ç ç¶æï¼"> <el-tag :type="scanForm.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> {{ scanForm.scanStatus }} </el-tag> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç 夿³¨ï¼"> <el-input v-model="scanForm.scanRemark" type="textarea" :rows="3" placeholder="请è¾å ¥æ«ç 夿³¨ä¿¡æ¯" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç è®°å½ï¼"> <el-table :data="scanRecords" border style="width: 100%"> <el-table-column label="åºå·" type="index" width="60" align="center" /> <el-table-column label="æ«ç æ¶é´" prop="scanTime" width="180" /> <el-table-column label="æ«ç 人" prop="scannerName" width="120" /> <el-table-column label="æ«ç ç¶æ" prop="scanStatus" width="100"> <template #default="scope"> <el-tag :type="scope.row.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> {{ scope.row.scanStatus }} </el-tag> </template> </el-table-column> <el-table-column label="夿³¨" prop="scanRemark" /> </el-table> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitScan">确认æ«ç </el-button> <el-button @click="closeScanDialog">åæ¶</el-button> </div> </template> </el-dialog> </div> </template> @@ -1220,6 +1422,153 @@ proxy.$modal.msgSuccess("ä¸è½½æå"); }; // æ«ç æ°å¢å¯¹è¯æ¡ç¸å ³åé const scanAddDialogVisible = ref(false); const scanAddForm = reactive({ scanContent: "", purchaseContractNumber: "", supplierName: "", projectName: "", contractAmount: "", paymentMethod: "", recorderName: "", scanRemark: "", }); const scanAddRules = { purchaseContractNumber: [{ required: true, message: "请è¾å ¥éè´ååå·", trigger: "blur" }], supplierName: [{ required: true, message: "请è¾å ¥ä¾åºååç§°", trigger: "blur" }], projectName: [{ required: true, message: "请è¾å ¥é¡¹ç®åç§°", trigger: "blur" }], }; // æ«ç ç»è®°å¯¹è¯æ¡ç¸å ³åé const scanDialogVisible = ref(false); const scanForm = reactive({ purchaseContractNumber: "", supplierName: "", projectName: "", scanTime: "", scannerName: "", scanStatus: "æªæ«ç ", scanRemark: "", }); const scanRules = { scanRemark: [{ required: true, message: "请è¾å ¥æ«ç 夿³¨", trigger: "blur" }], }; const scanRecords = ref([]); // æå¼æ«ç æ°å¢å¯¹è¯æ¡ const openScanAddDialog = () => { scanAddForm.scanContent = ""; scanAddForm.purchaseContractNumber = ""; scanAddForm.supplierName = ""; scanAddForm.projectName = ""; scanAddForm.contractAmount = ""; scanAddForm.paymentMethod = ""; scanAddForm.recorderName = userStore.nickName; scanAddForm.scanRemark = ""; scanAddDialogVisible.value = true; }; // è§£ææ«ç å å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ const parseScanContent = (content) => { if (!content) return; // 模æè§£æäºç»´ç å 容ï¼è¿éå¯ä»¥æ ¹æ®å®é éæ±è°æ´è§£æé»è¾ // å设æ«ç å å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ const parts = content.split('|'); if (parts.length >= 3) { scanAddForm.purchaseContractNumber = parts[0] || ""; scanAddForm.supplierName = parts[1] || ""; scanAddForm.projectName = parts[2] || ""; scanAddForm.contractAmount = parts[3] || ""; scanAddForm.paymentMethod = parts[4] || ""; } }; // å ³éæ«ç æ°å¢å¯¹è¯æ¡ const closeScanAddDialog = () => { scanAddDialogVisible.value = false; proxy.resetForm("scanAddFormRef"); }; // æäº¤æ«ç æ°å¢ const submitScanAdd = () => { proxy.$refs["scanAddFormRef"].validate((valid) => { if (valid) { // æå»ºæ°å¢æ°æ® const newData = { purchaseContractNumber: scanAddForm.purchaseContractNumber, supplierName: scanAddForm.supplierName, projectName: scanAddForm.projectName, contractAmount: scanAddForm.contractAmount, paymentMethod: scanAddForm.paymentMethod, recorderName: scanAddForm.recorderName, entryDate: getCurrentDate(), remark: scanAddForm.scanRemark, type: 2 }; // æ¨¡ææ°å¢æå proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); closeScanAddDialog(); // å¯ä»¥éæ©æ¯å¦å·æ°å表 // getList(); } }); }; // æå¼æ«ç ç»è®°å¯¹è¯æ¡ const openScanDialog = (row) => { scanForm.purchaseContractNumber = row.purchaseContractNumber; scanForm.supplierName = row.supplierName; scanForm.projectName = row.projectName; scanForm.scanTime = getCurrentDateTime(); scanForm.scannerName = userStore.nickName; scanForm.scanStatus = "æªæ«ç "; scanForm.scanRemark = ""; scanRecords.value = []; scanDialogVisible.value = true; }; // å ³éæ«ç ç»è®°å¯¹è¯æ¡ const closeScanDialog = () => { scanDialogVisible.value = false; proxy.resetForm("scanFormRef"); }; // æäº¤æ«ç ç»è®° const submitScan = () => { proxy.$refs["scanFormRef"].validate((valid) => { if (valid) { // æ·»å æ«ç è®°å½ scanRecords.value.push({ ...scanForm, id: Date.now(), // 模æID scanTime: getCurrentDateTime(), }); scanForm.scanStatus = "å·²æ«ç "; scanForm.scanRemark = scanForm.scanRemark || "æ "; proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); closeScanDialog(); } }); }; // è·åå½åæ¥ææ¶é´ function getCurrentDateTime() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } onMounted(() => { getList(); });