wip在产大屏
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user