You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
5.9 KiB
Vue
264 lines
5.9 KiB
Vue
<template>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title">
|
|
<span class="card-title-icon">
|
|
<Icon icon="fa-solid:wave-square" />
|
|
</span>
|
|
<span>产能完成数</span>
|
|
</div>
|
|
<div class="ops-header-right">
|
|
<span class="tag">{{ summary }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div ref="chartRef" class="chart"></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import * as echarts from 'echarts'
|
|
import { colors, style } from '../utils'
|
|
import { PlanApi } from '@/api/mes/plan'
|
|
|
|
const route = useRoute()
|
|
|
|
const opsDays: string[] = []
|
|
const opsLines: { name: string; value: number[] }[] = [
|
|
{ name: '全部产线', value: [] }
|
|
]
|
|
|
|
const currentLine = ref(opsLines[0].name)
|
|
const summary = ref('')
|
|
const chartRef = ref<HTMLElement | null>(null)
|
|
let chart: echarts.ECharts | null = null
|
|
|
|
const avg = (arr: number[]) => {
|
|
return Math.round((arr.reduce((s, v) => s + v, 0) / arr.length) * 10) / 10
|
|
}
|
|
|
|
const updateOps = (lineName: string) => {
|
|
currentLine.value = lineName
|
|
const item = opsLines.find((x) => x.name === lineName) || opsLines[0]
|
|
summary.value = `${item.name} · 日均完成 ${avg(item.value)}`
|
|
|
|
if (!chart) return
|
|
chart.setOption({
|
|
xAxis: { data: opsDays },
|
|
series: [{ data: item.value }]
|
|
})
|
|
}
|
|
|
|
const loadOpsData = async () => {
|
|
try {
|
|
const orgId = route.query.orgId
|
|
const res = await PlanApi.getLastSevenDaysCompletedCount({ orgId })
|
|
const raw = res && typeof res === 'object' && 'data' in (res as any) ? (res as any).data : res
|
|
const list = Array.isArray(raw) ? raw : []
|
|
const days: string[] = []
|
|
const values: number[] = []
|
|
list.forEach((item: any) => {
|
|
const src = item.day || item.date || item.time || item.hour || item.key || item.name || ''
|
|
let label = src
|
|
if (typeof label === 'string' && label.includes(' ')) {
|
|
label = label.split(' ')[0]
|
|
}
|
|
days.push(label)
|
|
const raw = item.totalWangong ?? item.count ?? 0
|
|
const n = Number(raw)
|
|
values.push(Number.isFinite(n) ? n : 0)
|
|
})
|
|
opsDays.length = 0
|
|
opsDays.push(...days)
|
|
opsLines.length = 0
|
|
opsLines.push({ name: '全部产线', value: values })
|
|
if (opsLines[0]) {
|
|
currentLine.value = opsLines[0].name
|
|
updateOps(currentLine.value)
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load ops trend:', e)
|
|
}
|
|
}
|
|
|
|
const initChart = async () => {
|
|
if (!chartRef.value) return
|
|
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
|
chart.setOption({
|
|
backgroundColor: 'transparent',
|
|
tooltip: { trigger: 'axis' },
|
|
legend: { top: 0, right: 0, textStyle: style.legendText },
|
|
grid: { top: '22%', left: '6%', right: '6%', bottom: '10%', containLabel: true },
|
|
xAxis: { type: 'category', data: opsDays, axisLine: style.axisLine, axisLabel: style.axisLabel },
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '产能完成数',
|
|
nameTextStyle: { color: '#a8b7d8', fontSize: 12 },
|
|
axisLabel: style.axisLabel,
|
|
splitLine: style.splitLine
|
|
},
|
|
series: [
|
|
{
|
|
name: '产能完成数',
|
|
type: 'line',
|
|
smooth: true,
|
|
showSymbol: false,
|
|
lineStyle: { width: 2, color: colors.blue },
|
|
itemStyle: { color: colors.blue },
|
|
data: []
|
|
}
|
|
]
|
|
})
|
|
await loadOpsData()
|
|
}
|
|
|
|
const resizeHandler = () => {
|
|
chart?.resize()
|
|
}
|
|
|
|
onMounted(() => {
|
|
initChart()
|
|
window.addEventListener('resize', resizeHandler)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', resizeHandler)
|
|
chart?.dispose()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.card {
|
|
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(30,64,175,0.85);
|
|
box-shadow:
|
|
0 18px 45px rgba(15,23,42,0.95),
|
|
0 0 0 1px rgba(15,23,42,1),
|
|
inset 0 0 0 1px rgba(56,189,248,0.05);
|
|
padding: 10px 10px 10px 10px;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card::before,
|
|
.card::after {
|
|
content: "";
|
|
position: absolute;
|
|
width: 13px;
|
|
height: 13px;
|
|
border-radius: 2px;
|
|
border: 1px solid rgba(56,189,248,0.75);
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.card::before {
|
|
top: -1px;
|
|
left: -1px;
|
|
border-right: none;
|
|
border-bottom: none;
|
|
}
|
|
|
|
.card::after {
|
|
right: -1px;
|
|
bottom: -1px;
|
|
border-left: none;
|
|
border-top: none;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
padding-bottom: 4px;
|
|
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
|
}
|
|
|
|
.card-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #e5f0ff;
|
|
}
|
|
|
|
.card-title-icon {
|
|
color: #22d3ee;
|
|
}
|
|
|
|
.card-body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.tag {
|
|
border-radius: 999px;
|
|
padding: 2px 6px;
|
|
font-size: 10px;
|
|
border: 1px solid rgba(148,163,184,0.4);
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.chart {
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 180px;
|
|
}
|
|
|
|
@media (max-width: 1600px) {
|
|
.chart { min-height: 160px; }
|
|
}
|
|
|
|
.ops-header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
justify-content: flex-end;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.tab-btn {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
border-radius: 999px;
|
|
border: 1px solid rgba(148, 163, 184, 0.55);
|
|
background: rgba(2, 6, 23, 0.28);
|
|
color: var(--muted);
|
|
padding: 3px 8px;
|
|
font-size: 10px;
|
|
line-height: 1;
|
|
transition: border-color 0.25s, box-shadow 0.25s, color 0.25s, background 0.25s;
|
|
}
|
|
|
|
.tab-btn:hover {
|
|
border-color: rgba(56, 189, 248, 0.9);
|
|
color: #e0f2fe;
|
|
}
|
|
|
|
.tab-btn.active {
|
|
border-color: rgba(34, 211, 238, 0.9);
|
|
color: #e0f2fe;
|
|
box-shadow: 0 0 14px rgba(34, 211, 238, 0.45);
|
|
background: radial-gradient(circle at 0 0, rgba(34, 211, 238, 0.32), rgba(2, 6, 23, 0.2) 60%);
|
|
}
|
|
</style>
|