add(mes): 添加计划监控页面

- 新增计划监控页面组件 monitorPlan.vue
- 实现生产工单信息列表查询功能
- 添加班次和机台的动态列生成
- 实现计划数、实际数和进度条的展示
- 增加日期范围选择功能
- 优化数据处理和展示逻辑
master
zch 4 months ago
parent 00d981595e
commit 70c141556a

@ -120,3 +120,17 @@ export const issuePlanInfo = (workshopId: string | number, planId: string | numb
method: 'get'
});
};
/**
*
* @param query
* @returns {*}
*/
export const queryMoritorPageList = (query?: PlanInfoQuery): AxiosPromise<PlanInfoVO[]> => {
return request({
url: '/mes/planInfo/queryMoritorPageList',
method: 'get',
params: query
});
};

@ -158,6 +158,18 @@ export interface PlanInfoVO {
* ID
*/
workshopId: number;
/**
*
*/
shiftName: string;
/**
* id
*/
shifitId?: string | number;
materialName?: string;
}
export interface PlanInfoForm extends BaseEntity {
@ -332,6 +344,18 @@ export interface PlanInfoForm extends BaseEntity {
materialBoMIdR?: string | number;
materialBoMNameR?: string | number;
/**
* id
*/
shifitId?: string | number;
/**
*
*/
shiftName: string;
materialName?: string;
}
export interface PlanInfoQuery extends PageQuery {
@ -495,6 +519,22 @@ export interface PlanInfoQuery extends PageQuery {
*
*/
params?: any;
/**
*
*/
shiftName: string;
/**
* id
*/
shifitId?: string | number;
/**
*
*/
materialName?: string;
}

@ -0,0 +1,663 @@
<template>
<div class='p-2'>
<transition :enter-active-class='proxy?.animate.searchAnimate.enter'
:leave-active-class='proxy?.animate.searchAnimate.leave'>
<div v-show='showSearch' class='mb-[10px]'>
<el-card shadow='hover'>
<el-form ref='queryFormRef' :model='queryParams' :inline='true'>
<el-form-item label='机台名称' prop='releaseId'>
<el-select v-model='queryParams.releaseId' placeholder='请选择机台名称'>
<el-option
v-for='item in releaseList'
:key='item.machineId'
:label='item.machineName'
:value='item.machineId'
/>
</el-select>
</el-form-item>
<el-form-item label='计划时间区间' prop='planTimeRange' label-width="120px">
<el-date-picker
v-model="dateRange"
type="daterange"
value-format="YYYY-MM-DD"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item label="班次" prop="shiftId">
<el-select v-model="queryParams.shiftId" placeholder="请选择班次" clearable>
<el-option v-for="item in shiftList"
:key="item.shiftId"
:label="item.shiftName"
:value="item.shiftId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type='primary' icon='Search' @click='handleQuery'>搜索</el-button>
<el-button icon='Refresh' @click='resetQuery'>重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow='never'>
<el-table v-loading='loading' :data='uniqueMachines' border>
<!-- 机台列 -->
<el-table-column label='机台' align='center' prop='machineName' fixed="left" width="120" />
<!-- 动态生成班次列 -->
<template v-for="shift in uniqueShifts" :key="shift.shiftId">
<el-table-column :label="shift.shiftName" align='center' min-width="460">
<el-table-column label='物料' align='center' min-width="220">
<template #default="scope">
<div v-for="(item, index) in getItemsByMachineAndShift(scope.row.machineId, shift.shiftId)" :key="index" class="cell-content material-cell">
<el-tooltip :content="item.materialName" placement="top" :hide-after="1500">
<span class="material-text">{{ item.materialName }}</span>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column label='计划数' align='center' width="80">
<template #default="scope">
<div v-for="(item, index) in getItemsByMachineAndShift(scope.row.machineId, shift.shiftId)" :key="index" class="cell-content plan-cell">
{{ item.planAmount }}
</div>
</template>
</el-table-column>
<el-table-column label='实际数' align='center' width="80">
<template #default="scope">
<div v-for="(item, index) in getItemsByMachineAndShift(scope.row.machineId, shift.shiftId)" :key="index" class="cell-content actual-cell">
{{ item.completeAmount || 0 }}
</div>
</template>
</el-table-column>
<el-table-column label='计划监控' align='center' width="120">
<template #default="scope">
<div v-for="(item, index) in getItemsByMachineAndShift(scope.row.machineId, shift.shiftId)" :key="index" class="cell-content progress-cell">
<div class="progress-bar">
<div class="progress-bar-inner"
:style="{ width: getItemCompletionPercentage(item) + '%', backgroundColor: getProgressBarColor(item) }">
{{ getItemCompletionPercentage(item) }}%
</div>
</div>
</div>
</template>
</el-table-column>
</el-table-column>
</template>
</el-table>
<pagination v-show='total > 0' :total='total' v-model:page='queryParams.pageNum'
v-model:limit='queryParams.pageSize' @pagination='getList' />
</el-card>
</div>
</template>
<script setup name='PlanInfo' lang='ts'>
import {
listPlanInfo,
getPlanInfo,
delPlanInfo,
addPlanInfo,
updatePlanInfo,
orderAddProductPlanList, issuePlanInfo, queryMoritorPageList
} from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery, PlanInfoForm } from '@/api/mes/planInfo/types';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
import BomSelect from '@/views/mes/materialBom/addBom.vue';
import MaterialSelect from '@/views/mes/baseMaterialInfo/addMaterial.vue';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getStationInfoList } from '@/api/mes/baseStationInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { ProdBaseMachineInfoVO } from '@/api/mes/prodBaseMachineInfo/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const planInfoList = ref<PlanInfoVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const planInfoFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const sfpBatchDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const formingBatchDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const vulBatchDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const shiftList = ref([]);
const classTeamList = ref([]);
const processList = ref([]);
let releaseList = ref([]);
const materialBomOpen = ref(false);
const bomSelectRef = ref();
const materialOpen = ref(false);
const materialSelectRef = ref();
const materialIdForm = ref();
const workshopId = ref();
const prodBaseMachineInfoList = ref<ProdBaseMachineInfoVO[]>([]);
const machineIds = ref<Array<string | number>>([]);
const machineQueryParams = ref({});
const initFormData: PlanInfoForm = {
planId: undefined,
productOrderId: undefined,
saleOrderId: undefined,
saleorderCode: undefined,
planCode: undefined,
dispatchCode: undefined,
materialId: undefined,
materialBomId: undefined,
processId: undefined,
processOrder: undefined,
lastProcessId: undefined,
finalProcessFlag: undefined,
releaseType: '1',
releaseId: undefined,
productionTime: undefined,
planAmount: 1,
dispatchAmount: undefined,
completeAmount: undefined,
planBeginTime: undefined,
planEndTime: undefined,
realBeginTime: undefined,
realEndTime: undefined,
attachId: undefined,
planStatus: undefined,
importFlag: undefined,
finishFlag: [],
priority: 1,
shiftId: undefined,
classTeamId: undefined,
modelCode: undefined,
remark: undefined,
materialBoMName: undefined,
workshopId: undefined,
shifitId: undefined,
shiftName: undefined,
};
const data = reactive<PageData<PlanInfoForm, PlanInfoQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
planId: undefined,
productOrderId: undefined,
saleOrderId: undefined,
saleorderCode: undefined,
planCode: undefined,
dispatchCode: undefined,
materialId: undefined,
materialBomId: undefined,
processId: undefined,
processOrder: undefined,
lastProcessId: undefined,
finalProcessFlag: undefined,
releaseType: undefined,
releaseId: undefined,
productionTime: undefined,
planAmount: undefined,
dispatchAmount: undefined,
completeAmount: undefined,
planBeginTime: undefined,
planEndTime: undefined,
realBeginTime: undefined,
realEndTime: undefined,
attachId: undefined,
planStatus: undefined,
importFlag: undefined,
finishFlag: undefined,
priority: undefined,
shiftId: undefined,
classTeamId: undefined,
modelCode: undefined,
workshopId: undefined,
params: {},
shiftName: undefined,
shifitId: undefined,
},
rules: {},
});
const { queryParams, form } = toRefs(data);
// const machineNameTags = ref([]);
const getWorkshopId = async () => {
//
const router = useRouter();
// workshopIdworkshopId
workshopId.value = router.currentRoute._rawValue.query && router.currentRoute._rawValue.query.workshopId;
// workshopIdqueryParamsworkshopId
queryParams.value.workshopId = workshopId.value;
};
/** 查询生产计划信息列表 */
const getList = async () => {
loading.value = true;
const res = await queryMoritorPageList(queryParams.value);
planInfoList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
sfpBatchDialog.visible = false;
formingBatchDialog.visible = false;
vulBatchDialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
form.value.workshopId = workshopId.value;
planInfoFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = [];
data.queryParams.params.monitorBeginTime = undefined;
data.queryParams.params.monitorEndTime = undefined;
queryFormRef.value?.resetFields();
handleQuery();
};
/*获取班次下拉框*/
const getShiftSelect = async () => {
let res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
/*获取班组下拉框*/
const getClassTeamSelect = async () => {
let res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
/*获取半制品工序下拉框*/
const getProcessSelect = async () => {
let res = await getProcessInfoList({ processType: 1 });
processList.value = res.data.filter(item => item.processId !== 17 && item.processId !== 18);
};
/*获取机台下拉框*/
const getReleaseSelect = async () => {
// machineQueryParams.value.processId = form.value.processId;
let res = await getProdBaseMachineInfoList(machineQueryParams.value);
releaseList.value = res.data;
prodBaseMachineInfoList.value = res.data;
};
/*在菜单管理中关闭了keep-alive并且增加了watch监听workshopId*/
/** 监听路由参数变化 */
watch(
() => useRouter().currentRoute.value.query.workshopId,
(newWorkshopId) => {
if (newWorkshopId !== undefined) {
workshopId.value = Number(newWorkshopId);
queryParams.value.workshopId = Number(newWorkshopId);
//
getList();
}
}
);
onMounted(() => {
getWorkshopId();//ID
getShiftSelect();
// getClassTeamSelect();
// getProcessSelect();
getReleaseSelect();
getList();
});
/*使 v-for
v-for="shift in uniqueShifts"遍历 uniqueShifts为每个班次生成一列
*/
//
const uniqueMachines = computed(() => {
const machines = [];
const machineIds = new Set();
planInfoList.value.forEach(item => {
if (item.releaseId && !machineIds.has(item.releaseId)) {
machineIds.add(item.releaseId);
machines.push({
machineId: item.releaseId,
machineName: item.releaseName
});
}
});
return machines;
});
//
const uniqueShifts = computed(() => {
const shifts = [];
const shiftIds = new Set();//id,
//
const hasUnassignedShift = planInfoList.value.some(item => !item.shiftId);
if (hasUnassignedShift) {
shifts.push({
shiftId: 'default',
shiftName: '未分班'
});
shiftIds.add('default');
}
//
planInfoList.value.forEach(item => {
if (item.shiftId && !shiftIds.has(item.shiftId)) {
shiftIds.add(item.shiftId);
shifts.push({
shiftId: item.shiftId,
shiftName: item.shiftName || `班次${item.shiftId}`//id
});
}
});
return shifts;
});
//
const groupedPlanInfo = computed(() => {//groupedPlanInfo
const grouped = {};
planInfoList.value.forEach(item => {
const machineId = item.releaseId;
const shiftId = item.shiftId || 'default'; // 使'default'nullshiftId
if (!machineId) return;//
if (!grouped[machineId]) {
grouped[machineId] = {};//
}
if (!grouped[machineId][shiftId]) {
grouped[machineId][shiftId] = [];//
}
grouped[machineId][shiftId].push(item);//
});
return grouped;
});
/*
* v-for="(item, index) in getItemsByMachineAndShift(scope.row.machineId, shift.shiftId)"遍历当前机台在当前班次下的生产计划信息生成具体的行内容
* */
// IDID
const getItemsByMachineAndShift = (machineId, shiftId) => {
if (shiftId === 'default') {
//
return planInfoList.value.filter(item =>
item.releaseId === machineId && !item.shiftId
);
}
//
return planInfoList.value.filter(item =>
item.releaseId === machineId && item.shiftId === shiftId
);
};
//
const getItemCompletionPercentage = (item) => {
if (!item.planAmount) return 0;
const percentage = (item.completeAmount || 0) / item.planAmount * 100;
return Math.round(percentage * 100) / 100;
};
//
const getProgressBarColor = (item) => {
const percentage = getItemCompletionPercentage(item);
if (percentage >= 100) return '#67C23A'; // 100%绿
if (percentage >= 60) return '#E6A23C'; // 60%
return '#F56C6C'; //
};
//
const dateRange = ref([]);
// queryParams.params
if (!data.queryParams.params) {
data.queryParams.params = {};
}
//
const handleDateRangeChange = (val) => {
if (val) {
data.queryParams.params.monitorBeginTime = val[0];
data.queryParams.params.monitorEndTime = val[1];
} else {
data.queryParams.params.monitorBeginTime = undefined;
data.queryParams.params.monitorEndTime = undefined;
}
};
</script>
<style>
/* 设置头部样式 */
.my-header {
/* 宽度为100% */
width: 100%;
/* 文本居中对齐 */
text-align: center;
}
/* 覆盖层对话框的头部样式 */
.el-overlay .el-overlay-dialog .el-dialog .el-dialog__header {
/* 去除底部边框 */
border-bottom: none;
}
/* 材料项样式 */
.material-item {
/* 底部外边距8px */
margin-bottom: 8px;
/* 内边距4px */
padding: 4px;
/* 底部边框1px颜色#eee */
border-bottom: 1px solid #eee;
}
/* 最后一个材料项样式 */
.material-item:last-child {
/* 底部外边距0 */
margin-bottom: 0;
/* 去除底部边框 */
border-bottom: none;
}
/* 进度条样式 */
.progress-bar {
/* 宽度100% */
width: 100%;
/* 高度20px */
height: 20px;
/* 背景色#f0f0f0 */
background-color: #f0f0f0;
/* 圆角半径4px */
border-radius: 4px;
/* 隐藏溢出内容 */
overflow: hidden;
}
/* 进度条内部样式 */
.progress-bar-inner {
/* 高度100% */
height: 100%;
/* 背景色#67C23A */
background-color: #67C23A;
/* 使用flex布局 */
display: flex;
/* 垂直居中对齐 */
align-items: center;
/* 水平居中对齐 */
justify-content: center;
/* 文字颜色白色 */
color: white;
/* 最小宽度30px */
min-width: 30px;
/* 宽度变化过渡效果 */
transition: width 0.3s ease;
}
/* 新添加的样式 */
.material-row {
margin-bottom: 12px;
padding: 6px;
border-bottom: 1px solid #eee;
display: flex;
flex-direction: column;
}
.material-row:last-child {
margin-bottom: 0;
border-bottom: none;
}
.material-name {
font-weight: bold;
margin-bottom: 4px;
text-align: left;
}
.material-count {
text-align: left;
margin-bottom: 2px;
}
.progress-container {
margin-bottom: 12px;
padding: 6px;
display: flex;
align-items: center;
height: 55px; /* 与material-row的平均高度保持一致 */
border-bottom: 1px solid #eee;
}
.progress-container:last-child {
margin-bottom: 0;
border-bottom: none;
}
/* 每个单元格内容的通用样式 */
.cell-content {
height: 40px;
line-height: 40px;
margin-bottom: 8px;
padding: 0 5px;
border-bottom: 1px solid #eee;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
align-items: center;
}
.cell-content:last-child {
margin-bottom: 0;
border-bottom: none;
}
/* 物料单元格特定样式 */
.material-cell {
justify-content: left;
padding-left: 10px;
font-weight: 500;
}
.material-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
/* 计划数单元格特定样式 */
.plan-cell {
justify-content: center;
}
/* 实际数单元格特定样式 */
.actual-cell {
justify-content: center;
}
/* 进度条单元格特定样式 */
.progress-cell {
justify-content: center;
padding: 0 5px;
}
/* 进度条样式 */
.progress-bar {
width: 100%;
height: 24px;
background-color: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar-inner {
height: 100%;
background-color: #67C23A;
display: flex;
align-items: center;
justify-content: center;
color: white;
min-width: 30px;
transition: width 0.3s ease;
}
</style>
Loading…
Cancel
Save