change(mes): 优化在制品跟踪报表功能并增强工序进度可视化

- 调整查询表单结构,注释部分字段
- 表格列新增“当前时间”、“总工序数”、“在制工序”、“剩余工序”等字段
- 优化工序进度展示方式,支持进行中、已完成、未开始三种状态的样式区分与动画效果
- 增强工序节点提示信息,显示工序名称
- 为图表标题统一设置字体大小,并优化导出按钮显示逻辑
- 扩展 WipProcessVO 类型定义,增加工序状态、时间、数量等相关字段```
master
zangch@mesnac.com 3 months ago
parent e4ffc8c590
commit a0c49b7175

@ -28,6 +28,51 @@ export interface ProcessProgressVO {
* *
*/ */
isCompleted?: boolean; isCompleted?: boolean;
/**
*
*/
isInProgress?: boolean;
/**
*
*/
planAmount?: number;
/**
*
*/
completeAmount?: number;
/**
*
*/
remainingAmount?: number;
/**
*
*/
processProgress?: number;
/**
*
*/
planBeginTime?: string;
/**
*
*/
planEndTime?: string;
/**
*
*/
realBeginTime?: string;
/**
*
*/
realEndTime?: string;
} }
export interface WipTrackingReportVO { export interface WipTrackingReportVO {

@ -4,7 +4,7 @@
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true">
<!-- <el-form-item label="日期" style="width: 300px"> <!-- <el-form-item label="日期范围" style="width: 300px">
<el-date-picker <el-date-picker
v-model="dateRange" v-model="dateRange"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
@ -22,8 +22,9 @@
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>-->
<!--
<el-form-item label="物料编号" prop="materialCode"> <el-form-item label="物料编号" prop="materialCode">
<el-input <el-input
v-model="queryParams.materialCode" v-model="queryParams.materialCode"
@ -31,7 +32,8 @@
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item>--> </el-form-item>
-->
<el-form-item label="物料名称" prop="materialName"> <el-form-item label="物料名称" prop="materialName">
<el-input <el-input
@ -41,8 +43,8 @@
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<!--
<!-- <el-form-item label="进度状态" prop="progressStatus"> <el-form-item label="进度状态" prop="progressStatus">
<el-select v-model="queryParams.progressStatus" placeholder="请选择进度状态" clearable @keyup.enter="handleQuery"> <el-select v-model="queryParams.progressStatus" placeholder="请选择进度状态" clearable @keyup.enter="handleQuery">
<el-option label="正常" value="正常" /> <el-option label="正常" value="正常" />
<el-option label="延期" value="延期" /> <el-option label="延期" value="延期" />
@ -59,6 +61,32 @@
</transition> </transition>
<!-- 统计图表卡片 --> <!-- 统计图表卡片 -->
<!-- <el-row :gutter="10" class="mb-[10px]">
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">进度状态分布</span>
</template>
<div ref="statusChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">整体进度分布</span>
</template>
<div ref="progressChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">工序完成率统计</span>
</template>
<div ref="processStatsChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
</el-row>-->
<el-row :gutter="10" class="mb-[10px]"> <el-row :gutter="10" class="mb-[10px]">
<el-col :span="12"> <el-col :span="12">
<el-card shadow="never"> <el-card shadow="never">
@ -78,6 +106,7 @@
</el-col> </el-col>
</el-row> </el-row>
<!-- 工序进度可视化 --> <!-- 工序进度可视化 -->
<el-card shadow="never" class="mb-[10px]"> <el-card shadow="never" class="mb-[10px]">
<template #header> <template #header>
@ -89,9 +118,9 @@
<el-card shadow="never"> <el-card shadow="never">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<!-- <el-col :span="1.5">--> <!-- <el-col :span="1.5">
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:prodReport:export']"></el-button>--> <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:prodReport:export']"></el-button>
<!-- </el-col>--> </el-col>-->
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" /> <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row> </el-row>
</template> </template>
@ -107,7 +136,11 @@
<el-table-column label="计划开工时间" align="center" prop="planBeginTime" v-if="columns[7].visible" width="150" /> <el-table-column label="计划开工时间" align="center" prop="planBeginTime" v-if="columns[7].visible" width="150" />
<el-table-column label="实际开工时间" align="center" prop="realBeginTime" v-if="columns[8].visible" width="150" /> <el-table-column label="实际开工时间" align="center" prop="realBeginTime" v-if="columns[8].visible" width="150" />
<el-table-column label="计划完工时间" align="center" prop="planEndTime" v-if="columns[9].visible" width="150" /> <el-table-column label="计划完工时间" align="center" prop="planEndTime" v-if="columns[9].visible" width="150" />
<el-table-column label="整体进度" align="center" prop="overallProgress" v-if="columns[10].visible" width="100"> <el-table-column label="当前时间" align="center" prop="currentTime" v-if="columns[10].visible" width="150" />
<el-table-column label="总工序数" align="center" prop="totalProcessCount" v-if="columns[11].visible" width="100" />
<el-table-column label="在制工序" align="center" prop="wipProcesses" v-if="columns[12].visible" width="150" show-overflow-tooltip />
<el-table-column label="剩余工序" align="center" prop="remainingProcesses" v-if="columns[13].visible" width="150" show-overflow-tooltip />
<el-table-column label="整体进度" align="center" prop="overallProgress" v-if="columns[14].visible" width="120">
<template #default="scope"> <template #default="scope">
<el-progress <el-progress
:percentage="parseFloat(scope.row.overallProgress)" :percentage="parseFloat(scope.row.overallProgress)"
@ -116,23 +149,42 @@
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="进度状态" align="center" prop="progressStatus" v-if="columns[11].visible" width="100"> <el-table-column label="进度状态" align="center" prop="progressStatus" v-if="columns[15].visible" width="100">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.progressStatus === '正常' ? 'success' : 'danger'"> <el-tag :type="scope.row.progressStatus === '正常' ? 'success' : 'danger'">
{{ scope.row.progressStatus }} {{ scope.row.progressStatus }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="工序进度" align="center" v-if="columns[12].visible" width="300"> <el-table-column label="工序进度" align="center" v-if="columns[16].visible" width="350">
<template #default="scope"> <template #default="scope">
<div class="process-progress-container"> <div class="process-progress-container">
<!-- <div
v-for="process in scope.row.processProgressList"
:key="process.processId"
class="process-step"
:class="{
'completed': process.isCompleted === 1,
'in-progress': process.isInProgress === 1,
'pending': process.isCompleted !== 1 && process.isInProgress !== 1
}"
:title="`${process.processName} - ${process.statusDesc}${process.processProgress ? ' (' + process.processProgress + '%)' : ''}`"
>-->
<div <div
v-for="process in scope.row.processProgressList" v-for="process in scope.row.processProgressList"
:key="process.processId" :key="process.processId"
class="process-step" class="process-step"
:class="{ 'completed': process.isCompleted, 'pending': !process.isCompleted }" :class="{
'completed': process.isCompleted === 1,
'in-progress': process.isInProgress === 1,
'pending': process.isCompleted !== 1 && process.isInProgress !== 1
}"
:title="`${process.processName}`"
> >
{{ process.processName }} <span class="process-name">{{ process.processName }}</span>
<!-- <span v-if="process.processProgress && process.processProgress > 0" class="process-percentage">
{{ process.processProgress }}%
</span>-->
</div> </div>
</div> </div>
</template> </template>
@ -165,9 +217,11 @@ const dateRange = ref<string[]>(['', '']);
const statusChartRef = ref<HTMLDivElement | null>(null); const statusChartRef = ref<HTMLDivElement | null>(null);
const progressChartRef = ref<HTMLDivElement | null>(null); const progressChartRef = ref<HTMLDivElement | null>(null);
const processChartRef = ref<HTMLDivElement | null>(null); const processChartRef = ref<HTMLDivElement | null>(null);
const processStatsChartRef = ref<HTMLDivElement | null>(null);
let statusChart: echarts.ECharts | null = null; let statusChart: echarts.ECharts | null = null;
let progressChart: echarts.ECharts | null = null; let progressChart: echarts.ECharts | null = null;
let processChart: echarts.ECharts | null = null; let processChart: echarts.ECharts | null = null;
let processStatsChart: echarts.ECharts | null = null;
// //
const columns = ref([ const columns = ref([
@ -181,9 +235,13 @@ const columns = ref([
{ key: 7, label: '计划开工时间', visible: true }, { key: 7, label: '计划开工时间', visible: true },
{ key: 8, label: '实际开工时间', visible: true }, { key: 8, label: '实际开工时间', visible: true },
{ key: 9, label: '计划完工时间', visible: true }, { key: 9, label: '计划完工时间', visible: true },
{ key: 10, label: '整体进度', visible: true }, { key: 10, label: '当前时间', visible: false },
{ key: 11, label: '进度状态', visible: true }, { key: 11, label: '总工序数', visible: true },
{ key: 12, label: '工序进度', visible: true } { key: 12, label: '在制工序', visible: true },
{ key: 13, label: '剩余工序', visible: true },
{ key: 14, label: '整体进度', visible: true },
{ key: 15, label: '进度状态', visible: true },
{ key: 16, label: '工序进度', visible: true }
]); ]);
const queryParams = ref<WipTrackingReportQuery>({ const queryParams = ref<WipTrackingReportQuery>({
@ -277,6 +335,9 @@ function initCharts() {
if (processChartRef.value) { if (processChartRef.value) {
processChart = echarts.init(processChartRef.value); processChart = echarts.init(processChartRef.value);
} }
if (processStatsChartRef.value) {
processStatsChart = echarts.init(processStatsChartRef.value);
}
} }
/** 更新图表数据 */ /** 更新图表数据 */
@ -284,6 +345,7 @@ function updateCharts() {
updateStatusChart(); updateStatusChart();
updateProgressChart(); updateProgressChart();
updateProcessChart(); updateProcessChart();
updateProcessStatsChart();
} }
/** 更新进度状态分布图 */ /** 更新进度状态分布图 */
@ -299,7 +361,8 @@ function updateStatusChart() {
const option = { const option = {
title: { title: {
text: '进度状态分布', text: '进度状态分布',
left: 'center' left: 'center',
textStyle: { fontSize: 14 }
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
@ -347,7 +410,8 @@ function updateProgressChart() {
const option = { const option = {
title: { title: {
text: '整体进度分布', text: '整体进度分布',
left: 'center' left: 'center',
textStyle: { fontSize: 14 }
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -381,6 +445,59 @@ function updateProgressChart() {
progressChart.setOption(option); progressChart.setOption(option);
} }
/** 更新工序完成率统计图 */
function updateProcessStatsChart() {
if (!processStatsChart || !reportList.value.length) return;
const processStats = {
'已完成': 0,
'进行中': 0,
'未开始': 0
};
reportList.value.forEach(item => {
if (item.processProgressList) {
item.processProgressList.forEach(process => {
if (process.isCompleted) {
processStats['已完成']++;
} else if (process.isInProgress) {
processStats['进行中']++;
} else {
processStats['未开始']++;
}
});
}
});
const option = {
title: {
text: '工序完成率统计',
left: 'center',
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '工序状态',
type: 'pie',
radius: ['40%', '70%'],
data: Object.entries(processStats).map(([name, value]) => ({ name, value })),
itemStyle: {
color: function(params: any) {
const colors = ['#722ed1', '#52c41a', '#d9d9d9'];
return colors[params.dataIndex];
}
}
}
]
};
processStatsChart.setOption(option);
}
/** 更新工序进度可视化图 */ /** 更新工序进度可视化图 */
function updateProcessChart() { function updateProcessChart() {
if (!processChart || !reportList.value.length) return; if (!processChart || !reportList.value.length) return;
@ -399,17 +516,13 @@ function updateProcessChart() {
const option = { const option = {
title: { title: {
text: '工序进度可视化前10个订单', text: '工序进度可视化前10个订单',
left: 'center' left: 'center',
textStyle: { fontSize: 14 }
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
formatter: function(params: any) { formatter: function(params: any) {
const data = params[0].data.value; const data = params[0].data.value;
// return `: ${data[0]}<br/>
// : ${data[1]}%<br/>
// : ${data[2]}<br/>
// : ${data[3]}<br/>
// : ${data[4]}`;
return ` return `
进度: ${data[1]}%<br/> 进度: ${data[1]}%<br/>
状态: ${data[2]}<br/> 状态: ${data[2]}<br/>
@ -456,6 +569,7 @@ function handleResize() {
statusChart?.resize(); statusChart?.resize();
progressChart?.resize(); progressChart?.resize();
processChart?.resize(); processChart?.resize();
processStatsChart?.resize();
} }
onMounted(() => { onMounted(() => {
@ -471,6 +585,7 @@ onBeforeUnmount(() => {
statusChart?.dispose(); statusChart?.dispose();
progressChart?.dispose(); progressChart?.dispose();
processChart?.dispose(); processChart?.dispose();
processStatsChart?.dispose();
}); });
</script> </script>
@ -493,19 +608,34 @@ onBeforeUnmount(() => {
} }
.process-step { .process-step {
padding: 4px 8px; padding: 6px 10px;
border-radius: 4px; border-radius: 6px;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
border: 1px solid; border: 1px solid;
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
min-width: 60px;
position: relative;
} }
.process-step.completed { .process-step.completed {
background-color: #722ed1; background-color: #722ed1;
color: white; color: white;
border-color: #722ed1; border-color: #722ed1;
box-shadow: 0 2px 8px rgba(114, 46, 209, 0.3);
}
.process-step.in-progress {
background-color: #52c41a;
color: white;
border-color: #52c41a;
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
animation: pulse 2s infinite;
} }
.process-step.pending { .process-step.pending {
@ -516,6 +646,30 @@ onBeforeUnmount(() => {
.process-step:hover { .process-step:hover {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.process-name {
font-weight: 600;
margin-bottom: 2px;
}
.process-percentage {
font-size: 10px;
opacity: 0.9;
font-weight: 400;
}
/* 进行中工序的脉动动画 */
@keyframes pulse {
0% {
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
50% {
box-shadow: 0 2px 12px rgba(82, 196, 26, 0.6);
}
100% {
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
} }
</style> </style>

Loading…
Cancel
Save