wip在产大屏
This commit is contained in:
@@ -101,7 +101,28 @@
|
|||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<main class="screen-body">
|
<main class="screen-body">
|
||||||
<div class="body-left">
|
<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>
|
||||||
<div class="body-center">
|
<div class="body-center">
|
||||||
<dv-border-box-8 :reverse="true" class="coil-panel">
|
<dv-border-box-8 :reverse="true" class="coil-panel">
|
||||||
@@ -206,7 +227,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body-right">
|
<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>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,7 +249,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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'
|
import { listPendingAction } from '@/api/wip'
|
||||||
|
|
||||||
const currentDate = ref('')
|
const currentDate = ref('')
|
||||||
@@ -230,6 +265,15 @@ const tableWrapRef = ref(null)
|
|||||||
const tableDrag = { active: false, startY: 0, startScroll: 0, clickOnly: true }
|
const tableDrag = { active: false, startY: 0, startScroll: 0, clickOnly: true }
|
||||||
const selectedCoil = ref(null)
|
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 = [
|
const processList = [
|
||||||
{ type: 501, name: '酸连轧' },
|
{ type: 501, name: '酸连轧' },
|
||||||
{ type: 502, name: '镀锌' },
|
{ type: 502, name: '镀锌' },
|
||||||
@@ -249,6 +293,58 @@ const statusLabels = {
|
|||||||
const processMap = {}
|
const processMap = {}
|
||||||
processList.forEach(p => { processMap[p.type] = p.name })
|
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) => {
|
const handleRowClick = (coil) => {
|
||||||
if (!tableDrag.clickOnly) return
|
if (!tableDrag.clickOnly) return
|
||||||
selectedCoil.value = coil
|
selectedCoil.value = coil
|
||||||
@@ -432,18 +528,113 @@ const updateScale = () => {
|
|||||||
scaleRatio.value = Math.min(w / 1920, h / 1080)
|
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(() => {
|
onMounted(() => {
|
||||||
updateScale()
|
updateScale()
|
||||||
window.addEventListener('resize', updateScale)
|
window.addEventListener('resize', updateScale)
|
||||||
|
window.addEventListener('resize', handleChartResize)
|
||||||
updateTime()
|
updateTime()
|
||||||
timeInterval = setInterval(updateTime, 1000)
|
timeInterval = setInterval(updateTime, 1000)
|
||||||
fetchData()
|
fetchData()
|
||||||
dataInterval = setInterval(fetchData, 30000)
|
dataInterval = setInterval(fetchData, 30000)
|
||||||
setTimeout(() => startTableAutoScroll(), 500)
|
setTimeout(() => startTableAutoScroll(), 500)
|
||||||
|
nextTick(() => {
|
||||||
|
initLeftChart()
|
||||||
|
initRightCharts()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', updateScale)
|
window.removeEventListener('resize', updateScale)
|
||||||
|
window.removeEventListener('resize', handleChartResize)
|
||||||
stopTableAutoScroll()
|
stopTableAutoScroll()
|
||||||
document.removeEventListener('mousemove', onTableDragMove)
|
document.removeEventListener('mousemove', onTableDragMove)
|
||||||
document.removeEventListener('mouseup', onTableDragEnd)
|
document.removeEventListener('mouseup', onTableDragEnd)
|
||||||
@@ -456,6 +647,9 @@ onBeforeUnmount(() => {
|
|||||||
clearInterval(dataInterval)
|
clearInterval(dataInterval)
|
||||||
dataInterval = null
|
dataInterval = null
|
||||||
}
|
}
|
||||||
|
if (leftChartInstance) { leftChartInstance.dispose(); leftChartInstance = null }
|
||||||
|
if (rightDonutInstance) { rightDonutInstance.dispose(); rightDonutInstance = null }
|
||||||
|
if (rightBarInstance) { rightBarInstance.dispose(); rightBarInstance = null }
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -1062,6 +1256,9 @@ onBeforeUnmount(() => {
|
|||||||
.body-right {
|
.body-right {
|
||||||
flex: 0 0 22%;
|
flex: 0 0 22%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-center {
|
.body-center {
|
||||||
@@ -1072,6 +1269,84 @@ onBeforeUnmount(() => {
|
|||||||
min-height: 0;
|
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 {
|
.coil-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user