计划排程-前端研发设计-tab
一. 功能概述
该模块实现了生产系统中的计划排程功能,支持工单的排程派工、撤销排程、查看排程详情和数据导入等操作。主要功能模块包括:工单列表展示、排程派工、撤销排程、查看详情、计划导入,以及产线资源展示等。
二. 架构设计
- 前端框架:Vue.js + Avue
- UI组件库:Element UI + Avue CRUD
- 状态管理:Vuex
- API交互:Axios(与后端API交互)
三 . 页面布局
页面头部
包含数据筛选框(工单号、客户名称、工单类型、工单状态、产品编码、工艺路线状态、排程时间)。
页面主体
包含工单列表(工单号、产品编码、产品型号、客户编码、客户名称、工单类型、工单状态、工艺路线状态、工单数量、要求发货时间、计划开始时间、计划结束时间、更新人、备注)。
四 . 核心模块与逻辑
4.1 工单管理模块
4.1.1 功能描述
支持按工单号、客户名称、工单类型、工单状态、产品编码、工艺路线状态、排程时间等字段进行搜索。
功能 | api |
工单计划分页 | /api/mes-product/workorderPlan/page-workOrder-with-process-and-plan?workOrderNo=GD000206&customerName=23&productPartNo=test666&workOrderTypeStr=1&workOrderStatus=1&woProcessStatusStr=3¤t=1&size=20 |
搜索框工单类型下拉 | /api/blade-system/dict/dictionary?code=order_type |
搜索框工单状态下拉 | /api/blade-system/dict/dictionary?code=mfg_work_order |
产线派工优先级下拉 | /api/blade-system/dict/dictionary?code=PRIORITY |
4.1.2 重点参数说明
名称 | 取值 | 备注 |
工单类型 | 数据字典,编号“order_type”, | 支持多选 |
工单状态 | 数据字典,编号“mfg_work_order” | 支持多选 |
工艺路线状态 | 写在dicData中 1:未设计 2:待审核 3:已发布 | 支持多选 |
4 .2 排程派工模块
4.2.1 功能描述
用于将工单分配到不同工段和产线,动态设置优先级、计划数量和UPH,并通过甘特图查看状态。
4.2.2 派工弹窗界面设计
工单信息
使用 <avue-form>
组件展示工单的基本信息:工单号、工单数量、产品编码、产品型号、客户编码、客户名称、备注。
产线派工
- 使用
<el-cascader>
组件选择工段和产线。 - 使用
<el-select>
组件设置优先级。 - 使用
<el-input-number>
设置工段计划数量和 UPH。 - 剩余工时:展示当前日期下的剩余可用工时。
- 计划数量:用户输入当日计划数量。
- 计划工时:根据计划数量和 UPH 自动计算。
甘特图组件
- 使用
<GanttComponent>
显示当前工单的产线资源占用情况。
4.2.3 前端-产线派工里的计算
前端基本变量说明
名称 | 变量 |
工段计划数量 | sectionPlanQuantity |
产线已排数量 | linePlanQuantity |
工段待排数量 | sectionRemainingQuantity |
每小时产能 | UPH |
计划工时 | dayPlanWorkHour |
剩余工时 | dayRemainingWorkHourOfLine |
计划数量 | dayPlanQuantity |
场景拆解
1. 调整UPH
计算公式:
计划工时=计划数量÷UPH计划工时 = 计划数量 ÷ UPH计划工时=计划数量÷UPH
代码实现:
Object.keys(row).forEach((key) => {
if (key.indexOf(':dayPlanQuantity') > -1 && row[key] > 0) {
let dayPlanWorkHourKey = key.split(':')[0] + ':dayPlanWorkHour';
let dayPlanWorkHour = Number(row[key] / row.uph).toFixed(2);
that.$set(that.dispatchDialog.plan.data[index], dayPlanWorkHourKey, dayPlanWorkHour);
that.$forceUpdate();
}
});
JavaScript
2. 调整计划数量
计算公式:
产线已排数量=每日计划数量之和;产线已排数量 = 每日计划数量之和;产线已排数量=每日计划数量之和;
工段待排数量=工段计划数量−产线已排数量;工段待排数量 = 工段计划数量-产线已排数量;工段待排数量=工段计划数量−产线已排数量;
前端校验: 若计划工时超出剩余工时,则系统弹出确认框【本次排程的计划工时已超过工作时长,请确认是否排程?】。
代码实现:
let date = key.split(':')[0];
let dayPlanWorkHourKey = date + ':dayPlanWorkHour';
// 当日计划工时
let dayPlanWorkHour = Number((row[key] || 0) / row.uph).toFixed(2);
let dayRemainingWorkHourOfLineKey = date + ':dayRemainingWorkHourOfLine';
// 当日剩余工时
let dayRemainingWorkHourOfLine = row[dayRemainingWorkHourOfLineKey];row.linePlanQuantity = Object.keys(row).reduce((pre, cur) => {
if (cur.indexOf(':dayPlanQuantity') > -1 && row[cur]) {
pre += Number(row[cur]);
}
return pre;
}, 0);
JavaScript
3. 增加/删除同工段不同产线
计算公式:
用户调整某条产线的计划数量时,系统自动计算该产线已排数量,同时计算出工段待排数量,将同工段下的所有工段待排数量调整统一。
工段待排数量=工段计划数量−所有产线已排数量之和工段待排数量=工段计划数量−所有产线已排数量之和工段待排数量=工段计划数量−所有产线已排数量之和
前端校验:
- 每次新增产线前,检查是否有未填写的 优先级 或 工段计划数量 ,提示【工段计划数量和优先级不能为空,请仔细检查】
- 当工单待排数量为负数时,则提示【累计的派工计划数量已超过工段待计划,请修改工段计划数量或日期的计划数量!】。
- 允许同一工段配置多条产线,但每条产线不得重复,重复给出提示【该产线已存在,请选择其它产线】。
新增行数据结构:
字段名 | 含义 | 初始化值 |
sectionAndLine | 工段和产线(级联选择) | ['', ''] |
priority | 优先级 | '' |
sectionPlanQuantity | 工段计划数量 | undefined |
uph | 每小时产能 | undefined |
linePlanQuantity | 已排数量 | '' |
sectionRemainingQuantity | 剩余可排数量 | '' |
核心代码:
addRow() { if (dataCheck) { this.$message.warning('工段计划数量和优先级不能为空,请仔细检查'); return; } // 添加新产线条目 this.dispatchDialog.plan.data.push({ sectionAndLine: ['', ''], // 工段和产线的级联选择 priority: '', // 优先级 sectionPlanQuantity: undefined, // 工段计划数量 uph: undefined, // 每小时产能 linePlanQuantity: '', // 已排数量(初始化为空) sectionRemainingQuantity: '' // 剩余可排数量(初始化为空) });}
JavaScript
dispatchPlanDel(row) { if (this.dispatchDialog.plan.data.length == 1) { this.dispatchDialog.plan.data = [ { sectionAndLine: ['', ''], priority: '', sectionPlanQuantity: undefined, uph: undefined, linePlanQuantity: '', sectionRemainingQuantity: '' } ]; } else { // 删除选中的产线 let index = row.$index; this.dispatchDialog.plan.data.splice(index, 1); // 查找同一工段的其他产线 let currentSectionId = row.sectionId; let sameSectionIdData = this.dispatchDialog.plan.data.filter( (item) => item.sectionAndLine[0] == currentSectionId ); // 如果该工段还有其他产线,更新剩余可排数量 if (sameSectionIdData && sameSectionIdData.length > 0) { let linePlanQuantitySum = sameSectionIdData.reduce( (sum, item) => sum + Number(item.linePlanQuantity || 0), 0 ); // 更新剩余可排数量 sameSectionIdData.forEach((item) => { item.sectionRemainingQuantity = Number(row.sectionPlanQuantity) - linePlanQuantitySum; }); } } });}
JavaScript
4.2.4 前端-产品工段三种状态展示
判断逻辑
- 未排:未安排产线的工单显示为灰色。
- 排程中:当前安排但未完成的工单显示为蓝色。(工段待排数量大于0)
- 已排完:已完成排程的工单显示为绿色。(工段待排数量小于等于0)
核心代码
// planStatus 1:未排 2:排程中 3:已排完
that.sectionStatusList.forEach((statusList) => {
if (statusList.sectionId == item.sectionId) {
if (parseInt(item.sectionRemainingQuantity) <= 0) {
statusList.planStatus = 3;
} else {
statusList.planStatus = 2;
}
}
});
JavaScript
4.2.5 前端-产线资源展示
甘特图颜色说明
- <90% :蓝色(正常)
- 90%-100% :绿色(紧张)
- >100% :红色(超负荷)
前端基本变量定义
字段名 | 类型 | 说明 |
id | String | 唯一标识任务或日期,通常由 工段ID + 产线ID 或 日期ID 组成,用于区分任务节点。 |
parent | String | 表示任务的父节点 ID,用于建立任务之间的层级关系(如工段-产线的关联)。 |
type | String | 表示任务类型,如 project 表示产线或工段任务,task 表示具体的日期任务。 |
text | String | 甘特图任务的显示文本,如产线名称或日期的工作描述。 |
start_date | String | 任务的开始日期,格式如 "YYYY-MM-DD" ,用于甘特图的时间轴展示。 |
duration | Number | 任务的持续时间(单位:小时),通常为 24 表示全天任务。 |
progress | Number | 当前任务的进度百分比(0-1之间),如 0.5 表示 50% 的进度。 |
uph | Number | 每小时产能(Units per Hour),表示产线每小时可以完成的工作量。 |
workHourAccumulate | Number | 累计的已排工时(小时),表示该产线或日期已分配的工作时间。 |
remainingWorkHour | Number | 剩余工时(小时),表示产线在当天还可使用的工时。 |
workHourAccumulatePercent | Number | 工时使用率的百分比,如 50 表示已使用一半的工作时间。 |
date | String | 表示具体的日期,格式如 "YYYY-MM-DD" 。 |
isNoWorkday | Boolean | 是否为非工作日,true 表示该日期为非工作日,不可安排任务。 |
detailData | Array | 存储与任务相关的详细信息,如当天的任务备注或排程细节。 |
openLineDuration | Number | 表示产线的开启时间段(小时),用于计算产线可用时间。 |
clearLineDuration | Number | 表示产线的清理时间段(小时),表示每次任务后需要的清理时间。 |
甘特图数据结构
const tasks = { initTime: [], // 甘特图的起始和结束时间 data: [], // 甘特图任务列表,包含每条产线及其每日数据 oldData: [], // 原始数据,用于对比和回滚 links: [], // 甘特图任务之间的依赖关系 calendarObj: {} // 非工作日信息,用于标记甘特图的休息日};
JavaScript
甘特图任务数据示例
tasks.data = [ { id: '1', type: 'project', text: '工段A/产线1', openLineDuration: 2, clearLineDuration: 1, uph: 60, start_date: '2024-10-20', duration: 24, progress: 0.5, remainingWorkHour: 16 }, { id: '1-1', parent: '1', date: '2024-10-20', isNoWorkday: false, text: '8 小时已排工时', workHourAccumulate: 8, remainingWorkHour: 8, workHourAccumulatePercent: 50, start_date: '2024-10-20', progress: 0.5, duration: 24, detailData: [] }];
JavaScript
初始化甘特图代码
gantt.init(this.$refs.ganttContainer);gantt.parse(this.tasks); // 解析任务数据
JavaScript
绘制甘特图代码
gantt.clearAll();gantt.config.start_date = this.tasks.initTime[0];gantt.config.end_date = this.tasks.initTime[1];gantt.parse(this.tasks); // 重新绘制甘特图
JavaScript
销毁甘特图代码
gantt.clearAll(); clearInterval(this.timer); this.timer = null;
JavaScript
悬浮框内容代码
gantt.templates.quick_info_content = function (start, end, task) { if (task.isNoWorkday) return false; return ` <div class="wrap"> <div>累计工时: ${task.workHourAccumulate} 剩余工时: ${task.remainingWorkHour}</div> <table class="gantt_cal_qi_content_table"> <tr><th>序号</th><th>工单号</th><th>产品编码</th><th>计划数量</th></tr> ${task.detailData.map((item, index) => ` <tr> <td>${index + 1}</td> <td>${item.workOrderNo}</td> <td>${item.productPartNo}</td> <td>${item.planQuantity}</td> </tr> `).join('')} </table> </div>`;};
JavaScript
非工作日样式
::v-deep .gantt_task_line.noWorkday { background-color: #9e9e9e; cursor: not-allowed;}
Plain Text
任务状态样式
::v-deep .gantt_bar_task.current .gantt_task_progress { background-color: #95f204;}
::v-deep .gantt_bar_task.danger .gantt_task_progress { background-color: red;}
Plain Text
4.2.6 前端-非生产日设计
非生产日不可以进行排程,若要排程,需先修改生产日历;非生产日数据来源于系统接口返回的数据,且不同工段可能有独立的非生产日设置。
非生产日任务数据格式
{ id: '1', // 任务唯一ID date: '2024-10-10', // 任务对应日期 isNoWorkday: true, // 是否为非生产日 parent: 'section1line1', // 父级(产线与工段ID组合) text: '', // 非生产日显示为空文本 progress: 0, // 非生产日进度为0 start_date: '2024-10-10', // 任务对应开始日期 workHourAccumulate: '', // 累计工时(空值表示非工作日无数据) remainingWorkHour: '', // 剩余工时 workHourAccumulatePercent: 0, // 累计工时百分比 duration: 24, // 持续时间为24小时 detailData: [] // 详细数据(为空表示无任务)}
JavaScript
非生产日判断逻辑
如果非生产日列表为空,则返回false。使用sectionId查找匹配的工段;检查目标日期是否在该工段的非生产日列表中,如果匹配,则返回true;否则返回false。
handleIsNoWorkday(dateNonWorkingList, date, sectionId) { if (!dateNonWorkingList || dateNonWorkingList.length === 0) { return false; // 无非工作日列表时默认不是非工作日 } const section = dateNonWorkingList.find((dateObj) => dateObj.sectionId === sectionId); if (section) { return section.nonWorkingDayList.includes(date); // 判断日期是否为非生产日 } return false; // 默认返回非非生产日}
JavaScript
非工作日样式
::v-deep .gantt_task_line.noWorkday { background-color: #9e9e9e; cursor: not-allowed;}
Plain Text
前端代码处理流程
- 获取查询时间范围,并设置
tasks.initTime
。 - 调用API,获取工段与产线数据以及日期的非生产日列表。
- 遍历工段及产线,将产线任务和非工作日分别加入结果集。
- 若某日期为非工作日,则标记为
isNoWorkday: true
,并设置其他任务属性为空。 - 将处理好的数据赋值给
tasks.data
,并绘制甘特图。
核心代码
let { queryTimeStart, queryTimeEnd } = this.handleGetQueryTime();that.tasks.initTime = [queryTimeStart, queryTimeEnd];getLineResourceByProductPart(productPartId, lineId, queryTimeStart, queryTimeEnd) .then((res) => { let data = res.data.data; let dateNonWorkingList = data.dateNonWorkingList; // 非工作日列表 let dateRangeArr = getDay(that.backDay).map((d) => d.split(':')[0]); let result = []; // 遍历工段和产线 data.sectionList.forEach((section) => { section.lineList.forEach((line) => { result.push({ id: section.sectionId + line.lineId, type: 'project', render: 'split', text: section.sectionName + '/' + line.lineName, openLineDuration: line.openLineDuration, clearLineDuration: line.clearLineDuration, uph: line.uph }); let dateCodeList = line.dateCodeList.map((item) => item.dateCode); dateRangeArr.forEach((dRange) => { if (dateCodeList.includes(dRange)) { // 添加有任务的日期(省略) } else { // 添加非生产日 let find = dateNonWorkingList.find((item) => item.sectionId === section.sectionId); if (find) { find.nonWorkingDayList.forEach((d) => { result.push({ id: find.sectionId + line.lineId + d, date: d, isNoWorkday: true, parent: section.sectionId + line.lineId, text: '', progress: 0, start_date: d, workHourAccumulate: '', remainingWorkHour: '', workHourAccumulatePercent: 0, duration: 24, detailData: [] }); }); } } }); });});
JavaScript
4.2.7 前端-API 使用
功能 | API | 请求方式 |
获取派工信息 | /api/mes-product/workorderPlan/line-info | GET |
提交派工计划 | /api/mes-product/workorderPlan/submit | POST |
获取甘特图数据 | /api/mes-product/workorderPlan/gantt | GET |
撤销排程 | /api/mes-product/workorderPlan/revoke | POST |
4 .3 撤销排程模块
4.3.1 功能描述
撤销排程模块允许用户在排程完成后撤销特定工单的排程,恢复到未排程状态。撤销操作会更新数据状态,并在界面上实时反映变化。
- 撤销排程入口:在已选工单上操作撤销排程。
- 状态校验:仅支持待生产状态的工单进行撤销,否则提示【只有待生产状态才能进行撤销排程】。
- 撤销原因输入:用户必须填写撤销原因,提交后撤销排程。
- 界面刷新:撤销成功后,界面刷新数据,工单状态由待生产变为待排程并更新甘特图。
4.3.2 组件设计
撤销排程对话框
<el-dialog title="撤销排程" custom-class="undoDialog" append-to-body :close-on-click-modal="false" :visible.sync="undoSchedule.visible" @close="$refs.form.resetForm()"> <avue-form ref="form" class="workInfoForm" v-model="undoSchedule.form" :option="undoSchedule.options" @submit="handleUndoSubmit" @reset-change="handleUndoReset"> </avue-form> <div class="dispatch-title"> <div class="dispatch-title-dot"></div> <div class="dispatch-title-text">已选中工单</div> </div> <avue-crud ref="dispatchCrud" :option="undoSchedule.plan.option" :data="undoSchedule.plan.data" @row-del="undoScheduleDel"> </avue-crud> <span slot="footer" class="dialog-footer"> <el-button size="small" icon="el-icon-circle-close" @click="$refs.form.resetForm()">取消</el-button> <el-button size="small" type="primary" icon="el-icon-circle-plus-outline" @click="$refs.form.submit()">提交</el-button> </span></el-dialog>
Plain Text
4.3.3 关键逻辑
撤销排程逻辑
- 检查所有选中的工单状态是否为待生产。
const statusValid = this.selectionList.every((s) => s.status === 2);if (!statusValid) { this.$message.warning('只有待生产状态才能进行撤销排程'); return;}this.undoSchedule.plan.data = structuredClone(this.selectionList);this.undoSchedule.visible = true;
JavaScript
提交撤销操作
- 调用后台
revokePlan
接口,将工单恢复至未排程状态。 - 撤销成功后,更新页面数据并关闭对话框。
handleUndoSubmit(form, done) { const ids = this.selectionList.map((list) => list.id); revokePlan({ workOrderIdSet: ids, reason: form.reason }).then((res) => { this.undoSchedule.visible = false; this.$message.success('撤销成功'); this.onLoad(this.page); }); done();}
JavaScript
五 . 前端API接口使用
功能 | API | 请求方法 |
获取工单列表 | /api/mes-product/workorderPlan/page | GET |
提交派工 | /api/mes-product/workorderPlan/submit | POST |
撤销排程 | /api/mes-product/workorderPlan/revoke | POST |
导入计划 | /api/mes-product/workorderPlan/import | POST |
获取产线资源 | /api/mes-product/workorderPlan/resources | GET |