zouyu
2026-01-20 648003a577ef7a03046269c77fc5cc64199945e7
src/views/inventoryManagement/stockWarningLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,347 @@
<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>