|
|
@@ -31,21 +31,64 @@
|
|
|
<!-- //详细信息 -->
|
|
|
<Info :workOrderInfo="workOrderInfo"></Info>
|
|
|
|
|
|
- <el-steps
|
|
|
- :active="activeIndex"
|
|
|
- space="20px"
|
|
|
- align-center
|
|
|
- style="margin-top: 18px"
|
|
|
+ <div
|
|
|
+ class="route-flow"
|
|
|
+ :style="{ height: stepsRowHeight + 'px' }"
|
|
|
>
|
|
|
- <el-step
|
|
|
- v-for="(item, index) in routeList"
|
|
|
- :key="index"
|
|
|
- :title="item.taskTypeName"
|
|
|
- @click.native="handIdx(index, item)"
|
|
|
- :description="desIndex == index ? '此处' : ''"
|
|
|
- :class="desIndex == index ? 'active' : ''"
|
|
|
- ></el-step>
|
|
|
- </el-steps>
|
|
|
+ <div
|
|
|
+ v-for="(col, colIndex) in stepColumns"
|
|
|
+ :key="`route-unit-${colIndex}`"
|
|
|
+ class="route-flow-unit"
|
|
|
+ >
|
|
|
+ <div v-if="colIndex > 0" class="route-connector">
|
|
|
+ <svg
|
|
|
+ class="route-connector-svg"
|
|
|
+ :viewBox="`0 0 ${connectorViewW} ${stepsRowHeight}`"
|
|
|
+ preserveAspectRatio="none"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ v-for="(line, li) in connectorLines(
|
|
|
+ stepColumns[colIndex - 1],
|
|
|
+ col
|
|
|
+ )"
|
|
|
+ :key="li"
|
|
|
+ :d="line.d"
|
|
|
+ class="route-connector-line"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="route-col">
|
|
|
+ <div
|
|
|
+ v-for="row in col.items"
|
|
|
+ :key="row.flatIndex"
|
|
|
+ class="route-node-row"
|
|
|
+ :class="{ 'is-parallel': row.item.isParallel == 1 }"
|
|
|
+ :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>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<el-tabs
|
|
|
v-model="detailActiveTab"
|
|
|
@@ -146,6 +189,7 @@
|
|
|
routeList: [],
|
|
|
activeIndex: 0,
|
|
|
desIndex: 0,
|
|
|
+ errorIndex: 10000,
|
|
|
curTaskObj: null,
|
|
|
newId: '',
|
|
|
routeObj: {
|
|
|
@@ -154,10 +198,46 @@
|
|
|
sourceTaskId: '',
|
|
|
/** 任务数据就绪后递增,强制各 Tab 子组件重挂载以重新拉数 */
|
|
|
tabsRenderKey: 0,
|
|
|
- detailActiveTab: 'pick'
|
|
|
+ detailActiveTab: 'pick',
|
|
|
+ stepNodeH: 38, // 每个工序节点的行高
|
|
|
+ stepConnectorW: 12, // 列间连接线 SVG 的最小宽度
|
|
|
+ connectorViewW: 56 // 连接线 SVG 的实际宽度(自适应测量)
|
|
|
};
|
|
|
},
|
|
|
|
|
|
+ computed: {
|
|
|
+ // 按 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;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
methods: {
|
|
|
handleClose() {
|
|
|
this.activeIndex = 0;
|
|
|
@@ -167,6 +247,47 @@
|
|
|
handleFull() {
|
|
|
this.isFullscreen = !this.isFullscreen;
|
|
|
this.$forceUpdate();
|
|
|
+ this.measureConnectorWidth();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 测量连接线区域的实际宽度(自适应布局下需实测)
|
|
|
+ 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 w = this.connectorViewW;
|
|
|
+ const midX = w / 2;
|
|
|
+ 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({
|
|
|
+ d: `M 0 ${y1} L ${midX} ${y1} L ${midX} ${y2} L ${w} ${y2}`
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return lines;
|
|
|
},
|
|
|
|
|
|
open(row) {
|
|
|
@@ -214,6 +335,7 @@
|
|
|
})
|
|
|
.finally(() => {
|
|
|
this.tabsRenderKey += 1;
|
|
|
+ this.measureConnectorWidth();
|
|
|
});
|
|
|
},
|
|
|
|
|
|
@@ -246,6 +368,17 @@
|
|
|
this.sourceTaskId = this.routeList[this.desIndex]?.sourceTaskId || '';
|
|
|
}
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ this._routeResizeHandler = () => this.measureConnectorWidth();
|
|
|
+ window.addEventListener('resize', this._routeResizeHandler);
|
|
|
+ },
|
|
|
+
|
|
|
+ destroyed() {
|
|
|
+ if (this._routeResizeHandler) {
|
|
|
+ window.removeEventListener('resize', this._routeResizeHandler);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
@@ -286,4 +419,168 @@
|
|
|
border-color: #ffa929;
|
|
|
color: #ffffff; /* 图标文字颜色 */
|
|
|
}
|
|
|
+
|
|
|
+ .route-flow {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 88px;
|
|
|
+ padding: 12px 0 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 0 auto !important;
|
|
|
+ min-width: 0 !important;
|
|
|
+ }
|
|
|
+ .route-connector {
|
|
|
+ flex: 1 1 0;
|
|
|
+ min-width: 0;
|
|
|
+ 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;
|
|
|
+ fill: none;
|
|
|
+ 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;
|
|
|
+ width: 100px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node-row.is-parallel {
|
|
|
+ margin-bottom: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node-row.is-parallel:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ max-width: none;
|
|
|
+ min-height: 0;
|
|
|
+ padding: 2px 8px;
|
|
|
+ background: #ffffff;
|
|
|
+ border: 2px solid #c0c4cc;
|
|
|
+ border-radius: 2px;
|
|
|
+ cursor: pointer;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ 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: #1890ff;
|
|
|
+
|
|
|
+ .route-node-name {
|
|
|
+ color: #1890ff;
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node.is-error {
|
|
|
+ border-color: #f56c6c;
|
|
|
+
|
|
|
+ .route-node-name {
|
|
|
+ color: #f56c6c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 并行工序节点(isParallel == 1):更紧凑
|
|
|
+ .route-node.is-compact {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ max-width: none;
|
|
|
+ min-height: 0;
|
|
|
+ padding: 1px 6px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .route-node-name {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node-team {
|
|
|
+ font-size: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node-name {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1.3;
|
|
|
+ color: #303133;
|
|
|
+ text-align: center;
|
|
|
+ word-break: break-all;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node-team {
|
|
|
+ margin-top: 2px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 10px;
|
|
|
+ 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: #1890ff;
|
|
|
+ border-radius: 8px;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
</style>
|