<template>
|
<div class="proccess-content">
|
<div class="process-left">
|
<div class="proccess-plan">
|
<div class="header-title">工序计划进度</div>
|
<div class="table-content">
|
<div class="table-head">
|
<div class="table-head_item">物料名称</div>
|
<div class="table-head_item">物料编码</div>
|
<div class="table-head_item">计划数量</div>
|
<div class="table-head_item">良品数</div>
|
<div class="table-head_item">不良品数</div>
|
<div class="table-head_item">未完工数量</div>
|
<div class="table-head_item">不良率</div>
|
</div>
|
<div @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" class="tablel_container">
|
<div ref="planContainer" class="scroll_container">
|
<div v-for="(item, index) in listData" :key="item.id" class="scroll_item"
|
:class="index % 2 == 0 ? 'scroll_item scroll_item_bg1' : 'scroll_item scroll_item_bg2'">
|
<div class="scroll_item_row">{{ item.materialName }}</div>
|
<div class="scroll_item_row">{{ item.materialCode }}</div>
|
<div class="scroll_item_row">{{ item.num }}</div>
|
<div class="scroll_item_row">{{ item.qualifiedNum }}</div>
|
<div class="scroll_item_row">{{ item.unqualifiedNum }}</div>
|
<div class="scroll_item_row">{{ item.undoneNum }}</div>
|
<div class="scroll_item_row">{{ (item.unqualifiedRate||0).toFixed(2) }}%</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="today-yield">
|
<div class="header-title">当日员工产量TOP10</div>
|
<div class="yield-content">
|
<div class="yield-content-left">
|
<div class="content_left_item1_content">
|
<div class="content_left_item1_content_row" v-for="(item, index) in top1" :key="index">
|
<div class="content_left_item1_content_row_name">
|
<div :class="index > 2 ? 'num bg1' : 'num bg2'">{{ index + 1 }}</div>
|
<span>{{ item.userName }}</span>
|
</div>
|
<div class="content_left_item1_content_row_line">
|
<el-progress :show-text="false" :percentage="(item.doneNum||0) / baseNum * 100">
|
</el-progress>
|
</div>
|
<div class="content_left_item1_content_row_num">{{ item.doneNum||0 }}</div>
|
</div>
|
</div>
|
</div>
|
<div class="yield-content-left">
|
<div class="content_left_item1_content">
|
<div class="content_left_item1_content_row" v-for="(item, index) in top2" :key="index">
|
<div class="content_left_item1_content_row_name">
|
<div class="num bg1">{{ index + 6 }}</div>
|
<span>{{ item.userName }}</span>
|
</div>
|
<div class="content_left_item1_content_row_line">
|
<el-progress :show-text="false" :percentage="item.doneNum / baseNum * 100">
|
</el-progress>
|
</div>
|
<div class="content_left_item1_content_row_num">{{ item.doneNum }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
</div>
|
</div>
|
<div class="process-right">
|
<div class="report-log">
|
<div class="header-title">报工日志</div>
|
<div class="table-content">
|
<div class="table-head">
|
<div class="table-head_item">员工名称</div>
|
<div class="table-head_item">物料名称</div>
|
<div class="table-head_item">物料编码</div>
|
<div class="table-head_item">报工时间</div>
|
<div class="table-head_item">良品数</div>
|
<div class="table-head_item">不良品数</div>
|
<div class="table-head_item">不良率</div>
|
</div>
|
<div @mouseenter="handleMouseEnterReport" @mouseleave="handleMouseLeaveReport" class="tablel_container">
|
<div ref="reportLogContainer" class="scroll_container">
|
<div v-for="(item, index) in reportListData" :key="item.id" class="scroll_item"
|
:class="index % 2 == 0 ? 'scroll_item scroll_item_bg1' : 'scroll_item scroll_item_bg2'">
|
<div class="scroll_item_row">{{ item.userName }}</div>
|
<div class="scroll_item_row">{{ item.materialName }}</div>
|
<div class="scroll_item_row">{{ item.materialCode }}</div>
|
<div class="scroll_item_row">{{ dateToSub(item.createTime) }}</div>
|
<div class="scroll_item_row">{{ item.qualifiedNum }}</div>
|
<div class="scroll_item_row">{{ item.unqualifiedNum }}</div>
|
<div class="scroll_item_row">{{ (item.unqualifiedRate||0).toFixed(2) }}%</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="bad-diagram">
|
<div class="header-title">近7天不良品分布</div>
|
<div class="bad-content">
|
<div id="day-distribution"></div>
|
<div class="pie">
|
<div class="content_right_top_nr_bottom_yuan">
|
<span>{{ allBad }}</span>
|
<span>不良项分布</span>
|
</div>
|
<div id="type-distribution"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { reactive, ref, toRefs, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
import { getProcedurePlanData, getTop, getProcedurePlansList, getRecordLogPage, getUnqualified7DayData, getUnqualifiedCateData } from '@/utils/api.js'
|
import { useCounterStore } from '@/stores/counter.js'
|
import { useRoute } from 'vue-router'
|
import { dateToSub } from '@/utils'
|
import * as echarts from 'echarts'
|
const route = useRoute()
|
const enterprise = useCounterStore()
|
let mainTimer = ref(null)
|
let planTimer = ref(null)
|
let reportTimer = ref(null)
|
let planContainer = ref(null)
|
let reportLogContainer = ref(null)
|
const data = reactive({
|
listData: [],
|
reportListData: [],
|
top1: [],
|
top2: [],
|
baseNum: 0,
|
allBad: 0,
|
dayDistribution: [],
|
typeDistribution: [
|
{ categoryName: '外观不良', unqualifiedNum: 15 },
|
{ categoryName: '有毛刺', unqualifiedNum: 12 },
|
{ categoryName: '尺寸不良', unqualifiedNum: 22 },
|
{ categoryName: '有划痕', unqualifiedNum: 5 },
|
{ categoryName: '其他', unqualifiedNum: 2 },
|
]
|
})
|
|
let { listData, top1, top2, baseNum, allBad, reportListData } = toRefs(data)
|
|
// start()
|
onBeforeUnmount(() => {
|
clearTimeout(planTimer.value)
|
clearTimeout(reportTimer.value)
|
clearTimeout(mainTimer.value)
|
})
|
onMounted(() => {
|
initData()
|
mainTimer.value = setInterval(() => {
|
initData()
|
}, 60000)
|
})
|
|
|
onUnmounted(() => {
|
clearTimeout(planTimer.value)
|
clearTimeout(reportTimer.value)
|
clearTimeout(mainTimer.value)
|
})
|
|
function initData() {
|
// 计划数
|
getProcedurePlanData(enterprise.companyId, enterprise.departId, { procedureId: route.query.procedureId })
|
.then(res => {
|
enterprise.setNum(res)
|
})
|
// top10
|
getTop(enterprise.companyId, enterprise.departId, { procedureId: route.query.procedureId })
|
.then(res => {
|
if (res.length) {
|
|
data.baseNum = res[0].doneNum
|
data.top1 = []
|
data.top2 = []
|
if (res.length <= 5) {
|
data.top1 = res
|
} else {
|
res.forEach((item, index) => {
|
if (index < 5) {
|
data.top1.push(item)
|
} else {
|
data.top2.push(item)
|
}
|
})
|
}
|
}
|
})
|
// 工序计划进度查询
|
getProcedurePlansList(enterprise.companyId, enterprise.departId, { procedureId: route.query.procedureId })
|
.then(res => {
|
data.listData = res
|
start()
|
})
|
// 报工日志分页查询
|
getRecordLogPage(enterprise.companyId, enterprise.departId, { capacity: 9999, page: 1, model: { procedureId: route.query.procedureId } })
|
.then(res => {
|
data.reportListData = res.records
|
reportStart()
|
})
|
getUnqualified7DayData(enterprise.companyId, enterprise.departId, route.query.procedureId)
|
.then(res => {
|
data.dayDistribution = res
|
setDayChart()
|
})
|
getUnqualifiedCateData(enterprise.companyId, enterprise.departId, route.query.procedureId)
|
.then(res => {
|
data.typeDistribution = res
|
data.allBad = res.reduce((accumulator, currentValue) => accumulator + currentValue.unqualifiedNum, 0)
|
setTypeChart()
|
})
|
}
|
|
function handleMouseEnter() {
|
clearTimeout(planTimer.value)
|
}
|
function handleMouseLeave() {
|
start()
|
}
|
|
function handleMouseEnterReport() {
|
clearTimeout(reportTimer.value)
|
}
|
function handleMouseLeaveReport() {
|
reportStart()
|
}
|
|
/**
|
* 工序计划
|
*/
|
// 开启定时器
|
function start() {
|
clearTimeout(planTimer.value)
|
// 定时器触发周期
|
// let speed = ref(100)
|
planTimer.value = setInterval(ListScroll, 100)
|
}
|
function ListScroll() {
|
let scrollDom = planContainer.value
|
if (!scrollDom) return
|
// 判读组件是否渲染完成
|
if (scrollDom.offsetHeight == 0) {
|
scrollDom = planContainer.value
|
} else {
|
// 如果列表数量过少不进行滚动
|
if (scrollDom.children.length < 10) {
|
clearTimeout(planTimer.value)
|
return
|
}
|
// 组件进行滚动
|
scrollDom.scrollTop += 1.5
|
// 判断是否滚动到底部
|
if (scrollDom.scrollTop >= (scrollDom.scrollHeight - scrollDom.clientHeight)) {
|
// 获取组件第一个节点
|
let first = scrollDom.children[0]
|
// 删除节点
|
scrollDom.removeChild(first)
|
// 将该节点拼接到组件最后
|
scrollDom.append(first)
|
}
|
}
|
}
|
/**
|
* 报工日志
|
*/
|
function reportStart() {
|
clearTimeout(reportTimer.value)
|
// 定时器触发周期
|
// let speed = ref(100)
|
reportTimer.value = setInterval(reportScroll, 100)
|
}
|
function reportScroll() {
|
let scrollDom = reportLogContainer.value
|
if (!scrollDom) return
|
// 判读组件是否渲染完成
|
if (scrollDom.offsetHeight == 0) {
|
scrollDom = reportLogContainer.value
|
} else {
|
// 如果列表数量过少不进行滚动
|
if (scrollDom.children.length < 10) {
|
clearTimeout(reportTimer.value)
|
return
|
}
|
// 组件进行滚动
|
scrollDom.scrollTop += 1.5
|
// 判断是否滚动到底部
|
if (scrollDom.scrollTop >= (scrollDom.scrollHeight - scrollDom.clientHeight)) {
|
// 获取组件第一个节点
|
let first = scrollDom.children[0]
|
// 删除节点
|
scrollDom.removeChild(first)
|
// 将该节点拼接到组件最后
|
scrollDom.append(first)
|
}
|
}
|
}
|
/**
|
* 七日折线图
|
*/
|
function setDayChart() {
|
let dayChartDom = document.getElementById('day-distribution');
|
let myChart = echarts.init(dayChartDom);
|
let dateList = []
|
let numList = []
|
|
data.dayDistribution.forEach(item => {
|
dateList.push(item.createTime)
|
numList.push(item.unqualifiedNum || 0)
|
})
|
let option;
|
option = {
|
title: {
|
text: ` 近七日不良品总数:${data.allBad}`,
|
textStyle: {
|
color: '#fff',
|
fontSize: 12
|
}
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
splitLine: {
|
lineStyle: {
|
width: 0.5,
|
color: ['#fff']
|
}
|
},
|
axisLabel: {
|
textStyle: {
|
color: '#fff'
|
}
|
},
|
data: dateList
|
},
|
|
yAxis: {
|
type: 'value',
|
splitLine: {
|
lineStyle: {
|
width: 0.5,
|
color: ['#fff']
|
}
|
},
|
axisLabel: {
|
textStyle: {
|
color: '#fff'
|
}
|
}
|
},
|
series: [
|
{
|
data: numList,
|
type: 'line',
|
lineStyle: {
|
color: '#03D2B5',
|
width: 1
|
},
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{
|
offset: 0,
|
color: 'rgba(3, 210, 181, .9)',
|
},
|
{
|
offset: 1,
|
color: 'rgba(3, 210, 181, 0)',
|
}
|
])
|
}
|
}
|
],
|
|
tooltip: {
|
trigger: 'axis'
|
},
|
legend: {
|
name: '1adssad',
|
itemWidth: 10,
|
itemHeight: 10,
|
itemGap: 4,
|
textStyle: {
|
color: '#fff'
|
}
|
}
|
}
|
|
option && myChart.setOption(option);
|
}
|
/**
|
* 不良类型分布
|
*/
|
function setTypeChart() {
|
let dayChartDom = document.getElementById('type-distribution');
|
let myChart = echarts.init(dayChartDom);
|
let legendData = []
|
let seriesData = []
|
data.typeDistribution.forEach(item => {
|
legendData.push(item.categoryName)
|
seriesData.push({ name: item.categoryName, value: item.unqualifiedNum })
|
})
|
let option = {
|
legend: {
|
show: false,
|
type: 'scroll',
|
orient: 'vertical',
|
right: 10,
|
top: 20,
|
bottom: 20,
|
data: legendData
|
},
|
series: [
|
{
|
// name: '姓名',
|
type: 'pie',
|
|
radius: ['60%', '70%'],
|
// avoidLabelOverlap: false,
|
itemStyle: {
|
borderRadius: 4,
|
borderColor: 'rgba(52, 88, 159, 0.4)',
|
borderWidth: 4
|
},
|
center: ['50%', '50%'],
|
labelLine: {
|
show: false
|
},
|
label: {
|
formatter: '{dot| } {title|{b} {c}}\n{per|{d}%}',
|
rich: {
|
title: {
|
color: '#fff',
|
lineHeight: 13,
|
fontSize: 10
|
},
|
per: {
|
color: '#01D9FE',
|
fontSize: 10
|
},
|
dot: {
|
backgroundColor: 'inherit',
|
width: 8,
|
height: 8,
|
borderRadius: 4
|
}
|
}
|
},
|
// labelLayout: {
|
// hideOverlap: true
|
// },
|
endLabel: {
|
show: true,
|
distance: 5,
|
color: "red"
|
},
|
data: seriesData,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 1)'
|
}
|
}
|
}
|
]
|
}
|
option && myChart.setOption(option);
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.proccess-content {
|
display: flex;
|
flex: 1;
|
.header-title {
|
background-image: url('@/assets/img/gongxu_title@2x.png');
|
height: 28px;
|
width: 100%;
|
background-size: 100% 28px;
|
background-repeat: no-repeat;
|
padding-left: 34px;
|
font-size: 16px;
|
font-weight: bold;
|
color: #FFFFFF;
|
line-height: 26px;
|
text-shadow: 0px 0px 10px rgba(0, 24, 72, 0.75);
|
}
|
|
.process-left {
|
flex: 1;
|
margin-right: 20px;
|
.proccess-plan {
|
height: 424px;
|
background: linear-gradient(180deg, rgba(52, 88, 159, 0) 0%, rgba(0, 86, 255, 0.4) 100%);
|
margin-bottom: 20px;
|
}
|
|
.today-yield {
|
height: 222px;
|
background: linear-gradient(180deg, rgba(52, 88, 159, 0) 0%, rgba(0, 86, 255, 0.4) 100%);
|
|
.yield-content {
|
display: flex;
|
|
.yield-content-left {
|
flex: 1;
|
|
.content_left_item1_content {
|
width: 100%;
|
padding: 20px;
|
box-sizing: border-box;
|
|
.content_left_item1_content_row {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 13px;
|
|
&:last-child {
|
margin: 0;
|
}
|
|
.content_left_item1_content_row_name {
|
flex-shrink: 0;
|
display: flex;
|
align-items: center;
|
|
span {
|
font-size: 13px;
|
font-family: SourceHanSansSC-Regular, SourceHanSansSC;
|
font-weight: 400;
|
color: #D2E0FF;
|
margin-left: 9px;
|
width: 50px;
|
white-space: nowrap;
|
text-overflow: ellipsis;
|
-webkit-text-overflow: ellipsis;
|
overflow: hidden;
|
}
|
|
.num {
|
width: 20px;
|
height: 20px;
|
line-height: 20px;
|
text-align: center;
|
font-size: 12px;
|
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
|
font-weight: 500;
|
color: #FFFFFF;
|
}
|
|
.bg1 {
|
background: url('@/assets/img/rank_blue@2x.png');
|
background-repeat: no-repeat;
|
background-size: 100% 100%;
|
}
|
|
.bg2 {
|
background: url('@/assets/img/rank_yellow@2x.png');
|
background-repeat: no-repeat;
|
background-size: 100% 100%;
|
}
|
}
|
|
.content_left_item1_content_row_line {
|
flex: 1;
|
margin: 0 15px;
|
|
&::v-deep(.el-progress-bar__outer) {
|
border-radius: 0%;
|
background: rgba(255, 255, 255, 0.13);
|
}
|
|
&::v-deep(.el-progress-bar__inner) {
|
border-radius: 0%;
|
background: linear-gradient(270deg, #00B0FF 0%, #345BA3 100%);
|
}
|
}
|
|
.content_left_item1_content_row_num {
|
font-size: 13px;
|
font-family: SourceHanSansSC-Regular, SourceHanSansSC;
|
font-weight: 400;
|
color: #D2E0FF;
|
}
|
}
|
}
|
}
|
}
|
|
}
|
}
|
|
.process-right {
|
flex: 1;
|
|
.report-log {
|
height: 424px;
|
background: linear-gradient(180deg, rgba(52, 88, 159, 0) 0%, rgba(0, 86, 255, 0.4) 100%);
|
margin-bottom: 20px;
|
}
|
|
.bad-diagram {
|
height: 222px;
|
background: linear-gradient(180deg, rgba(52, 88, 159, 0) 0%, rgba(0, 86, 255, 0.4) 100%);
|
|
.bad-content {
|
display: flex;
|
margin-top: 20px;
|
|
#day-distribution {
|
// padding: 20px;
|
box-sizing: border-box;
|
flex: 0.5;
|
height: 190px;
|
}
|
|
.pie {
|
flex: 0.4;
|
position: relative;
|
#type-distribution {
|
width: 100%;
|
height: 100%;
|
}
|
.content_right_top_nr_bottom_yuan {
|
position: absolute;
|
top: calc(50% - 50px);
|
left: calc(50% - 50px);;
|
z-index: 999;
|
width: 100px;
|
height: 100px;
|
border-radius: 50%;
|
border: 2px dashed #01D9FE;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
|
span {
|
&:first-child {
|
font-size: 26px;
|
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
|
font-weight: 500;
|
color: #FFFFFF;
|
}
|
|
&:last-child {
|
font-size: 13px;
|
font-family: SourceHanSansSC-Regular, SourceHanSansSC;
|
font-weight: 400;
|
color: rgba(255, 255, 255, 0.8);
|
}
|
}
|
}
|
|
|
}
|
|
}
|
}
|
}
|
}
|
|
|
.table-content {
|
width: 100%;
|
height: 397px;
|
// height: 361px;
|
// height: ;
|
padding: 20px;
|
box-sizing: border-box;
|
|
.table-head {
|
width: 100%;
|
height: 36px;
|
display: flex;
|
align-items: center;
|
background: rgba(52, 88, 159, 0.5);
|
|
.table-head_item {
|
flex: 1;
|
height: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 13px;
|
font-weight: 500;
|
color: #01D9FE;
|
|
}
|
}
|
|
.tablel_container {
|
width: 100%;
|
// height: calc(100% - 36px);
|
height: 361px;
|
|
.scroll_container {
|
width: 100%;
|
height: 100%;
|
overflow: hidden;
|
color: white;
|
|
.scroll_item_bg1 {
|
background: rgba(52, 88, 159, 0.2);
|
}
|
|
.scroll_item_bg2 {
|
background: rgba(52, 88, 159, 0.5);
|
}
|
|
.scroll_item {
|
width: 100%;
|
height: 36px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
|
.scroll_item_row {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 13px;
|
}
|
}
|
}
|
}
|
}</style>
|