瀏覽代碼

并行工序 修改工序报工步骤条

yusheng 4 天之前
父節點
當前提交
acb867cec6
共有 1 個文件被更改,包括 296 次插入44 次删除
  1. 296 44
      src/views/produceOrder/workReport.vue

+ 296 - 44
src/views/produceOrder/workReport.vue

@@ -56,30 +56,88 @@
           </div>
 
           <div class="drawer_content">
-            <el-steps
-              class="process-route-steps"
-              :active="activeIndex"
-              space="20px"
-              align-center
+            <div
+              class="route-flow"
+              :style="{ height: stepsRowHeight + 'px' }"
             >
-              <el-step
-                v-for="(item, index) in routeList"
-                :key="index"
-                @click.native="handIdx(index, item)"
-                :description="desIndex == index ? '此处' : ''"
-                :class="desIndex == index ? 'active' : ''"
-                :status="errorIndex == index ? 'error' : ''"
+              <div
+                v-for="(col, colIndex) in stepColumns"
+                :key="`route-unit-${colIndex}`"
+                class="route-flow-unit"
               >
-                <template slot="title">
-                  <div class="step-title">
-                    <div>{{ item.taskTypeName }}</div>
-                    <div v-if="showTaskTeamName(item)" class="step-team-name">
-                      {{ getTaskTeamName(item) }}
+                <div v-if="colIndex > 0" class="route-connector">
+                  <svg
+                    class="route-connector-svg"
+                    :viewBox="`0 0 ${connectorViewW} ${stepsRowHeight}`"
+                    preserveAspectRatio="none"
+                  >
+                    <defs>
+                      <marker
+                        id="route-flow-arrow"
+                        viewBox="0 0 10 10"
+                        refX="9"
+                        refY="5"
+                        markerWidth="7"
+                        markerHeight="7"
+                        markerUnits="userSpaceOnUse"
+                        orient="auto-start-reverse"
+                      >
+                        <path d="M 0 0 L 10 5 L 0 10 z" fill="#909399" />
+                      </marker>
+                    </defs>
+                    <line
+                      v-for="(line, li) in connectorLines(
+                        stepColumns[colIndex - 1],
+                        col
+                      )"
+                      :key="li"
+                      :x1="line.x1"
+                      :y1="line.y1"
+                      :x2="line.x2"
+                      :y2="line.y2"
+                      class="route-connector-line"
+                      marker-end="url(#route-flow-arrow)"
+                    />
+                  </svg>
+                </div>
+                <div class="route-col">
+                  <div
+                    v-for="row in col.items"
+                    :key="row.flatIndex"
+                    class="route-node-row"
+                    :style="{ height: stepNodeH + 'px' }"
+                  >
+                    <div
+                      class="route-node"
+                      :class="{
+                        'is-current': row.flatIndex === desIndex,
+                        'is-error': row.flatIndex === errorIndex,
+                        'is-done': row.flatIndex < activeIndex,
+                        'is-compact': row.item.isParallel == 1
+                      }"
+                      :title="row.item.taskTypeName"
+                      @click="handIdx(row.flatIndex, row.item)"
+                    >
+                      <div class="route-node-name">
+                        {{ row.item.taskTypeName
+                        }}<span
+                          v-if="row.flatIndex === desIndex"
+                          class="route-node-tag"
+                          >此处</span
+                        >
+                      </div>
+                      <div
+                        v-if="showTaskTeamName(row.item)"
+                        class="route-node-team"
+                      >
+                        {{ getTaskTeamName(row.item) }}
+                      </div>
                     </div>
                   </div>
-                </template>
-              </el-step>
-            </el-steps>
+                </div>
+              </div>
+            </div>
+
           </div>
 
           <el-tabs type="border-card" :key="drawerKey">
@@ -401,6 +459,9 @@
         activeIndex: 0,
         desIndex: 0,
         errorIndex: 10000,
