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.

350 lines
7.9 KiB
Vue

<template>
<div class="dashboard">
<!-- Top Cards -->
<div class="card-row">
<StatCard title="订单" :items="[
{ label: '销售总量', value: 1000 },
{ label: '未出库量', value: 100, color: 'red' },
{ label: '已出库量', value: 100, color: 'green' }
]" />
<StatCard title="工单" :items="[
{ label: '工单总数', value: 10 },
{ label: '未投产', value: 10, color: 'red' },
{ label: '已投产', value: 10, color: 'blue' },
{ label: '已完工', value: 10, color: 'green' }
]" />
<StatCard title="任务" :items="[
{ label: '任务总数', value: 10 },
{ label: '未开工', value: 10, color: 'red' },
{ label: '执行中', value: 10, color: 'blue' },
{ label: '已完工', value: 10, color: 'green' }
]" />
<StatCard title="产量" :items="[
{ label: '产量总量', value: 10 },
{ label: '完成率', value: '10%' },
{ label: '良品率', value: '90%', color: 'green' }
]" />
</div>
<!-- Middle -->
<div class="middle-row">
<div class="panel">
<div class="head">
<div>实时报工</div>
</div>
<el-timeline style="max-width: 600px">
<el-timeline-item :timestamp="item.time" placement="top" v-for="item in timeline ">
<div class="content">
<div style="color: #9EBDFE;font-size: 0.8vw;margin-bottom: 0.4vw;">{{ item.user }} {{ item.action }}</div>
<div>工单号:{{ item.order }}</div>
<div>物料:{{ item.material }}</div>
<div>良品 {{ item.good }} / 不良 {{ item.bad }}</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
<div class="panel">
<div class="head">
<div>生产预警</div>
</div>
<div class="th">
<div class="td">工单计划截止日</div>
<div class="td" style="text-align: right">计划数</div>
</div>
<div v-for="(d,i) in warning" :key="d.day" class="progress-item">
<div class="td" style="width: 50px">{{ d.day }}</div>
<div class="td" style="width: calc(100% - 130px); ">
<el-progress
:text-inside="true"
:stroke-width="6"
:percentage="(d.value / d.max)*100"
:status="i > 0 ? 'exception':''"
>
<div></div>
</el-progress>
<div :style="{left:((d.value / d.max)*100)+'%'}" class="progressNum" :class="i > 0 ? 'exception':''">
{{ d.value }}
</div>
</div>
<div class="td" style="width: 80px;padding-right: 12px;text-align: right">{{ d.max }}</div>
</div>
</div>
<div style="margin-right: 0" class="panel">
<div class="head">
<div>物料库存</div>
</div>
<div class="stock-summary">
<div class="stockCard">
<div class="num">100</div>
<div class="text">物料数</div>
</div>
<div class="stockCard">
<div class="num">1000.1234</div>
<div class="text">库存总量</div>
</div>
<div class="stockCard">
<div class="num">20</div>
<div class="text">超库存上限</div>
</div>
<div class="stockCard">
<div class="num">10</div>
<div class="text">超库存下限</div>
</div>
</div>
<div ref="pieRef" class="chart"></div>
</div>
</div>
<!-- Bottom Chart -->
<div style="margin-right: 0" class="panel">
<div class="head">
<div>物料库存趋势</div>
</div>
<div ref="lineRef" class="chart"></div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import * as echarts from 'echarts';
import StatCard from '@/components/StatCard.vue';
const timeline = [
{ time: '10:00', user: '张三', action: '印刷', order: 'GD2033322222', material: '电子标签001', good: 100, bad: 10 },
{ time: '09:00', user: '张三', action: '印刷', order: 'GD2033322222', material: '电子标签001', good: 100, bad: 10 }
];
const warning = [
{ day: '1天内', value: 1000, max: 2000 },
{ day: '2天内', value: 1000, max: 3000 },
{ day: '3天内', value: 1000, max: 3000 },
{ day: '4天内', value: 1000, max: 3000 },
{ day: '5天内', value: 1000, max: 3000 }
];
const pieRef = ref(null);
const lineRef = ref(null);
onMounted(() => {
const pie = echarts.init(pieRef.value);
pie.setOption({
tooltip: {},
series: [{
type: 'pie',
radius: '70%',
label: {
formatter: '{b}\n{c}'
},
itemStyle: {
borderRadius: 5 // 扇区圆角半径
},
data: [
{
name: '原料',
value: 100,
itemStyle: {
color: '#4A63EA'
}
},
{
name: '成品',
value: 150,
itemStyle: {
color: '#86AAF4'
}
},
{
name: '半成品',
value: 208,
itemStyle: {
color: '#698CF1'
}
},
{
name: '原辅料',
value: 149,
itemStyle: {
color: '#CDDFF9'
}
}
]
}]
});
const line = echarts.init(lineRef.value);
line.setOption({
tooltip: {},
grid:{
top:40,
left:80,
right:20,
bottom:30,
},
legend: { data: ['入库', '出库'] },
xAxis: { type: 'category', data: ['2022-01-01', '2022-01-02', '2022-01-03', '2022-01-04', '2022-01-05'] },
yAxis: { type: 'value' },
series: [
{
name: '入库',
type: 'bar',
data: [100, 140, 230, 100, 130],
itemStyle: {
color: '#4A63EA'
}
},
{
name: '出库',
type: 'bar',
data: [150, 100, 200, 150, 100],
itemStyle: {
color: '#85ABF4'
}
},
{
name: '库存',
type: 'line',
data: [100, 140, 230, 100, 130],
itemStyle: {
color: '#4B64EA'
},
label: {
show: true,
color: '#3D59E9'
}
}
]
});
});
</script>
<style scoped>
.dashboard {
padding: 16px;
background: #f5f7fa;
}
.card-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.middle-row {
display: grid;
grid-template-columns: 1fr 1fr 2fr;
margin-top: 16px;
}
.panel {
background: #fff;
padding: 16px;
border-radius: 8px;
margin-right: 16px;
.head {
color: #5291FD;
font-weight: bold;
border-bottom: 1px solid #ddd;
margin-bottom: 0.5vw;
padding-bottom: 0.5vw;
}
}
.timeline {
list-style: none;
padding: 0;
}
.timeline li {
display: flex;
margin-bottom: 12px;
}
.time {
width: 60px;
color: #409eff;
}
.content {
background: #f0f4ff;
padding: 8px;
border-radius: 6px;
flex: 1;
}
.progress-item {
width: 100%;
margin-bottom: 1.6vw;
.td {
display: inline-block;
font-size: 14px;
color: #666;
position: relative;
}
.progressNum {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
width: 2vw;
height: 1vw;
color: #fff;
line-height: 1vw;
background-color: #419EFF;
border-radius: 1vw;
}
.exception {
background-color: #F56C6C;
}
}
.chart {
width: 100%;
height: 260px;
}
.stock-summary {
width: 100%;
height: 5vw;
.stockCard {
display: inline-block;
width: 25%;
height: 100%;
text-align: center;
.num {
height: 60%;
line-height: 3vw;
font-size: 1.4vw;
font-weight: bold;
}
.text {
height: 40%;
color: #666;
}
}
}
.th {
width: 100%;
margin-bottom: 1vw;
.td {
width: 50%;
display: inline-block;
padding-right: 12px;
font-size: 14px;
color: #666;
}
}
</style>