Files
JY1.0/src/views/WarehouseManagement/WarehouseSummary/index.vue
2026-06-04 08:36:53 +08:00

767 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<el-card style="height: 850px">
<div style="margin-top: 20px; margin-bottom: 20px">
<h2 style="text-align: center; margin-bottom: 20px">仓储汇总</h2>
<el-row :gutter="20">
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('待入库')">
<div class="stat-label">待入库</div>
<div class="stat-number">{{ statistics.待入库 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('待出库')">
<div class="stat-label">待出库</div>
<div class="stat-number">{{ statistics.待出库 }}</div>
<div class="stat-sub-info">领料单 {{ 领料单数 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('可出库')">
<div class="stat-label">可出库</div>
<div class="stat-number">{{ statistics.可出库 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('变更BOM')">
<div class="stat-label">变更BOM</div>
<div class="stat-number">{{ statistics.变更BOM }}</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('补货中')">
<div class="stat-label">补货中</div>
<div class="stat-number">{{ statistics.补货中 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card " @click.native="showDetail('生产中')">
<div class="stat-label">生产中</div>
<div class="stat-number">{{ statistics.生产中 }}</div>
<div class="stat-sub-labels">
<span class="sub-normal" :class="{ 'sub-active': 生产中期限筛选 === '正常' }" @click.stop="filterProductionByStatus('正常')">正常 {{ 生产中统计.正常 }}</span>
<span class="sub-warning" :class="{ 'sub-active': 生产中期限筛选 === '临期' }" @click.stop="filterProductionByStatus('临期')">临期 {{ 生产中统计.临期 }}</span>
<span class="sub-danger" :class="{ 'sub-active': 生产中期限筛选 === '超期' }" @click.stop="filterProductionByStatus('超期')">超期 {{ 生产中统计.超期 }}</span>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('待补货')">
<div class="stat-label">待补货</div>
<div class="stat-number">{{ statistics.待投产 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" @click.native="showDetail('采购中')">
<div class="stat-label">采购中</div>
<div class="stat-number">{{ statistics.采购中 }}</div>
</el-card>
</el-col>
</el-row>
</div>
<div style="margin-top: 20px">
<div style="display: flex; align-items: center; margin-bottom: 10px">
<h3 style="margin: 0">{{ currentDetail }}详情</h3>
<el-button v-if="currentDetail === '生产中'" type="primary" size="mini" style="margin-left: 15px" @click="exportProductionDetail">导出</el-button>
</div>
<el-table
v-loading="loading"
:data="detailData"
highlight-current-row
border
stripe
size="small"
style="width: 100%"
height="400">
<el-table-column
v-for="(column, index) in detailColumns"
:key="index"
:width="column.width"
:label="column.label"
:align="column.align">
<template slot-scope="scope">
<span v-if="column.prop === '期限状态'" :class="getDeadlineStatusClass(scope.row.期限状态)">
{{ scope.row.期限状态 }}
</span>
<span v-else-if="column.prop === '超期时间' && scope.row.期限状态 === '超期'" class="text-danger">
{{ scope.row[column.prop] }}
</span>
<span v-else>
{{ scope.row[column.prop] }}
</span>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import XLSX from 'xlsx'
import FileSaver from 'file-saver'
export default {
data() {
return {
loading: false,
statistics: {
待入库: 0,
待出库: 0,
可出库: 0,
变更BOM: 0,
补货中: 0,
生产中: 0,
待投产: 0,
采购中: 0
},
currentDetail: '',
detailData: [],
detailColumns: [],
领料单流水号列表: [],
statisticsTimer: null,
生产中统计: {
正常: 0,
临期: 0,
超期: 0
},
生产中期限筛选: null,
生产中全部数据: [],
领料单数: 0
}
},
computed: {
...mapGetters([
'id',
'token'
])
},
created() {
this.getStatistics()
// 每半小时运行一次getStatistics
this.statisticsTimer = setInterval(() => {
this.getStatistics()
console.log(1)
}, 60 * 1000) // 30分钟 = 30 * 60 * 1000 毫秒
},
beforeDestroy() {
// 清除定时器
if (this.statisticsTimer) {
clearInterval(this.statisticsTimer)
}
},
methods: {
calculateDeadlineStatus(item) {
if (!item.补货期限) return '正常'
var deadline = new Date(item.补货期限)
var now = new Date()
var diffMs = deadline.getTime() - now.getTime()
var diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24))
if (diffDays < 0) return '超期'
if (diffDays <= 3) return '临期'
return '正常'
},
calculateOverdueDays(item) {
if (!item.补货期限) return 0
var deadline = new Date(item.补货期限)
var now = new Date()
var diffMs = now.getTime() - deadline.getTime()
var diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24))
return diffDays > 0 ? diffDays : 0
},
getDeadlineStatusClass(status) {
if (status === '超期') return 'text-danger'
if (status === '临期') return 'text-warning'
return ''
},
filterProductionByStatus(status) {
if (this.生产中期限筛选 === status) {
this.生产中期限筛选 = null
this.detailData = this.生产中全部数据
} else {
this.生产中期限筛选 = status
this.currentDetail = '生产中'
this.detailData = this.生产中全部数据.filter(item => item.期限状态 === status)
}
},
getStatistics() {
this.loading = true
// 待入库数据:采购合同组件中状态为为到货的订单数量
this.getPendingInStorage()
// 待出库数据:物料出库组件中所有领料单还未领的物料种类数量
this.getPendingOutStorage()
// 可出库数据:待出库数据的基础上又库存的数量
this.getAvailableOutStorage()
// 变更BOM数据BOM变更记录中待审核的数量
this.getBOMChange()
// 补货中数据:补货件查询组件中状态已到以外的订单数量
this.getReplenishing()
// 生产中数据:补货件查询组件中状态为加工中的订单数量
this.getInProduction()
// 待投产数据:补货件查询组件中状态为未开始的订单数量
this.getPendingProduction()
// 采购中数据:采购合同组件中状态为未到货的订单数量
this.getPurchasing()
this.loading = false
},
// 获取待入库数据
getPendingInStorage() {
var param = []
var Data = this.CreateData('11', '仓储管理_入库单_查询', param)
this.ExecDatabase(Data).then(response => {
let count = 0
response.data.forEach(item => {
if (item.是否入库 === false) {
count++
}
})
this.statistics.待入库 = count
})
},
// 获取待出库数据
getPendingOutStorage() {
var param = []
var Data = this.CreateData('11', '装配管理_未领完领料单_查询', param)
this.ExecDatabase(Data).then(response => {
// 获取领料单流水号
this.领料单流水号列表 = response.data.map(item => item.领料单流水号)
if (this.领料单流水号列表.length > 0) {
// 用领料单流水号查询物料明细
var param2 = []
param2[0] = ['领料单流水号列表', this.领料单流水号列表]
var Data2 = this.CreateData('11', '装配管理_领料单明细_查询_汇总', param2)
this.ExecDatabase(Data2).then(response2 => {
let count = 0
response2.data.forEach(item => {
if (item.已领数 < item.申请数) {
count++
}
})
this.statistics.待出库 = count
var uniqueOrders = new Set(response2.data.map(item => item.领料单号))
this.领料单数 = uniqueOrders.size
})
} else {
this.statistics.待出库 = 0
}
})
},
// 获取可出库数据
getAvailableOutStorage() {
var param = []
var Data = this.CreateData('11', '装配管理_未领完领料单_查询', param)
this.ExecDatabase(Data).then(response => {
// 获取领料单流水号
const 领料单流水号列表 = response.data.map(item => item.领料单流水号)
if (领料单流水号列表.length > 0) {
// 用领料单流水号查询物料明细
var param2 = []
param2[0] = ['领料单流水号列表', 领料单流水号列表]
var Data2 = this.CreateData('11', '装配管理_领料单明细_查询_汇总', param2)
this.ExecDatabase(Data2).then(response2 => {
// 计算库存数不为0的物料种类数量
let count = 0
response2.data.forEach(item => {
// 假设库存数字段名为"库存"或"库存数量"
if (item.已领数 < item.申请数 && item.库存数 > 0) {
count++
}
})
this.statistics.可出库 = count
})
} else {
this.statistics.可出库 = 0
}
})
},
// 获取变更BOM数据
getBOMChange() {
var param = []
var Data = this.CreateData('11', 'BOM变更记录_查询', param)
this.ExecDatabase(Data).then(response => {
let count = 0
response.data.forEach(item => {
if (item.状态 !=='已入库') {
count++
}
})
this.statistics.变更BOM = count
})
},
// 获取补货中数据
getReplenishing() {
let count = 0
var param = []
var Data = this.CreateData('11', '仓储管理_补货件查询', param)
this.ExecDatabase(Data).then(response => {
response.data.forEach(item => {
if (item.状态 !== '已到' && item.状态 !== '完成' && item.状态 !== '已完成' && item.状态 !== '入库' && item.状态 !== '已编制') {
count++
}
})
this.statistics.补货中 = count
})
},
// 获取生产中数据
getInProduction() {
var param = []
var Data = this.CreateData('11', '仓储管理_生产订单_查询', param)
this.ExecDatabase(Data).then(response => {
let count = 0
let normalCount = 0
let nearExpiryCount = 0
let expiredCount = 0
response.data.forEach(item => {
if (item.状态 === '加工中') {
count++
const status = this.calculateDeadlineStatus(item)
if (status === '超期') {
expiredCount++
} else if (status === '临期') {
nearExpiryCount++
} else {
normalCount++
}
}
})
this.statistics.生产中 = count
this.生产中统计.正常 = normalCount
this.生产中统计.临期 = nearExpiryCount
this.生产中统计.超期 = expiredCount
})
},
// 获取待投产数据
getPendingProduction() {
var param = []
var Data = this.CreateData('11', '仓储管理_待投产_查询', param)
this.ExecDatabase(Data).then(response => {
let count = 0
response.data.forEach(item => {
count++
})
this.statistics.待投产 = count
})
},
// 获取采购中数据
getPurchasing() {
var param = []
var Data = this.CreateData('11', '仓储管理_补货件查询', param)
this.ExecDatabase(Data).then(response => {
let count = 0
response.data.forEach(item => {
if (item.状态 === '未到' || item.状态 === '未采购' || item.状态 === '部分到') {
count++
}
})
this.statistics.采购中 = count
})
},
// 显示详情
showDetail(type) {
this.currentDetail = type
this.loading = true
this.生产中期限筛选 = null
switch (type) {
case '待入库':
this.getPendingInStorageDetail()
break
case '待出库':
this.getPendingOutStorageDetail()
break
case '可出库':
this.getAvailableOutStorageDetail()
break
case '变更BOM':
this.getBOMChangeDetail()
break
case '补货中':
this.getReplenishingDetail()
break
case '生产中':
this.getInProductionDetail()
break
case '待补货':
this.getPendingProductionDetail()
break
case '采购中':
this.getPurchasingDetail()
break
}
},
// 获取待入库详情
getPendingInStorageDetail() {
var param = []
var Data = this.CreateData('11', '仓储管理_入库单_查询', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => item.是否入库 === false)
this.detailColumns = [
{ prop: '入库单号', label: '入库单号', width: '150px', align: 'center' },
{ prop: '采购合同号', label: '采购合同号', width: '100px', align: 'center' },
{ prop: '供应商名称', label: '供应商', width: '120px', align: 'center' },
{ prop: '到货数', label: '数量', width: '150px', align: 'center' }
]
this.loading = false
})
},
// 获取待出库详情
getPendingOutStorageDetail() {
var param = []
param[0] = ['领料单流水号列表', this.领料单流水号列表]
var Data = this.CreateData('11', '装配管理_领料单明细_查询_汇总', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => (item.已领数 || 0) < (item.申请数 || 0))
this.detailColumns = [
{ prop: '领料单号', label: '领料单号', width: '100px', align: 'center' },
{ prop: '物料名称', label: '物料名称', width: '150px', align: 'center' },
{ prop: '图号或型号', label: '图号或型号', width: '150px', align: 'center' },
{ prop: '货位名称', label: '库位', width: '180px', align: 'center' },
{ prop: '库存数', label: '库存数', width: '100px', align: 'center' },
{ prop: '申请数', label: '申请数', width: '100px', align: 'center' },
{ prop: '已领数', label: '已领数', width: '100px', align: 'center' }
]
this.loading = false
})
},
// 获取可出库详情
getAvailableOutStorageDetail() {
var param = []
param[0] = ['领料单流水号列表', this.领料单流水号列表]
var Data = this.CreateData('11', '装配管理_领料单明细_查询_汇总', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => item.已领数 < item.申请数 && item.库存数 > 0)
this.detailColumns = [
{ prop: '领料单号', label: '领料单号', width: '100px', align: 'center' },
{ prop: '物料名称', label: '物料名称', width: '150px', align: 'center' },
{ prop: '图号或型号', label: '图号或型号', width: '150px', align: 'center' },
{ prop: '货位名称', label: '库位', width: '180px', align: 'center' },
{ prop: '库存数', label: '库存数', width: '100px', align: 'center' },
{ prop: '申请数', label: '申请数', width: '100px', align: 'center' },
{ prop: '已领数', label: '已领数', width: '100px', align: 'center' }
]
this.loading = false
})
},
// 获取变更BOM详情
getBOMChangeDetail() {
var param = []
var Data = this.CreateData('11', 'BOM变更记录_查询', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => item.状态 !== '已入库')
this.detailColumns = [
{ prop: '订单号', label: '订单号', width: '150px', align: 'center' },
{ prop: '产品名称', label: '产品名称', width: '150px', align: 'center' },
{ prop: '零件名称', label: '零件名称', width: '150px', align: 'center' },
{ prop: '图号', label: '图号或型号', width: '150px', align: 'center' },
{ prop: '变更类型', label: '变更类型', width: '120px', align: 'center' },
{ prop: '数量', label: '数量', width: '100px', align: 'center' }
]
this.loading = false
})
},
// 获取补货中详情
getReplenishingDetail() {
var param = []
var Data = this.CreateData('11', '仓储管理_补货件查询', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => item.状态 !== '已到' && item.状态 !== '完成' && item.状态 !== '已完成' && item.状态 !== '入库' && item.状态 !== '已编制')
this.detailColumns = [
{ prop: '补货单号', label: '补货单号', width: '150px', align: 'center' },
{ prop: '名称', label: '物料名称', width: '180px', align: 'center' },
{ prop: '补货数', label: '补货数量', width: '100px', align: 'center' },
{ prop: '状态', label: '状态', width: '100px', align: 'center' },
{ prop: '补货日期', label: '补货日期', width: '150px', align: 'center' },
{ prop: '补货期限', label: '补货期限', width: '150px', align: 'center' }
]
this.loading = false
})
},
// 获取生产中详情
getInProductionDetail() {
var param = []
var Data = this.CreateData('11', '仓储管理_生产订单_查询', param)
this.ExecDatabase(Data).then(response => {
var filtered = response.data.filter(item => item.状态 === '加工中' && item.补货数 !== item.入库数)
filtered.forEach(item => {
item.期限状态 = this.calculateDeadlineStatus(item)
item.超期时间 = this.calculateOverdueDays(item)
})
filtered.sort((a, b) => {
if (a.期限状态 === '超期' && b.期限状态 !== '超期') return -1
if (a.期限状态 !== '超期' && b.期限状态 === '超期') return 1
if (a.期限状态 === '超期' && b.期限状态 === '超期') {
return (b.超期时间 || 0) - (a.超期时间 || 0)
}
return new Date(a.补货期限) - new Date(b.补货期限)
})
this.生产中全部数据 = filtered
if (this.生产中期限筛选) {
this.detailData = filtered.filter(item => item.期限状态 === this.生产中期限筛选)
} else {
this.detailData = filtered
}
this.detailColumns = [
{ prop: '补货单号', label: '补货单号', width: '150px', align: 'center' },
{ prop: '名称', label: '物料名称', width: '180px', align: 'center' },
{ prop: '补货数', label: '补货数量', width: '100px', align: 'center' },
{ prop: '入库数', label: '入库数', width: '100px', align: 'center' },
{ prop: '状态', label: '状态', width: '100px', align: 'center' },
{ prop: '补货日期', label: '补货日期', width: '150px', align: 'center' },
{ prop: '补货期限', label: '补货期限', width: '150px', align: 'center' },
{ prop: '期限状态', label: '期限状态', width: '100px', align: 'center' },
{ prop: '超期时间', label: '超期时间(天)', width: '120px', align: 'center' }
]
this.loading = false
})
},
// 获取待投产详情
getPendingProductionDetail() {
var param = []
var Data = this.CreateData('11', '仓储管理_待投产_查询', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.slice(0, 1000)
this.detailColumns = [
{ prop: '物料名称', label: '物料名称', width: '180px', align: 'center' },
{ prop: '图号', label: '图号或型号', width: '150px', align: 'center' },
{ prop: '库存数量', label: '库存数量', width: '100px', align: 'center' },
{ prop: '应补数量', label: '应补数量', width: '100px', align: 'center' },
]
this.loading = false
})
},
// 获取采购中详情
getPurchasingDetail() {
var param = []
var Data = this.CreateData('11', '仓储管理_补货件查询', param)
this.ExecDatabase(Data).then(response => {
this.detailData = response.data.filter(item => item.状态 === '未到' || item.状态 === '未采购' || item.状态 === '部分到')
this.detailColumns = [
{ prop: '补货单号', label: '补货单号', width: '150px', align: 'center' },
{ prop: '名称', label: '物料名称', width: '180px', align: 'center' },
{ prop: '补货数', label: '补货数量', width: '100px', align: 'center' },
{ prop: '状态', label: '状态', width: '100px', align: 'center' },
{ prop: '补货日期', label: '补货日期', width: '150px', align: 'center' },
{ prop: '补货期限', label: '补货期限', width: '150px', align: 'center' }
]
this.loading = false
})
},
// 导出生产中详情
exportProductionDetail() {
if (!this.生产中全部数据 || this.生产中全部数据.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
var header = ['补货单号', '物料名称', '补货数量', '入库数', '状态', '补货日期', '补货期限', '期限状态', '超期时间(天)']
var data = this.生产中全部数据.map(item => {
return [
item.补货单号 || '',
item.名称 || '',
item.补货数 || '',
item.入库数 || '',
item.状态 || '',
item.补货日期 || '',
item.补货期限 || '',
item.期限状态 || '',
item.超期时间 || ''
]
})
// 添加空行和统计汇总
data.push([])
data.push(['统计汇总', '', '', '', '', '', '', '', ''])
data.push(['正常', this.生产中统计.正常, '', '', '', '', '', '', ''])
data.push(['临期', this.生产中统计.临期, '', '', '', '', '', '', ''])
data.push(['超期', this.生产中统计.超期, '', '', '', '', '', '', ''])
data.push(['总计', this.statistics.生产中, '', '', '', '', '', '', ''])
data.unshift(header)
var ws = XLSX.utils.aoa_to_sheet(data)
// 设置列宽
ws['!cols'] = [
{ wch: 15 }, { wch: 20 }, { wch: 10 }, { wch: 10 },
{ wch: 10 }, { wch: 15 }, { wch: 15 }, { wch: 10 }, { wch: 12 }
]
var wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, '生产中详情')
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'array' })
FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), '生产中详情.xlsx')
}
}
}
</script>
<style scoped>
.stat-card {
height: 120px;
cursor: pointer;
transition: all 0.3s ease;
padding: 10px 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 4px;
background-color: #fff;
position: relative;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.stat-card-production {
height: 150px;
}
.stat-number {
font-size: 36px;
font-weight: bold;
text-align: center;
margin-top: 10px;
color: #303133;
}
.stat-label {
font-size: 13px;
text-align: left;
color: #909399;
padding-left: 5px;
}
.stat-sub-info {
font-size: 12px;
text-align: center;
color: #909399;
margin-top: 4px;
}
.stat-sub-labels {
display: flex;
justify-content: space-around;
margin-top: 8px;
font-size: 12px;
}
.sub-normal {
color: #67c23a;
}
.sub-warning {
color: #e6a23c;
}
.sub-danger {
color: #f56c6c;
}
.sub-normal.sub-active,
.sub-warning.sub-active,
.sub-danger.sub-active {
font-weight: bold;
text-decoration: underline;
cursor: pointer;
}
.sub-normal,
.sub-warning,
.sub-danger {
cursor: pointer;
}
.text-danger {
color: #f56c6c;
font-weight: bold;
}
.text-warning {
color: #e6a23c;
font-weight: bold;
}
.summary-card {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 4px;
background-color: #fff;
}
.summary-label {
font-size: 14px;
color: #303133;
}
.el-card {
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.el-card__body {
padding: 20px;
}
.stat-card:nth-child(1) .stat-number {
color: #4caf50;
}
.stat-card:nth-child(2) .stat-number {
color: #ff9800;
}
.stat-card:nth-child(3) .stat-number {
color: #2196f3;
}
.stat-card:nth-child(4) .stat-number {
color: #9c27b0;
}
.stat-card:nth-child(5) .stat-number {
color: #e91e63;
}
.stat-card:nth-child(6) .stat-number {
color: #607d8b;
}
.stat-card:nth-child(7) .stat-number {
color: #ff5722;
}
</style>