소스 검색

新增工厂排产功能,包含甘特图展示和排产条件设置

yusheng 8 달 전
부모
커밋
225e86cfa8

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "countup.js": "^2.3.2",
     "cropperjs": "^1.5.12",
     "dayjs": "^1.11.8",
+    "dhtmlx-gantt": "^8.0.9",
     "echarts": "^5.3.3",
     "echarts-wordcloud": "^2.0.0",
     "ele-admin": "^1.11.2",

+ 27 - 0
src/api/productionPlan/index.js

@@ -372,3 +372,30 @@ export async function planIsReview(data) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+/**
+ * 生产计划排程
+ */
+export async function scheduling(data) {
+  const res = await request.post('/aps/scheduling/scheduling', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+//生产前准备
+export async function completenessCheck(data) {
+  const res = await request.post(`/aps/productionplan/preRelease`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+//任务派单
+export async function taskReleaseWork(data) {
+  const res = await request.post(`/aps/assign/saveEventAssign`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 2 - 0
src/api/productionPlan/order.js

@@ -45,3 +45,5 @@ export async function getPlanDeviceList({ produceVersionId }) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+

+ 8 - 1
src/api/saleOrder/index.js

@@ -326,7 +326,14 @@ export async function getFactoryarea(params) {
   }
   return Promise.reject(new Error(res.data.message));
 }
-
+// 获取工厂下的所有产线
+export async function listFactoryLineByFactoryId(params) {
+  const res = await request.post(`/main/factoryarea/listFactoryLineByFactoryId`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
 // 根据产品ID列表查询BOM分类
 export async function findBomCategoryByCategoryIds(data) {
   const res = await request.post(`/aps/salesorder/findBomCategoryByCategoryIds`, data);

+ 461 - 0
src/views/productionPlan/components/factoryProductionScheduling.vue

@@ -0,0 +1,461 @@
+<template>
+  <ele-modal
+    width="100%"
+    :visible.sync="visible"
+    :close-on-click-modal="false"
+    custom-class="ele-dialog-form"
+    title="工厂排产"
+    :maxable="true"
+  >
+    <projectGantt style="height: 500px" :ganttTasks="tableData"></projectGantt>
+    <div style="display: flex">
+      <div class="form-wrapper" style="width: calc(100% - 600px)">
+        <div class="btnList">
+          <el-button type="primary" @click="confirm">保存</el-button>
+          <el-popconfirm
+            class="ele-action"
+            title="确定要移除选中的计划吗?"
+            @confirm="remove()"
+            style="margin-left: 0"
+          >
+            <template v-slot:reference>
+              <el-button
+                type="danger"
+                @click="remove"
+                :disabled="selection.length == 0"
+                >移除行</el-button
+              >
+            </template>
+          </el-popconfirm>
+
+          <el-button
+            type="primary"
+            @click="moveUp"
+            :disabled="selection.length != 1"
+            >上移</el-button
+          >
+          <el-button
+            type="primary"
+            @click="moveDown"
+            :disabled="selection.length != 1"
+            >下移</el-button
+          >
+          <el-button type="primary" @click="exportList">数据导出</el-button>
+        </div>
+        <ele-pro-table
+          width="100%"
+          ref="table"
+          :columns="columns"
+          :datasource="form.scheduledPlans"
+          row-key="code"
+          :selection.sync="selection"
+          height="400px"
+          full-height="calc(100vh - 116px)"
+          :page-size="20"
+        ></ele-pro-table>
+      </div>
+      <div style="width: 600px">
+        <ele-pro-table
+          width="100%"
+          ref="table"
+          :columns="columns1"
+          :datasource="tableList1"
+          row-key="code"
+          height="400px"
+          full-height="calc(100vh - 116px)"
+          :page-size="20"
+        ></ele-pro-table>
+      </div>
+    </div>
+
+    <div slot="footer">
+      <el-button plain @click="cancel">取消</el-button>
+      <el-button type="primary" @click="confirm">确定</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import projectGantt from './gantt/project-gantt.vue';
+  // import dayjs from 'dayjs';
+  import { scheduling } from '@/api/productionPlan/index.js';
+  import { remove } from 'nprogress';
+  import { deepClone } from '@/utils';
+  import { name } from 'ele-admin/lib/ele-pro-table';
+  import { render } from 'nprogress';
+  export default {
+    components: { projectGantt },
+    data() {
+      return {
+        visible: false,
+        form: {},
+        tableList1: [],
+        tableData: [],
+        selection: [],
+        columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            slot: 'selection'
+          },
+          {
+            label: '序号',
+            prop: 'serialNumber',
+            width: 55,
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'mesStatusName',
+            label: '计划状态',
+            align: 'center',
+            minWidth: 160
+          },
+          {
+            prop: 'code',
+            label: '计划编号',
+            align: 'center',
+            minWidth: 160
+          },
+          {
+            prop: 'productionOrderNumber',
+            label: '生产订单号',
+            align: 'center',
+            minWidth: 160
+          },
+          {
+            prop: 'productionLineName',
+            label: '产线名称',
+            align: 'center',
+            minWidth: 160
+          },
+          {
+            prop: 'productionLineRhythm',
+            label: '产线节拍',
+            align: 'center',
+            minWidth: 160
+          },
+          {
+            prop: 'startTime',
+            label: '计划开始时间',
+            align: 'center',
+            width: 150,
+            showOverflowTooltip: true
+          },
+
+          {
+            prop: 'endTime',
+            label: '计划结束时间',
+            align: 'center',
+            width: 150,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'deliveryTime',
+            label: '交期',
+            align: 'center',
+            minWidth: 150
+          },
+          {
+            prop: 'batchNo',
+            label: '批次号',
+            align: 'center',
+            minWidth: 140
+          },
+
+          {
+            prop: 'productCode',
+            label: '编码',
+            align: 'center',
+
+            minWidth: 140
+          },
+          {
+            prop: 'productName',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            minWidth: 150,
+            showOverflowTooltip: true
+          },
+
+          {
+            prop: 'brandNo',
+            label: '牌号',
+            minWidth: 150,
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'model',
+            minWidth: 150,
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'productionType',
+            label: '生产类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140
+          },
+          {
+            prop: 'productNum',
+            label: '计划数量',
+            minWidth: 140,
+            align: 'center'
+          },
+          {
+            prop: 'unit',
+            label: '单位',
+            minWidth: 140,
+            align: 'center'
+          },
+          {
+            prop: 'assignmentName',
+            label: '作业名称',
+            minWidth: 140,
+            align: 'center'
+          },
+          {
+            prop: 'contractor',
+            label: '承制单位',
+            minWidth: 140,
+            align: 'center'
+          },
+          {
+            prop: 'planType',
+            label: '计划类别',
+            minWidth: 140,
+            align: 'center'
+          },
+          {
+            prop: 'produceType',
+            label: 'BOM类型',
+            align: 'center',
+            width: 120,
+            formatter: (row) => {
+              if (row.produceType == 1) {
+                return 'PBOM';
+              }
+              if (row.produceType == 2) {
+                return 'MBOM';
+              }
+              if (row.produceType == 3) {
+                return 'ABOM';
+              }
+              return '';
+            }
+          },
+          {
+            prop: 'bomCategoryName',
+            label: 'BOM版本',
+            align: 'center',
+            width: 130,
+            showOverflowTooltip: true,
+            formatter: (row) => {
+              if (row.bomCategoryName) {
+                return `${row.bomCategoryName} (V${row.bomCategoryVersions}.0)`;
+              }
+              return '';
+            }
+          },
+
+          {
+            prop: 'produceRoutingName',
+            label: '工艺路线',
+            align: 'center',
+            width: 140,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'priority',
+            label: '优先级',
+            align: 'center',
+            minWidth: 120,
+            slot: 'priority',
+            sortable: 'custom'
+          }
+        ],
+        columns1: [
+          {
+            prop: 'controlName',
+            label: '工序名称',
+            align: 'center'
+          },
+          {
+            prop: 'cycleTime',
+            label: '工序节拍',
+            align: 'center'
+          },
+          {
+            prop: 'teamName',
+            label: '班组名称',
+            align: 'center'
+          },
+
+          {
+            prop: 'orderStartTime',
+            label: '订单开始时间',
+            align: 'center'
+          },
+
+          {
+            prop: 'orderEndTime',
+            label: '订单结束时间',
+            align: 'center'
+          }
+        ]
+      };
+    },
+    methods: {
+      async open(row) {
+        let data = await scheduling({
+          factoryId: '1916747066214232065',
+          productionLineIds: [
+            ' 1980535365206806530',
+            '1980535365206806531',
+            '1980535365206806532',
+            '1980535365206806533',
+            '1980535365206806534',
+            '1980535365206806535'
+          ],
+          startSchedulingDate: '2025-11-13 00:00:00',
+          endSchedulingDate: '2025-12-12 23:59:59',
+          schedulingCode: 'DEBUG-MULTI-LINE',
+          schedulingName: '调试排产-多产线多班组',
+          productionPlanIds: [
+            1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011,
+            1012, 1013, 1014, 1015
+          ]
+        });
+        this.form = data;
+        this.tableData = [];
+
+        this.initGanttFlatRows(
+          data.planGanttList[1].resourceList[0].resourceList
+        );
+        console.log(this.tableData);
+        this.visible = true;
+      },
+
+      swap(index1, index2) {
+        let temp1 = deepClone(this.form.scheduledPlans[index1]);
+        let temp2 = deepClone(this.form.scheduledPlans[index2]);
+        this.$set(this.form.scheduledPlans, index1, temp2);
+        this.$set(this.form.scheduledPlans, index2, temp1);
+        this.indexSort();
+      },
+      indexSort() {
+        this.form.scheduledPlans.forEach((item, index) => {
+          this.$set(this.form.scheduledPlans[index], 'serialNumber', index + 1);
+        });
+      },
+      remove() {
+        let codeS = this.selection.map((item) => item.code);
+        this.form.scheduledPlans = this.form.scheduledPlans.filter(
+          (item) => !codeS.includes(item.code)
+        );
+        this.indexSort();
+      },
+      moveUp() {
+        let index = this.selection[0].serialNumber - 1;
+        if (index > 0) {
+          this.swap(index, index - 1);
+        }
+      },
+
+      moveDown() {
+        let index = this.selection[0].serialNumber - 1;
+        if (index < this.form.scheduledPlans.length - 1) {
+          this.swap(index, index + 1);
+        }
+      },
+      initGanttFlatRows(data, parentId) {
+        data.forEach((item) => {
+          if (item.type == 1) {
+            item['id'] = item.code;
+          } else {
+            item['id'] = parentId + item.code;
+          }
+          this.tableData.push({
+            id: item['id'],
+            code: item.code,
+            type: item.type,
+            name: item.productName ? item.code + item.productName : item.name,
+            text: item.productName || item.name,
+            executor: item.productName || item.name,
+            render: item.type != 1 ? 'split' : '',
+            start_date: item.type == 1 ? item.startTime + ':00' : '', // 开始时间-gantt必填字段
+            end_date: item.type == 1 ? item.endTime + ':00' : '' // 开始时间-gantt必填字段
+          });
+          if (item.resourcePeriods && item.type != 1) {
+            item.resourcePeriods.forEach((item1) => {
+              this.tableData.push({
+                code: item.code,
+                type: item.type,
+                name: item.name,
+                parent: item['id'],
+                id: item['id'] + item1.startTime,
+                text: item.name,
+                executor: item.name,
+                start_date: item1.startTime + ':00', // 开始时间-gantt必填字段
+                end_date: item1.endTime + ':00' // 开始时间-gantt必填字段
+              });
+            });
+          }
+
+          if (item.resourceList) {
+            this.initGanttFlatRows(item.resourceList, item['id']);
+          }
+          return this.tableData;
+        });
+        // return ganttFlatRows;
+      },
+      // 添加班组行合并
+      // addDuplicateCount(arr) {
+      //   const elementCount = {}; // 用于存储元素及其出现次数
+      //   arr.forEach((element, index) => {
+      //     if (elementCount[element.shiftGroupCode] === undefined) {
+      //       // 第一次出现,记录索引
+      //       elementCount[element.shiftGroupCode] = { index, count: 1 };
+      //     } else {
+      //       // 不是第一次出现,增加计数
+      //       elementCount[element.shiftGroupCode].count++;
+      //     }
+      //   });
+      //   arr.forEach((item, index) => {
+      //     if (elementCount[item.shiftGroupCode]?.index == index) {
+      //       item.groupRowSpan = elementCount[item.shiftGroupCode].count;
+      //     }
+      //   });
+      //   return arr;
+      // },
+
+      confirm() {
+        this.visible = false;
+      },
+      cancel() {
+        this.form = {};
+        this.visible = false;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .btnList {
+    button {
+      margin: 10px;
+    }
+  }
+</style>

+ 7 - 0
src/views/productionPlan/components/gantt/gantt.js

@@ -0,0 +1,7 @@
+import {gantt} from 'dhtmlx-gantt';
+
+export class Gantt {
+  constructor() {
+    return gantt;
+  }
+}

+ 378 - 0
src/views/productionPlan/components/gantt/project-gantt.vue

@@ -0,0 +1,378 @@
+<template>
+  <div ref="gantt_scrollbar" class="gantt-box">
+    <div ref="gantt" class="gantt-container"></div>
+  </div>
+</template>
+<script>
+  import { Gantt } from './gantt';
+  import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
+  let gantt = new Gantt();
+  let i = 0;
+  export default {
+    name: 'gantt',
+    props: {
+      ganttTasks: {
+        type: Array,
+        default: () => {
+          return [];
+        }
+      }
+    },
+    watch: {
+      ganttTasks: {
+        handler(val) {
+          this.initData();
+        },
+        deep: true
+      }
+    },
+    data() {
+      return {
+        ganttData: []
+      };
+    },
+    created() {
+      console.log(gantt);
+    },
+    mounted() {
+      this.initGantt(); // gantt初始化配置
+      this.initData(); // 数据format以及gantt reload
+    },
+
+    methods: {
+      initGantt() {
+        let _this = this;
+        // 在初始化前设置配置项
+        // gantt只读
+        //设置图表区域的日期坐标最大值 var date = new Date(dateString.replace(/-/,"/"))
+        //  gantt.config.start_date = new Date("2020-04-08".replace(/-/,"/")) ;
+        //gantt.config.end_date = new Date("2020-04-18".replace(/-/,"/")) ; //结束时间需要+1天
+        gantt.config.readonly = true;
+        gantt.config.select_task = false;
+        gantt.config.autofit = false;
+        gantt.config.xml_date = '%Y-%m-%d %H:%i:%s';
+        gantt.config.show_grid = false;
+        // //设置x轴日期
+        // gantt.config.scale_unit = 'day'
+        // gantt.config.step = 1
+        // gantt.config.date_scale = '星期' + '%D'
+        // gantt.config.columns = [
+        //   {
+        //     name: 'name',
+        //     width: 250,
+        //     label: '名称',
+        //     align: 'left',
+        //     // tree: true,
+        //     // open: true,
+
+        //     // onrender: function (task, node) {
+        //     //   if (task.rowSpan) {
+        //     //     node.parentNode.style.height = `${15 * task.rowSpan}px`;
+        //     //     node.childNodes[
+        //     //       node.childNodes.length - 1
+        //     //     ].style.lineHeight = `${15 * task.rowSpan}px`;
+        //     //   } else {
+        //     //     node.style.display = 'none';
+        //     //     node.parentNode.style.display = 'none';
+        //     //   }
+        //     // },
+        //     template: function (obj) {
+        //       return `
+        //          <div style="width:250px;text-overflow:ellipsis;overflow: hidden;">${
+        //            obj.name || ''
+        //          }</div>
+        //       `;
+        //       // <div class="gantt-span-${obj.level}" style="width:130px;text-overflow:ellipsis;overflow: hidden;">${obj.name}</div>
+        //     }
+        //   }
+        // ];
+        let daysStyle = function (date) {
+          let todayDate = new Date().setHours(0, 0, 0, 0);
+          let paramsDate = new Date(date).setHours(0, 0, 0, 0);
+          if (todayDate === paramsDate) {
+            return 'today';
+          }
+          if (date.getDay() === 0 || date.getDay() === 6) {
+            return 'weekend';
+          }
+          return '';
+        };
+        gantt.config.scales = [
+          { unit: 'month', step: 1, format: '%Y/%M',css:()=>'month' },
+          { unit: 'day', step: 1, date:'%d'+ ' 星期' + '%D',css: daysStyle }
+        ];
+        //这里在时间轴表格内的cell上添加class
+        gantt.templates.timeline_cell_class = function (item, date) {
+          if (date.getDay() == 0 || date.getDay() == 6) {
+            return 'weekend';
+          }
+        };
+        // 条形图里的文字
+        gantt.templates.task_text = function (start, end, task) {
+          return `<span gantt_task_content-id="${task.id}" style='text-align:left;color:#000;display:inline-block;'>&nbsp;&nbsp;${task.title}:${task.name}</span>`;
+        };
+        // 条形图自定义class
+        gantt.templates.task_class = function (start, end, task) {
+          return `task-222`;
+        };
+
+        gantt.config.scale_height = 60;
+        gantt.config.task_height = 30;
+        gantt.config.row_height = 30;
+        gantt.config.min_column_width = 25;
+        gantt.config.show_task_cells = false;
+        gantt.plugins({
+          marker: true
+        });
+        let today = new Date();
+        gantt.addMarker({
+          start_date: today,
+          css: 'today',
+          text: ''
+        });
+        gantt.plugins({
+          tooltip: true
+        });
+        // 自定义tooltip内容
+        gantt.templates.tooltip_text = function (start, end, task) {
+          const t = gantt;
+          return `<div style="font-size:14px;padding: 0 10px;width:250px;line-height:20px;white-space:normal;">
+		<p style="font-weight:bold;margin: 8px 0;">${task.text}</p>
+
+	</div>`;
+        };
+        gantt.attachEvent('onTaskClick', function (id, e) {
+          let box = document.querySelector('.gantt_tooltip');
+          if (box) {
+            box.parentNode.removeChild(box);
+          }
+          let classList = e.target.className.split(' ');
+          // 这里自定义了展开/收起树形的点击事件
+          if (classList.includes('gantt_open')) {
+            gantt.open(id); // 展开
+            return true;
+          }
+          if (classList.includes('gantt_close')) {
+            gantt.close(id); // 收起
+            return true;
+          }
+          // 这里是其他的点击事件
+          let task = _this.ganttData.find((item) => {
+            return item.id == id;
+          });
+          if (task.taskType === 'stage') {
+            // toDo()
+            return true;
+          }
+        });
+        gantt.i18n.setLocale('cn');
+
+        // 初始化gantt
+        gantt.init(this.$refs.gantt);
+        // 绑定数据
+        gantt.parse({
+          data: [], // 时间轴上横条的数据
+          link: [] // 连接线的数据
+        });
+      },
+      reload() {
+        console.log('重新渲染', this.ganttData);
+        gantt.clearAll(); // 从甘特图中删除所有任务和其他元素(包括标记)
+        this.ganttData = JSON.parse(JSON.stringify(this.ganttData));
+        gantt.parse({
+          data: this.ganttData,
+          link: []
+        }); // 数据解析
+        gantt.render(); // 呈现整个甘特图
+      },
+      getGanttData() {
+        // toDo
+        // formatGanttData()
+
+        this.ganttData = this.ganttTasks;
+        this.reload();
+      },
+      initData() {
+        this.ganttData = this.ganttTasks.map((current, ind, arry) => {
+          let newObj = {};
+
+          if (current.type == 1) {
+            newObj = Object.assign({}, current, {
+              color: '#02a7f0',
+              title: '计划'
+            });
+          } else if (current.type == 2) {
+            newObj = Object.assign({}, current, {
+              color: '#fec0dc',
+              title: '工序'
+            });
+          } else if (current.type == 3) {
+            newObj = Object.assign({}, current, {
+              color: '#62ddd4',
+              title: '工位'
+            });
+          } else if (current.type == 4) {
+            //计划节点
+            newObj = Object.assign({}, current, {
+              color: '#d1a6ff',
+              title: '计划节点'
+            });
+          } else if (current.type == 5) {
+            //任务
+            newObj = Object.assign({}, current, {
+              color: '#0e85fa',
+              title: '任务'
+            });
+          } else {
+            //默认色
+            newObj = Object.assign({}, current, {
+              color: '#62ddd4',
+              title: '计划'
+            });
+          }
+          return newObj;
+        });
+        this.$nextTick(() => {
+          let addTop = 0;
+          this.ganttTasks.forEach((item, index) => {
+            let taskElement = document.querySelector(
+              `[data-task-id="${item.id}"]`
+            );
+            let taskLine = document.querySelector(
+              `.gantt_task_line[data-task-id="${item.id}"]`
+            );
+            let taskContent = document.querySelector(
+              `[gantt_task_content-id="${item.id}"]`
+            );
+            if (taskElement) {
+              if (index != 0) {
+                taskElement.style.top =
+                  parseInt(taskElement.style.top) + addTop + 'px';
+                if (item.type == 1) {
+                  addTop += 15;
+                  taskElement.style.height = '40px';
+                }
+              }
+            }
+            if (taskLine) {
+              if (index != 0) {
+                if (item.type != 1) {
+                  taskLine.style.top = parseInt(taskLine.style.top) - 2 + 'px';
+                } else {
+                  taskLine.style.top =
+                    parseInt(taskLine.style.top) + addTop - 2 + 'px';
+                }
+              } else {
+                taskLine.style.top = 0 + 'px';
+              }
+            }
+            if (taskContent) {
+              console.log(taskContent.style)
+              // taskContent.parentNode.style.left = -taskContent.clientWidth+ 'px';
+              // taskContent.parentNode.style.textAlign = 'left'
+            }
+          });
+        });
+
+        this.reload();
+      }
+    },
+    activated() {
+      console.log(gantt, i++);
+      gantt.render(); // 呈现整个甘特图
+    }
+  };
+</script>
+<style lang="scss" scoped>
+  .gantt-box {
+    width: calc(100%);
+    height: calc(100%);
+  }
+  .gantt-container {
+    width: calc(100%);
+    height: calc(100%);
+  }
+
+  /* 周末高亮 */
+  :deep(.weekend) {
+    background: #f8f8f8;
+    color: #676767 !important;
+    // height: 47px!important;
+  }
+
+  /* 今日高亮 */
+  :deep(.gantt_task_scale) {
+    .gantt_scale_line > .gantt_scale_cell {
+      color: #333333;
+
+      &.today {
+        background: #72b9ff;
+        color: #ffffff;
+      }
+    }
+  }
+  /* 去掉文件夹图标 */
+  :deep(.gantt_tree_icon) {
+    &.gantt_folder_closed,
+    &.gantt_file,
+    &.gantt_folder_open {
+      display: none;
+    }
+  }
+  /* 把展开收起图标替换成element-ui的icon */
+  /* 这里没有找到在js中配置图标的方法,为省事,使用css */
+  /* 扒了element-ui的icon样式表 */
+  :deep(.gantt_tree_icon.gantt_open) {
+    background-image: none;
+    font-family: element-icons !important;
+    speak: none;
+    font-style: normal;
+    font-weight: 400;
+    font-variant: normal;
+    text-transform: none;
+    vertical-align: baseline;
+    display: inline-block;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    &::before {
+      content: '\e6df';
+    }
+  }
+  :deep(.gantt_tree_icon.gantt_close) {
+    background-image: none;
+    font-family: element-icons !important;
+    speak: none;
+    font-style: normal;
+    font-weight: 400;
+    font-variant: normal;
+    text-transform: none;
+    vertical-align: baseline;
+    display: inline-block;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    &::before {
+      content: '\e6e1';
+    }
+  }
+  :deep(.gantt_cell) {
+    border-right: 1px solid #ebebeb;
+  }
+  :deep(.task-222) {
+    height: 30px !important;
+    // top: 0px !important;
+    line-height: 30px !important;
+    border: none !important;
+  }
+  :deep(.gantt_split_parent) {
+    opacity: 0;
+  }
+  :deep(.gantt_task_content) {
+   width: auto;
+  }
+  :deep(.gantt_task_row){
+    border-bottom: none;
+  }
+  :deep(.month){
+    font-size: 18px;
+  }
+</style>

+ 185 - 0
src/views/productionPlan/components/productionScheduling.vue

@@ -0,0 +1,185 @@
+<template>
+  <ele-modal
+    width="600px"
+    :visible.sync="visible"
+    :close-on-click-modal="false"
+    row-key="code"
+    custom-class="ele-dialog-form"
+    :title="'排产条件'"
+    :maxable="true"
+  >
+    <div class="form-wrapper">
+      <el-form
+        :model="formData"
+        ref="form"
+        label-width="140px"
+        class="mt20"
+        :rules="rules"
+      >
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="工厂名称" prop="factoryId">
+              <el-select
+                v-model="formData.factoryId"
+                @change="handleChange"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item of factoryList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="产线名称" prop="productionLineId">
+              <el-select
+                v-model="formData.productionLineIds"
+                style="width: 100%"
+                multiple
+              >
+                <el-option
+                  v-for="item of productionLineList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="排产开始日期" prop="startSchedulingDate">
+              <el-date-picker
+                style="width: 100%"
+                v-model="formData.startSchedulingDate"
+                align="right"
+                type="date"
+                placeholder="选择日期"
+                format="yyyy-MM-dd"
+               value-format="yyyy-MM-dd HH:mm:ss"
+                :picker-options="{
+                  disabledDate(time) {
+                    return time.getTime() < Date.now();
+                  }
+                }"
+              >
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="排产结束日期" prop="endSchedulingDate">
+              <el-date-picker
+                style="width: 100%"
+                v-model="formData.endSchedulingDate"
+                align="right"
+                type="date"
+                placeholder="选择日期"
+                format="yyyy-MM-dd"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                :picker-options="{
+                  disabledDate(time) {
+                    return (
+                      time.getTime() <
+                      new Date(formData.startSchedulingDate).getTime()
+                    );
+                  }
+                }"
+              >
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="排产编号" prop="SchedulingCode">
+              <el-input
+                v-model="formData.SchedulingCode"
+                placeholder=" "
+                :disabled="true"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="排产名称" prop="SchedulingName">
+              <el-input
+                v-model="formData.SchedulingName"
+                placeholder="请输入"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+    <div slot="footer">
+      <el-button plain @click="cancel">取消</el-button>
+      <el-button type="primary" @click="confirm">确定</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import { getFactoryList, listFactoryLineByFactoryId } from '@/api/saleOrder';
+  import { getCode } from '@/api/codeManagement';
+  export default {
+    components: {},
+    data() {
+      return {
+        visible: false,
+        factoryList: [],
+        productionLineList: [],
+        formData: {
+          startSchedulingDate: '',
+          endSchedulingDate: '',
+          SchedulingCode: '',
+          SchedulingName: '',
+          factoryId: '',
+          productionLineIds: []
+        },
+        rules: {
+          startSchedulingDate: [
+            { required: true, message: '请选择排产开始日期', trigger: 'change' }
+          ],
+          endSchedulingDate: [
+            { required: true, message: '请选择排产结束日期', trigger: 'change' }
+          ],
+          SchedulingName: [
+            { required: true, message: '请输入排产名称', trigger: 'blur' }
+          ]
+        }
+      };
+    },
+    methods: {
+      async open() {
+        this.visible = true;
+        this.formData.SchedulingCode = await getCode(
+          'factory_productionScheduling_code'
+        );
+        this.getFactoryList();
+      },
+      async getFactoryList() {
+        this.factoryList = await getFactoryList();
+      },
+      async handleChange() {
+        this.productionLineList = await listFactoryLineByFactoryId([
+          this.formData.factoryId
+        ]);
+      },
+      confirm() {
+        this.$refs.form.validate((valid) => {
+          if (valid) {
+            this.$emit('success', this.formData);
+            this.cancel();
+          }
+        });
+      },
+      cancel() {
+        this.formData = {};
+        this.visible = false;
+        this.$refs.form.resetFields();
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped></style>

+ 81 - 8
src/views/productionPlan/index.vue

@@ -95,7 +95,10 @@
         <el-dropdown
           trigger="click"
           @command="homogeneityInspect"
-          v-if="$hasPermission('aps:productionpaln:homogeneityInspect')"
+          v-if="
+            $hasPermission('aps:productionpaln:homogeneityInspect') &&
+            activeName != 'released'
+          "
         >
           <el-button size="mini" type="success">齐套性检查</el-button>
           <el-dropdown-menu slot="dropdown">
@@ -104,6 +107,13 @@
             <el-dropdown-item command="4"> 受托件 </el-dropdown-item>
           </el-dropdown-menu>
         </el-dropdown>
+        <el-button
+          type="primary"
+          size="mini"
+          @click="productionSchedulingSuccess"
+          >工厂排产</el-button
+        >
+
         <el-button
           type="primary"
           size="mini"
@@ -150,6 +160,15 @@
           v-if="$hasPermission('aps:productionplan:changerequest')"
           >变更申请</el-button
         >
+        <!-- <el-button
+          class="my-btn"
+          size="mini"
+          v-if="$hasPermission('aps:productionplan:changerequest')"
+          >补料申请</el-button
+        > -->
+        <!-- <el-button type="primary" size="mini" @click="productionPreparations"
+          >生产前准备</el-button
+        > -->
         <el-button
           type="info"
           icon="el-icon-upload2"
@@ -166,7 +185,8 @@
         size="mini"
         @tab-click="handleSele"
       >
-        <el-tab-pane label="未发布" name="first"></el-tab-pane>
+        <el-tab-pane label="待排产" name="first"></el-tab-pane>
+        <el-tab-pane label="待发布" name="released"></el-tab-pane>
         <el-tab-pane label="已发布" name="second"></el-tab-pane>
         <el-tab-pane label="已变更" name="change"></el-tab-pane>
       </el-tabs>
@@ -372,6 +392,14 @@
           >
             流程
           </el-link>
+
+          <el-link
+            type="primary"
+            :underline="false"
+            @click="productionPreparations(row)"
+          >
+            生产前准备
+          </el-link>
         </template>
       </ele-pro-table>
     </el-card>
@@ -381,7 +409,13 @@
     <mergeDialog ref="mergeRef" @success="reload"></mergeDialog>
 
     <unpackDetails ref="DetailsRef"></unpackDetails>
-
+    <factoryProductionScheduling
+      ref="factoryProductionSchedulingRef"
+    ></factoryProductionScheduling>
+    <productionScheduling
+      ref="productionSchedulingRef"
+      @success="productionSchedulingSuccess"
+    ></productionScheduling>
     <homogeneityInspectDialog
       ref="homogeneityInspectDialog"
       @success="reload"
@@ -411,6 +445,8 @@
     />
 
     <processDetail ref="processDetailRef" />
+
+    <checkProductionPreparations ref="checkProductionPreparationsRef" />
   </div>
 </template>
 
@@ -439,6 +475,9 @@
   import importDialog from '@/components/upload/import-dialog.vue';
   import { parameterGetByCode } from '@/api/mainData/index';
   import processDetail from './components/detail/processDetail.vue';
+  import factoryProductionScheduling from './components/factoryProductionScheduling.vue';
+  import productionScheduling from './components/productionScheduling.vue';
+  import checkProductionPreparations from './components/checkProductionPreparations.vue';
 
   import {
     findBomCategoryByCategoryId,
@@ -456,7 +495,10 @@
       homogeneityInspectDialog,
       homogeneityInspectInstallDialog,
       importDialog,
-      processDetail
+      processDetail,
+      factoryProductionScheduling,
+      productionScheduling,
+      checkProductionPreparations
     },
     props: {
       timeDimensionPlanType: { type: Number, default: 1 },
@@ -961,11 +1003,19 @@
           }
         ];
       },
+      productionSchedulingOpen() {
+        this.$refs.productionSchedulingRef.open();
+      },
+      productionSchedulingSuccess(data) {
+        // data.productionPlanIds = this.selection.map((i) => i.id);
+        this.$refs.factoryProductionSchedulingRef.open(data);
+      },
+
       handleSele(e) {
-        if (e.index === '1') {
-          this.isshow = false;
-        } else {
+        if (e.index === '0') {
           this.isshow = true;
+        } else {
+          this.isshow = false;
         }
       },
 
@@ -1281,8 +1331,12 @@
         return this.selection.findIndex((item) => item.id == row.id) >= 0;
       },
 
+      //生产前准备
+      productionPreparations(item) {
+        this.$refs.checkProductionPreparationsRef.open(item);
+      },
+
       goDetail({ id }) {
-        console.log('尽然详情');
         this.$router.push({
           path: '/productionPlan/detail',
           query: { id }
@@ -1405,6 +1459,25 @@
     margin-bottom: 6px;
   }
 
+  .my-btn {
+    background-color: #d8701b !important;
+    border-color: #d8701b !important;
+    color: #fff !important;
+  }
+
+  .my-btn:hover,
+  .my-btn:focus {
+    background-color: #d8701b !important;
+    border-color: #d8701b !important;
+    color: #fff !important;
+  }
+
+  .my-btn:active {
+    background-color: #d8701b !important;
+    border-color: #d8701b !important;
+    color: #fff !important;
+  }
+
   .check_box {
     width: 100%;
     display: flex;

+ 1 - 1
vue.config.js

@@ -41,7 +41,7 @@ module.exports = {
         // target: 'http://192.168.1.251:18186',
         // target: 'http://192.168.1.102:18086',
 
-        target: 'http://192.168.1.125:18086',
+        target: 'http://192.168.1.251:18086',
 
         // target: 'http://192.168.1.116:18086',