+        stepNodeH: 50, // 每个工序节点的行高
+        stepConnectorW: 56, // 列间箭头连接区最小宽度
+        connectorViewW: 56, // 连接线 SVG 的实际宽度(自适应测量)
         drawer: false,
         workData: {},
         isFullscreen: false,
@@ -462,6 +523,36 @@
       },
       rightShow() {
         return (type) => {};
+      },
+      // 按 isParallel 连续段把工序拆分成“列”,并行工序同列垂直堆叠展示
+      stepColumns() {
+        const list = Array.isArray(this.routeList) ? this.routeList : [];
+        const cols = [];
+        list.forEach((item, flatIndex) => {
+          const isParallel =
+            !!item &&
+            (item.isParallel === 1 ||
+              item.isParallel === '1' ||
+              item.isParallel === true);
+          const cur = cols[cols.length - 1];
+          if (isParallel && cur && cur.parallel) {
+            cur.items.push({ item, flatIndex });
+          } else {
+            cols.push({
+              parallel: isParallel,
+              items: [{ item, flatIndex }]
+            });
+          }
+        });
+        return cols;
+      },
+      // 流程图整体高度 = 最大并行列的节点数 * 节点行高
+      stepsRowHeight() {
+        const maxRows = this.stepColumns.reduce(
+          (m, col) => Math.max(m, col.items.length),
+          1
+        );
+        return maxRows * this.stepNodeH;
       }
     },
 
@@ -476,6 +567,45 @@
       this.workListIds = [];
     },
     methods: {
+      // 测量连接线区域的实际宽度(自适应布局下需实测)
+      measureConnectorWidth() {
+        this.$nextTick(() => {
+          const el = this.$el && this.$el.querySelector('.route-connector');
+          if (!el) {
+            return;
+          }
+          const w = Math.max(
+            Math.round(el.getBoundingClientRect().width),
+            this.stepConnectorW
+          );
+          if (w !== this.connectorViewW) {
+            this.connectorViewW = w;
+          }
+        });
+      },
+      // 计算两列之间的连线:左列每个节点与右列每个节点全连接(分叉/合并)
+      connectorLines(leftCol, rightCol) {
+        const total = this.stepsRowHeight;
+        const h = this.stepNodeH;
+        const centers = (n) => {
+          const offset = (total - n * h) / 2;
+          return Array.from({ length: n }, (_, i) => offset + (i + 0.5) * h);
+        };
+        const leftYs = centers(leftCol.items.length);
+        const rightYs = centers(rightCol.items.length);
+        const lines = [];
+        leftYs.forEach((y1) => {
+          rightYs.forEach((y2) => {
+            lines.push({
+              x1: 0,
+              y1,
+              x2: Math.max(this.connectorViewW - 8, 8),
+              y2
+            });
+          });
+        });
+        return lines;
+      },
       tabClickValue() {
         this.name = '';
         this.seekInput();
@@ -699,6 +829,7 @@
       handleFull() {
         this.isFullscreen = !this.isFullscreen;
         this.$forceUpdate();
+        this.measureConnectorWidth();
       },
 
       // 获取工序列表
@@ -881,6 +1012,7 @@
         if (this.routeList[currentIndex]) {
           this.handIdx(this.activeIndex, this.routeList[currentIndex]);
         }
+        this.measureConnectorWidth();
       },
 
       async getCodeData(req) {
@@ -1518,6 +1650,8 @@
           });
         }
       });
+      this._routeResizeHandler = () => this.measureConnectorWidth();
+      window.addEventListener('resize', this._routeResizeHandler);
     },
 
     destroyed() {
@@ -1530,6 +1664,10 @@
           });
         }
       });
+      if (this._routeResizeHandler) {
+        window.removeEventListener('resize', this._routeResizeHandler);
+        this._routeResizeHandler = null;
+      }
     }
   };
 </script>
@@ -1683,12 +1821,6 @@
     margin: 5px 10px;
     box-sizing: border-box;
 
