Просмотр исходного кода

修改工厂排产界面和逻辑

695593266@qq.com 1 месяц назад
Родитель
Сommit
45d990c65a

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

@@ -489,3 +489,28 @@ export async function teamSchedulingGanttChart(data) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+/**
+ * 生产计划工厂排程
+ */
+export async function factorySchedulingGanttChart(data) {
+  const res = await request.post(
+    '/aps/scheduling/planSchedulingGanttChart',
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 根据计划id查询多个生产计划
+ */
+export async function getPlansByIds(ids) {
+  const res = await request.post('/aps/productionplan/listByIds', ids);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 66 - 7
src/views/productionPlan/components/factoryProductionScheduling.vue

@@ -12,7 +12,22 @@
       :ganttTasks="tableData"
       :planList="form.scheduledPlans || []"
       :calendarSourceData="calendarSourceData"
-    ></projectGantt>
+      :defaultTab="'factory'"
+      @tab-change="handleTabChange"
+    >
+      <template v-slot:toolbar>
+        <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          size="mini"
+          value-format="yyyy-MM-dd"
+          range-separator="至"
+          start-placeholder="计划开始时间"
+          end-placeholder="计划结束时间"
+          @change="handleDateChange"
+        ></el-date-picker>
+      </template>
+    </projectGantt>
     <div style="display: flex">
       <div class="form-wrapper" style="width: calc(100% - 600px)">
         <!-- <div class="btnList">
@@ -85,12 +100,17 @@
   // import dayjs from 'dayjs';
   import {
     scheduling,
-    teamSchedulingGanttChart
+    teamSchedulingGanttChart,
+    factorySchedulingGanttChart
   } from '@/api/productionPlan/index.js';
   import { deepClone } from '@/utils';
   export default {
     components: { projectGantt },
     data() {
+      const now = new Date();
+      const startDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
+      const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
+      const endDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
       return {
         visible: false,
         form: {},
@@ -98,6 +118,9 @@
         tableData: [],
         calendarSourceData: [],
         selection: [],
+        planIds: [],
+        activeTab: 'factory',
+        dateRange: [startDate, endDate],
         columns: [
           {
             width: 45,
@@ -326,14 +349,50 @@
     methods: {
       async open(row) {
         let planIds = row.map((item) => item.id);
-        let data = await teamSchedulingGanttChart({
-          planIds
-        });
-        this.calendarSourceData = data || [];
-        this.tableData = this.initGanttFlatRows(data || []);
+        this.planIds = planIds;
+        this.activeTab = 'factory';
+        const now = new Date();
+        const startDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
+        const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
+        const endDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
+        this.dateRange = [startDate, endDate];
+        await this.loadGanttData();
         this.visible = true;
       },
 
+      async loadGanttData() {
+        if (this.activeTab === 'factory') {
+          let params = {
+            planIds: this.planIds
+          };
+          if (this.dateRange && this.dateRange.length === 2) {
+            params.startDate = this.dateRange[0];
+            params.endDate = this.dateRange[1];
+          }
+          let data = await factorySchedulingGanttChart(params);
+          this.calendarSourceData = data || [];
+          this.tableData = this.initGanttFlatRows(data || []);
+        } else {
+          let data = await teamSchedulingGanttChart({
+            planIds: this.planIds
+          });
+          this.calendarSourceData = data || [];
+          this.tableData = this.initGanttFlatRows(data || []);
+        }
+      },
+
+      async handleTabChange(tabKey) {
+        this.activeTab = tabKey;
+        await this.loadGanttData();
+      },
+
+      async handleDateChange(val) {
+        this.dateRange = val;
+        if (this.activeTab === 'factory') {
+          await this.loadGanttData();
+        }
+      },
+
       swap(index1, index2) {
         let temp1 = deepClone(this.form.scheduledPlans[index1]);
         let temp2 = deepClone(this.form.scheduledPlans[index2]);

+ 1 - 0
src/views/productionPlan/components/gantt/project-gantt.constants.js

@@ -1,6 +1,7 @@
 export const DAY_MS = 24 * 60 * 60 * 1000;
 
 export const RESOURCE_TABS = Object.freeze([
+  { key: 'factory', label: '工厂' },
   { key: 'team', label: '班组' },
   { key: 'line', label: '产线' }
 ]);

+ 2 - 2
src/views/productionPlan/components/gantt/project-gantt.utils.js

@@ -134,7 +134,7 @@ export function buildCalendarTooltipHtml(entries) {
   `;
 }
 
-export function buildTooltipHtml(task, planMetaByCode = {}) {
+export function buildTooltipHtml(task, planMetaByCode = {}, activeTab = '') {
   const planMeta = planMetaByCode[task.code] || {};
   const quantityText = [
     task.qty ?? task.productNum ?? planMeta.productNum,
@@ -190,7 +190,7 @@ export function buildTooltipHtml(task, planMetaByCode = {}) {
       value: planMeta.batchNo || ''
     },
     {
-      label: '班组',
+      label: activeTab === 'factory' ? '工厂' : '班组',
       value:
         task.teamName || planMeta.teamName || planMeta.executionTeamName || ''
     }

+ 59 - 19
src/views/productionPlan/components/gantt/project-gantt.vue

@@ -16,6 +16,7 @@
         >
           工作日历
         </el-button>
+        <slot name="toolbar"></slot>
       </div>
       <el-button
         size="mini"
@@ -124,9 +125,24 @@
         default: () => {
           return [];
         }
+      },
+      defaultTab: {
+        type: String,
+        default: 'team'
+      },
+      dateRange: {
+        type: Array,
+        default: () => {
+          return null;
+        }
       }
     },
     watch: {
+      defaultTab(val) {
+        if (val && val !== this.activeTab) {
+          this.activeTab = val;
+        }
+      },
       ganttTasks: {
         handler(val) {
           this.initData();
@@ -142,12 +158,20 @@
       },
       viewMode() {
         this.syncGanttVisibility();
+      },
+      dateRange: {
+        handler(val) {
+          if (val && val.length === 2) {
+            this.initData();
+          }
+        },
+        deep: true
       }
     },
     data() {
       return {
         ganttData: [],
-        activeTab: 'team',
+        activeTab: this.defaultTab,
         selectedResource: '',
         keyword: '',
         viewMode: 'gantt',
@@ -393,19 +417,25 @@
         if (!this.calendarFilteredTasks.length) {
           return [];
         }
-        const firstTask = this.calendarFilteredTasks[0];
-        let minDate = startOfMonth(firstTask.startDate);
-        let maxDate = endOfMonth(firstTask.endDate);
-        this.calendarFilteredTasks.forEach((item) => {
-          const start = startOfMonth(item.startDate);
-          const end = endOfMonth(item.endDate);
-          if (start.getTime() < minDate.getTime()) {
-            minDate = start;
-          }
-          if (end.getTime() > maxDate.getTime()) {
-            maxDate = end;
-          }
-        });
+        let minDate, maxDate;
+        if (this.dateRange && this.dateRange.length === 2) {
+          minDate = startOfDay(parseDate(this.dateRange[0]));
+          maxDate = startOfDay(parseDate(this.dateRange[1]));
+        } else {
+          const firstTask = this.calendarFilteredTasks[0];
+          minDate = startOfMonth(firstTask.startDate);
+          maxDate = endOfMonth(firstTask.endDate);
+          this.calendarFilteredTasks.forEach((item) => {
+            const start = startOfMonth(item.startDate);
+            const end = endOfMonth(item.endDate);
+            if (start.getTime() < minDate.getTime()) {
+              minDate = start;
+            }
+            if (end.getTime() > maxDate.getTime()) {
+              maxDate = end;
+            }
+          });
+        }
         const days = [];
         let current = new Date(minDate);
         while (current.getTime() <= maxDate.getTime()) {
@@ -510,6 +540,7 @@
           return;
         }
         this.activeTab = tabKey;
+        this.$emit('tab-change', tabKey);
       },
       handleResourceSelect(item) {
         this.selectedResource =
@@ -598,7 +629,7 @@
         }
         this.hoverTooltip = {
           visible: true,
-          content: buildTooltipHtml(task, this.planMetaByCode),
+          content: buildTooltipHtml(task, this.planMetaByCode, this.activeTab),
           style: {
             left: `${event.clientX + 16}px`,
             top: `${event.clientY + 16}px`
@@ -915,7 +946,7 @@
         });
         // 自定义tooltip内容
         gantt.templates.tooltip_text = function (start, end, task) {
-          return buildTooltipHtml(task, _this.planMetaByCode);
+          return buildTooltipHtml(task, _this.planMetaByCode, _this.activeTab);
         };
         gantt.attachEvent('onTaskClick', function (id, e) {
           let box = document.querySelector('.gantt_tooltip');
@@ -952,13 +983,22 @@
         });
       },
       reload() {
-        gantt.clearAll(); // 从甘特图中删除所有任务和其他元素(包括标记)
+        gantt.clearAll();
+        if (this.dateRange && this.dateRange.length === 2) {
+          gantt.config.start_date = new Date(this.dateRange[0]);
+          gantt.config.end_date = new Date(this.dateRange[1]);
+          gantt.config.fit_tasks = false;
+        } else {
+          gantt.config.start_date = null;
+          gantt.config.end_date = null;
+          gantt.config.fit_tasks = true;
+        }
         this.ganttData = JSON.parse(JSON.stringify(this.ganttData));
         gantt.parse({
           data: this.ganttData,
           link: []
-        }); // 数据解析
-        gantt.render(); // 呈现整个甘特图
+        });
+        gantt.render();
       },
       getGanttData() {
         // toDo

+ 27 - 1
src/views/productionPlan/components/newFactoryProductionScheduling.columns.js

@@ -139,7 +139,26 @@ export function createSchedulingColumns() {
       label: '计划类别',
       minWidth: 140,
       showOverflowTooltip: true,
-      align: 'center'
+      align: 'center',
+      formatter: (row) => {
+        const planTypeMap = [
+          { label: '内销计划', value: '1' },
+          { label: '外销计划', value: '2' },
+          { label: '预制计划', value: '3' },
+          { label: '改型计划', value: '4' },
+          { label: '返工返修计划', value: '5' }
+        ];
+        const obj = planTypeMap.find((i) => i.value == row.planType);
+        return obj && obj.label;
+      }
+    },
+    {
+      prop: 'dotLineStatus',
+      slot: 'dotLineStatus',
+      label: '布点状态',
+      align: 'center',
+      width: 80,
+      showOverflowTooltip: true
     },
     {
       prop: 'produceType',
@@ -187,6 +206,13 @@ export function createSchedulingColumns() {
       minWidth: 120,
       slot: 'priority',
       sortable: 'custom'
+    },
+    {
+      label: '操作',
+      align: 'center',
+      width: 100,
+      slot: 'action',
+      fixed: 'right'
     }
   ];
 }

+ 121 - 53
src/views/productionPlan/components/newFactoryProductionScheduling.vue

@@ -13,11 +13,27 @@
       :ganttTasks="tableData"
       :planList="planDataList"
       :calendarSourceData="calendarSourceData"
-    ></projectGantt>
+      :defaultTab="'factory'"
+      :dateRange="dateRange"
+      @tab-change="handleTabChange"
+    >
+      <template v-slot:toolbar>
+        <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          size="mini"
+          value-format="yyyy-MM-dd"
+          range-separator="至"
+          start-placeholder="计划开始时间"
+          end-placeholder="计划结束时间"
+          @change="handleDateChange"
+        ></el-date-picker>
+      </template>
+    </projectGantt>
     <div class="scheduling-content">
       <div class="form-wrapper">
         <div class="btnList">
-          <el-button
+          <!-- <el-button
             type="primary"
             size="mini"
             :disabled="entryReadonly"
@@ -32,7 +48,7 @@
             @click="openPlanPicker(3, '添加临时计划')"
           >
             添加临时计划
-          </el-button>
+          </el-button> -->
         </div>
         <!-- <div class="btnList">
           <el-button type="primary" @click="confirm">保存</el-button>
@@ -73,11 +89,31 @@
           :datasource="planDataList"
           row-key="id"
           :selection.sync="selection"
-          @update:selection="handlePlanSelectionChange"
           height="400px"
           full-height="calc(100vh - 116px)"
           :page-size="20"
-        ></ele-pro-table>
+        >
+          <template v-slot:action="{ row }">
+            <!-- <el-button type="text" size="small" @click="handlePublish(row)">
+              发布
+            </el-button> -->
+            <el-link
+              type="primary"
+              :underline="false"
+              @click="handlePublish(row)"
+            >
+              发布
+            </el-link>
+          </template>
+          <template v-slot:dotLineStatus="{ row }">
+            <el-tag type="success" size="mini" v-if="row.dotLineStatus == 1">
+              已布点
+            </el-tag>
+            <el-tag type="danger" size="mini" v-if="row.dotLineStatus == 0">
+              未布点
+            </el-tag>
+          </template>
+        </ele-pro-table>
       </div>
       <div
         v-loading="dotLineLoading"
@@ -149,7 +185,9 @@
   import TaskConfigPanel from './newFactoryProductionScheduling/TaskConfigPanel.vue';
   import {
     getList,
-    teamSchedulingGanttChart
+    teamSchedulingGanttChart,
+    factorySchedulingGanttChart,
+    getPlansByIds
   } from '@/api/productionPlan/index.js';
   import { getTaskListById } from '@/api/materialPlan';
   import { teamPage } from '@/api/mainData';
@@ -182,6 +220,18 @@
       TaskConfigPanel
     },
     data() {
+      const now = new Date();
+      const startDate = `${now.getFullYear()}-${String(
+        now.getMonth() + 1
+      ).padStart(2, '0')}-01`;
+      const lastDay = new Date(
+        now.getFullYear(),
+        now.getMonth() + 1,
+        0
+      ).getDate();
+      const endDate = `${now.getFullYear()}-${String(
+        now.getMonth() + 1
+      ).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
       return {
         visible: false,
         entryReadonly: false,
@@ -194,6 +244,8 @@
         planDataList: [],
         allPlanDataList: [],
         selection: [],
+        activeTab: 'factory',
+        dateRange: [startDate, endDate],
         planPickerVisible: false,
         planPickerTitle: '添加计划',
         planPickerLoading: false,
@@ -442,45 +494,17 @@
 
       // Selected plan detail
       handlePlanSelectionChange(rows) {
-        const selectedRows = Array.isArray(rows) ? rows : [];
-        const currentPlan =
-          selectedRows.length > 0
-            ? selectedRows[selectedRows.length - 1]
-            : null;
-        if (selectedRows.length > 1 && currentPlan) {
-          this.selection = [currentPlan];
-          this.$nextTick(() => {
-            const table = this.$refs.planTable;
-            table?.clearSelection?.();
-            table?.toggleRowSelection?.(currentPlan, true);
-          });
-        }
-        const selectedPlanId =
-          currentPlan && currentPlan.id != null ? String(currentPlan.id) : '';
-        const currentPlanId = String(this.currentPlan?.id || '');
-        if (
-          selectedPlanId &&
-          selectedPlanId === currentPlanId &&
-          (this.dotLineLoading || this.taskList.length > 0)
-        ) {
-          return;
-        }
-        if (!selectedRows.length) {
-          this.currentPlan = null;
-          this.planRoutingPayload = null;
-          this.taskList = [];
-          this.dotLineLoading = false;
-          return;
-        }
-        if (!selectedPlanId) {
-          this.currentPlan = null;
-          this.planRoutingPayload = null;
-          this.taskList = [];
-          this.dotLineLoading = false;
+        this.selection = Array.isArray(rows) ? rows : [];
+      },
+      handlePublish(row) {
+        if (!row.bomCategoryId) {
+          this.$message.warning('该计划没有配置BOM请先配置BOM');
           return;
         }
-        this.currentPlan = currentPlan;
-        this.loadPlanDotLineDetail(currentPlan);
+        this.currentPlan = row;
+        this.selection = [row];
+        this.syncTableSelection('planTable', [row]);
+        this.loadPlanDotLineDetail(row);
       },
       async loadPlanDotLineDetail(currentPlan) {
         this.dotLineLoading = true;
@@ -853,18 +877,47 @@
       },
 
       // Scheduling data refresh
+      async handleTabChange(tabKey) {
+        this.activeTab = tabKey;
+        await this.refreshSchedulingView();
+      },
+
+      async handleDateChange(val) {
+        this.dateRange = val;
+        if (this.activeTab === 'factory') {
+          await this.refreshSchedulingView();
+        }
+      },
+
       async refreshSchedulingView() {
-        const planIds = (this.planDataList || [])
-          .map((item) => item.id)
-          .filter((id) => id != null && id !== '');
-        if (!planIds.length) {
-          this.calendarSourceData = [];
-          this.tableData = [];
-          return;
+        let data;
+        const params = {};
+        if (this.dateRange && this.dateRange.length === 2) {
+          params.startDate = this.dateRange[0];
+          params.endDate = this.dateRange[1];
+        }
+        if (this.activeTab === 'factory') {
+          data = await factorySchedulingGanttChart(params);
+
+          const extractedPlanIds = new Set();
+          (data || []).forEach((item) => {
+            (item.resourcePeriods || []).forEach((period) => {
+              if (period.planId) extractedPlanIds.add(period.planId);
+            });
+            (item.resourceList || []).forEach((resource) => {
+              if (resource.planId) extractedPlanIds.add(resource.planId);
+            });
+          });
+
+          if (extractedPlanIds.size > 0) {
+            const plans = await this.loadPlansByIds(
+              Array.from(extractedPlanIds)
+            );
+            this.planDataList = this.buildPlanTableRows(plans);
+          }
+        } else {
+          data = await teamSchedulingGanttChart(params);
         }
-        const data = await teamSchedulingGanttChart({
-          planIds
-        });
         this.calendarSourceData = data || [];
         this.tableData = this.initGanttFlatRows(data || []);
       },
@@ -908,6 +961,20 @@
         this.dotLineFullscreen = false;
         this.calendarSourceData = [];
         this.tableData = [];
+        this.activeTab = 'factory';
+        const now = new Date();
+        this.dateRange = [
+          `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
+            2,
+            '0'
+          )}-01`,
+          `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
+            2,
+            '0'
+          )}-${String(
+            new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()
+          ).padStart(2, '0')}`
+        ];
         await this.refreshSchedulingView();
 
         this.visible = true;
@@ -1194,6 +1261,7 @@
     display: flex;
     align-items: flex-end;
     gap: 12px;
+    margin-top: 12px;
   }
 
   .form-wrapper {