wip在产大屏

This commit is contained in:
jhd
2026-05-29 16:26:59 +08:00
parent f432ff093c
commit a4df18890d

View File

@@ -101,7 +101,28 @@
<!-- 主内容区域 -->
<main class="screen-body">
<div class="body-left">
<!-- 左侧预留 -->
<dv-border-box-8 :reverse="true" class="chart-panel">
<div class="chart-inner">
<div class="section-title">
<span class="section-title-dot"></span>
<span>今日完成</span>
</div>
<div class="summary-cards">
<div class="summary-card">
<span class="summary-number">{{ todayCompletedCount }}</span>
<span class="summary-label">完成卷数</span>
</div>
<div class="summary-divider"></div>
<div class="summary-card">
<span class="summary-number weight">{{ todayCompletedWeight }}</span>
<span class="summary-label">总重量 (kg)</span>
</div>
</div>
<div class="mini-chart-wrap">
<div ref="leftChartRef" class="mini-chart"></div>
</div>
</div>
</dv-border-box-8>
</div>
<div class="body-center">
<dv-border-box-8 :reverse="true" class="coil-panel">
@@ -206,7 +227,20 @@
</div>
</div>
<div class="body-right">
<!-- 右侧预留 -->
<dv-border-box-8 :reverse="true" class="chart-panel">
<div class="chart-inner">
<div class="section-title">
<span class="section-title-dot"></span>
<span>工序分布</span>
</div>
<div class="mini-chart-wrap half">
<div ref="rightDonutRef" class="mini-chart"></div>
</div>
<div class="mini-chart-wrap half">
<div ref="rightBarRef" class="mini-chart"></div>
</div>
</div>
</dv-border-box-8>
</div>
</main>
</div>
@@ -215,7 +249,8 @@
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
import * as echarts from 'echarts'
import { listPendingAction } from '@/api/wip'
const currentDate = ref('')
@@ -230,6 +265,15 @@ const tableWrapRef = ref(null)
const tableDrag = { active: false, startY: 0, startScroll: 0, clickOnly: true }
const selectedCoil = ref(null)
// Chart refs
const leftChartRef = ref(null)
const rightDonutRef = ref(null)
const rightBarRef = ref(null)
let leftChartInstance = null
let rightDonutInstance = null
let rightBarInstance = null
const chartColors = ['#00d4ff', '#7c63ff', '#00ff88', '#f0ad4e', '#ff4d4f', '#ff85c0']
const processList = [
{ type: 501, name: '酸连轧' },
{ type: 502, name: '镀锌' },
@@ -249,6 +293,58 @@ const statusLabels = {
const processMap = {}
processList.forEach(p => { processMap[p.type] = p.name })
// ===== 今日完成统计 =====
const getTodayStart = () => {
const now = new Date()
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
}
const todayCompleted = computed(() => {
const todayStart = getTodayStart()
return allRecords.value.filter(r =>
Number(r.actionStatus) === 2 &&
r.completeTime && new Date(r.completeTime).getTime() >= todayStart
)
})
const todayCompletedCount = computed(() => todayCompleted.value.length)
const todayCompletedWeight = computed(() => {
return todayCompleted.value.reduce((sum, r) => sum + (Number(r.weight) || 0), 0).toFixed(1)
})
const todayCompletedByProcess = computed(() => {
const map = {}
todayCompleted.value.forEach(r => {
const type = Number(r.actionType)
if (!map[type]) map[type] = 0
map[type]++
})
return processList.map(p => ({
...p,
count: map[p.type] || 0
})).sort((a, b) => b.count - a.count)
})
// ===== 工序 WIP 分布统计 =====
const wipByProcess = computed(() => {
const startTs = getTimeStart()
const map = {}
processList.forEach(p => { map[p.type] = 0 })
allRecords.value.forEach(r => {
const type = Number(r.actionType)
const status = Number(r.actionStatus)
const t = r.createTime ? new Date(r.createTime).getTime() : 0
if (status <= 1 && t >= startTs && map[type] !== undefined) {
map[type]++
}
})
return processList.map(p => ({
...p,
count: map[p.type] || 0
}))
})
const handleRowClick = (coil) => {
if (!tableDrag.clickOnly) return
selectedCoil.value = coil
@@ -432,18 +528,113 @@ const updateScale = () => {
scaleRatio.value = Math.min(w / 1920, h / 1080)
}
// ===== 统计图表 =====
const initLeftChart = () => {
if (!leftChartRef.value || leftChartInstance) return
leftChartInstance = echarts.init(leftChartRef.value, null, { renderer: 'canvas' })
updateLeftChart()
}
const updateLeftChart = () => {
if (!leftChartInstance) return
const data = todayCompletedByProcess.value
leftChartInstance.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: 10, right: 30, top: 8, bottom: 8, containLabel: true },
xAxis: { type: 'value', splitLine: { lineStyle: { color: 'rgba(0,212,255,0.08)' } }, axisLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 10 } },
yAxis: { type: 'category', data: data.map(d => d.name), axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11 } },
series: [{
type: 'bar',
data: data.map((d, i) => ({ value: d.count, itemStyle: { color: chartColors[i % chartColors.length] } })),
barWidth: 14,
label: { show: true, position: 'right', color: 'rgba(255,255,255,0.6)', fontSize: 11, formatter: (p) => p.value > 0 ? p.value : '' }
}]
})
}
const initRightCharts = () => {
if (rightDonutRef.value && !rightDonutInstance) {
rightDonutInstance = echarts.init(rightDonutRef.value, null, { renderer: 'canvas' })
updateRightDonut()
}
if (rightBarRef.value && !rightBarInstance) {
rightBarInstance = echarts.init(rightBarRef.value, null, { renderer: 'canvas' })
updateRightBar()
}
}
const updateRightDonut = () => {
if (!rightDonutInstance) return
const data = wipByProcess.value.filter(d => d.count > 0)
if (data.length === 0) {
rightDonutInstance.setOption({ title: { text: '暂无数据', textStyle: { color: 'rgba(255,255,255,0.3)', fontSize: 12 }, left: 'center', top: 'center' }, series: [] })
return
}
rightDonutInstance.setOption({
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
series: [{
type: 'pie',
radius: ['40%', '65%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
itemStyle: { borderRadius: 4, borderColor: 'rgba(10,20,40,0.8)', borderWidth: 2 },
label: { show: true, color: 'rgba(255,255,255,0.7)', fontSize: 10, formatter: '{b}\n{c}' },
labelLine: { lineStyle: { color: 'rgba(0,212,255,0.2)' } },
data: data.map((d, i) => ({ name: d.name, value: d.count, itemStyle: { color: chartColors[i % chartColors.length] } }))
}]
})
}
const updateRightBar = () => {
if (!rightBarInstance) return
const data = wipByProcess.value
rightBarInstance.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: 10, right: 10, top: 8, bottom: 20, containLabel: true },
xAxis: { type: 'category', data: data.map(d => d.name), axisLabel: { color: 'rgba(255,255,255,0.6)', fontSize: 9, interval: 0, rotate: 0 }, axisLine: { lineStyle: { color: 'rgba(0,212,255,0.1)' } }, axisTick: { show: false } },
yAxis: { type: 'value', splitLine: { lineStyle: { color: 'rgba(0,212,255,0.08)' } }, axisLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 9 } },
series: [{
type: 'bar',
data: data.map((d, i) => ({ value: d.count, itemStyle: { color: chartColors[i % chartColors.length] } })),
barWidth: 14,
label: { show: true, position: 'top', color: 'rgba(255,255,255,0.6)', fontSize: 9, formatter: (p) => p.value > 0 ? p.value : '' }
}]
})
}
const handleChartResize = () => {
if (leftChartInstance) leftChartInstance.resize()
if (rightDonutInstance) rightDonutInstance.resize()
if (rightBarInstance) rightBarInstance.resize()
}
// 数据变化时更新图表
watch(allRecords, () => {
nextTick(() => {
updateLeftChart()
updateRightDonut()
updateRightBar()
})
})
onMounted(() => {
updateScale()
window.addEventListener('resize', updateScale)
window.addEventListener('resize', handleChartResize)
updateTime()
timeInterval = setInterval(updateTime, 1000)
fetchData()
dataInterval = setInterval(fetchData, 30000)
setTimeout(() => startTableAutoScroll(), 500)
nextTick(() => {
initLeftChart()
initRightCharts()
})
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updateScale)
window.removeEventListener('resize', handleChartResize)
stopTableAutoScroll()
document.removeEventListener('mousemove', onTableDragMove)
document.removeEventListener('mouseup', onTableDragEnd)
@@ -456,6 +647,9 @@ onBeforeUnmount(() => {
clearInterval(dataInterval)
dataInterval = null
}
if (leftChartInstance) { leftChartInstance.dispose(); leftChartInstance = null }
if (rightDonutInstance) { rightDonutInstance.dispose(); rightDonutInstance = null }
if (rightBarInstance) { rightBarInstance.dispose(); rightBarInstance = null }
})
</script>
@@ -1062,6 +1256,9 @@ onBeforeUnmount(() => {
.body-right {
flex: 0 0 22%;
min-width: 0;
display: flex;
flex-direction: column;
min-height: 0;
}
.body-center {
@@ -1072,6 +1269,84 @@ onBeforeUnmount(() => {
min-height: 0;
}
.chart-panel {
flex: 1;
background: rgba(10, 20, 40, 0.5);
display: flex;
flex-direction: column;
min-height: 0;
}
.chart-inner {
display: flex;
flex-direction: column;
padding: 16px 12px 12px;
box-sizing: border-box;
height: 100%;
min-height: 0;
overflow: hidden;
}
.summary-cards {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
flex-shrink: 0;
}
.summary-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 10px 4px;
background: rgba(0, 212, 255, 0.06);
border: 1px solid rgba(0, 212, 255, 0.12);
border-radius: 6px;
}
.summary-number {
font-size: 26px;
font-weight: bold;
font-family: 'Courier New', monospace;
color: #00d4ff;
text-shadow: 0 0 10px rgba(0, 212, 255, 0.4);
}
.summary-number.weight {
font-size: 20px;
}
.summary-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.45);
letter-spacing: 1px;
}
.summary-divider {
width: 1px;
height: 50px;
background: rgba(0, 212, 255, 0.15);
flex-shrink: 0;
}
.mini-chart-wrap {
flex: 1;
min-height: 0;
position: relative;
}
.mini-chart-wrap.half {
flex: 0 0 50%;
}
.mini-chart {
width: 100%;
height: 100%;
}
.coil-panel {
flex: 1;
min-height: 0;