@mx-sose-front/mx-sose-graph 1.2.8 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +94440 -1420
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/src/components/InteractionLayer.vue.d.ts.map +1 -1
- package/dist/src/components/Matrix/Matrix.vue.d.ts.map +1 -1
- package/dist/src/hooks/useConnectionPreview.d.ts +500 -0
- package/dist/src/hooks/useConnectionPreview.d.ts.map +1 -0
- package/dist/src/store/graphStore.d.ts +5 -1
- package/dist/src/store/graphStore.d.ts.map +1 -1
- package/dist/src/utils/autoLayout.d.ts +44 -0
- package/dist/src/utils/autoLayout.d.ts.map +1 -0
- package/dist/src/utils/edgeUtils.d.ts +2 -2
- package/dist/src/utils/edgeUtils.d.ts.map +1 -1
- package/dist/src/utils/pinUtils.d.ts +2 -0
- package/dist/src/utils/pinUtils.d.ts.map +1 -1
- package/dist/src/utils/shapeInitialBounds.d.ts +10 -0
- package/dist/src/utils/shapeInitialBounds.d.ts.map +1 -0
- package/dist/src/view/graph.vue.d.ts +4 -4
- package/dist/src/view/graph.vue.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/InteractionLayer.vue +34 -406
- package/src/components/Matrix/Matrix.vue +71 -5
- package/src/hooks/useConnectionPreview.ts +405 -0
- package/src/store/graphStore.ts +24 -4
- package/src/utils/autoLayout.ts +388 -0
- package/src/utils/edgeUtils.ts +77 -63
- package/src/utils/pinUtils.ts +5 -0
- package/src/utils/shapeInitialBounds.ts +42 -0
- package/src/view/graph.vue +4 -2
|
@@ -107,7 +107,6 @@ import NameEditor from "./NameEditor/NameEditor.vue";
|
|
|
107
107
|
// 工具:几何/命中/样式/拖拽
|
|
108
108
|
import {
|
|
109
109
|
toLocalPoint,
|
|
110
|
-
inBounds,
|
|
111
110
|
getBounds,
|
|
112
111
|
getDiagramRect,
|
|
113
112
|
rectFromPoints,
|
|
@@ -118,11 +117,7 @@ import {
|
|
|
118
117
|
isInsideCanvasClient,
|
|
119
118
|
} from "../utils/geom";
|
|
120
119
|
import { pickTarget } from "../utils/hittest";
|
|
121
|
-
import {
|
|
122
|
-
actionButtonsStyle,
|
|
123
|
-
getMarqueeStyle,
|
|
124
|
-
getLayerStyle,
|
|
125
|
-
} from "../utils/diagram";
|
|
120
|
+
import { actionButtonsStyle, getMarqueeStyle } from "../utils/diagram";
|
|
126
121
|
import { withDrag } from "../utils/dom";
|
|
127
122
|
import { checkNestViaFront } from "../utils/policy";
|
|
128
123
|
import { getShapeComponent, getShapeStyle } from "../render/shape-renderer";
|
|
@@ -143,6 +138,7 @@ import { useNameEdit } from "../hooks/useNameEdit";
|
|
|
143
138
|
import { guardOperate } from "../utils/license-guard";
|
|
144
139
|
import { useResize } from "../hooks/useResize";
|
|
145
140
|
import { useHighlight, type IHighlightUtils } from "../hooks/useHighlight";
|
|
141
|
+
import { useConnectionPreview } from "../hooks/useConnectionPreview";
|
|
146
142
|
import { createRAFThrottle, createRAFDebounce } from "../utils/rafThrottle";
|
|
147
143
|
import {
|
|
148
144
|
buildPackageOutlineOverlay,
|
|
@@ -312,14 +308,6 @@ const marqueeAnchor = ref<{ x: number; y: number } | null>(null);
|
|
|
312
308
|
// 是否多选
|
|
313
309
|
const isMultiSelected = computed(() => graphStore.selectedIds.length > 1);
|
|
314
310
|
|
|
315
|
-
// 连接层相关状态
|
|
316
|
-
const mousePosition = ref({ x: 0, y: 0 });
|
|
317
|
-
const showLine = ref(false);
|
|
318
|
-
const currentConnectPoint = ref({ x: 0, y: 0 });
|
|
319
|
-
const isConnecting = ref(false); // 是否正在连接状态
|
|
320
|
-
const targetConnectPoint = ref({ x: 0, y: 0 }); // 目标连接点
|
|
321
|
-
const targetShape = ref<Shape | null>(null); // 目标图形
|
|
322
|
-
|
|
323
311
|
// 高亮相关 - 使用 Composable
|
|
324
312
|
const {
|
|
325
313
|
overlayBounds: highlightOverlayBounds,
|
|
@@ -336,12 +324,42 @@ const highlightUtils: IHighlightUtils = {
|
|
|
336
324
|
clearHighlightTimeout,
|
|
337
325
|
};
|
|
338
326
|
|
|
339
|
-
// 保持兼容性:highlightedShape 计算属性
|
|
340
327
|
const highlightedShape = computed(() => {
|
|
341
328
|
if (!highlightedShapeId.value) return null;
|
|
342
329
|
return graphStore.shapeMap.get(highlightedShapeId.value) ?? null;
|
|
343
330
|
});
|
|
344
331
|
|
|
332
|
+
const pendingActionButton = ref(false);
|
|
333
|
+
|
|
334
|
+
const {
|
|
335
|
+
mousePosition,
|
|
336
|
+
showLine,
|
|
337
|
+
currentConnectPoint,
|
|
338
|
+
isConnecting,
|
|
339
|
+
targetConnectPoint,
|
|
340
|
+
targetShape,
|
|
341
|
+
recordClickPoint,
|
|
342
|
+
isConnectAllowed,
|
|
343
|
+
dotStyle,
|
|
344
|
+
svgStyle,
|
|
345
|
+
linePoints,
|
|
346
|
+
layerStyle,
|
|
347
|
+
cancelConnection,
|
|
348
|
+
initializeConnectPoint,
|
|
349
|
+
getCurrentTargetModels,
|
|
350
|
+
resolveConnectTargetAtPoint,
|
|
351
|
+
handleMouseMove,
|
|
352
|
+
handleMouseLeave,
|
|
353
|
+
} = useConnectionPreview({
|
|
354
|
+
props,
|
|
355
|
+
graphStore,
|
|
356
|
+
highlightUtils,
|
|
357
|
+
connectMode,
|
|
358
|
+
layerRef,
|
|
359
|
+
highlightedShape,
|
|
360
|
+
pendingActionButton,
|
|
361
|
+
});
|
|
362
|
+
|
|
345
363
|
// 包图元使用“文件夹轮廓”高亮,其他图元继续沿用默认矩形覆盖层。
|
|
346
364
|
const isPackageShape = (shape: Shape | null | undefined) => {
|
|
347
365
|
return !!shape?.shapeKey && !!props.packages?.includes(shape.shapeKey);
|
|
@@ -375,59 +393,8 @@ const highlightPackageOutline = computed(() =>
|
|
|
375
393
|
}),
|
|
376
394
|
);
|
|
377
395
|
|
|
378
|
-
const getCurrentTargetModels = () => {
|
|
379
|
-
if (connectMode.value === "action") {
|
|
380
|
-
return (
|
|
381
|
-
props.connectShapeData?.scenarioMenus?.find(
|
|
382
|
-
(menu) => menu.code === props.connectShapeData?.shapeKey
|
|
383
|
-
)?.targetModels ?? props.connectShapeData?.targetModels
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return props.connectShapeData?.targetModels;
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const resolveConnectTargetAtPoint = (pt: { x: number; y: number }) => {
|
|
391
|
-
const hit = pickTarget(graphStore.shapes, pt);
|
|
392
|
-
|
|
393
|
-
if (
|
|
394
|
-
["shape", "pin"].includes(hit.kind) &&
|
|
395
|
-
hit.shape?.id &&
|
|
396
|
-
hit.shape.id !== props.connectShapeData?.sourceId
|
|
397
|
-
) {
|
|
398
|
-
return hit.shape;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const hoveredShape = highlightedShape.value;
|
|
402
|
-
if (
|
|
403
|
-
!hoveredShape?.id ||
|
|
404
|
-
hoveredShape.id === props.connectShapeData?.sourceId
|
|
405
|
-
) {
|
|
406
|
-
return null;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const bounds = getBounds(hoveredShape);
|
|
410
|
-
const minSide = Math.min(bounds.width, bounds.height);
|
|
411
|
-
const padding = minSide <= 30 ? 8 : minSide <= 40 ? 6 : 0;
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
inBounds(pt, {
|
|
415
|
-
x: bounds.x - padding,
|
|
416
|
-
y: bounds.y - padding,
|
|
417
|
-
width: bounds.width + padding * 2,
|
|
418
|
-
height: bounds.height + padding * 2,
|
|
419
|
-
})
|
|
420
|
-
) {
|
|
421
|
-
return hoveredShape;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return null;
|
|
425
|
-
};
|
|
426
|
-
|
|
427
396
|
const sourceShape = ref<Shape | null>(null);
|
|
428
|
-
|
|
429
|
-
// 等待后端创建图元中,冻结连线预览
|
|
430
|
-
const pendingActionButton = ref(false);
|
|
397
|
+
// 等待后端创建图元中,冻结连线预览(pendingActionButton 见 useConnectionPreview)
|
|
431
398
|
|
|
432
399
|
// 正在交互:缩放中 或 元素拖动中
|
|
433
400
|
const isBusy = computed(
|
|
@@ -868,9 +835,6 @@ const showContextMenu = ref(false);
|
|
|
868
835
|
const contextMenuPosition = ref({ x: 0, y: 0 });
|
|
869
836
|
const contextMenuTarget = ref<Shape | null>(null);
|
|
870
837
|
|
|
871
|
-
// 是否允许连接当前高亮的图元
|
|
872
|
-
const isConnectAllowed = ref(false);
|
|
873
|
-
|
|
874
838
|
// 处理右键点击事件
|
|
875
839
|
const handleContextMenu = (event: MouseEvent) => {
|
|
876
840
|
return guardOperate(async () => {
|
|
@@ -975,206 +939,6 @@ watch(
|
|
|
975
939
|
}
|
|
976
940
|
);
|
|
977
941
|
|
|
978
|
-
// 鼠标移动事件处理 - 使用 RAF 节流优化
|
|
979
|
-
const handleMouseMove = (event: MouseEvent) => {
|
|
980
|
-
// 使用 RAF 节流,同一帧内多次调用会合并为最后一次
|
|
981
|
-
connectMouseMoveThrottle.throttle((evt: MouseEvent) => {
|
|
982
|
-
const { clientX, clientY } = evt;
|
|
983
|
-
const localPoint = toLocalPoint(evt, layerRef.value);
|
|
984
|
-
// 使用工具类检查是否在画布内
|
|
985
|
-
if (EdgeUtils.isWithinCanvas(clientX, clientY)) {
|
|
986
|
-
mousePosition.value = { x: localPoint.x, y: localPoint.y };
|
|
987
|
-
|
|
988
|
-
// 只有在连接状态下才显示线条
|
|
989
|
-
if (isConnecting.value && !pendingActionButton.value) {
|
|
990
|
-
showLine.value = true;
|
|
991
|
-
updateConnectPointToNearest(localPoint.x, localPoint.y);
|
|
992
|
-
checkHoverTarget(localPoint.x, localPoint.y);
|
|
993
|
-
}
|
|
994
|
-
} else {
|
|
995
|
-
if (isConnecting.value) {
|
|
996
|
-
showLine.value = false;
|
|
997
|
-
}
|
|
998
|
-
highlightShape(null, false); // 使用highlightShape函数取消高亮,恢复原始样式
|
|
999
|
-
}
|
|
1000
|
-
})(event);
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
// 用于跟踪上一次的hoverShape,避免重复发射事件
|
|
1004
|
-
let lastHoverShapeId: string | null = null;
|
|
1005
|
-
|
|
1006
|
-
// 创建 RAF 节流实例用于连线时的鼠标移动
|
|
1007
|
-
const connectMouseMoveThrottle = createRAFThrottle();
|
|
1008
|
-
|
|
1009
|
-
// 用于防抖 edge-check 事件和高亮,只有鼠标停留一段时间后才触发
|
|
1010
|
-
let edgeCheckDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1011
|
-
const EDGE_CHECK_DEBOUNCE_DELAY = 150; // 150ms 防抖延迟
|
|
1012
|
-
|
|
1013
|
-
const checkHoverTarget = (x: number, y: number) => {
|
|
1014
|
-
if (!props.diagramBounds) return;
|
|
1015
|
-
|
|
1016
|
-
// 使用工具类检查悬停目标
|
|
1017
|
-
const hoverShape = EdgeUtils.checkHoverTarget(
|
|
1018
|
-
x,
|
|
1019
|
-
y,
|
|
1020
|
-
graphStore.shapes,
|
|
1021
|
-
props.diagramBounds,
|
|
1022
|
-
props.connectShapeData?.sourceId
|
|
1023
|
-
);
|
|
1024
|
-
clearHighlightTimeout();
|
|
1025
|
-
|
|
1026
|
-
if (hoverShape) {
|
|
1027
|
-
// 检查连接有效性
|
|
1028
|
-
const targetModels = getCurrentTargetModels();
|
|
1029
|
-
let isAllowed = true;
|
|
1030
|
-
if (
|
|
1031
|
-
targetModels &&
|
|
1032
|
-
Array.isArray(targetModels) &&
|
|
1033
|
-
targetModels.length > 0
|
|
1034
|
-
) {
|
|
1035
|
-
const hoverShapeType = hoverShape.shapeKey || "";
|
|
1036
|
-
isAllowed = targetModels.includes(hoverShapeType);
|
|
1037
|
-
}
|
|
1038
|
-
// 检查parenShapeId是否匹配(无论hoverShape是否改变都要检查)
|
|
1039
|
-
if (props.connectShapeData?.sourceId) {
|
|
1040
|
-
const sourceShape = graphStore.shapeMap.get(
|
|
1041
|
-
props.connectShapeData.sourceId
|
|
1042
|
-
);
|
|
1043
|
-
if (
|
|
1044
|
-
sourceShape &&
|
|
1045
|
-
sourceShape.parenShapeId !== hoverShape.parenShapeId &&
|
|
1046
|
-
hoverShape.shapeType !== "pin" &&
|
|
1047
|
-
sourceShape.shapeType !== "pin"
|
|
1048
|
-
) {
|
|
1049
|
-
isAllowed = false;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// 当 hoverShape 改变时,清除之前的防抖定时器
|
|
1054
|
-
if (lastHoverShapeId !== hoverShape.id) {
|
|
1055
|
-
// 清除旧的防抖定时器
|
|
1056
|
-
if (edgeCheckDebounceTimer) {
|
|
1057
|
-
clearTimeout(edgeCheckDebounceTimer);
|
|
1058
|
-
edgeCheckDebounceTimer = null;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// 立即取消之前图元的高亮(如果有)
|
|
1062
|
-
highlightShape(null, false);
|
|
1063
|
-
isConnectAllowed.value = false;
|
|
1064
|
-
|
|
1065
|
-
lastHoverShapeId = hoverShape.id;
|
|
1066
|
-
|
|
1067
|
-
// 只有当 connectShapeData 存在时才设置防抖定时器
|
|
1068
|
-
if (props.connectShapeData) {
|
|
1069
|
-
// 使用防抖:只有鼠标停留一段时间后才发射 edge-check 事件和高亮
|
|
1070
|
-
edgeCheckDebounceTimer = setTimeout(() => {
|
|
1071
|
-
// 使用更严格的优先级判断逻辑,确保只要有一个属性有实际值就使用它
|
|
1072
|
-
let sourceModelId;
|
|
1073
|
-
// 如果modelId是有效字符串或数字,使用modelId
|
|
1074
|
-
if (
|
|
1075
|
-
props.connectShapeData?.modelId &&
|
|
1076
|
-
props.connectShapeData.modelId.toString().trim() !== ""
|
|
1077
|
-
) {
|
|
1078
|
-
sourceModelId = props.connectShapeData.modelId;
|
|
1079
|
-
}
|
|
1080
|
-
// 否则,如果sourceModelId是有效字符串或数字,使用sourceModelId
|
|
1081
|
-
else if (
|
|
1082
|
-
props.connectShapeData?.sourceModelId &&
|
|
1083
|
-
props.connectShapeData.sourceModelId.toString().trim() !== ""
|
|
1084
|
-
) {
|
|
1085
|
-
sourceModelId = props.connectShapeData.sourceModelId;
|
|
1086
|
-
}
|
|
1087
|
-
// 只有当sourceModelId有值时就发射事件,并将isAllowed的值一并传递
|
|
1088
|
-
if (sourceModelId) {
|
|
1089
|
-
eventBus.emit("edge-check", {
|
|
1090
|
-
sourceModelId: sourceModelId, // 显式指定键值对,避免属性简写可能带来的混淆
|
|
1091
|
-
targetModelId: hoverShape.modelId,
|
|
1092
|
-
isAllowed: isAllowed, // 将验证结果一并发射
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// 高亮目标图元 - 根据isAllowed决定高亮颜色
|
|
1097
|
-
highlightShape(hoverShape, true, isAllowed);
|
|
1098
|
-
isConnectAllowed.value = isAllowed;
|
|
1099
|
-
}, EDGE_CHECK_DEBOUNCE_DELAY);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
} else {
|
|
1103
|
-
// 如果没有悬停在目标上
|
|
1104
|
-
// 清除防抖定时器
|
|
1105
|
-
if (edgeCheckDebounceTimer) {
|
|
1106
|
-
clearTimeout(edgeCheckDebounceTimer);
|
|
1107
|
-
edgeCheckDebounceTimer = null;
|
|
1108
|
-
}
|
|
1109
|
-
// 重置lastHoverShapeId
|
|
1110
|
-
lastHoverShapeId = null;
|
|
1111
|
-
// 立即取消高亮(无需延迟,因为高亮本身已有防抖)
|
|
1112
|
-
clearHighlightTimeout();
|
|
1113
|
-
highlightShape(null, false);
|
|
1114
|
-
isConnectAllowed.value = false;
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
// 更新连接点到最近的点
|
|
1119
|
-
const updateConnectPointToNearest = (mouseX: number, mouseY: number) => {
|
|
1120
|
-
if (!props.connectShapeData?.sourceId || !props.diagramBounds) return;
|
|
1121
|
-
|
|
1122
|
-
const sourceShape = props.connectShapeData?.sourceId
|
|
1123
|
-
? graphStore.shapeMap.get(props.connectShapeData.sourceId)
|
|
1124
|
-
: undefined;
|
|
1125
|
-
|
|
1126
|
-
// 使用同步版本,避免 Worker 异步延迟导致连线跟不上鼠标
|
|
1127
|
-
const nearestPoint = EdgeUtils.findNearestConnectPoint(
|
|
1128
|
-
{ x: mouseX, y: mouseY },
|
|
1129
|
-
sourceShape
|
|
1130
|
-
);
|
|
1131
|
-
|
|
1132
|
-
if (
|
|
1133
|
-
nearestPoint &&
|
|
1134
|
-
nearestPoint.x !== undefined &&
|
|
1135
|
-
nearestPoint.y !== undefined
|
|
1136
|
-
) {
|
|
1137
|
-
currentConnectPoint.value = {
|
|
1138
|
-
x: nearestPoint.x,
|
|
1139
|
-
y: nearestPoint.y,
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
// 监听isConnecting变化,重置连接状态
|
|
1145
|
-
watch(
|
|
1146
|
-
() => isConnecting.value,
|
|
1147
|
-
(newVal) => {
|
|
1148
|
-
if (!newVal) {
|
|
1149
|
-
// 清除防抖定时器
|
|
1150
|
-
if (edgeCheckDebounceTimer) {
|
|
1151
|
-
clearTimeout(edgeCheckDebounceTimer);
|
|
1152
|
-
edgeCheckDebounceTimer = null;
|
|
1153
|
-
}
|
|
1154
|
-
// 重置 lastHoverShapeId
|
|
1155
|
-
lastHoverShapeId = null;
|
|
1156
|
-
isConnectAllowed.value = false;
|
|
1157
|
-
highlightShape(null, false); // 取消图元高亮
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
);
|
|
1161
|
-
|
|
1162
|
-
// 监听edgeCheck变化,当后端验证结果返回后更新高亮颜色
|
|
1163
|
-
watch(
|
|
1164
|
-
() => props.edgeCheck,
|
|
1165
|
-
(newEdgeCheck) => {
|
|
1166
|
-
// 只有在连接状态下且有高亮图元时才更新
|
|
1167
|
-
if (
|
|
1168
|
-
isConnecting.value &&
|
|
1169
|
-
highlightedShape.value &&
|
|
1170
|
-
newEdgeCheck !== undefined
|
|
1171
|
-
) {
|
|
1172
|
-
// 使用后端验证结果更新高亮颜色
|
|
1173
|
-
highlightShape(highlightedShape.value, true, newEdgeCheck);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
);
|
|
1177
|
-
|
|
1178
942
|
// 处理连接层点击事件
|
|
1179
943
|
const handleConnectLayerClick = (
|
|
1180
944
|
event: MouseEvent,
|
|
@@ -1312,142 +1076,6 @@ const completeConnection = async (
|
|
|
1312
1076
|
clearHighlightTimeout();
|
|
1313
1077
|
}
|
|
1314
1078
|
};
|
|
1315
|
-
// 鼠标离开事件处理
|
|
1316
|
-
const handleMouseLeave = () => {
|
|
1317
|
-
if (isConnecting.value) {
|
|
1318
|
-
showLine.value = false;
|
|
1319
|
-
}
|
|
1320
|
-
highlightShape(null, false); // 取消图元高亮
|
|
1321
|
-
clearHighlightTimeout();
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
// 初始化连接点位置
|
|
1325
|
-
const initializeConnectPoint = () => {
|
|
1326
|
-
if (props.connectShapeData?.sourceId && props.diagramBounds) {
|
|
1327
|
-
const sourceShape = props.connectShapeData?.sourceId
|
|
1328
|
-
? graphStore.shapeMap.get(props.connectShapeData.sourceId)
|
|
1329
|
-
: undefined;
|
|
1330
|
-
|
|
1331
|
-
// 使用工具类初始化连接点,传递当前鼠标位置和diagramBounds
|
|
1332
|
-
// 这样可以确保即使是第一次连线,也能计算出图元边界上的合适连接点,而不是使用中心点
|
|
1333
|
-
const initialPoint = EdgeUtils.initializeConnectPoint(
|
|
1334
|
-
sourceShape,
|
|
1335
|
-
recordClickPoint.value, // 传递鼠标点击位置
|
|
1336
|
-
props.diagramBounds // 传递图表边界
|
|
1337
|
-
);
|
|
1338
|
-
|
|
1339
|
-
if (initialPoint && sourceShape) {
|
|
1340
|
-
// 确保sourceShape存在,防止从工具栏拖拽时错误初始化
|
|
1341
|
-
currentConnectPoint.value = initialPoint;
|
|
1342
|
-
|
|
1343
|
-
if (!isConnecting.value) {
|
|
1344
|
-
// 进入连线状态前清空选中状态
|
|
1345
|
-
graphStore.clearSelection();
|
|
1346
|
-
isConnecting.value = true;
|
|
1347
|
-
showLine.value = true;
|
|
1348
|
-
}
|
|
1349
|
-
} else {
|
|
1350
|
-
// 如果没有找到sourceShape,重置连接状态
|
|
1351
|
-
isConnecting.value = false;
|
|
1352
|
-
showLine.value = false;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
// 直接计算黑点样式 - 使用动态连接点
|
|
1358
|
-
const dotStyle = computed(() => {
|
|
1359
|
-
if (!props.connectShapeData || !props.diagramBounds) {
|
|
1360
|
-
return { display: "none" };
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// 使用动态连接点位置
|
|
1364
|
-
const { x, y } = currentConnectPoint.value;
|
|
1365
|
-
|
|
1366
|
-
return {
|
|
1367
|
-
position: "absolute" as const,
|
|
1368
|
-
left: `${x}px`,
|
|
1369
|
-
top: `${y}px`,
|
|
1370
|
-
width: "8px",
|
|
1371
|
-
height: "8px",
|
|
1372
|
-
pointerEvents: "none" as const,
|
|
1373
|
-
zIndex: 1001,
|
|
1374
|
-
transform: "translate(-50%, -50%)",
|
|
1375
|
-
};
|
|
1376
|
-
});
|
|
1377
|
-
|
|
1378
|
-
// SVG 样式
|
|
1379
|
-
const svgStyle = computed(() => {
|
|
1380
|
-
if (
|
|
1381
|
-
!props.connectShapeData ||
|
|
1382
|
-
!props.connectShapeData.bounds ||
|
|
1383
|
-
!props.diagramBounds
|
|
1384
|
-
) {
|
|
1385
|
-
return { display: "none" };
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
return {
|
|
1389
|
-
position: "absolute" as const,
|
|
1390
|
-
left: "0px",
|
|
1391
|
-
top: "0px",
|
|
1392
|
-
width: "100%",
|
|
1393
|
-
height: "100%",
|
|
1394
|
-
pointerEvents: "none" as const,
|
|
1395
|
-
zIndex: 1000,
|
|
1396
|
-
};
|
|
1397
|
-
});
|
|
1398
|
-
|
|
1399
|
-
// 计算直角线路径点(增加状态有效性判断)
|
|
1400
|
-
const linePoints = computed(() => {
|
|
1401
|
-
// 新增:若未处于连线状态 / 线条未显示 / 无有效连接点,直接返回空值
|
|
1402
|
-
if (
|
|
1403
|
-
!isConnecting.value ||
|
|
1404
|
-
!showLine.value ||
|
|
1405
|
-
!props.connectShapeData ||
|
|
1406
|
-
!props.diagramBounds ||
|
|
1407
|
-
currentConnectPoint.value.x === 0 || // 排除无效默认值
|
|
1408
|
-
currentConnectPoint.value.y === 0
|
|
1409
|
-
) {
|
|
1410
|
-
return ""; // 返回空字符串,SVG 不会渲染任何线条
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
const { x: startX, y: startY } = currentConnectPoint.value;
|
|
1414
|
-
|
|
1415
|
-
let endX, endY;
|
|
1416
|
-
// 仅在连线状态下计算终点(避免无效值)
|
|
1417
|
-
if (isConnecting.value) {
|
|
1418
|
-
endX = mousePosition.value.x;
|
|
1419
|
-
endY = mousePosition.value.y;
|
|
1420
|
-
} else if (targetConnectPoint.value) {
|
|
1421
|
-
endX = targetConnectPoint.value.x;
|
|
1422
|
-
endY = targetConnectPoint.value.y;
|
|
1423
|
-
} else {
|
|
1424
|
-
return "";
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
// 使用工具类计算线路径
|
|
1428
|
-
return EdgeUtils.calculateLinePoints(
|
|
1429
|
-
{ x: startX, y: startY },
|
|
1430
|
-
{ x: endX, y: endY }
|
|
1431
|
-
);
|
|
1432
|
-
});
|
|
1433
|
-
|
|
1434
|
-
// 连接层样式 - 定位到 diagramBounds 位置,确保在最顶层
|
|
1435
|
-
const layerStyle = computed(() => getLayerStyle(props.diagramBounds));
|
|
1436
|
-
|
|
1437
|
-
// 取消连线的方法(完全重置所有相关状态)
|
|
1438
|
-
const cancelConnection = () => {
|
|
1439
|
-
EdgeUtils.cancelConnection(
|
|
1440
|
-
{
|
|
1441
|
-
isConnecting,
|
|
1442
|
-
currentConnectPoint,
|
|
1443
|
-
mousePosition,
|
|
1444
|
-
targetConnectPoint,
|
|
1445
|
-
targetShape,
|
|
1446
|
-
showLine,
|
|
1447
|
-
},
|
|
1448
|
-
highlightUtils
|
|
1449
|
-
);
|
|
1450
|
-
};
|
|
1451
1079
|
|
|
1452
1080
|
// 监听 connectShapeData 变化,重新初始化连接点
|
|
1453
1081
|
watch(
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="matrix">
|
|
2
|
+
<div ref="matrixRef" class="matrix">
|
|
3
3
|
|
|
4
4
|
<!-- 矩阵表格:左侧行树(文件夹+方框 / 橙圆C+三角),表头仅 1-7,虚线层级 -->
|
|
5
|
-
<div class="matrix-table-wrap">
|
|
5
|
+
<div class="matrix-table-wrap" :style="tableWrapStyle">
|
|
6
|
+
<div class="matrix-table-scroll">
|
|
6
7
|
<table class="matrix-table">
|
|
7
8
|
<thead>
|
|
8
9
|
<tr v-for="(row, rowIndex) in columnHeaderRows" :key="rowIndex">
|
|
@@ -147,6 +148,7 @@
|
|
|
147
148
|
</tr>
|
|
148
149
|
</tbody>
|
|
149
150
|
</table>
|
|
151
|
+
</div>
|
|
150
152
|
</div>
|
|
151
153
|
<!-- 矩阵右键菜单 -->
|
|
152
154
|
<MatrixContextMenu
|
|
@@ -162,7 +164,7 @@
|
|
|
162
164
|
</template>
|
|
163
165
|
|
|
164
166
|
<script setup lang="ts">
|
|
165
|
-
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
167
|
+
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
|
166
168
|
import type { TreeNode, ColumnHeaderCell } from '../../types'
|
|
167
169
|
import type { Matrix } from '../../types'
|
|
168
170
|
import {
|
|
@@ -177,6 +179,27 @@ import { useGraphStore } from '../../store/graphStore'
|
|
|
177
179
|
import { getIcon } from '../../utils/iconLoader'
|
|
178
180
|
import MatrixContextMenu from '../MatrixContextMenu/MatrixContextMenu.vue'
|
|
179
181
|
|
|
182
|
+
/** 表格滚动区高度 = .matrix 内容区高度 - 预留高度 */
|
|
183
|
+
const MATRIX_TABLE_WRAP_HEIGHT_OFFSET = 50
|
|
184
|
+
|
|
185
|
+
const matrixRef = ref<HTMLElement | null>(null)
|
|
186
|
+
const tableWrapHeight = ref<number | null>(null)
|
|
187
|
+
|
|
188
|
+
const tableWrapStyle = computed(() => {
|
|
189
|
+
if (tableWrapHeight.value == null) return undefined
|
|
190
|
+
return { height: `${tableWrapHeight.value}px` }
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
function updateTableWrapHeight(): void {
|
|
194
|
+
const el = matrixRef.value
|
|
195
|
+
if (!el) return
|
|
196
|
+
const h = el.clientHeight
|
|
197
|
+
if (h <= 0) return
|
|
198
|
+
tableWrapHeight.value = Math.max(0, h - MATRIX_TABLE_WRAP_HEIGHT_OFFSET)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let matrixResizeObserver: ResizeObserver | null = null
|
|
202
|
+
|
|
180
203
|
/** 当前选中的行 id、列 id(点击行头/列头/单元格时更新并通过 eventBus 发出) */
|
|
181
204
|
const selectedRowId = ref<string | null>(null)
|
|
182
205
|
const selectedColumnId = ref<string | null>(null)
|
|
@@ -228,12 +251,35 @@ function onMatrixDataChange(payload: Matrix): void {
|
|
|
228
251
|
}
|
|
229
252
|
|
|
230
253
|
const handleMatrixDataChange = (payload: Matrix) => onMatrixDataChange(payload)
|
|
254
|
+
|
|
255
|
+
function setupMatrixResizeObserver(): void {
|
|
256
|
+
const el = matrixRef.value
|
|
257
|
+
if (!el) return
|
|
258
|
+
updateTableWrapHeight()
|
|
259
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
260
|
+
matrixResizeObserver?.disconnect()
|
|
261
|
+
matrixResizeObserver = new ResizeObserver(() => updateTableWrapHeight())
|
|
262
|
+
matrixResizeObserver.observe(el)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function teardownMatrixResizeObserver(): void {
|
|
266
|
+
matrixResizeObserver?.disconnect()
|
|
267
|
+
matrixResizeObserver = null
|
|
268
|
+
}
|
|
269
|
+
|
|
231
270
|
onMounted(() => {
|
|
232
271
|
eventBus.on('matrix-data-change', handleMatrixDataChange)
|
|
272
|
+
nextTick(() => {
|
|
273
|
+
setupMatrixResizeObserver()
|
|
274
|
+
window.addEventListener('resize', updateTableWrapHeight)
|
|
275
|
+
})
|
|
233
276
|
})
|
|
234
277
|
onUnmounted(() => {
|
|
235
278
|
eventBus.off('matrix-data-change', handleMatrixDataChange)
|
|
279
|
+
teardownMatrixResizeObserver()
|
|
280
|
+
window.removeEventListener('resize', updateTableWrapHeight)
|
|
236
281
|
})
|
|
282
|
+
|
|
237
283
|
const rowTreeNodes = computed(() => matrixData.value.rowTree)
|
|
238
284
|
const columnTreeNodes = computed(() => matrixData.value.columnTree)
|
|
239
285
|
const matrixNodes = computed(() => matrixData.value.crossData)
|
|
@@ -283,6 +329,12 @@ const columnHeaderRows = computed((): ColHeaderCell[][] => {
|
|
|
283
329
|
})
|
|
284
330
|
})
|
|
285
331
|
|
|
332
|
+
watch(
|
|
333
|
+
[matrixData, columnHeaderRows, flattenedRows, rowExpandedIds, columnExpandedIds],
|
|
334
|
+
() => nextTick(updateTableWrapHeight),
|
|
335
|
+
{ deep: true },
|
|
336
|
+
)
|
|
337
|
+
|
|
286
338
|
/** 行树连接器:计算每行在每个祖先深度是否还有后续兄弟(用于画 │ 或 └) */
|
|
287
339
|
const flatRowsWithConnectors = computed(() => {
|
|
288
340
|
const items = flattenedRows.value
|
|
@@ -474,6 +526,8 @@ defineExpose({ setMatrixData })
|
|
|
474
526
|
.matrix {
|
|
475
527
|
width: 100%;
|
|
476
528
|
height: 100%;
|
|
529
|
+
min-width: 0;
|
|
530
|
+
min-height: 0;
|
|
477
531
|
display: flex;
|
|
478
532
|
flex-direction: column;
|
|
479
533
|
overflow: hidden;
|
|
@@ -552,13 +606,25 @@ defineExpose({ setMatrixData })
|
|
|
552
606
|
}
|
|
553
607
|
|
|
554
608
|
.matrix-table-wrap {
|
|
555
|
-
flex:
|
|
556
|
-
|
|
609
|
+
flex: 0 0 auto;
|
|
610
|
+
width: 100%;
|
|
611
|
+
min-width: 0;
|
|
612
|
+
overflow-x: auto;
|
|
613
|
+
overflow-y: auto;
|
|
557
614
|
padding: 12px;
|
|
615
|
+
box-sizing: border-box;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* 内层撑开表格宽度,外层 wrap 才能正确出现横向滚动条 */
|
|
619
|
+
.matrix-table-scroll {
|
|
620
|
+
display: inline-block;
|
|
621
|
+
min-width: 100%;
|
|
622
|
+
vertical-align: top;
|
|
558
623
|
}
|
|
559
624
|
|
|
560
625
|
.matrix-table {
|
|
561
626
|
border-collapse: collapse;
|
|
627
|
+
width: max-content;
|
|
562
628
|
min-width: 100%;
|
|
563
629
|
font-size: 12px;
|
|
564
630
|
|