<template>
|
<div class="warehouse-container">
|
<div class="header">
|
<h1 class="title">仓库监控中心</h1>
|
<div class="datetime">{{ currentTime }}</div>
|
</div>
|
|
<div class="nav-menu">
|
<router-link to="/dashboard" class="nav-item">综合看板</router-link>
|
<router-link to="/warehouse" class="nav-item active">仓库监控</router-link>
|
<router-link to="/production" class="nav-item">生产监控</router-link>
|
<router-link to="/inventory" class="nav-item">库存预警</router-link>
|
</div>
|
|
<div class="main-content">
|
<!-- 仓库区域状态 -->
|
<div class="warehouse-areas">
|
<div class="area-card" v-for="area in areas" :key="area.name">
|
<div class="area-header">
|
<span class="area-name">{{ area.name }}</span>
|
<span class="area-capacity">容量: {{ area.used }}/{{ area.total }}</span>
|
</div>
|
<div class="area-progress">
|
<div class="progress-bar" :style="{ width: (area.used / area.total * 100) + '%', background: area.color }"></div>
|
</div>
|
<div class="area-stats">
|
<div class="stat-item">
|
<span class="stat-label">货架数</span>
|
<span class="stat-value">{{ area.shelves }}</span>
|
</div>
|
<div class="stat-item">
|
<span class="stat-label">库位数</span>
|
<span class="stat-value">{{ area.positions }}</span>
|
</div>
|
<div class="stat-item">
|
<span class="stat-label">利用率</span>
|
<span class="stat-value">{{ (area.used / area.total * 100).toFixed(1) }}%</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 仓库布局图 -->
|
<div class="warehouse-layout">
|
<div class="card-title">仓库实时布局</div>
|
<div class="layout-grid">
|
<div v-for="zone in 8" :key="zone" class="warehouse-zone">
|
<div class="zone-label">{{ String.fromCharCode(64 + zone) }}区</div>
|
<div class="zone-slots">
|
<div v-for="slot in 12" :key="slot"
|
class="slot"
|
:class="getSlotStatus(zone, slot)">
|
{{ String.fromCharCode(64 + zone) }}-{{ Math.ceil(slot / 4) }}-{{ slot % 4 || 4 }}
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="legend">
|
<div class="legend-item">
|
<span class="legend-color occupied"></span>
|
<span>已占用</span>
|
</div>
|
<div class="legend-item">
|
<span class="legend-color empty"></span>
|
<span>空闲</span>
|
</div>
|
<div class="legend-item">
|
<span class="legend-color reserved"></span>
|
<span>预留</span>
|
</div>
|
<div class="legend-item">
|
<span class="legend-color locked"></span>
|
<span>锁定</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 温湿度监控 -->
|
<div class="env-monitor">
|
<div class="env-card" v-for="(env, index) in envData" :key="index">
|
<div class="env-icon" :style="{ background: env.gradient }">
|
<el-icon :size="28">
|
<Monitor v-if="index < 3" />
|
<Warning v-else />
|
</el-icon>
|
</div>
|
<div class="env-content">
|
<div class="env-label">{{ env.location }}</div>
|
<div class="env-value">
|
<span class="temp">{{ env.temperature }}°C</span>
|
<span class="humidity">{{ env.humidity }}%</span>
|
</div>
|
<div class="env-status" :class="env.status === '正常' ? 'normal' : 'warning'">
|
{{ env.status }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import { ref, onMounted, onUnmounted } from 'vue'
|
import { http } from '@/utils/http'
|
import { formatDateTime } from '@/utils'
|
|
export default {
|
name: 'Warehouse',
|
setup() {
|
const currentTime = ref('')
|
|
const areas = ref([
|
{ name: '原材料区', used: 2150, total: 3000, shelves: 50, positions: 3000, color: '#5470c6' },
|
{ name: '半成品区', used: 1680, total: 2500, shelves: 42, positions: 2500, color: '#91cc75' },
|
{ name: '成品区', used: 2890, total: 4000, shelves: 60, positions: 4000, color: '#fac858' },
|
{ name: '辅料区', used: 980, total: 1500, shelves: 30, positions: 1500, color: '#ee6666' }
|
])
|
|
const envData = ref([
|
{ location: 'A区-原料仓', temperature: 22.5, humidity: 55, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
|
{ location: 'B区-半成品仓', temperature: 23.8, humidity: 58, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
|
{ location: 'C区-成品仓', temperature: 21.2, humidity: 52, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
|
{ location: 'D区-辅料仓', temperature: 26.5, humidity: 68, status: '预警', icon: 'Warning', gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }
|
])
|
|
let timer
|
const updateTime = () => {
|
currentTime.value = formatDateTime(new Date())
|
}
|
|
// 模拟库位状态
|
const getSlotStatus = (zone, slot) => {
|
const random = (zone * slot) % 10
|
if (random < 6) return 'occupied'
|
if (random < 8) return 'empty'
|
if (random < 9) return 'reserved'
|
return 'locked'
|
}
|
|
// 获取仓库数据
|
const fetchWarehouseData = async () => {
|
try {
|
// 实际使用时调用接口
|
// const res = await http.get('/wms/warehouse/status')
|
} catch (error) {
|
console.error('获取仓库数据失败:', error)
|
}
|
}
|
|
onMounted(() => {
|
updateTime()
|
timer = setInterval(updateTime, 1000)
|
fetchWarehouseData()
|
})
|
|
onUnmounted(() => {
|
clearInterval(timer)
|
})
|
|
return {
|
currentTime,
|
areas,
|
envData,
|
getSlotStatus
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.warehouse-container {
|
width: 100%;
|
height: 100%;
|
padding: 20px;
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
overflow-y: auto;
|
}
|
|
.header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 0 20px;
|
height: 60px;
|
background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
|
border-radius: 10px;
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
}
|
|
.title {
|
font-size: 24px;
|
font-weight: bold;
|
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
}
|
|
.datetime {
|
font-size: 16px;
|
color: #4facfe;
|
}
|
|
.nav-menu {
|
display: flex;
|
gap: 15px;
|
padding: 0 20px;
|
}
|
|
.nav-item {
|
padding: 10px 25px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 8px;
|
color: #fff;
|
text-decoration: none;
|
transition: all 0.3s;
|
}
|
|
.nav-item:hover {
|
background: rgba(79, 172, 254, 0.2);
|
border-color: #4facfe;
|
}
|
|
.nav-item.active {
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
border-color: transparent;
|
}
|
|
.main-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.warehouse-areas {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 15px;
|
}
|
|
.area-card {
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
}
|
|
.area-header {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 15px;
|
}
|
|
.area-name {
|
font-size: 16px;
|
font-weight: bold;
|
color: #4facfe;
|
}
|
|
.area-capacity {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.6);
|
}
|
|
.area-progress {
|
height: 8px;
|
background: rgba(255, 255, 255, 0.1);
|
border-radius: 4px;
|
margin-bottom: 15px;
|
overflow: hidden;
|
}
|
|
.progress-bar {
|
height: 100%;
|
border-radius: 4px;
|
transition: width 0.5s;
|
}
|
|
.area-stats {
|
display: flex;
|
justify-content: space-between;
|
}
|
|
.stat-item {
|
text-align: center;
|
}
|
|
.stat-label {
|
font-size: 12px;
|
color: rgba(255, 255, 255, 0.5);
|
display: block;
|
margin-bottom: 5px;
|
}
|
|
.stat-value {
|
font-size: 16px;
|
font-weight: bold;
|
color: #fff;
|
}
|
|
.warehouse-layout {
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
}
|
|
.card-title {
|
font-size: 16px;
|
font-weight: bold;
|
color: #4facfe;
|
margin-bottom: 15px;
|
}
|
|
.layout-grid {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 15px;
|
margin-bottom: 20px;
|
}
|
|
.warehouse-zone {
|
background: rgba(0, 0, 0, 0.2);
|
border-radius: 8px;
|
padding: 15px;
|
}
|
|
.zone-label {
|
text-align: center;
|
font-size: 14px;
|
font-weight: bold;
|
color: #4facfe;
|
margin-bottom: 10px;
|
}
|
|
.zone-slots {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 8px;
|
}
|
|
.slot {
|
padding: 8px 4px;
|
font-size: 10px;
|
text-align: center;
|
border-radius: 4px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
color: rgba(255, 255, 255, 0.7);
|
transition: all 0.3s;
|
cursor: pointer;
|
}
|
|
.slot:hover {
|
transform: scale(1.05);
|
}
|
|
.slot.occupied {
|
background: rgba(84, 112, 198, 0.5);
|
border-color: #5470c6;
|
}
|
|
.slot.empty {
|
background: rgba(67, 233, 123, 0.2);
|
border-color: #43e97b;
|
}
|
|
.slot.reserved {
|
background: rgba(250, 200, 88, 0.3);
|
border-color: #fac858;
|
}
|
|
.slot.locked {
|
background: rgba(238, 102, 102, 0.3);
|
border-color: #ee6666;
|
}
|
|
.legend {
|
display: flex;
|
justify-content: center;
|
gap: 30px;
|
}
|
|
.legend-item {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.7);
|
}
|
|
.legend-color {
|
width: 20px;
|
height: 20px;
|
border-radius: 4px;
|
}
|
|
.legend-color.occupied {
|
background: rgba(84, 112, 198, 0.5);
|
border: 1px solid #5470c6;
|
}
|
|
.legend-color.empty {
|
background: rgba(67, 233, 123, 0.2);
|
border: 1px solid #43e97b;
|
}
|
|
.legend-color.reserved {
|
background: rgba(250, 200, 88, 0.3);
|
border: 1px solid #fac858;
|
}
|
|
.legend-color.locked {
|
background: rgba(238, 102, 102, 0.3);
|
border: 1px solid #ee6666;
|
}
|
|
.env-monitor {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 15px;
|
}
|
|
.env-card {
|
display: flex;
|
align-items: center;
|
gap: 15px;
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
}
|
|
.env-icon {
|
width: 50px;
|
height: 50px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 10px;
|
color: #fff;
|
}
|
|
.env-content {
|
flex: 1;
|
}
|
|
.env-label {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.6);
|
margin-bottom: 5px;
|
}
|
|
.env-value {
|
display: flex;
|
gap: 15px;
|
font-size: 18px;
|
font-weight: bold;
|
margin-bottom: 5px;
|
}
|
|
.env-value .temp {
|
color: #f5576c;
|
}
|
|
.env-value .humidity {
|
color: #4facfe;
|
}
|
|
.env-status {
|
font-size: 12px;
|
}
|
|
.env-status.normal {
|
color: #67c23a;
|
}
|
|
.env-status.warning {
|
color: #e6a23c;
|
}
|
|
/* 响应式适配 */
|
@media screen and (max-width: 1600px) {
|
.warehouse-areas {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.layout-grid {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
}
|
|
@media screen and (max-width: 1366px) {
|
.warehouse-areas {
|
grid-template-columns: repeat(2, 1fr);
|
gap: 10px;
|
}
|
|
.layout-grid {
|
grid-template-columns: repeat(2, 1fr);
|
gap: 10px;
|
}
|
|
.env-monitor {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
}
|
|
@media screen and (max-width: 1024px) {
|
.warehouse-areas {
|
grid-template-columns: 1fr 1fr;
|
}
|
|
.layout-grid {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.warehouse-zone {
|
padding: 10px;
|
}
|
|
.zone-slots {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
}
|
|
@media screen and (max-width: 768px) {
|
.warehouse-container {
|
padding: 10px;
|
}
|
|
.header {
|
flex-direction: column;
|
height: auto;
|
padding: 10px;
|
gap: 10px;
|
}
|
|
.title {
|
font-size: 16px;
|
}
|
|
.warehouse-areas {
|
grid-template-columns: 1fr;
|
}
|
|
.area-card {
|
padding: 12px;
|
}
|
|
.layout-grid {
|
grid-template-columns: 1fr;
|
}
|
|
.zone-slots {
|
grid-template-columns: repeat(2, 1fr);
|
gap: 5px;
|
}
|
|
.slot {
|
font-size: 9px;
|
padding: 6px 2px;
|
}
|
|
.legend {
|
flex-wrap: wrap;
|
gap: 15px;
|
}
|
|
.env-monitor {
|
grid-template-columns: 1fr;
|
}
|
|
.env-card {
|
padding: 12px;
|
}
|
}
|
</style>
|