Ver Fonte

新增计划描点

695593266@qq.com há 2 meses atrás
pai
commit
49519854d8

+ 436 - 0
src/views/productionPlan/components/planDotLine.vue

@@ -0,0 +1,436 @@
+<template>
+  <ele-modal
+    v-if="visible"
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :append-to-body="true"
+    :visible.sync="dialogVisible"
+    :close-on-click-modal="false"
+    width="70%"
+    :maxable="true"
+    :resizable="true"
+    :title="title"
+    @close="onModalClose"
+  >
+    <div class="plan-dot-line">
+      <div class="top-route">
+        <div class="panel-title">工艺路线</div>
+        <el-empty
+          v-if="taskList.length === 0"
+          description="暂无工艺路线"
+        ></el-empty>
+        <div v-else class="route-line">
+          <div
+            v-for="(item, index) in taskList"
+            :key="`route-${item._taskKey}`"
+            class="route-item"
+          >
+            <span
+              class="route-node"
+              :class="{ 'route-node--done': isConfigured(item.dotLineConfig) }"
+            >
+              {{ item.name || item.taskName || `工艺${index + 1}` }}
+            </span>
+            <span v-if="index < taskList.length - 1" class="route-arrow"
+              >→</span
+            >
+          </div>
+        </div>
+      </div>
+      <div class="config-panel">
+        <div class="panel-title">工艺配置</div>
+        <el-empty
+          v-if="taskList.length === 0"
+          description="暂无工艺"
+        ></el-empty>
+        <div v-else class="task-config-table-wrap">
+          <el-table
+            :data="taskList"
+            border
+            size="small"
+            :row-key="getRowKey"
+            max-height="420"
+            class="config-table"
+          >
+            <el-table-column
+              type="index"
+              label="序号"
+              width="48"
+              align="center"
+              header-align="center"
+            />
+            <el-table-column
+              label="工序"
+              min-width="90"
+              show-overflow-tooltip
+              class-name="task-name-cell"
+              align="center"
+              header-align="center"
+            >
+              <template slot-scope="{ row, $index }">
+                <span class="task-name-text">{{
+                  row.name || row.taskName || `工艺${$index + 1}`
+                }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="班组"
+              min-width="130"
+              align="center"
+              header-align="center"
+            >
+              <template slot-scope="{ row }">
+                <el-select
+                  v-model="row.dotLineConfig.teamId"
+                  placeholder="请选择班组"
+                  clearable
+                  filterable
+                  class="config-table-control"
+                >
+                  <el-option
+                    v-for="team in teamOptions"
+                    :key="team.id"
+                    :label="team.name"
+                    :value="team.id"
+                  />
+                </el-select>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="开始时间"
+              min-width="168"
+              align="center"
+              header-align="center"
+            >
+              <template slot-scope="{ row }">
+                <el-date-picker
+                  v-model="row.dotLineConfig.startTime"
+                  type="datetime"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  placeholder="开始时间"
+                  class="config-table-control"
+                  @change="handleTimeChange(row, 'startTime')"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="结束时间"
+              min-width="168"
+              align="center"
+              header-align="center"
+            >
+              <template slot-scope="{ row }">
+                <el-date-picker
+                  v-model="row.dotLineConfig.endTime"
+                  type="datetime"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  placeholder="结束时间"
+                  class="config-table-control"
+                  @change="handleTimeChange(row, 'endTime')"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="类型"
+              min-width="100"
+              align="center"
+              header-align="center"
+            >
+              <template slot-scope="{ row }">
+                <el-select
+                  v-model="row.dotLineConfig.type"
+                  placeholder="请选择类型"
+                  clearable
+                  class="config-table-control"
+                >
+                  <el-option
+                    v-for="type in typeOptions"
+                    :key="type.value"
+                    :label="type.label"
+                    :value="type.value"
+                  />
+                </el-select>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+    <div slot="footer">
+      <el-button size="mini" @click="onModalClose">取消</el-button>
+      <el-button size="mini" type="primary" @click="handleSave">保存</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import { getTaskListById } from '@/api/materialPlan';
+  import { teamPage } from '@/api/mainData';
+  export default {
+    data() {
+      return {
+        visible: false,
+        dialogVisible: false,
+        title: '计划描点',
+        taskList: [],
+        teamOptions: [],
+        currentPlan: null,
+        typeOptions: [
+          { label: '自制', value: '1' },
+          { label: '请托', value: '2' },
+          { label: '委外', value: '3' }
+        ],
+        // 与 typeOptions 一致:自制 / 请托 / 委外
+        TYPE_HOMEMADE: '1',
+        TYPE_ENTRUST: '2',
+        TYPE_OUTSOURCE: '3'
+      };
+    },
+    methods: {
+      getRowKey(row) {
+        return row._taskKey;
+      },
+      async open(data) {
+        this.currentPlan = data || null;
+        await Promise.all([this.loadTaskList(), this.loadTeamOptions()]);
+        this.fillSavedTaskConfig();
+        this.visible = true;
+        this.dialogVisible = true;
+      },
+      async loadTaskList() {
+        if (!this.currentPlan || !this.currentPlan.produceRoutingId) {
+          this.taskList = [];
+          return;
+        }
+        const list = await getTaskListById(this.currentPlan.produceRoutingId);
+        this.taskList = (list || []).map((item, index) => ({
+          ...item,
+          _taskKey: item.id || item.taskId || item.sourceTaskId || index,
+          dotLineConfig: {
+            teamId: '',
+            startTime: '',
+            endTime: '',
+            type: this.TYPE_HOMEMADE
+          }
+        }));
+      },
+      async loadTeamOptions() {
+        try {
+          const res = await teamPage({ size: 500 });
+          const list = res?.list || [];
+          this.teamOptions = list.map((team) => ({
+            id: team.id,
+            name: team.name
+          }));
+        } catch (error) {
+          this.teamOptions = [];
+        }
+      },
+      fillSavedTaskConfig() {
+        const savedList = this.currentPlan?.dotLineTaskList || [];
+        if (!savedList.length) {
+          return;
+        }
+        const map = {};
+        savedList.forEach((item) => {
+          map[item._taskKey] = item.dotLineConfig || {};
+        });
+        this.taskList = this.taskList.map((item) => {
+          const merged = {
+            ...item.dotLineConfig,
+            ...(map[item._taskKey] || {})
+          };
+          if (!merged.type) {
+            merged.type = this.TYPE_HOMEMADE;
+          }
+          return {
+            ...item,
+            dotLineConfig: merged
+          };
+        });
+      },
+      isConfigured(config) {
+        if (!config) {
+          return false;
+        }
+        return !!(
+          config.teamId ||
+          config.startTime ||
+          config.endTime ||
+          config.type
+        );
+      },
+      handleTimeChange(item, changeKey) {
+        const cfg = item?.dotLineConfig || {};
+        if (!cfg.startTime || !cfg.endTime) {
+          return;
+        }
+        const start = new Date(cfg.startTime).getTime();
+        const end = new Date(cfg.endTime).getTime();
+        if (!Number.isNaN(start) && !Number.isNaN(end) && end < start) {
+          this.$message.warning('结束时间不能小于开始时间');
+          this.$set(cfg, changeKey, '');
+        }
+      },
+      handleSave() {
+        const { TYPE_HOMEMADE, TYPE_ENTRUST, TYPE_OUTSOURCE } = this;
+        const taskLabel = (task) => task.name || task.taskName || '当前工艺';
+
+        for (const item of this.taskList) {
+          const cfg = item.dotLineConfig || {};
+          if (!cfg.type) {
+            this.$set(item.dotLineConfig, 'type', TYPE_HOMEMADE);
+          }
+        }
+
+        for (const item of this.taskList) {
+          const cfg = item.dotLineConfig || {};
+          const type = cfg.type || TYPE_HOMEMADE;
+
+          if (type === TYPE_HOMEMADE) {
+            if (!cfg.teamId || !cfg.startTime || !cfg.endTime) {
+              this.$message.warning(
+                `${taskLabel(item)}类型为自制时,需填写班组、开始时间和结束时间`
+              );
+              return;
+            }
+            const start = new Date(cfg.startTime).getTime();
+            const end = new Date(cfg.endTime).getTime();
+            if (Number.isNaN(start) || Number.isNaN(end)) {
+              this.$message.warning(`${taskLabel(item)}开始时间或结束时间无效`);
+              return;
+            }
+            if (end < start) {
+              this.$message.warning(
+                `${taskLabel(item)}结束时间不能小于开始时间`
+              );
+              return;
+            }
+          } else if (type === TYPE_ENTRUST || type === TYPE_OUTSOURCE) {
+            if (cfg.startTime && cfg.endTime) {
+              const start = new Date(cfg.startTime).getTime();
+              const end = new Date(cfg.endTime).getTime();
+              if (!Number.isNaN(start) && !Number.isNaN(end) && end < start) {
+                this.$message.warning(
+                  `${taskLabel(item)}结束时间不能小于开始时间`
+                );
+                return;
+              }
+            }
+          }
+        }
+        if (this.currentPlan) {
+          this.$set(this.currentPlan, 'dotLineTaskList', this.taskList);
+        }
+        this.$emit('save', {
+          plan: this.currentPlan,
+          taskList: this.taskList
+        });
+        this.$message.success('保存成功');
+        this.onModalClose();
+      },
+
+      onModalClose() {
+        this.visible = false;
+        this.dialogVisible = false;
+        this.taskList = [];
+        this.currentPlan = null;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .plan-dot-line {
+    min-height: 360px;
+    display: block;
+  }
+
+  .top-route,
+  .config-panel {
+    border: 1px solid #ebeef5;
+    border-radius: 4px;
+    padding: 12px;
+    background: #fff;
+  }
+
+  .config-panel {
+    margin-top: 12px;
+  }
+
+  .panel-title {
+    font-size: 14px;
+    font-weight: 600;
+    margin-bottom: 10px;
+  }
+
+  .route-line {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    row-gap: 4px;
+    color: #303133;
+  }
+
+  .route-item {
+    display: inline-flex;
+    align-items: center;
+    font-size: 12px;
+  }
+
+  .route-node {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 38px;
+    height: 20px;
+    border-radius: 4px;
+    padding: 0 7px;
+    color: #ffffff;
+    background: #909399;
+    font-size: 12px;
+    font-weight: 600;
+    line-height: 1;
+    box-sizing: border-box;
+  }
+
+  .route-node--done {
+    background: #56bf1d;
+  }
+
+  .route-arrow {
+    margin: 0 5px;
+    color: #909399;
+    font-size: 12px;
+    line-height: 1;
+  }
+
+  .task-config-table-wrap {
+    width: 100%;
+  }
+
+  .config-table ::v-deep .el-table th > .cell,
+  .config-table ::v-deep .el-table td > .cell {
+    font-size: 14px;
+    text-align: center;
+  }
+
+  .config-table ::v-deep .el-table__body .cell {
+    padding-left: 6px;
+    padding-right: 6px;
+  }
+
+  .config-table ::v-deep td.task-name-cell .task-name-text {
+    font-size: 14px;
+    font-weight: 500;
+    line-height: 1.4;
+  }
+
+  .config-table ::v-deep .config-table-control {
+    width: 100% !important;
+  }
+
+  .config-table ::v-deep .config-table-control.el-date-editor {
+    max-width: 100%;
+  }
+</style>

+ 24 - 1
src/views/productionPlan/index.vue

@@ -150,6 +150,10 @@
           v-if="$hasPermission('aps:productionplan:changerequest')"
           >变更申请</el-button
         >
+
+        <el-button type="danger" size="mini" @click="planDotLine"
+          >计划描点</el-button
+        >
         <!-- <el-button
           class="my-btn"
           size="mini"
@@ -434,6 +438,8 @@
       type="plan"
       @update="reload"
     />
+
+    <planDotLine ref="planDotLineRef" />
   </div>
 </template>
 
@@ -463,6 +469,7 @@
   import { parameterGetByCode } from '@/api/mainData/index';
   import processDetail from './components/detail/processDetail.vue';
   import checkProductionPreparations from './components/checkProductionPreparations.vue';
+  import planDotLine from './components/planDotLine.vue';
 
   import {
     findBomCategoryByCategoryId,
@@ -481,7 +488,8 @@
       homogeneityInspectInstallDialog,
       importDialog,
       processDetail,
-      checkProductionPreparations
+      checkProductionPreparations,
+      planDotLine
     },
     props: {
       timeDimensionPlanType: { type: Number, default: 1 },
@@ -1303,6 +1311,21 @@
         }
       },
 
+      planDotLine() {
+        if (this.selection.length == 0) {
+          return this.$message.warning('请选择一个计划!');
+        }
+
+        if (this.selection[0].approvalStatus == 1) {
+          return this.$message.warning('该计划正在审核中!');
+        }
+
+        if (this.selection.length > 1) {
+          return this.$message.warning('计划描点只能选择一条计划!');
+        }
+        this.$refs.planDotLineRef.open(this.selection[0]);
+      },
+
       selectionFilter(row) {
         return this.selection.findIndex((item) => item.id == row.id) >= 0;
       },