src/views/warehouseManagement/KeyCoalLocks/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/warehouseManagement/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/warehouseManagement/stockWarning/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/views/warehouseManagement/KeyCoalLocks/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1138 @@ <template> <div class="app-container"> <!-- æç´¢è¡¨å --> <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form-item label="卿°ç½åç§°ï¼"> <el-input v-model="searchForm.tankName" placeholder="请è¾å ¥å¨æ°ç½åç§°" clearable style="width: 200px" /> </el-form-item> <el-form-item label="卿°ç½ç±»åï¼"> <el-select v-model="searchForm.tankType" placeholder="è¯·éæ©å¨æ°ç½ç±»å" clearable style="width: 200px"> <el-option label="æ¶²åæ°å¨ç½" value="æ¶²åæ°å¨ç½" /> <el-option label="å缩æ°å¨ç½" value="å缩æ°å¨ç½" /> <el-option label="å¤©ç¶æ°å¨ç½" value="å¤©ç¶æ°å¨ç½" /> <el-option label="æ°§æ°å¨ç½" value="æ°§æ°å¨ç½" /> </el-select> </el-form-item> <el-form-item label="é¢è¦ç±»åï¼"> <el-select v-model="searchForm.warningType" placeholder="è¯·éæ©é¢è¦ç±»å" clearable style="width: 200px"> <el-option label="æ°ä½ä¸è¶³" value="æ°ä½ä¸è¶³" /> <el-option label="ååå¼å¸¸" value="ååå¼å¸¸" /> <el-option label="温度å¼å¸¸" value="温度å¼å¸¸" /> <el-option label="æ³æ¼é¢è¦" value="æ³æ¼é¢è¦" /> </el-select> </el-form-item> <el-form-item label="é¢è¦çº§å«ï¼"> <el-select v-model="searchForm.warningLevel" placeholder="è¯·éæ©é¢è¦çº§å«" clearable style="width: 200px"> <el-option label="ç´§æ¥" value="ç´§æ¥" /> <el-option label="éè¦" value="éè¦" /> <el-option label="ä¸è¬" value="ä¸è¬" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">æç´¢</el-button> <el-button @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> </div> <!-- æ°æ®è¡¨æ ¼ --> <div class="table_list"> <!-- æä½æé® --> <div class="table-operations"> <el-button type="primary" @click="handleAdd">æ°å¢é¢è¦è§å</el-button> <el-button type="success" @click="handleBatchProcess">æ¹éå¤ç</el-button> <el-button @click="handleExport">导åº</el-button> </div> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" style="width: 100%" height="calc(100vh - 280px)" > <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <!-- åºç¡ä¿¡æ¯å段 --> <el-table-column label="卿°ç½ç¼ç " prop="tankCode" width="120" show-overflow-tooltip /> <el-table-column label="卿°ç½åç§°" prop="tankName" width="200" show-overflow-tooltip /> <el-table-column label="卿°ç½ç±»å" prop="tankType" width="120" show-overflow-tooltip /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" width="150" show-overflow-tooltip /> <el-table-column label="容积(m³)" prop="volume" width="100" show-overflow-tooltip /> <!-- åºåç¸å ³å段 --> <el-table-column label="å½åæ°ä½é" prop="currentGasLevel" width="120" show-overflow-tooltip> <template #default="scope"> <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span> </template> </el-table-column> <el-table-column label="å®å ¨æ°ä½é" prop="safetyGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="æä½æ°ä½é" prop="minGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="æé«æ°ä½é" prop="maxGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="å½ååå(MPa)" prop="currentPressure" width="140" show-overflow-tooltip /> <!-- é¢è¦è§ååæ®µ --> <el-table-column label="é¢è¦ç±»å" prop="warningType" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getWarningTypeTag(scope.row.warningType)"> {{ scope.row.warningType }} </el-tag> </template> </el-table-column> <el-table-column label="é¢è¦çº§å«" prop="warningLevel" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getWarningLevelTag(scope.row.warningLevel)"> {{ scope.row.warningLevel }} </el-tag> </template> </el-table-column> <el-table-column label="é¢è¦éå¼" prop="warningThreshold" width="100" show-overflow-tooltip /> <el-table-column label="æ¯å¦å¯ç¨" prop="isEnabled" width="100" show-overflow-tooltip> <template #default="scope"> <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" /> </template> </el-table-column> <!-- æ¶é´ç¸å ³å段 --> <el-table-column label="é¢è¦æ¶é´" prop="warningTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è¦æç»å¤©æ°" prop="warningDuration" width="120" show-overflow-tooltip /> <el-table-column label="æåæ´æ°æ¶é´" prop="lastUpdateTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è®¡å è£ æ¶é´" prop="expectedRefillTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è®¡ç¼ºæ°æ¶é´" prop="expectedShortageTime" width="150" show-overflow-tooltip> <template #default="scope"> <div v-if="scope.row.expectedShortageTime"> <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired"> <el-tag type="danger">已缺æ°</el-tag> </div> <div v-else class="countdown-timer"> <span :class="getCountdownClass(scope.row.expectedShortageTime)"> {{ getCountdown(scope.row.expectedShortageTime).text }} </span> </div> </div> <span v-else>-</span> </template> </el-table-column> <!-- æä½å --> <el-table-column fixed="right" label="æä½" width="200" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="handleEdit(scope.row)">ç¼è¾</el-button> <el-button link type="success" size="small" @click="handleProcess(scope.row)">å¤ç</el-button> <el-button link type="danger" size="small" @click="handleDelete(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> <!-- å页 --> <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> </div> <!-- æ°å¢/ç¼è¾é¢è¦è§åå¼¹çª --> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢é¢è¦è§å' : 'ç¼è¾é¢è¦è§å'" width="50%" @close="closeDialog" > <el-form :model="form" :rules="rules" ref="formRef" label-width="140px"> <el-row :gutter="20"> <!-- åºç¡ä¿¡æ¯ --> <el-col :span="12"> <el-form-item label="卿°ç½ç¼ç ï¼" prop="tankCode"> <el-input v-model="form.tankCode" placeholder="请è¾å ¥å¨æ°ç½ç¼ç " /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="卿°ç½åç§°ï¼" prop="tankName"> <el-input v-model="form.tankName" placeholder="请è¾å ¥å¨æ°ç½åç§°" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="卿°ç½ç±»åï¼" prop="tankType"> <el-select v-model="form.tankType" placeholder="è¯·éæ©å¨æ°ç½ç±»å" style="width: 100%"> <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="specificationModel"> <el-input v-model="form.specificationModel" placeholder="请è¾å ¥è§æ ¼åå·" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="容积(m³)ï¼" prop="volume"> <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½åæ°ä½é(%)ï¼" prop="currentGasLevel"> <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- åºåç¸å ³ --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="å®å ¨æ°ä½é(%)ï¼" prop="safetyGasLevel"> <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æä½æ°ä½é(%)ï¼" prop="minGasLevel"> <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="æé«æ°ä½é(%)ï¼" prop="maxGasLevel"> <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½ååå(MPa)ï¼" prop="currentPressure"> <el-input-number v-model="form.currentPressure" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- é¢è¦è§å --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦ç±»åï¼" prop="warningType"> <el-select v-model="form.warningType" placeholder="è¯·éæ©é¢è¦ç±»å" style="width: 100%"> <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="warningLevel"> <el-select v-model="form.warningLevel" placeholder="è¯·éæ©é¢è¦çº§å«" style="width: 100%"> <el-option label="ç´§æ¥" value="ç´§æ¥" /> <el-option label="éè¦" value="éè¦" /> <el-option label="ä¸è¬" value="ä¸è¬" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦éå¼ï¼" prop="warningThreshold"> <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ¯å¦å¯ç¨ï¼" prop="isEnabled"> <el-switch v-model="form.isEnabled" /> </el-form-item> </el-col> </el-row> <!-- æ¶é´ç¸å ³ --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦æ¶é´ï¼" prop="warningTime"> <el-date-picker v-model="form.warningTime" type="datetime" placeholder="è¯·éæ©é¢è¦æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¢è®¡å è£ æ¶é´ï¼" prop="expectedRefillTime"> <el-date-picker v-model="form.expectedRefillTime" type="datetime" placeholder="è¯·éæ©é¢è®¡å è£ æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è®¡ç¼ºæ°æ¶é´ï¼" prop="expectedShortageTime"> <el-date-picker v-model="form.expectedShortageTime" type="datetime" placeholder="è¯·éæ©é¢è®¡ç¼ºæ°æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¢è¦è§åæè¿°ï¼" prop="warningRule"> <el-input v-model="form.warningRule" type="textarea" :rows="3" placeholder="请è¾å ¥é¢è¦è§åæè¿°" /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="submitForm">确认</el-button> </div> </template> </el-dialog> <!-- 缺æ°é¢è¦å¼¹æ¡ --> <el-dialog v-model="shortageWarningVisible" title="â ï¸ ç¼ºæ°é¢è¦" width="400px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" > <div class="shortage-warning-content"> <div class="warning-icon"> <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon> </div> <div class="warning-message"> <h3>{{ currentWarningTank.tankName }}</h3> <p>卿°ç½å·²ç¼ºæ°ï¼è¯·åæ¶å¤çï¼</p> <p class="warning-details"> 卿°ç½ç¼ç ï¼{{ currentWarningTank.tankCode }}<br> 卿°ç½ç±»åï¼{{ currentWarningTank.tankType }}<br> å½åæ°ä½éï¼{{ currentWarningTank.currentGasLevel }}% </p> </div> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleShortageWarning">ç«å³å¤ç</el-button> <el-button @click="closeShortageWarning">ç¨åå¤ç</el-button> </div> </template> </el-dialog> <!-- 缺æ°é¢è¦å¼¹æ¡ --> <el-dialog v-model="shortageWarningVisible" title="â ï¸ ç¼ºæ°é¢è¦" width="400px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" > <div class="shortage-warning-content"> <div class="warning-icon"> <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon> </div> <div class="warning-message"> <h3>{{ currentWarningTank.tankName }}</h3> <p>卿°ç½å·²ç¼ºæ°ï¼è¯·åæ¶å¤çï¼</p> <p class="warning-details"> 卿°ç½ç¼ç ï¼{{ currentWarningTank.tankCode }}<br> 卿°ç½ç±»åï¼{{ currentWarningTank.tankType }}<br> å½åæ°ä½éï¼{{ currentWarningTank.currentGasLevel }}% </p> </div> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleShortageWarning">ç«å³å¤ç</el-button> <el-button @click="closeShortageWarning">ç¨åå¤ç</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted, onUnmounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { WarningFilled } from '@element-plus/icons-vue' import pagination from '@/components/PIMTable/Pagination.vue' // 注éæAPIå¯¼å ¥ï¼ä½¿ç¨åæ°æ® // import { // getStockWarningPage, // addStockWarning, // updateStockWarning, // deleteStockWarning, // batchProcessStockWarning, // exportStockWarning, // toggleStockWarningStatus // } from '@/api/inventoryManagement/stockWarning.js' const { proxy } = getCurrentInstance() // ååºå¼æ°æ® const tableData = ref([]) const tableLoading = ref(false) const selectedRows = ref([]) const dialogFormVisible = ref(false) const operationType = ref('add') const total = ref(0) // 缺æ°é¢è¦ç¸å ³ const shortageWarningVisible = ref(false) const currentWarningTank = ref({}) const countdownTimer = ref(null) // å页忰 const page = reactive({ current: 1, size: 10 }) // æç´¢è¡¨å const searchForm = reactive({ tankName: '', tankType: '', warningType: '', warningLevel: '' }) // è¡¨åæ°æ® const form = reactive({ id: null, tankCode: '', tankName: '', tankType: '', specificationModel: '', volume: 0, currentGasLevel: 0, safetyGasLevel: 0, minGasLevel: 0, maxGasLevel: 0, currentPressure: 0, warningType: '', warningLevel: '', warningThreshold: 0, isEnabled: true, warningTime: '', warningDuration: 0, lastUpdateTime: '', expectedRefillTime: '', expectedShortageTime: '', warningRule: '' }) // 表åéªè¯è§å const rules = { tankCode: [{ required: true, message: '请è¾å ¥å¨æ°ç½ç¼ç ', trigger: 'blur' }], tankName: [{ required: true, message: '请è¾å ¥å¨æ°ç½åç§°', trigger: 'blur' }], tankType: [{ required: true, message: 'è¯·éæ©å¨æ°ç½ç±»å', trigger: 'change' }], warningType: [{ required: true, message: 'è¯·éæ©é¢è¦ç±»å', trigger: 'change' }], warningLevel: [{ required: true, message: 'è¯·éæ©é¢è¦çº§å«', trigger: 'change' }], warningThreshold: [{ required: true, message: '请è¾å ¥é¢è¦éå¼', trigger: 'blur' }] } // è·åå计æ¶ä¿¡æ¯ const getCountdown = (expectedTime) => { if (!expectedTime) return { text: '-', isExpired: false } const now = new Date().getTime() const expected = new Date(expectedTime).getTime() const diff = expected - now if (diff <= 0) { return { text: '已缺æ°', isExpired: true } } const days = Math.floor(diff / (1000 * 60 * 60 * 24)) const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) if (days > 0) { return { text: `${days}天${hours}å°æ¶`, isExpired: false } } else if (hours > 0) { return { text: `${hours}å°æ¶${minutes}åé`, isExpired: false } } else { return { text: `${minutes}åé`, isExpired: false } } } // è·ååè®¡æ¶æ ·å¼ç±» const getCountdownClass = (expectedTime) => { if (!expectedTime) return '' const now = new Date().getTime() const expected = new Date(expectedTime).getTime() const diff = expected - now if (diff <= 0) { return 'countdown-expired' } else if (diff <= 24 * 60 * 60 * 1000) { // 24å°æ¶å return 'countdown-urgent' } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天å return 'countdown-warning' } else { return 'countdown-normal' } } // æ£æ¥ç¼ºæ°é¢è¦ const checkShortageWarnings = () => { tableData.value.forEach(tank => { if (tank.expectedShortageTime) { const countdown = getCountdown(tank.expectedShortageTime) if (countdown.isExpired && !tank.warningShown) { // æ è®°å·²æ¾ç¤ºé¢è¦ï¼é¿å éå¤å¼¹æ¡ tank.warningShown = true showShortageWarning(tank) } } }) } // æ¾ç¤ºç¼ºæ°é¢è¦å¼¹æ¡ const showShortageWarning = (tank) => { currentWarningTank.value = tank shortageWarningVisible.value = true // ææ¾æç¤ºé³ï¼å¯éï¼ // const audio = new Audio('/path/to/warning-sound.mp3') // audio.play() } // å¤ç缺æ°é¢è¦ const handleShortageWarning = () => { ElMessage.success(`æ£å¨å¤ç卿°ç½ ${currentWarningTank.value.tankName} ç缺æ°é®é¢`) shortageWarningVisible.value = false // è¿éå¯ä»¥è°ç¨å¤çAPI } // å¤ç缺æ°é¢è¦ const closeShortageWarning = () => { // ElMessage.success(`æ£å¨å¤ç卿°ç½ ${currentWarningTank.value.tankName} ç缺æ°é®é¢`) shortageWarningVisible.value = false // è¿éå¯ä»¥è°ç¨å¤çAPI } // çæåæ°æ® const generateMockData = () => { const mockData = [ { id: 1, tankCode: 'TANK001', tankName: 'æ¶²åæ°å¨ç½A', tankType: 'æ¶²åæ°å¨ç½', specificationModel: 'LPG-5000L', volume: 5000, currentGasLevel: 15, safetyGasLevel: 30, minGasLevel: 10, maxGasLevel: 95, currentPressure: 2.5, warningType: 'æ°ä½ä¸è¶³', warningLevel: 'ç´§æ¥', warningThreshold: 20, isEnabled: true, warningTime: '2024-01-15 08:30:00', warningDuration: 3, lastUpdateTime: '2024-01-15 10:00:00', expectedRefillTime: '2024-01-16 14:00:00', expectedShortageTime: '2024-01-15 18:30:00', // ä»å¤©ä¸å6:30ç¼ºæ° warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' }, { id: 2, tankCode: 'TANK002', tankName: 'å缩æ°å¨ç½B', tankType: 'å缩æ°å¨ç½', specificationModel: 'COMP-3000L', volume: 3000, currentGasLevel: 45, safetyGasLevel: 25, minGasLevel: 15, maxGasLevel: 90, currentPressure: 8.2, warningType: 'ååå¼å¸¸', warningLevel: 'éè¦', warningThreshold: 10, isEnabled: true, warningTime: '2024-01-14 16:20:00', warningDuration: 2, lastUpdateTime: '2024-01-15 09:15:00', expectedRefillTime: '2024-01-17 09:00:00', expectedShortageTime: '2024-01-18 12:00:00', // 3天åç¼ºæ° warningRule: 'å½ååè¶ è¿8MPaæ¶è§¦åé¢è¦' }, { id: 3, tankCode: 'TANK003', tankName: 'å¤©ç¶æ°å¨ç½C', tankType: 'å¤©ç¶æ°å¨ç½', specificationModel: 'NG-8000L', volume: 8000, currentGasLevel: 75, safetyGasLevel: 20, minGasLevel: 10, maxGasLevel: 95, currentPressure: 4.8, warningType: '温度å¼å¸¸', warningLevel: 'ä¸è¬', warningThreshold: 5, isEnabled: true, warningTime: '2024-01-13 11:45:00', warningDuration: 1, lastUpdateTime: '2024-01-15 08:45:00', expectedRefillTime: '2024-01-20 10:00:00', expectedShortageTime: '2024-01-22 15:30:00', // 7天åç¼ºæ° warningRule: '彿¸©åº¦è¶ è¿60°Cæ¶è§¦åé¢è¦' }, { id: 4, tankCode: 'TANK004', tankName: 'æ°§æ°å¨ç½D', tankType: 'æ°§æ°å¨ç½', specificationModel: 'O2-2000L', volume: 2000, currentGasLevel: 8, safetyGasLevel: 25, minGasLevel: 5, maxGasLevel: 90, currentPressure: 6.5, warningType: 'æ³æ¼é¢è¦', warningLevel: 'ç´§æ¥', warningThreshold: 15, isEnabled: true, warningTime: '2024-01-15 07:15:00', warningDuration: 4, lastUpdateTime: '2024-01-15 11:30:00', expectedRefillTime: '2024-01-15 16:00:00', expectedShortageTime: '2024-01-15 14:00:00', // ä»å¤©ä¸å2ç¹ç¼ºæ° warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' }, { id: 5, tankCode: 'TANK005', tankName: 'æ¶²åæ°å¨ç½E', tankType: 'æ¶²åæ°å¨ç½', specificationModel: 'LPG-6000L', volume: 6000, currentGasLevel: 35, safetyGasLevel: 30, minGasLevel: 15, maxGasLevel: 95, currentPressure: 3.2, warningType: 'æ°ä½ä¸è¶³', warningLevel: 'éè¦', warningThreshold: 20, isEnabled: false, warningTime: '2024-01-14 14:30:00', warningDuration: 2, lastUpdateTime: '2024-01-15 09:00:00', expectedRefillTime: '2024-01-19 08:00:00', expectedShortageTime: '2024-01-21 10:00:00', // 6天åç¼ºæ° warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' }, { id: 6, tankCode: 'TANK006', tankName: 'å缩æ°å¨ç½F', tankType: 'å缩æ°å¨ç½', specificationModel: 'COMP-4000L', volume: 4000, currentGasLevel: 85, safetyGasLevel: 20, minGasLevel: 10, maxGasLevel: 90, currentPressure: 7.8, warningType: 'ååå¼å¸¸', warningLevel: 'ä¸è¬', warningThreshold: 8, isEnabled: true, warningTime: '2024-01-12 09:20:00', warningDuration: 1, lastUpdateTime: '2024-01-15 08:30:00', expectedRefillTime: '2024-01-25 14:00:00', expectedShortageTime: '2024-01-28 16:00:00', // 13天åç¼ºæ° warningRule: 'å½ååè¶ è¿8MPaæ¶è§¦åé¢è¦' }, { id: 7, tankCode: 'TANK007', tankName: 'å¤©ç¶æ°å¨ç½G', tankType: 'å¤©ç¶æ°å¨ç½', specificationModel: 'NG-10000L', volume: 10000, currentGasLevel: 92, safetyGasLevel: 15, minGasLevel: 8, maxGasLevel: 95, currentPressure: 5.2, warningType: '温度å¼å¸¸', warningLevel: 'éè¦', warningThreshold: 6, isEnabled: true, warningTime: '2024-01-11 16:45:00', warningDuration: 1, lastUpdateTime: '2024-01-15 07:45:00', expectedRefillTime: '2024-01-30 09:00:00', expectedShortageTime: '2024-02-05 12:00:00', // 21天åç¼ºæ° warningRule: '彿¸©åº¦è¶ è¿60°Cæ¶è§¦åé¢è¦' }, { id: 8, tankCode: 'TANK008', tankName: 'æ°§æ°å¨ç½H', tankType: 'æ°§æ°å¨ç½', specificationModel: 'O2-1500L', volume: 1500, currentGasLevel: 12, safetyGasLevel: 30, minGasLevel: 8, maxGasLevel: 90, currentPressure: 4.5, warningType: 'æ³æ¼é¢è¦', warningLevel: 'ç´§æ¥', warningThreshold: 12, isEnabled: true, warningTime: '2024-01-15 06:30:00', warningDuration: 5, lastUpdateTime: '2024-01-15 12:15:00', expectedRefillTime: '2024-01-15 20:00:00', expectedShortageTime: '2024-01-15 17:30:00', // ä»å¤©ä¸å5:30ç¼ºæ° warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' } ] // æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® let filteredData = mockData.filter(item => { if (searchForm.tankName && !item.tankName.includes(searchForm.tankName)) return false if (searchForm.tankType && item.tankType !== searchForm.tankType) return false if (searchForm.warningType && item.warningType !== searchForm.warningType) return false if (searchForm.warningLevel && item.warningLevel !== searchForm.warningLevel) return false return true }) // å页å¤ç const start = (page.current - 1) * page.size const end = start + page.size const paginatedData = filteredData.slice(start, end) return { records: paginatedData, total: filteredData.length } } // è·ååè¡¨æ°æ® const getList = async () => { tableLoading.value = true try { // 模æç½ç»å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) const result = generateMockData() tableData.value = result.records total.value = result.total // æ£æ¥ç¼ºæ°é¢è¦ checkShortageWarnings() } catch (error) { console.error('è·åå表失败:', error) ElMessage.error('è·åå表失败') } finally { tableLoading.value = false } } // æç´¢ const handleQuery = () => { page.current = 1 getList() } // éç½®æç´¢ const resetQuery = () => { Object.keys(searchForm).forEach(key => { searchForm[key] = '' }) handleQuery() } // å页åå const paginationChange = (obj) => { page.current = obj.page page.size = obj.limit getList() } // è¡¨æ ¼éæ©åå const handleSelectionChange = (selection) => { selectedRows.value = selection } // æ°å¢ const handleAdd = () => { operationType.value = 'add' resetForm() dialogFormVisible.value = true } // ç¼è¾ const handleEdit = (row) => { operationType.value = 'edit' Object.assign(form, row) dialogFormVisible.value = true } // å¤çé¢è¦ const handleProcess = async (row) => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 300)) ElMessage.success(`æ£å¨å¤çé¢è¦ï¼${row.tankName}`) getList() } catch (error) { ElMessage.error('å¤çé¢è¦å¤±è´¥') } } // å é¤ const handleDelete = async (row) => { try { await ElMessageBox.confirm(`ç¡®å®è¦å é¤é¢è¦è§åï¼${row.tankName}åï¼`, 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 300)) ElMessage.success('å 餿å') getList() } catch (error) { if (error !== 'cancel') { ElMessage.error('å é¤å¤±è´¥') } } } // æ¹éå¤ç const handleBatchProcess = async () => { if (selectedRows.value.length === 0) { ElMessage.warning('è¯·éæ©è¦å¤ççé¢è¦') return } try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) ElMessage.success(`æ¹éå¤çäº ${selectedRows.value.length} æ¡é¢è¦`) getList() } catch (error) { ElMessage.error('æ¹éå¤ç失败') } } // å¯¼åº const handleExport = async () => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 800)) // çæå¯¼åºæ°æ® const exportData = generateMockData().records const csvContent = generateCSV(exportData) // å建ä¸è½½é¾æ¥ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `卿°ç½é¢è¦æ°æ®_${new Date().getTime()}.csv` link.click() window.URL.revokeObjectURL(url) ElMessage.success('å¯¼åºæå') } catch (error) { ElMessage.error('导åºå¤±è´¥') } } // çæCSVå 容 const generateCSV = (data) => { const headers = [ '卿°ç½ç¼ç ', '卿°ç½åç§°', '卿°ç½ç±»å', 'è§æ ¼åå·', '容积(m³)', 'å½åæ°ä½é(%)', 'å®å ¨æ°ä½é(%)', 'æä½æ°ä½é(%)', 'æé«æ°ä½é(%)', 'å½ååå(MPa)', 'é¢è¦ç±»å', 'é¢è¦çº§å«', 'é¢è¦éå¼', 'æ¯å¦å¯ç¨', 'é¢è¦æ¶é´', 'é¢è¦æç»å¤©æ°', 'æåæ´æ°æ¶é´', 'é¢è®¡å è£ æ¶é´', 'é¢è®¡ç¼ºæ°æ¶é´', 'é¢è¦è§åæè¿°' ] const csvRows = [headers.join(',')] data.forEach(item => { const row = [ item.tankCode, item.tankName, item.tankType, item.specificationModel, item.volume, item.currentGasLevel, item.safetyGasLevel, item.minGasLevel, item.maxGasLevel, item.currentPressure, item.warningType, item.warningLevel, item.warningThreshold, item.isEnabled ? 'æ¯' : 'å¦', item.warningTime, item.warningDuration, item.lastUpdateTime, item.expectedRefillTime, item.expectedShortageTime, item.warningRule ] csvRows.push(row.join(',')) }) return csvRows.join('\n') } // å¯ç¨ç¶æåå const handleEnableChange = async (row) => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 200)) ElMessage.success(`${row.tankName} çå¯ç¨ç¶æå·²æ´æ°`) } catch (error) { ElMessage.error('ç¶ææ´æ°å¤±è´¥') // æ¢å¤åç¶æ row.isEnabled = !row.isEnabled } } // æäº¤è¡¨å const submitForm = async () => { try { await proxy.$refs.formRef.validate() // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) if (operationType.value === 'add') { ElMessage.success('æ°å¢æå') } else { ElMessage.success('ç¼è¾æå') } closeDialog() getList() } catch (error) { if (!error.errors) { ElMessage.error(operationType.value === 'add' ? 'æ°å¢å¤±è´¥' : 'ç¼è¾å¤±è´¥') } } } // å ³éå¼¹çª const closeDialog = () => { dialogFormVisible.value = false resetForm() } // é置表å const resetForm = () => { Object.keys(form).forEach(key => { if (key === 'isEnabled') { form[key] = true } else if (typeof form[key] === 'number') { form[key] = 0 } else { form[key] = '' } }) proxy.$refs.formRef?.resetFields() } // è·åæ°ä½éæ ·å¼ç±» const getGasLevelClass = (row) => { if (row.currentGasLevel < row.minGasLevel) { return 'text-danger' } else if (row.currentGasLevel > row.maxGasLevel) { return 'text-warning' } return 'text-success' } // è·åé¢è¦ç±»åæ ç¾æ ·å¼ const getWarningTypeTag = (type) => { const typeMap = { 'æ°ä½ä¸è¶³': 'danger', 'ååå¼å¸¸': 'warning', '温度å¼å¸¸': 'info', 'æ³æ¼é¢è¦': 'danger' } return typeMap[type] || 'info' } // è·åé¢è¦çº§å«æ ç¾æ ·å¼ const getWarningLevelTag = (level) => { const levelMap = { 'ç´§æ¥': 'danger', 'éè¦': 'warning', 'ä¸è¬': 'info' } return levelMap[level] || 'info' } // å¯å¨å计æ¶å®æ¶å¨ const startCountdownTimer = () => { countdownTimer.value = setInterval(() => { checkShortageWarnings() }, 60000) // æ¯å鿣æ¥ä¸æ¬¡ } // 忢å计æ¶å®æ¶å¨ const stopCountdownTimer = () => { if (countdownTimer.value) { clearInterval(countdownTimer.value) countdownTimer.value = null } } // 页é¢å è½½ onMounted(() => { getList() startCountdownTimer() }) // 页é¢å¸è½½ onUnmounted(() => { stopCountdownTimer() }) </script> <style scoped lang="scss"> .app-container { padding: 20px; .table-operations { text-align: right; margin-bottom: 20px; .el-button { margin-top: 20px; margin-right: 10px; } } .table_list { background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .text-danger { color: #f56c6c; font-weight: bold; } .text-warning { color: #e6a23c; font-weight: bold; } .text-success { color: #67c23a; font-weight: bold; } .dialog-footer { text-align: right; } // åè®¡æ¶æ ·å¼ .countdown-timer { font-weight: bold; } .countdown-normal { color: #67c23a; } .countdown-warning { color: #e6a23c; } .countdown-urgent { color: #f56c6c; animation: blink 1s infinite; } .countdown-expired { color: #f56c6c; font-weight: bold; } @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.5; } } // 缺æ°é¢è¦å¼¹æ¡æ ·å¼ .shortage-warning-content { text-align: center; padding: 20px 0; .warning-icon { margin-bottom: 20px; } .warning-message { h3 { color: #f56c6c; margin-bottom: 10px; } p { margin-bottom: 10px; color: #606266; } .warning-details { background: #f5f7fa; padding: 15px; border-radius: 4px; text-align: left; font-size: 14px; line-height: 1.6; } } } } </style> src/views/warehouseManagement/index.vue
@@ -68,6 +68,7 @@ style="width: 100%; height: calc(100vh - 30em)" show-summary :summary-method="summarizeChildrenTable" :row-class-name="tableRowClassName" > <el-table-column type="selection" width="55" align="center" /> <el-table-column @@ -897,6 +898,14 @@ } }); }; // æ·»å 夿è¡ç±»åç彿° const tableRowClassName = (row) => { if (activeTab.value === 'officialInventory' && row.row.inventoryQuantity < 3) { return 'low-inventory-row'; } return ''; }; // å ³éå®¡æ ¸å¼¹æ¡ const cancelReview = () => { proxy.$refs.formRef.resetFields(); @@ -908,4 +917,8 @@ :deep(.el-table) { margin: 20px 0; } :deep(.low-inventory-row) { background-color: #ffcccc; } </style> src/views/warehouseManagement/stockWarning/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1138 @@ <template> <div class="app-container"> <!-- æç´¢è¡¨å --> <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form-item label="卿°ç½åç§°ï¼"> <el-input v-model="searchForm.tankName" placeholder="请è¾å ¥å¨æ°ç½åç§°" clearable style="width: 200px" /> </el-form-item> <el-form-item label="卿°ç½ç±»åï¼"> <el-select v-model="searchForm.tankType" placeholder="è¯·éæ©å¨æ°ç½ç±»å" clearable style="width: 200px"> <el-option label="æ¶²åæ°å¨ç½" value="æ¶²åæ°å¨ç½" /> <el-option label="å缩æ°å¨ç½" value="å缩æ°å¨ç½" /> <el-option label="å¤©ç¶æ°å¨ç½" value="å¤©ç¶æ°å¨ç½" /> <el-option label="æ°§æ°å¨ç½" value="æ°§æ°å¨ç½" /> </el-select> </el-form-item> <el-form-item label="é¢è¦ç±»åï¼"> <el-select v-model="searchForm.warningType" placeholder="è¯·éæ©é¢è¦ç±»å" clearable style="width: 200px"> <el-option label="æ°ä½ä¸è¶³" value="æ°ä½ä¸è¶³" /> <el-option label="ååå¼å¸¸" value="ååå¼å¸¸" /> <el-option label="温度å¼å¸¸" value="温度å¼å¸¸" /> <el-option label="æ³æ¼é¢è¦" value="æ³æ¼é¢è¦" /> </el-select> </el-form-item> <el-form-item label="é¢è¦çº§å«ï¼"> <el-select v-model="searchForm.warningLevel" placeholder="è¯·éæ©é¢è¦çº§å«" clearable style="width: 200px"> <el-option label="ç´§æ¥" value="ç´§æ¥" /> <el-option label="éè¦" value="éè¦" /> <el-option label="ä¸è¬" value="ä¸è¬" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">æç´¢</el-button> <el-button @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> </div> <!-- æ°æ®è¡¨æ ¼ --> <div class="table_list"> <!-- æä½æé® --> <div class="table-operations"> <el-button type="primary" @click="handleAdd">æ°å¢é¢è¦è§å</el-button> <el-button type="success" @click="handleBatchProcess">æ¹éå¤ç</el-button> <el-button @click="handleExport">导åº</el-button> </div> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" style="width: 100%" height="calc(100vh - 280px)" > <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <!-- åºç¡ä¿¡æ¯å段 --> <el-table-column label="卿°ç½ç¼ç " prop="tankCode" width="120" show-overflow-tooltip /> <el-table-column label="卿°ç½åç§°" prop="tankName" width="200" show-overflow-tooltip /> <el-table-column label="卿°ç½ç±»å" prop="tankType" width="120" show-overflow-tooltip /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" width="150" show-overflow-tooltip /> <el-table-column label="容积(m³)" prop="volume" width="100" show-overflow-tooltip /> <!-- åºåç¸å ³å段 --> <el-table-column label="å½åæ°ä½é" prop="currentGasLevel" width="120" show-overflow-tooltip> <template #default="scope"> <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span> </template> </el-table-column> <el-table-column label="å®å ¨æ°ä½é" prop="safetyGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="æä½æ°ä½é" prop="minGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="æé«æ°ä½é" prop="maxGasLevel" width="120" show-overflow-tooltip /> <el-table-column label="å½ååå(MPa)" prop="currentPressure" width="140" show-overflow-tooltip /> <!-- é¢è¦è§ååæ®µ --> <el-table-column label="é¢è¦ç±»å" prop="warningType" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getWarningTypeTag(scope.row.warningType)"> {{ scope.row.warningType }} </el-tag> </template> </el-table-column> <el-table-column label="é¢è¦çº§å«" prop="warningLevel" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getWarningLevelTag(scope.row.warningLevel)"> {{ scope.row.warningLevel }} </el-tag> </template> </el-table-column> <el-table-column label="é¢è¦éå¼" prop="warningThreshold" width="100" show-overflow-tooltip /> <el-table-column label="æ¯å¦å¯ç¨" prop="isEnabled" width="100" show-overflow-tooltip> <template #default="scope"> <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" /> </template> </el-table-column> <!-- æ¶é´ç¸å ³å段 --> <el-table-column label="é¢è¦æ¶é´" prop="warningTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è¦æç»å¤©æ°" prop="warningDuration" width="120" show-overflow-tooltip /> <el-table-column label="æåæ´æ°æ¶é´" prop="lastUpdateTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è®¡å è£ æ¶é´" prop="expectedRefillTime" width="150" show-overflow-tooltip /> <el-table-column label="é¢è®¡ç¼ºæ°æ¶é´" prop="expectedShortageTime" width="150" show-overflow-tooltip> <template #default="scope"> <div v-if="scope.row.expectedShortageTime"> <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired"> <el-tag type="danger">已缺æ°</el-tag> </div> <div v-else class="countdown-timer"> <span :class="getCountdownClass(scope.row.expectedShortageTime)"> {{ getCountdown(scope.row.expectedShortageTime).text }} </span> </div> </div> <span v-else>-</span> </template> </el-table-column> <!-- æä½å --> <el-table-column fixed="right" label="æä½" width="200" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="handleEdit(scope.row)">ç¼è¾</el-button> <el-button link type="success" size="small" @click="handleProcess(scope.row)">å¤ç</el-button> <el-button link type="danger" size="small" @click="handleDelete(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> <!-- å页 --> <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> </div> <!-- æ°å¢/ç¼è¾é¢è¦è§åå¼¹çª --> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢é¢è¦è§å' : 'ç¼è¾é¢è¦è§å'" width="50%" @close="closeDialog" > <el-form :model="form" :rules="rules" ref="formRef" label-width="140px"> <el-row :gutter="20"> <!-- åºç¡ä¿¡æ¯ --> <el-col :span="12"> <el-form-item label="卿°ç½ç¼ç ï¼" prop="tankCode"> <el-input v-model="form.tankCode" placeholder="请è¾å ¥å¨æ°ç½ç¼ç " /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="卿°ç½åç§°ï¼" prop="tankName"> <el-input v-model="form.tankName" placeholder="请è¾å ¥å¨æ°ç½åç§°" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="卿°ç½ç±»åï¼" prop="tankType"> <el-select v-model="form.tankType" placeholder="è¯·éæ©å¨æ°ç½ç±»å" style="width: 100%"> <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="specificationModel"> <el-input v-model="form.specificationModel" placeholder="请è¾å ¥è§æ ¼åå·" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="容积(m³)ï¼" prop="volume"> <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½åæ°ä½é(%)ï¼" prop="currentGasLevel"> <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- åºåç¸å ³ --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="å®å ¨æ°ä½é(%)ï¼" prop="safetyGasLevel"> <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æä½æ°ä½é(%)ï¼" prop="minGasLevel"> <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="æé«æ°ä½é(%)ï¼" prop="maxGasLevel"> <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½ååå(MPa)ï¼" prop="currentPressure"> <el-input-number v-model="form.currentPressure" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- é¢è¦è§å --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦ç±»åï¼" prop="warningType"> <el-select v-model="form.warningType" placeholder="è¯·éæ©é¢è¦ç±»å" style="width: 100%"> <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="warningLevel"> <el-select v-model="form.warningLevel" placeholder="è¯·éæ©é¢è¦çº§å«" style="width: 100%"> <el-option label="ç´§æ¥" value="ç´§æ¥" /> <el-option label="éè¦" value="éè¦" /> <el-option label="ä¸è¬" value="ä¸è¬" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦éå¼ï¼" prop="warningThreshold"> <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ¯å¦å¯ç¨ï¼" prop="isEnabled"> <el-switch v-model="form.isEnabled" /> </el-form-item> </el-col> </el-row> <!-- æ¶é´ç¸å ³ --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è¦æ¶é´ï¼" prop="warningTime"> <el-date-picker v-model="form.warningTime" type="datetime" placeholder="è¯·éæ©é¢è¦æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¢è®¡å è£ æ¶é´ï¼" prop="expectedRefillTime"> <el-date-picker v-model="form.expectedRefillTime" type="datetime" placeholder="è¯·éæ©é¢è®¡å è£ æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="é¢è®¡ç¼ºæ°æ¶é´ï¼" prop="expectedShortageTime"> <el-date-picker v-model="form.expectedShortageTime" type="datetime" placeholder="è¯·éæ©é¢è®¡ç¼ºæ°æ¶é´" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¢è¦è§åæè¿°ï¼" prop="warningRule"> <el-input v-model="form.warningRule" type="textarea" :rows="3" placeholder="请è¾å ¥é¢è¦è§åæè¿°" /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="submitForm">确认</el-button> </div> </template> </el-dialog> <!-- 缺æ°é¢è¦å¼¹æ¡ --> <el-dialog v-model="shortageWarningVisible" title="â ï¸ ç¼ºæ°é¢è¦" width="400px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" > <div class="shortage-warning-content"> <div class="warning-icon"> <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon> </div> <div class="warning-message"> <h3>{{ currentWarningTank.tankName }}</h3> <p>卿°ç½å·²ç¼ºæ°ï¼è¯·åæ¶å¤çï¼</p> <p class="warning-details"> 卿°ç½ç¼ç ï¼{{ currentWarningTank.tankCode }}<br> 卿°ç½ç±»åï¼{{ currentWarningTank.tankType }}<br> å½åæ°ä½éï¼{{ currentWarningTank.currentGasLevel }}% </p> </div> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleShortageWarning">ç«å³å¤ç</el-button> <el-button @click="closeShortageWarning">ç¨åå¤ç</el-button> </div> </template> </el-dialog> <!-- 缺æ°é¢è¦å¼¹æ¡ --> <el-dialog v-model="shortageWarningVisible" title="â ï¸ ç¼ºæ°é¢è¦" width="400px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" > <div class="shortage-warning-content"> <div class="warning-icon"> <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon> </div> <div class="warning-message"> <h3>{{ currentWarningTank.tankName }}</h3> <p>卿°ç½å·²ç¼ºæ°ï¼è¯·åæ¶å¤çï¼</p> <p class="warning-details"> 卿°ç½ç¼ç ï¼{{ currentWarningTank.tankCode }}<br> 卿°ç½ç±»åï¼{{ currentWarningTank.tankType }}<br> å½åæ°ä½éï¼{{ currentWarningTank.currentGasLevel }}% </p> </div> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleShortageWarning">ç«å³å¤ç</el-button> <el-button @click="closeShortageWarning">ç¨åå¤ç</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted, onUnmounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { WarningFilled } from '@element-plus/icons-vue' import pagination from '@/components/PIMTable/Pagination.vue' // 注éæAPIå¯¼å ¥ï¼ä½¿ç¨åæ°æ® // import { // getStockWarningPage, // addStockWarning, // updateStockWarning, // deleteStockWarning, // batchProcessStockWarning, // exportStockWarning, // toggleStockWarningStatus // } from '@/api/inventoryManagement/stockWarning.js' const { proxy } = getCurrentInstance() // ååºå¼æ°æ® const tableData = ref([]) const tableLoading = ref(false) const selectedRows = ref([]) const dialogFormVisible = ref(false) const operationType = ref('add') const total = ref(0) // 缺æ°é¢è¦ç¸å ³ const shortageWarningVisible = ref(false) const currentWarningTank = ref({}) const countdownTimer = ref(null) // å页忰 const page = reactive({ current: 1, size: 10 }) // æç´¢è¡¨å const searchForm = reactive({ tankName: '', tankType: '', warningType: '', warningLevel: '' }) // è¡¨åæ°æ® const form = reactive({ id: null, tankCode: '', tankName: '', tankType: '', specificationModel: '', volume: 0, currentGasLevel: 0, safetyGasLevel: 0, minGasLevel: 0, maxGasLevel: 0, currentPressure: 0, warningType: '', warningLevel: '', warningThreshold: 0, isEnabled: true, warningTime: '', warningDuration: 0, lastUpdateTime: '', expectedRefillTime: '', expectedShortageTime: '', warningRule: '' }) // 表åéªè¯è§å const rules = { tankCode: [{ required: true, message: '请è¾å ¥å¨æ°ç½ç¼ç ', trigger: 'blur' }], tankName: [{ required: true, message: '请è¾å ¥å¨æ°ç½åç§°', trigger: 'blur' }], tankType: [{ required: true, message: 'è¯·éæ©å¨æ°ç½ç±»å', trigger: 'change' }], warningType: [{ required: true, message: 'è¯·éæ©é¢è¦ç±»å', trigger: 'change' }], warningLevel: [{ required: true, message: 'è¯·éæ©é¢è¦çº§å«', trigger: 'change' }], warningThreshold: [{ required: true, message: '请è¾å ¥é¢è¦éå¼', trigger: 'blur' }] } // è·åå计æ¶ä¿¡æ¯ const getCountdown = (expectedTime) => { if (!expectedTime) return { text: '-', isExpired: false } const now = new Date().getTime() const expected = new Date(expectedTime).getTime() const diff = expected - now if (diff <= 0) { return { text: '已缺æ°', isExpired: true } } const days = Math.floor(diff / (1000 * 60 * 60 * 24)) const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) if (days > 0) { return { text: `${days}天${hours}å°æ¶`, isExpired: false } } else if (hours > 0) { return { text: `${hours}å°æ¶${minutes}åé`, isExpired: false } } else { return { text: `${minutes}åé`, isExpired: false } } } // è·ååè®¡æ¶æ ·å¼ç±» const getCountdownClass = (expectedTime) => { if (!expectedTime) return '' const now = new Date().getTime() const expected = new Date(expectedTime).getTime() const diff = expected - now if (diff <= 0) { return 'countdown-expired' } else if (diff <= 24 * 60 * 60 * 1000) { // 24å°æ¶å return 'countdown-urgent' } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天å return 'countdown-warning' } else { return 'countdown-normal' } } // æ£æ¥ç¼ºæ°é¢è¦ const checkShortageWarnings = () => { tableData.value.forEach(tank => { if (tank.expectedShortageTime) { const countdown = getCountdown(tank.expectedShortageTime) if (countdown.isExpired && !tank.warningShown) { // æ è®°å·²æ¾ç¤ºé¢è¦ï¼é¿å éå¤å¼¹æ¡ tank.warningShown = true showShortageWarning(tank) } } }) } // æ¾ç¤ºç¼ºæ°é¢è¦å¼¹æ¡ const showShortageWarning = (tank) => { currentWarningTank.value = tank shortageWarningVisible.value = true // ææ¾æç¤ºé³ï¼å¯éï¼ // const audio = new Audio('/path/to/warning-sound.mp3') // audio.play() } // å¤ç缺æ°é¢è¦ const handleShortageWarning = () => { ElMessage.success(`æ£å¨å¤ç卿°ç½ ${currentWarningTank.value.tankName} ç缺æ°é®é¢`) shortageWarningVisible.value = false // è¿éå¯ä»¥è°ç¨å¤çAPI } // å¤ç缺æ°é¢è¦ const closeShortageWarning = () => { // ElMessage.success(`æ£å¨å¤ç卿°ç½ ${currentWarningTank.value.tankName} ç缺æ°é®é¢`) shortageWarningVisible.value = false // è¿éå¯ä»¥è°ç¨å¤çAPI } // çæåæ°æ® const generateMockData = () => { const mockData = [ { id: 1, tankCode: 'TANK001', tankName: 'æ¶²åæ°å¨ç½A', tankType: 'æ¶²åæ°å¨ç½', specificationModel: 'LPG-5000L', volume: 5000, currentGasLevel: 15, safetyGasLevel: 30, minGasLevel: 10, maxGasLevel: 95, currentPressure: 2.5, warningType: 'æ°ä½ä¸è¶³', warningLevel: 'ç´§æ¥', warningThreshold: 20, isEnabled: true, warningTime: '2024-01-15 08:30:00', warningDuration: 3, lastUpdateTime: '2024-01-15 10:00:00', expectedRefillTime: '2024-01-16 14:00:00', expectedShortageTime: '2024-01-15 18:30:00', // ä»å¤©ä¸å6:30ç¼ºæ° warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' }, { id: 2, tankCode: 'TANK002', tankName: 'å缩æ°å¨ç½B', tankType: 'å缩æ°å¨ç½', specificationModel: 'COMP-3000L', volume: 3000, currentGasLevel: 45, safetyGasLevel: 25, minGasLevel: 15, maxGasLevel: 90, currentPressure: 8.2, warningType: 'ååå¼å¸¸', warningLevel: 'éè¦', warningThreshold: 10, isEnabled: true, warningTime: '2024-01-14 16:20:00', warningDuration: 2, lastUpdateTime: '2024-01-15 09:15:00', expectedRefillTime: '2024-01-17 09:00:00', expectedShortageTime: '2024-01-18 12:00:00', // 3天åç¼ºæ° warningRule: 'å½ååè¶ è¿8MPaæ¶è§¦åé¢è¦' }, { id: 3, tankCode: 'TANK003', tankName: 'å¤©ç¶æ°å¨ç½C', tankType: 'å¤©ç¶æ°å¨ç½', specificationModel: 'NG-8000L', volume: 8000, currentGasLevel: 75, safetyGasLevel: 20, minGasLevel: 10, maxGasLevel: 95, currentPressure: 4.8, warningType: '温度å¼å¸¸', warningLevel: 'ä¸è¬', warningThreshold: 5, isEnabled: true, warningTime: '2024-01-13 11:45:00', warningDuration: 1, lastUpdateTime: '2024-01-15 08:45:00', expectedRefillTime: '2024-01-20 10:00:00', expectedShortageTime: '2024-01-22 15:30:00', // 7天åç¼ºæ° warningRule: '彿¸©åº¦è¶ è¿60°Cæ¶è§¦åé¢è¦' }, { id: 4, tankCode: 'TANK004', tankName: 'æ°§æ°å¨ç½D', tankType: 'æ°§æ°å¨ç½', specificationModel: 'O2-2000L', volume: 2000, currentGasLevel: 8, safetyGasLevel: 25, minGasLevel: 5, maxGasLevel: 90, currentPressure: 6.5, warningType: 'æ³æ¼é¢è¦', warningLevel: 'ç´§æ¥', warningThreshold: 15, isEnabled: true, warningTime: '2024-01-15 07:15:00', warningDuration: 4, lastUpdateTime: '2024-01-15 11:30:00', expectedRefillTime: '2024-01-15 16:00:00', expectedShortageTime: '2024-01-15 14:00:00', // ä»å¤©ä¸å2ç¹ç¼ºæ° warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' }, { id: 5, tankCode: 'TANK005', tankName: 'æ¶²åæ°å¨ç½E', tankType: 'æ¶²åæ°å¨ç½', specificationModel: 'LPG-6000L', volume: 6000, currentGasLevel: 35, safetyGasLevel: 30, minGasLevel: 15, maxGasLevel: 95, currentPressure: 3.2, warningType: 'æ°ä½ä¸è¶³', warningLevel: 'éè¦', warningThreshold: 20, isEnabled: false, warningTime: '2024-01-14 14:30:00', warningDuration: 2, lastUpdateTime: '2024-01-15 09:00:00', expectedRefillTime: '2024-01-19 08:00:00', expectedShortageTime: '2024-01-21 10:00:00', // 6天åç¼ºæ° warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' }, { id: 6, tankCode: 'TANK006', tankName: 'å缩æ°å¨ç½F', tankType: 'å缩æ°å¨ç½', specificationModel: 'COMP-4000L', volume: 4000, currentGasLevel: 85, safetyGasLevel: 20, minGasLevel: 10, maxGasLevel: 90, currentPressure: 7.8, warningType: 'ååå¼å¸¸', warningLevel: 'ä¸è¬', warningThreshold: 8, isEnabled: true, warningTime: '2024-01-12 09:20:00', warningDuration: 1, lastUpdateTime: '2024-01-15 08:30:00', expectedRefillTime: '2024-01-25 14:00:00', expectedShortageTime: '2024-01-28 16:00:00', // 13天åç¼ºæ° warningRule: 'å½ååè¶ è¿8MPaæ¶è§¦åé¢è¦' }, { id: 7, tankCode: 'TANK007', tankName: 'å¤©ç¶æ°å¨ç½G', tankType: 'å¤©ç¶æ°å¨ç½', specificationModel: 'NG-10000L', volume: 10000, currentGasLevel: 92, safetyGasLevel: 15, minGasLevel: 8, maxGasLevel: 95, currentPressure: 5.2, warningType: '温度å¼å¸¸', warningLevel: 'éè¦', warningThreshold: 6, isEnabled: true, warningTime: '2024-01-11 16:45:00', warningDuration: 1, lastUpdateTime: '2024-01-15 07:45:00', expectedRefillTime: '2024-01-30 09:00:00', expectedShortageTime: '2024-02-05 12:00:00', // 21天åç¼ºæ° warningRule: '彿¸©åº¦è¶ è¿60°Cæ¶è§¦åé¢è¦' }, { id: 8, tankCode: 'TANK008', tankName: 'æ°§æ°å¨ç½H', tankType: 'æ°§æ°å¨ç½', specificationModel: 'O2-1500L', volume: 1500, currentGasLevel: 12, safetyGasLevel: 30, minGasLevel: 8, maxGasLevel: 90, currentPressure: 4.5, warningType: 'æ³æ¼é¢è¦', warningLevel: 'ç´§æ¥', warningThreshold: 12, isEnabled: true, warningTime: '2024-01-15 06:30:00', warningDuration: 5, lastUpdateTime: '2024-01-15 12:15:00', expectedRefillTime: '2024-01-15 20:00:00', expectedShortageTime: '2024-01-15 17:30:00', // ä»å¤©ä¸å5:30ç¼ºæ° warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' } ] // æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® let filteredData = mockData.filter(item => { if (searchForm.tankName && !item.tankName.includes(searchForm.tankName)) return false if (searchForm.tankType && item.tankType !== searchForm.tankType) return false if (searchForm.warningType && item.warningType !== searchForm.warningType) return false if (searchForm.warningLevel && item.warningLevel !== searchForm.warningLevel) return false return true }) // å页å¤ç const start = (page.current - 1) * page.size const end = start + page.size const paginatedData = filteredData.slice(start, end) return { records: paginatedData, total: filteredData.length } } // è·ååè¡¨æ°æ® const getList = async () => { tableLoading.value = true try { // 模æç½ç»å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) const result = generateMockData() tableData.value = result.records total.value = result.total // æ£æ¥ç¼ºæ°é¢è¦ checkShortageWarnings() } catch (error) { console.error('è·åå表失败:', error) ElMessage.error('è·åå表失败') } finally { tableLoading.value = false } } // æç´¢ const handleQuery = () => { page.current = 1 getList() } // éç½®æç´¢ const resetQuery = () => { Object.keys(searchForm).forEach(key => { searchForm[key] = '' }) handleQuery() } // å页åå const paginationChange = (obj) => { page.current = obj.page page.size = obj.limit getList() } // è¡¨æ ¼éæ©åå const handleSelectionChange = (selection) => { selectedRows.value = selection } // æ°å¢ const handleAdd = () => { operationType.value = 'add' resetForm() dialogFormVisible.value = true } // ç¼è¾ const handleEdit = (row) => { operationType.value = 'edit' Object.assign(form, row) dialogFormVisible.value = true } // å¤çé¢è¦ const handleProcess = async (row) => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 300)) ElMessage.success(`æ£å¨å¤çé¢è¦ï¼${row.tankName}`) getList() } catch (error) { ElMessage.error('å¤çé¢è¦å¤±è´¥') } } // å é¤ const handleDelete = async (row) => { try { await ElMessageBox.confirm(`ç¡®å®è¦å é¤é¢è¦è§åï¼${row.tankName}åï¼`, 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 300)) ElMessage.success('å 餿å') getList() } catch (error) { if (error !== 'cancel') { ElMessage.error('å é¤å¤±è´¥') } } } // æ¹éå¤ç const handleBatchProcess = async () => { if (selectedRows.value.length === 0) { ElMessage.warning('è¯·éæ©è¦å¤ççé¢è¦') return } try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) ElMessage.success(`æ¹éå¤çäº ${selectedRows.value.length} æ¡é¢è¦`) getList() } catch (error) { ElMessage.error('æ¹éå¤ç失败') } } // å¯¼åº const handleExport = async () => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 800)) // çæå¯¼åºæ°æ® const exportData = generateMockData().records const csvContent = generateCSV(exportData) // å建ä¸è½½é¾æ¥ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `卿°ç½é¢è¦æ°æ®_${new Date().getTime()}.csv` link.click() window.URL.revokeObjectURL(url) ElMessage.success('å¯¼åºæå') } catch (error) { ElMessage.error('导åºå¤±è´¥') } } // çæCSVå 容 const generateCSV = (data) => { const headers = [ '卿°ç½ç¼ç ', '卿°ç½åç§°', '卿°ç½ç±»å', 'è§æ ¼åå·', '容积(m³)', 'å½åæ°ä½é(%)', 'å®å ¨æ°ä½é(%)', 'æä½æ°ä½é(%)', 'æé«æ°ä½é(%)', 'å½ååå(MPa)', 'é¢è¦ç±»å', 'é¢è¦çº§å«', 'é¢è¦éå¼', 'æ¯å¦å¯ç¨', 'é¢è¦æ¶é´', 'é¢è¦æç»å¤©æ°', 'æåæ´æ°æ¶é´', 'é¢è®¡å è£ æ¶é´', 'é¢è®¡ç¼ºæ°æ¶é´', 'é¢è¦è§åæè¿°' ] const csvRows = [headers.join(',')] data.forEach(item => { const row = [ item.tankCode, item.tankName, item.tankType, item.specificationModel, item.volume, item.currentGasLevel, item.safetyGasLevel, item.minGasLevel, item.maxGasLevel, item.currentPressure, item.warningType, item.warningLevel, item.warningThreshold, item.isEnabled ? 'æ¯' : 'å¦', item.warningTime, item.warningDuration, item.lastUpdateTime, item.expectedRefillTime, item.expectedShortageTime, item.warningRule ] csvRows.push(row.join(',')) }) return csvRows.join('\n') } // å¯ç¨ç¶æåå const handleEnableChange = async (row) => { try { // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 200)) ElMessage.success(`${row.tankName} çå¯ç¨ç¶æå·²æ´æ°`) } catch (error) { ElMessage.error('ç¶ææ´æ°å¤±è´¥') // æ¢å¤åç¶æ row.isEnabled = !row.isEnabled } } // æäº¤è¡¨å const submitForm = async () => { try { await proxy.$refs.formRef.validate() // 模æAPIè°ç¨å»¶è¿ await new Promise(resolve => setTimeout(resolve, 500)) if (operationType.value === 'add') { ElMessage.success('æ°å¢æå') } else { ElMessage.success('ç¼è¾æå') } closeDialog() getList() } catch (error) { if (!error.errors) { ElMessage.error(operationType.value === 'add' ? 'æ°å¢å¤±è´¥' : 'ç¼è¾å¤±è´¥') } } } // å ³éå¼¹çª const closeDialog = () => { dialogFormVisible.value = false resetForm() } // é置表å const resetForm = () => { Object.keys(form).forEach(key => { if (key === 'isEnabled') { form[key] = true } else if (typeof form[key] === 'number') { form[key] = 0 } else { form[key] = '' } }) proxy.$refs.formRef?.resetFields() } // è·åæ°ä½éæ ·å¼ç±» const getGasLevelClass = (row) => { if (row.currentGasLevel < row.minGasLevel) { return 'text-danger' } else if (row.currentGasLevel > row.maxGasLevel) { return 'text-warning' } return 'text-success' } // è·åé¢è¦ç±»åæ ç¾æ ·å¼ const getWarningTypeTag = (type) => { const typeMap = { 'æ°ä½ä¸è¶³': 'danger', 'ååå¼å¸¸': 'warning', '温度å¼å¸¸': 'info', 'æ³æ¼é¢è¦': 'danger' } return typeMap[type] || 'info' } // è·åé¢è¦çº§å«æ ç¾æ ·å¼ const getWarningLevelTag = (level) => { const levelMap = { 'ç´§æ¥': 'danger', 'éè¦': 'warning', 'ä¸è¬': 'info' } return levelMap[level] || 'info' } // å¯å¨å计æ¶å®æ¶å¨ const startCountdownTimer = () => { countdownTimer.value = setInterval(() => { checkShortageWarnings() }, 60000) // æ¯å鿣æ¥ä¸æ¬¡ } // 忢å计æ¶å®æ¶å¨ const stopCountdownTimer = () => { if (countdownTimer.value) { clearInterval(countdownTimer.value) countdownTimer.value = null } } // 页é¢å è½½ onMounted(() => { getList() startCountdownTimer() }) // 页é¢å¸è½½ onUnmounted(() => { stopCountdownTimer() }) </script> <style scoped lang="scss"> .app-container { padding: 20px; .table-operations { text-align: right; margin-bottom: 20px; .el-button { margin-top: 20px; margin-right: 10px; } } .table_list { background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .text-danger { color: #f56c6c; font-weight: bold; } .text-warning { color: #e6a23c; font-weight: bold; } .text-success { color: #67c23a; font-weight: bold; } .dialog-footer { text-align: right; } // åè®¡æ¶æ ·å¼ .countdown-timer { font-weight: bold; } .countdown-normal { color: #67c23a; } .countdown-warning { color: #e6a23c; } .countdown-urgent { color: #f56c6c; animation: blink 1s infinite; } .countdown-expired { color: #f56c6c; font-weight: bold; } @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.5; } } // 缺æ°é¢è¦å¼¹æ¡æ ·å¼ .shortage-warning-content { text-align: center; padding: 20px 0; .warning-icon { margin-bottom: 20px; } .warning-message { h3 { color: #f56c6c; margin-bottom: 10px; } p { margin-bottom: 10px; color: #606266; } .warning-details { background: #f5f7fa; padding: 15px; border-radius: 4px; text-align: left; font-size: 14px; line-height: 1.6; } } } } </style>