-    .process-route-steps {
-      min-height: 120px;
-      padding: 18px 8px 22px;
-      box-sizing: border-box;
-    }
-
     .taskTitle {
       display: flex;
       width: 100%;
@@ -1719,33 +1851,153 @@
     color: #ffffff; /* 图标文字颜色 */
   }
 
-  ::v-deep .process-route-steps {
-    .el-step__main {
-      padding-top: 10px;
+  .route-flow {
+    display: flex;
+    align-items: center;
+    min-height: 88px;
+    padding: 12px 8px 16px;
+    box-sizing: border-box;
+    overflow-x: auto;
+  }
+
+  .route-flow-unit {
+    display: flex;
+    align-items: center;
+    height: 100%;
+    flex: 1 1 0;
+    min-width: 140px;
+  }
+  .route-flow-unit:nth-of-type(1){
+    flex:0 !important;
+  }
+  .route-connector {
+    flex: 1 1 40px;
+    min-width: 40px;
+    display: flex;
+    align-items: center;
+    height: 100%;
+  }
+
+  .route-connector-svg {
+    display: block;
+    width: 100%;
+    height: 100%;
+  }
+
+  .route-connector-line {
+    stroke: #909399;
+    stroke-width: 1.5;
+    vector-effect: non-scaling-stroke;
+  }
+
+  .route-col {
+    flex: 0 0 auto;
+    margin: 0 auto;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    height: 100%;
+  }
+
+  .route-node-row {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .route-node {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    min-width: 84px;
+    max-width: 160px;
+    min-height: 38px;
+    padding: 3px 12px;
+    background: #ffffff;
+    border: 2px solid #c0c4cc;
+    border-radius: 999px;
+    cursor: pointer;
+    box-sizing: border-box;
+    transition: border-color 0.2s, box-shadow 0.2s;
+
+    &:hover {
+      border-color: #157a2c;
+    }
+  }
+
+  .route-node.is-done {
+    border-color: #1890ff;
+
+    .route-node-name {
+      color: #1890ff;
+    }
+  }
+
+  .route-node.is-current {
+    border-color: #ffa929;
+    box-shadow: 0 0 6px rgba(255, 169, 41, 0.45);
+
+    .route-node-name {
+      color: #ffa929;
+      font-weight: 700;
     }
+  }
+
+  .route-node.is-error {
+    border-color: #f56c6c;
 
-    .el-step__title {
-      font-size: 15px;
-      font-weight: 500;
-      line-height: 1.45;
+    .route-node-name {
+      color: #f56c6c;
     }
+  }
 
-    .step-team-name {
-      margin-top: 4px;
-      color: #606266;
-      font-size: 13px;
-      font-weight: 400;
-      line-height: 1.2;
-      word-break: break-all;
+  // 并行工序节点(isParallel == 1):更紧凑
+  .route-node.is-compact {
+    min-width: 64px;
+    max-width: 110px;
+    min-height: 28px;
+    padding: 1px 8px;
+
+    .route-node-name {
+      font-size: 12px;
     }
 
-    .el-step__icon {
-      width: 32px;
-      height: 32px;
-      font-size: 18px;
+    .route-node-team {
+      font-size: 10px;
     }
   }
 
+  .route-node-name {
+    font-size: 13px;
+    font-weight: 500;
+    line-height: 1.35;
+    color: #303133;
+    text-align: center;
+    word-break: break-all;
+  }
+
+  .route-node-team {
+    margin-top: 2px;
+    color: #606266;
+    font-size: 11px;
+    line-height: 1.2;
+    text-align: center;
+    word-break: break-all;
+  }
+
+  .route-node-tag {
+    display: inline-block;
+    margin-left: 4px;
+    padding: 0 6px;
+    font-size: 11px;
+    line-height: 16px;
+    color: #ffffff;
+    background: #ffa929;
+    border-radius: 8px;
+    vertical-align: middle;
+  }
+
   ::v-deep .el-tabs__content {
     padding: 1px !important;
   }