|
|
|
|
@ -15,10 +15,10 @@
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="生产订单号" prop="orderCode">
|
|
|
|
|
<el-form-item label="计划单号" prop="planCode">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="queryParams.orderCode"
|
|
|
|
|
placeholder="请输入生产订单号"
|
|
|
|
|
v-model="queryParams.planCode"
|
|
|
|
|
placeholder="请输入计划单号"
|
|
|
|
|
clearable
|
|
|
|
|
@keyup.enter="handleQuery"
|
|
|
|
|
/>
|
|
|
|
|
@ -35,14 +35,14 @@
|
|
|
|
|
</el-form-item>
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
<el-form-item label="物料名称" prop="materialName">
|
|
|
|
|
<!-- <el-form-item label="物料名称" prop="materialName">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="queryParams.materialName"
|
|
|
|
|
placeholder="请输入物料名称"
|
|
|
|
|
clearable
|
|
|
|
|
@keyup.enter="handleQuery"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form-item>-->
|
|
|
|
|
<!--
|
|
|
|
|
<el-form-item label="进度状态" prop="progressStatus">
|
|
|
|
|
<el-select v-model="queryParams.progressStatus" placeholder="请选择进度状态" clearable @keyup.enter="handleQuery">
|
|
|
|
|
@ -87,7 +87,7 @@
|
|
|
|
|
</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-card shadow="never">
|
|
|
|
|
<template #header>
|
|
|
|
|
@ -104,16 +104,28 @@
|
|
|
|
|
<div ref="progressChartRef" style="width: 100%; height: 300px"></div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>-->
|
|
|
|
|
|
|
|
|
|
<!-- 工序工单统计(按工序聚合 Top10) -->
|
|
|
|
|
<el-row :gutter="10" class="mb-[10px]">
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
<template #header>
|
|
|
|
|
<span class="font-bold">工序统计</span>
|
|
|
|
|
</template>
|
|
|
|
|
<div ref="processStatsChartRef" style="width: 100%; height: 320px"></div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 工序进度可视化 -->
|
|
|
|
|
<el-card shadow="never" class="mb-[10px]">
|
|
|
|
|
<!-- <el-card shadow="never" class="mb-[10px]">
|
|
|
|
|
<template #header>
|
|
|
|
|
<span class="font-bold">工序进度可视化</span>
|
|
|
|
|
</template>
|
|
|
|
|
<div ref="processChartRef" style="width: 100%; height: 400px"></div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-card>-->
|
|
|
|
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
<template #header>
|
|
|
|
|
@ -125,54 +137,119 @@
|
|
|
|
|
</el-row>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<el-table v-loading="loading" :data="reportList" border row-key="orderCode">
|
|
|
|
|
<!-- 展开列:树形展示工序子节点(数量与进度) -->
|
|
|
|
|
<el-table-column type="expand" width="50">
|
|
|
|
|
<el-table v-loading="loading" :data="processStatsList" border row-key="processName">
|
|
|
|
|
<!-- 工序聚合统计列 -->
|
|
|
|
|
<el-table-column label="工序名称" align="center" prop="processName" v-if="columns[0].visible" min-width="160" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="计划数量" align="center" prop="planQty" v-if="columns[1].visible" width="120" />
|
|
|
|
|
<el-table-column label="已完成数量" align="center" prop="completedQty" v-if="columns[2].visible" width="120" />
|
|
|
|
|
<el-table-column label="未完成数量" align="center" prop="uncompletedQty" v-if="columns[3].visible" width="120" />
|
|
|
|
|
<el-table-column label="完成率" align="center" v-if="columns[4].visible" width="160">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-tree
|
|
|
|
|
:data="buildProcessTree(scope.row)"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
:expand-on-click-node="false"
|
|
|
|
|
:indent="18"
|
|
|
|
|
class="process-tree"
|
|
|
|
|
>
|
|
|
|
|
<template #default="{ node, data }">
|
|
|
|
|
<div class="tree-node" :class="'status-' + (data.status || 'pending')">
|
|
|
|
|
<el-tooltip
|
|
|
|
|
effect="light"
|
|
|
|
|
placement="top"
|
|
|
|
|
:content="`计划:${data.planAmount ?? 0},完成:${data.completeAmount ?? 0},剩余:${data.remainingAmount ?? 0}`"
|
|
|
|
|
>
|
|
|
|
|
<span class="node-title">{{ data.label }}</span>
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
<el-progress
|
|
|
|
|
v-if="data.progress != null"
|
|
|
|
|
:percentage="Number(data.progress)"
|
|
|
|
|
:color="getProgressColor(Number(data.progress))"
|
|
|
|
|
:stroke-width="4"
|
|
|
|
|
:show-text="false"
|
|
|
|
|
class="node-progress"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-tree>
|
|
|
|
|
<div style="display:flex;align-items:center;justify-content:center;">
|
|
|
|
|
<span style="min-width:42px;text-align:right;font-size:12px;color:#606266;">{{ Number(scope.row.completionRateNum || 0) }}%</span>
|
|
|
|
|
<el-progress
|
|
|
|
|
:percentage="Number(scope.row.completionRateNum || 0)"
|
|
|
|
|
:color="getProgressColor(Number(scope.row.completionRateNum || 0))"
|
|
|
|
|
:stroke-width="8"
|
|
|
|
|
:show-text="false"
|
|
|
|
|
style="width:90px;margin-left:8px;"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="生产订单号" align="center" prop="orderCode" v-if="columns[0].visible" width="140" />
|
|
|
|
|
<el-table-column label="物料编号" align="center" prop="materialCode" v-if="columns[1].visible" width="120" />
|
|
|
|
|
<el-table-column label="物料名称" align="center" prop="materialName" v-if="columns[2].visible" width="180" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="规格型号" align="center" prop="materialSpec" v-if="columns[3].visible" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="计划总数量" align="center" prop="planAmount" v-if="columns[4].visible" width="100" />
|
|
|
|
|
<el-table-column label="在制数量" align="center" prop="wipAmount" v-if="columns[5].visible" width="100" />
|
|
|
|
|
<el-table-column label="已完成数量" align="center" prop="completeAmount" v-if="columns[6].visible" width="100" />
|
|
|
|
|
<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="planEndTime" v-if="columns[9].visible" width="150" />
|
|
|
|
|
<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">
|
|
|
|
|
<!-- 展开列:列出该工序下的计划(计划编号) -->
|
|
|
|
|
<el-table-column type="expand" width="50">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-table :data="buildProcessPlanRows(scope.row)" border size="small" style="width: 100%" @expand-change="onPlanRowExpand">
|
|
|
|
|
<el-table-column type="expand" width="50">
|
|
|
|
|
<template #default="pScope">
|
|
|
|
|
<div>
|
|
|
|
|
<el-table v-if="planChildrenCache[getPlanChildKey(pScope.row)] && planChildrenCache[getPlanChildKey(pScope.row)].length"
|
|
|
|
|
:data="planChildrenCache[getPlanChildKey(pScope.row)]"
|
|
|
|
|
border size="small" style="width: 100%">
|
|
|
|
|
<el-table-column label="计划ID" prop="planId" width="120" />
|
|
|
|
|
<el-table-column label="计划编号" prop="planCode" width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="工序ID" prop="processId" width="100" />
|
|
|
|
|
<el-table-column label="工序顺序" prop="processOrder" width="100" />
|
|
|
|
|
<el-table-column label="计划数" prop="planAmount" width="110" />
|
|
|
|
|
<el-table-column label="已完数" prop="completeAmount" width="110" />
|
|
|
|
|
<el-table-column label="完成率" width="160">
|
|
|
|
|
<template #default="cScope">
|
|
|
|
|
<div style="display:flex;align-items:center;">
|
|
|
|
|
<span style="min-width:42px;text-align:right;font-size:12px;color:#606266;">
|
|
|
|
|
{{ Number(cScope.row.planAmount) > 0 ? Math.round((Number(cScope.row.completeAmount || 0) * 100.0) / Number(cScope.row.planAmount)) : 0 }}%
|
|
|
|
|
</span>
|
|
|
|
|
<el-progress
|
|
|
|
|
:percentage="Number(cScope.row.planAmount) > 0 ? Math.round((Number(cScope.row.completeAmount || 0) * 100.0) / Number(cScope.row.planAmount)) : 0"
|
|
|
|
|
:color="getProgressColor(Number(cScope.row.planAmount) > 0 ? Math.round((Number(cScope.row.completeAmount || 0) * 100.0) / Number(cScope.row.planAmount)) : 0)"
|
|
|
|
|
:stroke-width="6"
|
|
|
|
|
:show-text="false"
|
|
|
|
|
style="width:100px;margin-left:8px;"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="班组" prop="teamName" width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="工位" prop="stationName" width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="实际开工" prop="realBeginTime" width="160" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="实际完工" prop="realEndTime" width="160" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="状态" prop="planStatus" width="120">
|
|
|
|
|
<template #default="cScope">
|
|
|
|
|
<el-tag :type="getPlanStatusTagType(cScope.row.planStatus)" size="small">
|
|
|
|
|
{{ getPlanStatusLabel(cScope.row.planStatus) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-empty v-else description="无子节点或未加载" />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="计划编号" prop="planCode" width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="物料编号" prop="materialCode" width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="物料名称" prop="materialName" min-width="160" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="计划数" prop="planAmount" width="100" />
|
|
|
|
|
<el-table-column label="已完数" prop="completeAmount" width="100" />
|
|
|
|
|
<el-table-column label="剩余数" prop="remainingAmount" width="100" />
|
|
|
|
|
<el-table-column label="完成率" width="160">
|
|
|
|
|
<template #default="pScope">
|
|
|
|
|
<div style="display:flex;align-items:center;justify-content:flex-start;">
|
|
|
|
|
<span style="min-width:42px;text-align:right;font-size:12px;color:#606266;">{{ Number(pScope.row.processProgress || 0) }}%</span>
|
|
|
|
|
<el-progress
|
|
|
|
|
:percentage="Number(pScope.row.processProgress || 0)"
|
|
|
|
|
:color="getProgressColor(Number(pScope.row.processProgress || 0))"
|
|
|
|
|
:stroke-width="6"
|
|
|
|
|
:show-text="false"
|
|
|
|
|
style="width:100px;margin-left:8px;"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="状态" width="100">
|
|
|
|
|
<template #default="pScope">
|
|
|
|
|
<el-tag :type="pScope.row.isCompleted ? 'success' : (pScope.row.isInProgress ? 'warning' : 'info')" size="small">
|
|
|
|
|
{{ pScope.row.isCompleted ? '已完' : (pScope.row.isInProgress ? '在制' : '未开') }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="计划编号" align="center" prop="planCode" v-if="false" width="140" />
|
|
|
|
|
<el-table-column label="物料编号" align="center" prop="materialCode" v-if="false" width="120" />
|
|
|
|
|
<el-table-column label="物料名称" align="center" prop="materialName" v-if="false" width="180" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="规格型号" align="center" prop="materialSpec" v-if="false" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="计划总数量" align="center" prop="planAmount" v-if="false" width="100" />
|
|
|
|
|
<el-table-column label="在制数量" align="center" prop="wipAmount" v-if="false" width="100" />
|
|
|
|
|
<el-table-column label="已完成数量" align="center" prop="completeAmount" v-if="false" width="100" />
|
|
|
|
|
<el-table-column label="计划开工时间" align="center" prop="planBeginTime" v-if="false" width="150" />
|
|
|
|
|
<el-table-column label="实际开工时间" align="center" prop="realBeginTime" v-if="false" width="150" />
|
|
|
|
|
<el-table-column label="计划完工时间" align="center" prop="planEndTime" v-if="false" width="150" />
|
|
|
|
|
<el-table-column label="当前时间" align="center" prop="currentTime" v-if="false" width="150" />
|
|
|
|
|
<el-table-column label="总工序数" align="center" prop="totalProcessCount" v-if="false" width="100" />
|
|
|
|
|
<el-table-column label="在制工序" align="center" prop="wipProcesses" v-if="false" width="150" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="剩余工序" align="center" prop="remainingProcesses" v-if="false" width="150" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="整体进度" align="center" prop="overallProgress" v-if="false" width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-progress
|
|
|
|
|
:percentage="parseFloat(scope.row.overallProgress)"
|
|
|
|
|
@ -181,14 +258,14 @@
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="进度状态" align="center" prop="progressStatus" v-if="columns[15].visible" width="100">
|
|
|
|
|
<el-table-column label="进度状态" align="center" prop="progressStatus" v-if="false" width="100">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-tag :type="scope.row.progressStatus === '正常' ? 'success' : 'danger'">
|
|
|
|
|
{{ scope.row.progressStatus }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="工序进度" align="center" v-if="columns[16].visible" width="350">
|
|
|
|
|
<el-table-column label="工序进度" align="center" v-if="false" width="350">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<div class="process-progress-container">
|
|
|
|
|
<!-- <div
|
|
|
|
|
@ -235,10 +312,12 @@ import type { ElFormInstance } from 'element-plus';
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
import { listWipTrackingReport, exportWipTrackingReport } from '@/api/mes/wipTrackingReport';
|
|
|
|
|
import { WipTrackingReportVO, WipTrackingReportQuery, ProcessProgressVO } from '@/api/mes/wipTrackingReport/types';
|
|
|
|
|
import { planProcessChildren } from '@/api/mes/prodReport';
|
|
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
|
|
|
|
const reportList = ref<WipTrackingReportVO[]>([]);
|
|
|
|
|
const processStatsList = ref<any[]>([]);
|
|
|
|
|
const loading = ref(true);
|
|
|
|
|
const showSearch = ref(true);
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
@ -257,29 +336,20 @@ let processStatsChart: echarts.ECharts | null = null;
|
|
|
|
|
|
|
|
|
|
// 列显隐信息
|
|
|
|
|
const columns = ref([
|
|
|
|
|
{ key: 0, label: '生产订单号', visible: true },
|
|
|
|
|
{ key: 1, label: '物料编号', visible: true },
|
|
|
|
|
{ key: 2, label: '物料名称', visible: true },
|
|
|
|
|
{ key: 3, label: '规格型号', visible: true },
|
|
|
|
|
{ key: 4, label: '计划总数量', visible: true },
|
|
|
|
|
{ key: 5, label: '在制数量', visible: false },
|
|
|
|
|
{ key: 6, label: '已完成数量', visible: true },
|
|
|
|
|
{ key: 7, label: '计划开工时间', visible: true },
|
|
|
|
|
{ key: 8, label: '实际开工时间', visible: true },
|
|
|
|
|
{ key: 9, label: '计划完工时间', visible: true },
|
|
|
|
|
{ key: 10, label: '当前时间', visible: false },
|
|
|
|
|
{ key: 11, label: '总工序数', visible: true },
|
|
|
|
|
{ key: 12, label: '在制工序', visible: false },
|
|
|
|
|
{ key: 13, label: '剩余工序', visible: false },
|
|
|
|
|
{ key: 14, label: '整体进度', visible: true },
|
|
|
|
|
{ key: 15, label: '进度状态', visible: true },
|
|
|
|
|
{ key: 16, label: '工序进度', visible: false }
|
|
|
|
|
{ key: 0, label: '工序名称', visible: true },
|
|
|
|
|
{ key: 1, label: '计划数量', visible: true },
|
|
|
|
|
{ key: 2, label: '已完成数量', visible: true },
|
|
|
|
|
{ key: 3, label: '未完成数量', visible: true },
|
|
|
|
|
{ key: 4, label: '完成率', visible: true }
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 计划子节点缓存:key = planCode__processId
|
|
|
|
|
const planChildrenCache = ref<Record<string, any[]>>({});
|
|
|
|
|
|
|
|
|
|
const queryParams = ref<WipTrackingReportQuery>({
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
orderCode: '',
|
|
|
|
|
planCode: '',
|
|
|
|
|
materialCode: '',
|
|
|
|
|
materialName: '',
|
|
|
|
|
progressStatus: '',
|
|
|
|
|
@ -301,16 +371,26 @@ watch(dateRange, (newVal) => {
|
|
|
|
|
/** 查询在制品跟踪报表列表 */
|
|
|
|
|
function getList() {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
listWipTrackingReport(queryParams.value).then((response: any) => {
|
|
|
|
|
reportList.value = response.rows;
|
|
|
|
|
total.value = response.total;
|
|
|
|
|
loading.value = false;
|
|
|
|
|
// 为保证“工序统计”与子节点计划列表不受后端按计划分页影响,
|
|
|
|
|
// 这里按工序视角一次性拉取足够大的计划数据集进行前端聚合。
|
|
|
|
|
const baseQuery = { ...queryParams.value };
|
|
|
|
|
const allQuery = { ...baseQuery, pageNum: 1, pageSize: 100000 };
|
|
|
|
|
listWipTrackingReport(allQuery)
|
|
|
|
|
.then((response: any) => {
|
|
|
|
|
// 使用全量计划数据进行工序聚合统计与子列表构建
|
|
|
|
|
reportList.value = response?.rows || [];
|
|
|
|
|
// 顶层分页改为按工序维度,total 等于工序统计项数量
|
|
|
|
|
processStatsList.value = buildProcessStatsFromReport(reportList.value);
|
|
|
|
|
total.value = processStatsList.value.length;
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
|
|
|
|
// 更新图表
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
updateCharts();
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
updateCharts();
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 搜索按钮操作 */
|
|
|
|
|
@ -326,7 +406,7 @@ function resetQuery() {
|
|
|
|
|
queryParams.value = {
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
orderCode: '',
|
|
|
|
|
planCode: '',
|
|
|
|
|
materialCode: '',
|
|
|
|
|
materialName: '',
|
|
|
|
|
progressStatus: '',
|
|
|
|
|
@ -356,6 +436,30 @@ function getProgressColor(percentage: number) {
|
|
|
|
|
return '#67c23a';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 计划状态标签文案 */
|
|
|
|
|
function getPlanStatusLabel(status: any) {
|
|
|
|
|
const s = String(status ?? '0');
|
|
|
|
|
switch (s) {
|
|
|
|
|
case '0': return '未派工';
|
|
|
|
|
case '1': return '已派工';
|
|
|
|
|
case '2': return '进行中';
|
|
|
|
|
case '3': return '已完成';
|
|
|
|
|
default: return '未知';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 计划状态标签样式 */
|
|
|
|
|
function getPlanStatusTagType(status: any) {
|
|
|
|
|
const s = String(status ?? '0');
|
|
|
|
|
switch (s) {
|
|
|
|
|
case '0': return 'info';
|
|
|
|
|
case '1': return 'warning';
|
|
|
|
|
case '2': return 'primary';
|
|
|
|
|
case '3': return 'success';
|
|
|
|
|
default: return 'info';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 树形结构配置 */
|
|
|
|
|
const treeProps = { children: 'children', label: 'label' } as const;
|
|
|
|
|
|
|
|
|
|
@ -373,13 +477,90 @@ function buildProcessTree(row: WipTrackingReportVO) {
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
id: row.productOrderId || row.orderCode,
|
|
|
|
|
label: `${row.orderCode}(在制工序(${children.filter(c => c.status === 'in_progress').length}))`,
|
|
|
|
|
id: (row as any).planCode || (row as any).orderCode || '',
|
|
|
|
|
label: `${(row as any).planCode || ''}(在制工序(${children.filter(c => c.status === 'in_progress').length}))`,
|
|
|
|
|
children
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建展开区计划列表数据(按工序筛选所有派工计划)
|
|
|
|
|
* 从 reportList 的每个订单的 processProgressList 中,筛选出工序名称匹配的项
|
|
|
|
|
*/
|
|
|
|
|
function buildProcessPlanRows(procRow: any) {
|
|
|
|
|
const rows: any[] = [];
|
|
|
|
|
const targetProcessName = procRow?.processName;
|
|
|
|
|
if (!targetProcessName) return rows;
|
|
|
|
|
|
|
|
|
|
(reportList.value || []).forEach(order => {
|
|
|
|
|
const procs = (order as any).processProgressList || [];
|
|
|
|
|
procs.forEach((p: any) => {
|
|
|
|
|
if (p?.processName === targetProcessName) {
|
|
|
|
|
const planAmount = Number(p?.planAmount ?? 0);
|
|
|
|
|
const completeAmount = Number(p?.completeAmount ?? 0);
|
|
|
|
|
const remainingAmount = p?.remainingAmount != null
|
|
|
|
|
? Number(p.remainingAmount)
|
|
|
|
|
: Math.max(planAmount - completeAmount, 0);
|
|
|
|
|
const progressNum = p?.processProgress != null
|
|
|
|
|
? Number(p.processProgress)
|
|
|
|
|
: (planAmount > 0 ? Math.round((completeAmount * 100.0) / planAmount) : 0);
|
|
|
|
|
rows.push({
|
|
|
|
|
planCode: p?.planCode || (order as any).planCode || '',
|
|
|
|
|
processId: p?.processId || '',
|
|
|
|
|
materialName: p?.materialName || (order as any).materialName || '',
|
|
|
|
|
materialCode: p?.materialCode || (order as any).materialCode || '',
|
|
|
|
|
planAmount,
|
|
|
|
|
completeAmount,
|
|
|
|
|
remainingAmount,
|
|
|
|
|
processProgress: progressNum,
|
|
|
|
|
isCompleted: (p?.isCompleted === 1) || !!p?.isCompleted,
|
|
|
|
|
isInProgress: (p?.isInProgress === 1) || !!p?.isInProgress
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 进度高的排前
|
|
|
|
|
rows.sort((a, b) => Number(b.processProgress || 0) - Number(a.processProgress || 0));
|
|
|
|
|
return rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 生成计划子节点缓存Key */
|
|
|
|
|
function getPlanChildKey(row: any) {
|
|
|
|
|
return `${row?.planCode || ''}__${row?.processId || ''}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 异步获取计划子节点(按 plan_code + process_id) */
|
|
|
|
|
async function fetchPlanChildren(row: any) {
|
|
|
|
|
const key = getPlanChildKey(row);
|
|
|
|
|
if (planChildrenCache.value[key]) {
|
|
|
|
|
return planChildrenCache.value[key];
|
|
|
|
|
}
|
|
|
|
|
// 参数校验:必须存在有效的 planCode 和数值型 processId
|
|
|
|
|
const planCode = row?.planCode;
|
|
|
|
|
const processIdNum = Number(row?.processId);
|
|
|
|
|
const hasValidProc = !isNaN(processIdNum);
|
|
|
|
|
if (!planCode || !hasValidProc) {
|
|
|
|
|
planChildrenCache.value[key] = [];
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res: any = await planProcessChildren({ planCode, processId: processIdNum });
|
|
|
|
|
const data = (res?.data || res?.rows || res) || [];
|
|
|
|
|
planChildrenCache.value[key] = data;
|
|
|
|
|
return data;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
planChildrenCache.value[key] = [];
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 内层计划表展开时触发:加载子节点 */
|
|
|
|
|
function onPlanRowExpand(row: any) {
|
|
|
|
|
fetchPlanChildren(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 初始化图表 */
|
|
|
|
|
function initCharts() {
|
|
|
|
|
if (statusChartRef.value) {
|
|
|
|
|
@ -484,7 +665,7 @@ function updateProgressChart() {
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: '订单数量',
|
|
|
|
|
name: '派工数量',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
data: Object.values(progressRanges),
|
|
|
|
|
itemStyle: {
|
|
|
|
|
@ -503,65 +684,70 @@ function updateProgressChart() {
|
|
|
|
|
|
|
|
|
|
/** 更新工序完成率统计图 */
|
|
|
|
|
function updateProcessStatsChart() {
|
|
|
|
|
if (!processStatsChart || !reportList.value.length) return;
|
|
|
|
|
if (!processStatsChart || !processStatsList.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 sorted = [...processStatsList.value]
|
|
|
|
|
.sort((a, b) => (Number(b.planQty || 0) - Number(a.planQty || 0)))
|
|
|
|
|
.slice(0, 10);
|
|
|
|
|
const processNames = sorted.map((r: any) => r.processName || '未知工序');
|
|
|
|
|
const completedData = sorted.map((r: any) => Number(r.completedQty || 0));
|
|
|
|
|
const uncompletedData = sorted.map((r: any) => Number(r.uncompletedQty || 0));
|
|
|
|
|
const completionRateData = sorted.map((r: any) => Number(r.completionRateNum || 0));
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '工序完成率统计',
|
|
|
|
|
left: 'center',
|
|
|
|
|
textStyle: { fontSize: 14 }
|
|
|
|
|
},
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
},
|
|
|
|
|
title: { text: '工序生产统计(Top10)', left: 'center', textStyle: { fontSize: 14 } },
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
legend: { data: ['已完成数量', '未完成数量', '完成率%'], bottom: 0 },
|
|
|
|
|
grid: { left: '3%', right: '6%', top: 40, bottom: 60, containLabel: true },
|
|
|
|
|
xAxis: { type: 'category', data: processNames },
|
|
|
|
|
yAxis: [
|
|
|
|
|
{ type: 'value', name: '数量' },
|
|
|
|
|
{ type: 'value', name: '完成率%', min: 0, max: 100, position: 'right' }
|
|
|
|
|
],
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{ name: '已完成数量', type: 'bar', data: completedData },
|
|
|
|
|
{ name: '未完成数量', type: 'bar', data: uncompletedData },
|
|
|
|
|
{ name: '完成率%', type: 'line', yAxisIndex: 1, data: completionRateData, smooth: true }
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
processStatsChart.setOption(option);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 合并三张计划表的工序统计结果 */
|
|
|
|
|
function buildProcessStatsFromReport(list: any[]) {
|
|
|
|
|
const map = new Map<string, { processName: string; planQty: number; completedQty: number; uncompletedQty: number; }>();
|
|
|
|
|
(list || []).forEach((order: any) => {
|
|
|
|
|
const procs = (order?.processProgressList || []) as any[];
|
|
|
|
|
procs.forEach((p: any) => {
|
|
|
|
|
const name = String(p?.processName || '未知工序');
|
|
|
|
|
const plan = Number(p?.planAmount || 0);
|
|
|
|
|
const completed = Number(p?.completeAmount || 0);
|
|
|
|
|
const uncompleted = Math.max(plan - completed, 0);
|
|
|
|
|
const agg = map.get(name);
|
|
|
|
|
if (agg) {
|
|
|
|
|
agg.planQty += plan;
|
|
|
|
|
agg.completedQty += completed;
|
|
|
|
|
agg.uncompletedQty += uncompleted;
|
|
|
|
|
} else {
|
|
|
|
|
map.set(name, { processName: name, planQty: plan, completedQty: completed, uncompletedQty: uncompleted });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return Array.from(map.values()).map((it) => {
|
|
|
|
|
const rate = it.planQty > 0 ? Number(((it.completedQty * 100.0) / it.planQty).toFixed(2)) : 0;
|
|
|
|
|
return { ...it, completionRateNum: rate, completionRate: `${rate}%` };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 更新工序进度可视化图 */
|
|
|
|
|
function updateProcessChart() {
|
|
|
|
|
if (!processChart || !reportList.value.length) return;
|
|
|
|
|
|
|
|
|
|
const processData = reportList.value.slice(0, 10).map(item => ({
|
|
|
|
|
name: item.orderCode,
|
|
|
|
|
name: item.dispatchCode,
|
|
|
|
|
value: [
|
|
|
|
|
item.orderCode,
|
|
|
|
|
item.dispatchCode,
|
|
|
|
|
item.overallProgressNum || 0,
|
|
|
|
|
item.progressStatus,
|
|
|
|
|
item.wipProcesses || '',
|
|
|
|
|
@ -571,7 +757,7 @@ function updateProcessChart() {
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '工序进度可视化(前10个订单)',
|
|
|
|
|
text: '工序进度可视化(前10个派工)',
|
|
|
|
|
left: 'center',
|
|
|
|
|
textStyle: { fontSize: 14 }
|
|
|
|
|
},
|
|
|
|
|
@ -645,6 +831,45 @@ onBeforeUnmount(() => {
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* 让子节点数据与工序名同一行,并利用右侧空间 */
|
|
|
|
|
.process-tree {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
}
|
|
|
|
|
.process-tree :deep(.el-tree-node__content) {
|
|
|
|
|
min-height: 26px;
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
}
|
|
|
|
|
.tree-node {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.node-title {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
max-width: 240px;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
.node-metrics {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
color: #606266;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
.node-percent {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
color: #909399;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
.node-progress {
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
min-width: 180px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.process-tree {
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
@ -657,14 +882,25 @@ onBeforeUnmount(() => {
|
|
|
|
|
.tree-node {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
.tree-node .node-title {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
.tree-node .node-metrics {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #606266;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.tree-node .node-percent {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
min-width: 36px;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
.node-progress {
|
|
|
|
|
width: 120px;
|
|
|
|
|
width: 90px;
|
|
|
|
|
}
|
|
|
|
|
.status-completed .node-title { color: #67c23a; }
|
|
|
|
|
.status-in_progress .node-title { color: #e6a23c; }
|
|
|
|
|
|