|
|
@@ -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;
|
|
|
}
|