计划排程

56 阅读11分钟

计划排程-前端研发设计-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&current=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% :红色(超负荷)

前端基本变量定义

字段名类型说明
idString唯一标识任务或日期,通常由 工段ID + 产线ID日期ID 组成,用于区分任务节点。
parentString表示任务的父节点 ID,用于建立任务之间的层级关系(如工段-产线的关联)。
typeString表示任务类型,如 project 表示产线或工段任务,task 表示具体的日期任务。
textString甘特图任务的显示文本,如产线名称或日期的工作描述。
start_dateString任务的开始日期,格式如 "YYYY-MM-DD",用于甘特图的时间轴展示。
durationNumber任务的持续时间(单位:小时),通常为 24 表示全天任务。
progressNumber当前任务的进度百分比(0-1之间),如 0.5 表示 50% 的进度。
uphNumber每小时产能(Units per Hour),表示产线每小时可以完成的工作量。
workHourAccumulateNumber累计的已排工时(小时),表示该产线或日期已分配的工作时间。
remainingWorkHourNumber剩余工时(小时),表示产线在当天还可使用的工时。
workHourAccumulatePercentNumber工时使用率的百分比,如 50 表示已使用一半的工作时间。
dateString表示具体的日期,格式如 "YYYY-MM-DD"
isNoWorkdayBoolean是否为非工作日,true 表示该日期为非工作日,不可安排任务。
detailDataArray存储与任务相关的详细信息,如当天的任务备注或排程细节。
openLineDurationNumber表示产线的开启时间段(小时),用于计算产线可用时间。
clearLineDurationNumber表示产线的清理时间段(小时),表示每次任务后需要的清理时间。

甘特图数据结构

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

前端代码处理流程

  1. 获取查询时间范围,并设置tasks.initTime
  2. 调用API,获取工段与产线数据以及日期的非生产日列表。
  3. 遍历工段及产线,将产线任务和非工作日分别加入结果集。
  4. 若某日期为非工作日,则标记为isNoWorkday: true,并设置其他任务属性为空。
  5. 将处理好的数据赋值给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-infoGET
提交派工计划/api/mes-product/workorderPlan/submitPOST
获取甘特图数据/api/mes-product/workorderPlan/ganttGET
撤销排程/api/mes-product/workorderPlan/revokePOST

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/pageGET
提交派工/api/mes-product/workorderPlan/submitPOST
撤销排程/api/mes-product/workorderPlan/revokePOST
导入计划/api/mes-product/workorderPlan/importPOST
获取产线资源/api/mes-product/workorderPlan/resourcesGET