| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="产å大类ï¼"> |
| | | <el-input |
| | | v-model="searchForm.productCategory" |
| | | placeholder="请è¾å
¥äº§å大类" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·ï¼"> |
| | | <el-input |
| | | v-model="searchForm.specificationModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="é¢è¦ç¶æï¼"> |
| | | <el-select |
| | | v-model="searchForm.warningStatus" |
| | | placeholder="è¯·éæ©é¢è¦ç¶æ" |
| | | clearable |
| | | style="width: 150px" |
| | | > |
| | | <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="actions"></div> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%" |
| | | height="calc(100vh - 280px)" |
| | | > |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="æ¹æ¬¡å·" prop="code" width="130" show-overflow-tooltip /> |
| | | <el-table-column label="产å大类" prop="productCategory" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" show-overflow-tooltip /> |
| | | <el-table-column label="å½ååºå" prop="currentStock" width="120" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getStockClass(scope.row)">{{ scope.row.currentStock || 0 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½åºå" prop="warnNum" width="120" show-overflow-tooltip /> |
| | | <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="warningStatus" width="100" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.warningStatus === 'å·²é¢è¦' ? 'danger' : 'success'"> |
| | | {{ scope.row.warningStatus || 'æ£å¸¸' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="é¢è¦æ¶é´" prop="warningTime" 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> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { |
| | | getStockWarningLedgerPage |
| | | } from '@/api/inventoryManagement/stockWarningLedger.js' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const total = ref(0) |
| | | |
| | | // å页忰 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | warningStatus: '' |
| | | }) |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { |
| | | ...page, |
| | | ...searchForm |
| | | } |
| | | getStockWarningLedgerPage(params) |
| | | .then(res => { |
| | | tableLoading.value = false |
| | | if (res.code === 200) { |
| | | tableData.value = res.data.records || [] |
| | | total.value = res.data.total || 0 |
| | | |
| | | // 计ç®é¢è¦çº§å«åç¶æ |
| | | tableData.value = tableData.value.map(item => { |
| | | const currentStock = parseFloat(item.inboundNum0 || item.currentStock || 0) |
| | | const warnNum = parseFloat(item.warnNum || 0) |
| | | const safetyStock = parseFloat(item.safetyStock || warnNum * 1.2) |
| | | |
| | | // 计ç®é¢è¦çº§å« |
| | | if (currentStock <= 0) { |
| | | item.warningLevel = 'ç´§æ¥' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else if (currentStock < warnNum) { |
| | | item.warningLevel = 'éè¦' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else if (currentStock < safetyStock) { |
| | | item.warningLevel = 'ä¸è¬' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else { |
| | | item.warningLevel = '' |
| | | item.warningStatus = 'æ£å¸¸' |
| | | } |
| | | |
| | | // 计ç®é¢è®¡ç¼ºè´§æ¶é´ï¼åºäºæ¥åæ¶èéï¼è¿éç®åå¤çï¼ |
| | | if (item.warningStatus === 'å·²é¢è¦' && currentStock > 0 && warnNum > 0) { |
| | | const dailyConsumption = warnNum / 30 // å设30天æ¶è宿ä½åºå |
| | | const daysRemaining = Math.floor(currentStock / dailyConsumption) |
| | | if (daysRemaining > 0) { |
| | | const date = new Date() |
| | | date.setDate(date.getDate() + daysRemaining) |
| | | item.expectedShortageTime = date.toISOString().split('T')[0] |
| | | } |
| | | } |
| | | |
| | | item.currentStock = currentStock |
| | | item.safetyStock = safetyStock |
| | | |
| | | return item |
| | | }) |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false |
| | | ElMessage.error(err.msg || 'è·åæ°æ®å¤±è´¥') |
| | | }) |
| | | } |
| | | |
| | | // æç´¢ |
| | | 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 getStockClass = (row) => { |
| | | const currentStock = parseFloat(row.currentStock || row.inboundNum0 || 0) |
| | | const warnNum = parseFloat(row.warnNum || 0) |
| | | |
| | | if (currentStock <= 0) { |
| | | return 'text-danger' |
| | | } else if (currentStock < warnNum) { |
| | | return 'text-warning' |
| | | } |
| | | return 'text-success' |
| | | } |
| | | |
| | | // è·åé¢è¦çº§å«æ ç¾æ ·å¼ |
| | | const getWarningLevelTag = (level) => { |
| | | const levelMap = { |
| | | 'ç´§æ¥': 'danger', |
| | | 'éè¦': 'warning', |
| | | 'ä¸è¬': 'info' |
| | | } |
| | | return levelMap[level] || 'info' |
| | | } |
| | | |
| | | // è·åå计æ¶ä¿¡æ¯ |
| | | 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' |
| | | } |
| | | } |
| | | |
| | | // 页é¢å è½½ |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | |
| | | .search_form { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .table_list { |
| | | background: #fff; |
| | | border-radius: 4px; |
| | | padding: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .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; } |
| | | } |
| | | } |
| | | </style> |