Files
bj_power/bj_power_wms_client/frontend/src/pages/Display.vue
T

164 lines
5.7 KiB
Vue
Raw Normal View History

2026-08-27 19:50:57 +08:00
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import request from '../utils/request'
import { fmtNum } from '../utils/format'
const now = ref(new Date())
let clockTimer = null
let pollTimer = null
const overview = ref({})
const maxZoneQty = computed(() => {
const vals = Object.values(zoneList.value).map(v => Number(v.qty) || 0)
return Math.max(...vals, 1)
})
const zoneList = computed(() => {
const detail = overview.value.zoneDetail || {}
return Object.entries(detail)
.map(([zone, qty]) => ({ zone, qty: Number(qty) || 0 }))
.sort((a, b) => b.qty - a.qty)
})
const kpis = computed(() => [
{ key: 'totalQty', label: '在库总量', value: fmtNum(overview.value.totalQty) },
{ key: 'lockedQty', label: '锁定量', value: fmtNum(overview.value.lockedQty) },
{ key: 'snInStock', label: 'SN 在库', value: fmtNum(overview.value.snInStock) },
{ key: 'semiInStock', label: '半成品在库', value: fmtNum(overview.value.semiInStock) },
{ key: 'packageCount', label: '包装箱数', value: fmtNum(overview.value.packageCount) },
{ key: 'inToday', label: '今日入库单', value: fmtNum(overview.value.inToday) },
{ key: 'outToday', label: '今日出库单', value: fmtNum(overview.value.outToday) }
])
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const clock = computed(() => {
const d = now.value
const p = (n) => String(n).padStart(2, '0')
return {
date: `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`,
time: `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`,
week: `星期${weekdays[d.getDay()].replace('周', '')}`
}
})
async function load() {
try {
overview.value = await request.get('/display/overview')
} catch { /* 大屏轮询失败静默重试 */ }
}
onMounted(() => {
clockTimer = setInterval(() => { now.value = new Date() }, 1000)
load()
pollTimer = setInterval(load, 5000)
})
onBeforeUnmount(() => {
clearInterval(clockTimer)
clearInterval(pollTimer)
})
</script>
<template>
<div class="screen">
<header class="head">
<div class="side left">北自所 · 库房监控</div>
<h1 class="title">库存动态大屏</h1>
<div class="side right">
<span class="date">{{ clock.date }}</span>
<span class="time">{{ clock.time }}</span>
<span>{{ clock.week }}</span>
</div>
</header>
<section class="kpi-grid">
<div v-for="k in kpis" :key="k.key" class="kpi">
<div class="kpi-value">{{ k.value }}</div>
<div class="kpi-label">{{ k.label }}</div>
</div>
</section>
<section class="zones">
<div class="zone-title">区域明细区域 / 质量状态</div>
<div class="zone-list">
<div v-for="z in zoneList" :key="z.zone" class="zone-row">
<div class="zone-name">{{ z.zone }}</div>
<div class="bar-wrap">
<div class="bar" :style="{ width: `${(z.qty / maxZoneQty) * 100}%` }"></div>
</div>
<div class="zone-qty">{{ z.qty.toLocaleString('zh-CN') }}</div>
</div>
<div v-if="!zoneList.length" class="empty">暂无区域数据</div>
</div>
</section>
<footer class="foot">数据来源WMS 后端 · 5 秒自动刷新</footer>
</div>
</template>
<style scoped>
.screen {
min-height: 100vh;
background:
radial-gradient(ellipse at 50% 0%, rgba(0, 170, 255, 0.18), transparent 55%),
linear-gradient(180deg, #04102a 0%, #071e42 60%, #04102a 100%);
color: #d7ecff;
display: flex;
flex-direction: column;
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
.head {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 36px 10px;
border-bottom: 1px solid rgba(64, 158, 255, 0.35);
}
.title {
margin: 0; font-size: clamp(20px, 2.6vw, 34px); letter-spacing: 8px;
color: #fff; text-shadow: 0 0 14px rgba(64, 190, 255, 0.9);
}
.side { font-size: 13px; color: #7fb8ff; }
.right { text-align: right; }
.date { margin-right: 12px; }
.time {
font-size: 22px; font-weight: 700; color: #4fd7ff; margin-right: 8px;
font-variant-numeric: tabular-nums;
}
.kpi-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 14px; padding: 22px 36px 6px;
}
.kpi {
border: 1px solid rgba(64, 158, 255, 0.45);
background: linear-gradient(180deg, rgba(13, 47, 96, 0.75), rgba(7, 26, 58, 0.85));
border-radius: 8px; padding: 18px 10px; text-align: center;
box-shadow: inset 0 0 24px rgba(35, 130, 255, 0.25);
}
.kpi-value {
font-size: clamp(28px, 3vw, 46px); font-weight: 800; color: #61e6ff;
text-shadow: 0 0 16px rgba(80, 220, 255, 0.7);
font-variant-numeric: tabular-nums;
}
.kpi-label { margin-top: 6px; font-size: 14px; color: #9cc7ff; letter-spacing: 2px; }
.zones { flex: 1; padding: 16px 36px 8px; overflow: auto; }
.zone-title {
font-size: 16px; letter-spacing: 3px; color: #bfe0ff;
border-left: 4px solid #409eff; padding-left: 10px; margin-bottom: 12px;
}
.zone-row {
display: flex; align-items: center; gap: 12px;
padding: 7px 0; border-bottom: 1px dashed rgba(64, 158, 255, 0.15);
}
.zone-name { width: 200px; text-align: right; color: #cfe6ff; font-size: 14px; }
.bar-wrap { flex: 1; height: 16px; background: rgba(19, 56, 110, 0.6); border-radius: 9px; overflow: hidden; }
.bar {
height: 100%; border-radius: 9px;
background: linear-gradient(90deg, #1673ff, #37e2ff);
box-shadow: 0 0 10px rgba(55, 210, 255, 0.65);
transition: width 0.6s ease;
}
.zone-qty { width: 90px; color: #61e6ff; font-weight: 700; font-variant-numeric: tabular-nums; }
.empty { color: #5f86b8; padding: 30px 0; text-align: center; }
.foot { padding: 12px 36px 16px; text-align: center; color: #47709f; font-size: 12px; }
</style>