@mx-sose-front/mx-sose-graph 1.1.6 → 1.1.7
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.d.ts +1 -104
- package/dist/index.esm.js +73 -373
- 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/style.css +1 -1
- package/package.json +1 -1
- package/src/components/InteractionLayer.vue +3 -93
- package/src/store/graphStore.ts +51 -233
- package/src/utils/containers.ts +2 -4
- package/src/utils/drag.ts +8 -48
- package/src/utils/keyboardUtils.ts +11 -25
- package/src/utils/shapeOps/shapeOps.ts +25 -69
- package/src/view/graph.vue +2 -4
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@charset "UTF-8";.selection-box[data-v-98567c09]{pointer-events:none;background:transparent}.resize-handles[data-v-98567c09]{position:relative;width:100%;height:100%}.resize-handle[data-v-98567c09]{position:absolute;width:10px;height:10px;background-color:#007bff;border:2px solid #fff;border-radius:50%;pointer-events:all;transition:all .2s ease;box-shadow:0 2px 4px #0000004d;z-index:999}.resize-handle.is-disabled[data-v-98567c09]{cursor:default!important}.resize-handle[data-v-98567c09]:hover{background-color:#0056b3;transform:scale(1.2)}.action-buttons[data-v-98567c09]{display:flex;flex-direction:column;gap:4px;pointer-events:all;background:rgba(255,255,255,.95);padding:6px;border-radius:4px;box-shadow:0 2px 8px #00000026;border:1px solid #e0e0e0;backdrop-filter:blur(2px)}.action-btn[data-v-98567c09]{width:28px;height:28px;border:1px solid #d0d0d0;border-radius:3px;background:linear-gradient(to bottom,#f8f9fa,#e9ecef);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;font-family:Arial,sans-serif;color:#495057;transition:all .2s ease;box-shadow:0 1px 2px #0000000d}.action-btn[data-v-98567c09]:hover{background:linear-gradient(to bottom,#e3f2fd,#bbdefb);border-color:#90caf9;transform:translateY(-1px);box-shadow:0 2px 4px #2196f34d;color:#1976d2}.edit-btn[data-v-98567c09]:hover{background:#e3f2fd}.border-btn[data-v-98567c09]{padding-bottom:4px;border-bottom:1px solid #e0e0e0}.name-text-box-container[data-v-f0d3e2ba]{position:absolute;z-index:1001;pointer-events:all}.name-text-box[data-v-f0d3e2ba]{border:1px dashed #007bff;background:rgba(255,255,255,.2);cursor:pointer;pointer-events:all;transition:all .2s ease;border-radius:4px;box-shadow:0 0 0 1px #007bff1a;height:100%;display:flex;align-items:center;justify-content:center}.name-text-box[data-v-f0d3e2ba]:hover{border-color:#0056b3;background:rgba(0,123,255,.05);box-shadow:0 0 0 2px #007bff33;transform:scale(1.02)}.name-editor-container[data-v-f0d3e2ba]{position:absolute;z-index:1002;display:flex;justify-content:center;align-items:center;pointer-events:all}.name-input[data-v-f0d3e2ba]{width:calc(100% - 20px);padding:2px 4px;border:2px solid #007bff;border-radius:6px;font-size:12px;font-weight:600;background:#fff;box-shadow:0 4px 12px #00000026;outline:none;text-align:center;font-family:inherit;resize:none;overflow:hidden}.name-input[data-v-f0d3e2ba]:focus{border-color:#0056b3;box-shadow:0 0 0 3px #007bff40}.context-menu[data-v-0f7f66ba]{background-color:#fff;border:1px solid #ebeef5;border-radius:6px;box-shadow:0 2px 12px #0000001a;padding:6px 0;overflow-y:auto;box-sizing:border-box;transition:opacity .2s ease,transform .2s ease;transform-origin:top left}.context-menu[style*="display: block"][data-v-0f7f66ba]{animation:fadeIn-0f7f66ba .2s ease forwards}@keyframes fadeIn-0f7f66ba{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar{width:6px}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar-thumb{background-color:#ddd;border-radius:3px}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar-track{background-color:#f5f5f5}.menu-item[data-v-0f7f66ba]{padding:0 15px;cursor:pointer;font-size:14px;color:#606266;border-radius:4px;height:36px;line-height:36px;box-sizing:border-box}.menu-item[data-v-0f7f66ba]:hover{background-color:#f5f7fa;color:#303133}.menu-disabled[data-v-0f7f66ba]{color:#c0c4cc!important;cursor:not-allowed!important;pointer-events:none}.menu-disabled[data-v-0f7f66ba]:hover{background-color:transparent!important}.menu-disabled img[data-v-0f7f66ba]{opacity:.5}.context-menu[style*="--edge-hint: left"][data-v-0f7f66ba]{border-top-left-radius:0}.context-menu[style*="--edge-hint: right"][data-v-0f7f66ba]{border-top-right-radius:0}.context-menu[style*="--edge-hint: top"][data-v-0f7f66ba]{border-top-left-radius:0;border-top-right-radius:0}.context-menu[style*="--edge-hint: bottom"][data-v-0f7f66ba]{border-bottom-left-radius:0;border-bottom-right-radius:0}.display-tooltip[data-v-0f7f66ba]{background-color:#fff;border:1px solid #ebeef5;border-radius:6px;box-shadow:0 2px 12px #0000001a;padding:6px 0;min-width:120px;animation:tooltipFadeIn-0f7f66ba .2s ease forwards}.tooltip-content[data-v-0f7f66ba]{overflow:hidden}.tooltip-item[data-v-0f7f66ba]{padding:8px 16px;cursor:pointer;font-size:14px;color:#606266;border-radius:4px;transition:background-color .2s ease;display:flex;align-items:center}.tooltip-item[data-v-0f7f66ba]:hover{background-color:#f5f7fa;color:#303133}.tooltip-item span[data-v-0f7f66ba]{display:block}@keyframes tooltipFadeIn-0f7f66ba{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.connection-line[data-v-c4c3e8c0]{position:absolute;pointer-events:none;z-index:10}.interaction-layer[data-v-a7475c72]{position:absolute;top:0;left:0;width:100%;height:100%;transform-origin:0 0;transform:scale(var(--c994dcd8));pointer-events:all;z-index:999}.hover-container-outline[data-v-a7475c72]{position:absolute;pointer-events:none;padding:6px;top:6px}.hover-container-outline.is-valid[data-v-a7475c72]{border:2px solid #0000ff}.hover-container-outline.is-invalid[data-v-a7475c72]{border:2px solid #f1eded;background:rgba(240,237,237,.842)}.resize-ghost[data-v-a7475c72]{position:absolute;border:2px solid #8a6d3b;background:transparent;box-shadow:0 0 0 2px #8a6d3b26 inset;pointer-events:none;z-index:9999}.connect-layer[data-v-a7475c72]{position:absolute;pointer-events:none;z-index:1000}.connect-dot-direct[data-v-a7475c72],.connect-dot-target[data-v-a7475c72]{width:8px;height:8px;background-color:#000;border-radius:50%;border:1px solid #fff;box-shadow:0 0 4px #0000004d}.connection-line[data-v-a7475c72]{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1000}.highlight-overlay[data-v-a7475c72]{position:absolute;pointer-events:none;box-sizing:border-box;z-index:1001;border-radius:2px}.highlight-overlay.blue[data-v-a7475c72]{border:3px solid #1890ff}.highlight-overlay.red[data-v-a7475c72]{border:3px solid #f56c6c}.ghost-outline[data-v-a7475c72]{position:absolute;border:4px solid #8a6d3b;background:transparent;pointer-events:none;border-radius:4px;z-index:9998;box-sizing:border-box}.diagram-shape[data-v-1f4cb409]{background:#e5ecf0;border-radius:10px;box-shadow:0 2px 8px #0000001a;display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;margin:0;padding:0;border:var(--bw) var(--bs) var(--bc)}.hit-edge[data-v-1f4cb409]{position:absolute;background:transparent;z-index:999;pointer-events:auto}.hit-top[data-v-1f4cb409]{left:0;right:0;top:0;height:var(--bw)}.hit-right[data-v-1f4cb409]{top:0;right:0;bottom:0;width:var(--bw)}.hit-bottom[data-v-1f4cb409]{left:0;right:0;bottom:0;height:var(--bw)}.hit-left[data-v-1f4cb409]{top:0;left:0;bottom:0;width:var(--bw)}.diagram-header[data-v-1f4cb409]{background:rgba(255,255,255,.5);padding:3px 16px 3px 5px;display:inline-flex;align-items:center;position:relative;white-space:nowrap;align-self:flex-start}.diagram-header[data-v-1f4cb409]:after{content:"";position:absolute;bottom:-12px;right:-12px;width:20px;height:20px;background-color:var(--5113555a);transform:rotate(45deg);z-index:0}.diagram-title[data-v-1f4cb409]{font-family:var(--name-ff);font-size:var(--name-fs);font-weight:400;line-height:normal;letter-spacing:0em;font-variation-settings:"opsz" auto;font-feature-settings:"kern" on;color:var(--name-color);position:relative;z-index:2}.diagram-shape[data-v-05edf0d1]{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;pointer-events:auto;touch-action:none;-webkit-touch-action:none;-ms-touch-action:none;zoom:1;transform:none!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important}.diagram-svg[data-v-05edf0d1]{width:100%;height:100%;transform:none!important;-webkit-transform:none!important}.capability-shape[data-v-5ba17051]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-5ba17051]{display:block}.capability-shape[data-v-8e8054b6]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-8e8054b6]{display:block}.capability-shape[data-v-27d34586]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-27d34586]{display:block}.capability-shape[data-v-5f0b14f5]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-5f0b14f5]{display:block}.capability-shape[data-v-4f0bb734]{display:inline-block;position:relative;overflow:visible;box-sizing:content-box}[data-v-4f0bb734] .name-text-svg{text-align:left}[data-v-4f0bb734] .shape-name-svg{text-align:center}[data-v-4f0bb734] .content-text-svg{font-size:13px;line-height:1.4;color:#000;font-family:Arial,sans-serif;min-height:20px;overflow:visible;width:100%;box-sizing:border-box}[data-v-4f0bb734] .content-item{margin-bottom:4px;word-break:break-word;white-space:normal;overflow:visible;width:100%;box-sizing:border-box}[data-v-4f0bb734] .content-item:last-child{margin-bottom:0}.edge-component[data-v-8ed11c37]{position:absolute;pointer-events:all;cursor:pointer}.edge-svg[data-v-8ed11c37]{width:100%;height:100%;overflow:visible}.edge-svg polyline[data-v-8ed11c37]{pointer-events:all;stroke-linecap:round;stroke-linejoin:round}.edge-svg circle[data-v-8ed11c37]{pointer-events:none}.diagram-shape[data-v-daf180ad]{-webkit-user-select:none;-moz-user-select:none;user-select:none}.diagram-svg[data-v-daf180ad]{width:100%;height:100%}.capability-shape[data-v-ec8f07ea]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-ec8f07ea]{display:block}.output-pin-shape[data-v-2081b7c7]{display:flex;align-items:center;justify-content:center;overflow:visible}.output-pin-svg[data-v-2081b7c7]{display:block;overflow:visible}.port-shape[data-v-4aa98dd6]{display:flex;align-items:center;justify-content:center;overflow:visible}.port-svg[data-v-4aa98dd6]{display:block;overflow:visible}.diagram-list-tooltip[data-v-4e4791fa]{position:fixed;background:#ffffff;border:1px solid #e5e7eb;border-radius:6px;padding:10px 6px;box-shadow:0 4px 12px #00000026;z-index:9999;width:180px;max-width:300px}.tooltip-title[data-v-4e4791fa]{padding:0 12px}.diagram-item{&[data-v-4e4791fa]{display:flex;align-items:center;padding:0 12px;cursor:pointer;border-radius:4px;height:36px;line-height:36px;transition:background-color .2s}.diagram-name[data-v-4e4791fa]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}}.diagram-item[data-v-4e4791fa]:hover{background-color:#f0f3f6}.diagram-icon[data-v-4e4791fa]{width:24px;height:24px;margin-right:6px}.graph-view[data-v-f90f1704]{width:100%;height:100%;background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diagram-content[data-v-f90f1704]{position:absolute;inset:10px 0 10px 10px;overflow:auto;touch-action:none}.shapes-container[data-v-f90f1704]{position:relative;width:100%;height:100%;min-height:600px;transform-origin:0 0;transform:scale(var(--478247fa))}.shapes-container[data-v-f90f1704]>*{position:absolute;will-change:transform}.zoom-level[data-v-f90f1704]{position:absolute;bottom:30px;right:15px;font-size:10px;font-weight:500;min-width:35px;text-align:center;color:#333;background:rgba(255,255,255,.9);padding:2px 6px;border-radius:3px;box-shadow:0 1px 3px #0000001a}.cut-overlay-svg[data-v-f90f1704]{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:998}
|
|
1
|
+
@charset "UTF-8";.selection-box[data-v-98567c09]{pointer-events:none;background:transparent}.resize-handles[data-v-98567c09]{position:relative;width:100%;height:100%}.resize-handle[data-v-98567c09]{position:absolute;width:10px;height:10px;background-color:#007bff;border:2px solid #fff;border-radius:50%;pointer-events:all;transition:all .2s ease;box-shadow:0 2px 4px #0000004d;z-index:999}.resize-handle.is-disabled[data-v-98567c09]{cursor:default!important}.resize-handle[data-v-98567c09]:hover{background-color:#0056b3;transform:scale(1.2)}.action-buttons[data-v-98567c09]{display:flex;flex-direction:column;gap:4px;pointer-events:all;background:rgba(255,255,255,.95);padding:6px;border-radius:4px;box-shadow:0 2px 8px #00000026;border:1px solid #e0e0e0;backdrop-filter:blur(2px)}.action-btn[data-v-98567c09]{width:28px;height:28px;border:1px solid #d0d0d0;border-radius:3px;background:linear-gradient(to bottom,#f8f9fa,#e9ecef);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;font-family:Arial,sans-serif;color:#495057;transition:all .2s ease;box-shadow:0 1px 2px #0000000d}.action-btn[data-v-98567c09]:hover{background:linear-gradient(to bottom,#e3f2fd,#bbdefb);border-color:#90caf9;transform:translateY(-1px);box-shadow:0 2px 4px #2196f34d;color:#1976d2}.edit-btn[data-v-98567c09]:hover{background:#e3f2fd}.border-btn[data-v-98567c09]{padding-bottom:4px;border-bottom:1px solid #e0e0e0}.name-text-box-container[data-v-f0d3e2ba]{position:absolute;z-index:1001;pointer-events:all}.name-text-box[data-v-f0d3e2ba]{border:1px dashed #007bff;background:rgba(255,255,255,.2);cursor:pointer;pointer-events:all;transition:all .2s ease;border-radius:4px;box-shadow:0 0 0 1px #007bff1a;height:100%;display:flex;align-items:center;justify-content:center}.name-text-box[data-v-f0d3e2ba]:hover{border-color:#0056b3;background:rgba(0,123,255,.05);box-shadow:0 0 0 2px #007bff33;transform:scale(1.02)}.name-editor-container[data-v-f0d3e2ba]{position:absolute;z-index:1002;display:flex;justify-content:center;align-items:center;pointer-events:all}.name-input[data-v-f0d3e2ba]{width:calc(100% - 20px);padding:2px 4px;border:2px solid #007bff;border-radius:6px;font-size:12px;font-weight:600;background:#fff;box-shadow:0 4px 12px #00000026;outline:none;text-align:center;font-family:inherit;resize:none;overflow:hidden}.name-input[data-v-f0d3e2ba]:focus{border-color:#0056b3;box-shadow:0 0 0 3px #007bff40}.context-menu[data-v-0f7f66ba]{background-color:#fff;border:1px solid #ebeef5;border-radius:6px;box-shadow:0 2px 12px #0000001a;padding:6px 0;overflow-y:auto;box-sizing:border-box;transition:opacity .2s ease,transform .2s ease;transform-origin:top left}.context-menu[style*="display: block"][data-v-0f7f66ba]{animation:fadeIn-0f7f66ba .2s ease forwards}@keyframes fadeIn-0f7f66ba{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar{width:6px}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar-thumb{background-color:#ddd;border-radius:3px}.context-menu[data-v-0f7f66ba]::-webkit-scrollbar-track{background-color:#f5f5f5}.menu-item[data-v-0f7f66ba]{padding:0 15px;cursor:pointer;font-size:14px;color:#606266;border-radius:4px;height:36px;line-height:36px;box-sizing:border-box}.menu-item[data-v-0f7f66ba]:hover{background-color:#f5f7fa;color:#303133}.menu-disabled[data-v-0f7f66ba]{color:#c0c4cc!important;cursor:not-allowed!important;pointer-events:none}.menu-disabled[data-v-0f7f66ba]:hover{background-color:transparent!important}.menu-disabled img[data-v-0f7f66ba]{opacity:.5}.context-menu[style*="--edge-hint: left"][data-v-0f7f66ba]{border-top-left-radius:0}.context-menu[style*="--edge-hint: right"][data-v-0f7f66ba]{border-top-right-radius:0}.context-menu[style*="--edge-hint: top"][data-v-0f7f66ba]{border-top-left-radius:0;border-top-right-radius:0}.context-menu[style*="--edge-hint: bottom"][data-v-0f7f66ba]{border-bottom-left-radius:0;border-bottom-right-radius:0}.display-tooltip[data-v-0f7f66ba]{background-color:#fff;border:1px solid #ebeef5;border-radius:6px;box-shadow:0 2px 12px #0000001a;padding:6px 0;min-width:120px;animation:tooltipFadeIn-0f7f66ba .2s ease forwards}.tooltip-content[data-v-0f7f66ba]{overflow:hidden}.tooltip-item[data-v-0f7f66ba]{padding:8px 16px;cursor:pointer;font-size:14px;color:#606266;border-radius:4px;transition:background-color .2s ease;display:flex;align-items:center}.tooltip-item[data-v-0f7f66ba]:hover{background-color:#f5f7fa;color:#303133}.tooltip-item span[data-v-0f7f66ba]{display:block}@keyframes tooltipFadeIn-0f7f66ba{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.connection-line[data-v-c4c3e8c0]{position:absolute;pointer-events:none;z-index:10}.interaction-layer[data-v-24a22c8c]{position:absolute;top:0;left:0;width:100%;height:100%;transform-origin:0 0;transform:scale(var(--65f24407));pointer-events:all;z-index:999}.hover-container-outline[data-v-24a22c8c]{position:absolute;pointer-events:none;padding:6px;top:6px}.hover-container-outline.is-valid[data-v-24a22c8c]{border:2px solid #0000ff}.hover-container-outline.is-invalid[data-v-24a22c8c]{border:2px solid #f1eded;background:rgba(240,237,237,.842)}.resize-ghost[data-v-24a22c8c]{position:absolute;border:2px solid #8a6d3b;background:transparent;box-shadow:0 0 0 2px #8a6d3b26 inset;pointer-events:none;z-index:9999}.connect-layer[data-v-24a22c8c]{position:absolute;pointer-events:none;z-index:1000}.connect-dot-direct[data-v-24a22c8c],.connect-dot-target[data-v-24a22c8c]{width:8px;height:8px;background-color:#000;border-radius:50%;border:1px solid #fff;box-shadow:0 0 4px #0000004d}.connection-line[data-v-24a22c8c]{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1000}.highlight-overlay[data-v-24a22c8c]{position:absolute;pointer-events:none;box-sizing:border-box;z-index:1001;border-radius:2px}.highlight-overlay.blue[data-v-24a22c8c]{border:3px solid #1890ff}.highlight-overlay.red[data-v-24a22c8c]{border:3px solid #f56c6c}.diagram-shape[data-v-1f4cb409]{background:#e5ecf0;border-radius:10px;box-shadow:0 2px 8px #0000001a;display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;margin:0;padding:0;border:var(--bw) var(--bs) var(--bc)}.hit-edge[data-v-1f4cb409]{position:absolute;background:transparent;z-index:999;pointer-events:auto}.hit-top[data-v-1f4cb409]{left:0;right:0;top:0;height:var(--bw)}.hit-right[data-v-1f4cb409]{top:0;right:0;bottom:0;width:var(--bw)}.hit-bottom[data-v-1f4cb409]{left:0;right:0;bottom:0;height:var(--bw)}.hit-left[data-v-1f4cb409]{top:0;left:0;bottom:0;width:var(--bw)}.diagram-header[data-v-1f4cb409]{background:rgba(255,255,255,.5);padding:3px 16px 3px 5px;display:inline-flex;align-items:center;position:relative;white-space:nowrap;align-self:flex-start}.diagram-header[data-v-1f4cb409]:after{content:"";position:absolute;bottom:-12px;right:-12px;width:20px;height:20px;background-color:var(--5113555a);transform:rotate(45deg);z-index:0}.diagram-title[data-v-1f4cb409]{font-family:var(--name-ff);font-size:var(--name-fs);font-weight:400;line-height:normal;letter-spacing:0em;font-variation-settings:"opsz" auto;font-feature-settings:"kern" on;color:var(--name-color);position:relative;z-index:2}.diagram-shape[data-v-05edf0d1]{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;pointer-events:auto;touch-action:none;-webkit-touch-action:none;-ms-touch-action:none;zoom:1;transform:none!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important}.diagram-svg[data-v-05edf0d1]{width:100%;height:100%;transform:none!important;-webkit-transform:none!important}.capability-shape[data-v-5ba17051]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-5ba17051]{display:block}.capability-shape[data-v-8e8054b6]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-8e8054b6]{display:block}.capability-shape[data-v-27d34586]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-27d34586]{display:block}.capability-shape[data-v-5f0b14f5]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-5f0b14f5]{display:block}.capability-shape[data-v-4f0bb734]{display:inline-block;position:relative;overflow:visible;box-sizing:content-box}[data-v-4f0bb734] .name-text-svg{text-align:left}[data-v-4f0bb734] .shape-name-svg{text-align:center}[data-v-4f0bb734] .content-text-svg{font-size:13px;line-height:1.4;color:#000;font-family:Arial,sans-serif;min-height:20px;overflow:visible;width:100%;box-sizing:border-box}[data-v-4f0bb734] .content-item{margin-bottom:4px;word-break:break-word;white-space:normal;overflow:visible;width:100%;box-sizing:border-box}[data-v-4f0bb734] .content-item:last-child{margin-bottom:0}.edge-component[data-v-8ed11c37]{position:absolute;pointer-events:all;cursor:pointer}.edge-svg[data-v-8ed11c37]{width:100%;height:100%;overflow:visible}.edge-svg polyline[data-v-8ed11c37]{pointer-events:all;stroke-linecap:round;stroke-linejoin:round}.edge-svg circle[data-v-8ed11c37]{pointer-events:none}.diagram-shape[data-v-daf180ad]{-webkit-user-select:none;-moz-user-select:none;user-select:none}.diagram-svg[data-v-daf180ad]{width:100%;height:100%}.capability-shape[data-v-ec8f07ea]{display:flex;align-items:center;justify-content:center;overflow:hidden}.capability-svg[data-v-ec8f07ea]{display:block}.output-pin-shape[data-v-2081b7c7]{display:flex;align-items:center;justify-content:center;overflow:visible}.output-pin-svg[data-v-2081b7c7]{display:block;overflow:visible}.port-shape[data-v-4aa98dd6]{display:flex;align-items:center;justify-content:center;overflow:visible}.port-svg[data-v-4aa98dd6]{display:block;overflow:visible}.diagram-list-tooltip[data-v-4e4791fa]{position:fixed;background:#ffffff;border:1px solid #e5e7eb;border-radius:6px;padding:10px 6px;box-shadow:0 4px 12px #00000026;z-index:9999;width:180px;max-width:300px}.tooltip-title[data-v-4e4791fa]{padding:0 12px}.diagram-item{&[data-v-4e4791fa]{display:flex;align-items:center;padding:0 12px;cursor:pointer;border-radius:4px;height:36px;line-height:36px;transition:background-color .2s}.diagram-name[data-v-4e4791fa]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}}.diagram-item[data-v-4e4791fa]:hover{background-color:#f0f3f6}.diagram-icon[data-v-4e4791fa]{width:24px;height:24px;margin-right:6px}.graph-view[data-v-d4982c78]{width:100%;height:100%;background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diagram-content[data-v-d4982c78]{position:absolute;inset:10px 0 10px 10px;overflow:auto;touch-action:none}.shapes-container[data-v-d4982c78]{position:relative;width:100%;height:100%;min-height:600px;transform-origin:0 0;transform:scale(var(--18ea7fc8))}.shapes-container[data-v-d4982c78]>*{position:absolute;will-change:transform}.zoom-level[data-v-d4982c78]{position:absolute;bottom:30px;right:15px;font-size:10px;font-weight:500;min-width:35px;text-align:center;color:#333;background:rgba(255,255,255,.9);padding:2px 6px;border-radius:3px;box-shadow:0 1px 3px #0000001a}.cut-overlay-svg[data-v-d4982c78]{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:998}
|
package/package.json
CHANGED
|
@@ -26,24 +26,9 @@
|
|
|
26
26
|
<!-- 框选预览矩形 -->
|
|
27
27
|
<div v-show="marqueeRect" class="marquee-rect"
|
|
28
28
|
:style="getMarqueeStyle(marqueeRect || { x: 0, y: 0, width: 0, height: 0 })" />
|
|
29
|
-
<!-- 拖动和缩放的预览框
|
|
30
|
-
<
|
|
31
|
-
v-for="g in dragGhostRects"
|
|
32
|
-
:key="g.id"
|
|
33
|
-
class="ghost-outline"
|
|
34
|
-
:style="{
|
|
35
|
-
left: g.x + 'px',
|
|
36
|
-
top: g.y + 'px',
|
|
37
|
-
width: g.width + 'px',
|
|
38
|
-
height: g.height + 'px',
|
|
39
|
-
}"
|
|
40
|
-
/>
|
|
41
|
-
<!-- 缩放预览框仍使用完整组件(数量少,需要看到内容变化) -->
|
|
42
|
-
<component v-for="g in resizeGhosts" :key="g.id" class="ghost-shape" :is="getShapeComponent(g)" :shape="g"
|
|
29
|
+
<!-- 拖动和缩放的预览框 -->
|
|
30
|
+
<component v-for="g in allGhosts" :key="g.id" class="ghost-shape" :is="getShapeComponent(g)" :shape="g"
|
|
43
31
|
:style="getGhostShapeStyle(g)" />
|
|
44
|
-
<!-- 高亮图元 -->
|
|
45
|
-
<component v-if="highlightedGhost" :key="highlightedGhost.id" class="ghost-shape" :is="getShapeComponent(highlightedGhost)" :shape="highlightedGhost"
|
|
46
|
-
:style="getGhostShapeStyle(highlightedGhost)" />
|
|
47
32
|
<!-- 名称编辑组件 -->
|
|
48
33
|
<NameEditor :selected-shape="graphStore.selectedShape" :can-edit="!graphStore.pendingNestedIds.includes(
|
|
49
34
|
graphStore.selectedShape?.id || ''
|
|
@@ -172,71 +157,7 @@ const hoverRect = computed(() => {
|
|
|
172
157
|
const b = getBounds(s);
|
|
173
158
|
return { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
174
159
|
});
|
|
175
|
-
|
|
176
|
-
// 拖动预览框 - 只返回简单的矩形数据,用于渲染轻量级 div
|
|
177
|
-
type GhostRect = { id: string; x: number; y: number; width: number; height: number };
|
|
178
|
-
|
|
179
|
-
const dragGhostRects = computed<GhostRect[]>(() => {
|
|
180
|
-
if (!graphStore.isDragging || isExternalCreateDragging.value) return [];
|
|
181
|
-
|
|
182
|
-
const rects: GhostRect[] = [];
|
|
183
|
-
const dragBase = graphStore.dragBase;
|
|
184
|
-
const descendantsSnapshot = graphStore.dragDescendantsSnapshot;
|
|
185
|
-
|
|
186
|
-
for (const ghost of graphStore.ghostShadow) {
|
|
187
|
-
if (!ghost.bounds) continue;
|
|
188
|
-
|
|
189
|
-
// 从 dragBase 获取拖动开始时的坐标(而不是实时坐标)
|
|
190
|
-
const baseBounds = dragBase[ghost.id];
|
|
191
|
-
if (!baseBounds) continue;
|
|
192
|
-
|
|
193
|
-
// 计算位移量
|
|
194
|
-
const dx = (ghost.bounds.x ?? 0) - baseBounds.x;
|
|
195
|
-
const dy = (ghost.bounds.y ?? 0) - baseBounds.y;
|
|
196
|
-
|
|
197
|
-
// 添加父元素预览框
|
|
198
|
-
rects.push({
|
|
199
|
-
id: ghost.id,
|
|
200
|
-
x: ghost.bounds.x ?? 0,
|
|
201
|
-
y: ghost.bounds.y ?? 0,
|
|
202
|
-
width: ghost.bounds.width ?? 0,
|
|
203
|
-
height: ghost.bounds.height ?? 0,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// 从快照中获取后代,应用位移(不需要每帧递归收集)
|
|
207
|
-
const cachedDescendants = descendantsSnapshot.get(ghost.id);
|
|
208
|
-
if (cachedDescendants && cachedDescendants.length > 0) {
|
|
209
|
-
for (const desc of cachedDescendants) {
|
|
210
|
-
rects.push({
|
|
211
|
-
id: desc.id,
|
|
212
|
-
x: desc.x + dx,
|
|
213
|
-
y: desc.y + dy,
|
|
214
|
-
width: desc.width,
|
|
215
|
-
height: desc.height,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return rects;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// 缩放预览框 - 需要完整渲染组件
|
|
224
|
-
const resizeGhosts = computed<Shape[]>(() => resizeGhostShadow.value);
|
|
225
|
-
|
|
226
|
-
// 高亮图元的 ghost
|
|
227
|
-
const highlightedGhost = computed<Shape | null>(() => {
|
|
228
|
-
if (!highlightedShape.value) return null;
|
|
229
|
-
return {
|
|
230
|
-
...highlightedShape.value,
|
|
231
|
-
meta: {
|
|
232
|
-
...(highlightedShape.value as any).meta,
|
|
233
|
-
isHighlighted: true,
|
|
234
|
-
isGhost: true,
|
|
235
|
-
},
|
|
236
|
-
} as Shape;
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// 保留 allGhosts 用于兼容(如果其他地方用到)
|
|
160
|
+
// 把拖动缩放预览框合并,
|
|
240
161
|
const allGhosts = computed<Shape[]>(() => {
|
|
241
162
|
const byId = new Map<string, Shape>();
|
|
242
163
|
// 收集所有Ghost形状
|
|
@@ -1854,15 +1775,4 @@ defineExpose({
|
|
|
1854
1775
|
.highlight-overlay.red {
|
|
1855
1776
|
border: 3px solid #f56c6c;
|
|
1856
1777
|
}
|
|
1857
|
-
|
|
1858
|
-
/* 拖动预览框 - 简化的轮廓框,性能更好 */
|
|
1859
|
-
.ghost-outline {
|
|
1860
|
-
position: absolute;
|
|
1861
|
-
border: 4px solid #8a6d3b;
|
|
1862
|
-
background: transparent;
|
|
1863
|
-
pointer-events: none;
|
|
1864
|
-
border-radius: 4px;
|
|
1865
|
-
z-index: 9998;
|
|
1866
|
-
box-sizing: border-box;
|
|
1867
|
-
}
|
|
1868
1778
|
</style>
|
package/src/store/graphStore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import { ref, computed, shallowReactive,
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref, computed, shallowReactive, nextTick, type Ref } from 'vue'
|
|
3
3
|
import type { Shape, OwnerPayload, ShapeSizeUpdatePayload } from '../types'
|
|
4
4
|
import { eventBus } from './eventBus'
|
|
5
5
|
import { getPolicy, checkNestViaFront, } from '../utils/policy'
|
|
@@ -74,132 +74,43 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
74
74
|
const cutShapeIds = ref<Set<string>>(new Set())
|
|
75
75
|
// 剪贴板中图元的数量(用于工具栏按钮状态同步)
|
|
76
76
|
const copiedShapesCount = ref<number>(0)
|
|
77
|
-
// 拖动时的后代坐标快照(只在 startDrag 时计算一次,避免每帧重算)
|
|
78
|
-
// key: 被拖动的父元素 ID, value: 所有后代的坐标快照
|
|
79
|
-
type DescendantSnapshot = { id: string; x: number; y: number; width: number; height: number };
|
|
80
|
-
const dragDescendantsSnapshot = ref<Map<string, DescendantSnapshot[]>>(new Map())
|
|
81
77
|
// 计算属性
|
|
82
78
|
const shapeCount = computed(() => shapes.value.length)
|
|
83
79
|
const hasSelectedShape = computed(() => selectedShape.value !== null)
|
|
84
80
|
|
|
85
|
-
// ========== 增量索引:手动维护,避免 computed 全量重建 ==========
|
|
86
|
-
|
|
87
81
|
/**
|
|
88
|
-
*
|
|
89
|
-
* 用于快速通过ID查找图元对象
|
|
90
|
-
* key: 图元 ID, value: 图元对象
|
|
91
|
-
* 性能优化: O(1) 增删改查,避免每次 shapes 变化都 O(n) 重建
|
|
92
|
-
*/
|
|
93
|
-
const _shapeMapRef = shallowRef(new Map<string, Shape>())
|
|
94
|
-
// 使用 computed 包装,确保 Pinia 正确解包
|
|
95
|
-
const shapeMap = computed(() => _shapeMapRef.value)
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 父子关系索引映射(增量维护版本)
|
|
82
|
+
* 父子关系索引映射
|
|
99
83
|
* 用于快速查找某个图元的所有直接子图元
|
|
100
84
|
* key: 父图元 ID, value: 子图元 ID 数组
|
|
101
|
-
* 性能优化: O(1)
|
|
85
|
+
* 性能优化: O(n) 构建, O(1) 查询
|
|
102
86
|
*/
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const parentChildMap = computed(() => _parentChildMapRef.value)
|
|
106
|
-
|
|
107
|
-
// ========== 索引维护内部方法 ==========
|
|
108
|
-
|
|
109
|
-
/** 添加图元到索引 */
|
|
110
|
-
function _indexAdd(shape: Shape) {
|
|
111
|
-
_shapeMapRef.value.set(shape.id, shape)
|
|
112
|
-
if (shape.parenShapeId) {
|
|
113
|
-
const children = _parentChildMapRef.value.get(shape.parenShapeId)
|
|
114
|
-
if (children) {
|
|
115
|
-
if (!children.includes(shape.id)) {
|
|
116
|
-
children.push(shape.id)
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
_parentChildMapRef.value.set(shape.parenShapeId, [shape.id])
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** 从索引中移除图元 */
|
|
125
|
-
function _indexRemove(shapeId: string) {
|
|
126
|
-
const shape = _shapeMapRef.value.get(shapeId)
|
|
127
|
-
if (!shape) return
|
|
128
|
-
_shapeMapRef.value.delete(shapeId)
|
|
129
|
-
if (shape.parenShapeId) {
|
|
130
|
-
const children = _parentChildMapRef.value.get(shape.parenShapeId)
|
|
131
|
-
if (children) {
|
|
132
|
-
const idx = children.indexOf(shapeId)
|
|
133
|
-
if (idx !== -1) children.splice(idx, 1)
|
|
134
|
-
if (children.length === 0) {
|
|
135
|
-
_parentChildMapRef.value.delete(shape.parenShapeId)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// 如果这个图元是父节点,清理它的子节点列表
|
|
140
|
-
_parentChildMapRef.value.delete(shapeId)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** 更新图元索引(处理父节点变更) */
|
|
144
|
-
function _indexUpdate(shape: Shape, oldParentId: string | null | undefined) {
|
|
145
|
-
_shapeMapRef.value.set(shape.id, shape)
|
|
146
|
-
const newParentId = shape.parenShapeId ?? null
|
|
147
|
-
const normalizedOldParent = oldParentId ?? null
|
|
148
|
-
|
|
149
|
-
if (normalizedOldParent !== newParentId) {
|
|
150
|
-
// 从旧父节点移除
|
|
151
|
-
if (normalizedOldParent) {
|
|
152
|
-
const oldChildren = _parentChildMapRef.value.get(normalizedOldParent)
|
|
153
|
-
if (oldChildren) {
|
|
154
|
-
const idx = oldChildren.indexOf(shape.id)
|
|
155
|
-
if (idx !== -1) oldChildren.splice(idx, 1)
|
|
156
|
-
if (oldChildren.length === 0) {
|
|
157
|
-
_parentChildMapRef.value.delete(normalizedOldParent)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// 添加到新父节点
|
|
162
|
-
if (newParentId) {
|
|
163
|
-
const newChildren = _parentChildMapRef.value.get(newParentId)
|
|
164
|
-
if (newChildren) {
|
|
165
|
-
if (!newChildren.includes(shape.id)) {
|
|
166
|
-
newChildren.push(shape.id)
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
_parentChildMapRef.value.set(newParentId, [shape.id])
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** 全量重建索引(用于 replace 操作或初始化) */
|
|
176
|
-
function _rebuildIndex() {
|
|
177
|
-
const newShapeMap = new Map<string, Shape>()
|
|
178
|
-
const newParentChildMap = new Map<string, string[]>()
|
|
179
|
-
|
|
87
|
+
const parentChildMap = computed(() => {
|
|
88
|
+
const map = new Map<string, string[]>();
|
|
180
89
|
shapes.value.forEach(shape => {
|
|
181
|
-
newShapeMap.set(shape.id, shape)
|
|
182
90
|
if (shape.parenShapeId) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
children.push(shape.id)
|
|
186
|
-
} else {
|
|
187
|
-
newParentChildMap.set(shape.parenShapeId, [shape.id])
|
|
91
|
+
if (!map.has(shape.parenShapeId)) {
|
|
92
|
+
map.set(shape.parenShapeId, []);
|
|
188
93
|
}
|
|
94
|
+
map.get(shape.parenShapeId)!.push(shape.id);
|
|
189
95
|
}
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
_parentChildMapRef.value = newParentChildMap
|
|
194
|
-
triggerRef(_shapeMapRef)
|
|
195
|
-
triggerRef(_parentChildMapRef)
|
|
196
|
-
}
|
|
96
|
+
});
|
|
97
|
+
return map;
|
|
98
|
+
})
|
|
197
99
|
|
|
198
|
-
/**
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 图元ID索引映射
|
|
102
|
+
* 用于快速通过ID查找图元对象
|
|
103
|
+
* key: 图元 ID, value: 图元对象
|
|
104
|
+
* 性能优化: O(n) 构建, O(1) 查询
|
|
105
|
+
* 解决嵌套图元场景下大量 find() 调用导致的性能问题
|
|
106
|
+
*/
|
|
107
|
+
const shapeMap = computed(() => {
|
|
108
|
+
const map = new Map<string, Shape>();
|
|
109
|
+
shapes.value.forEach(shape => {
|
|
110
|
+
map.set(shape.id, shape);
|
|
111
|
+
});
|
|
112
|
+
return map;
|
|
113
|
+
})
|
|
203
114
|
|
|
204
115
|
//图元shapeKey
|
|
205
116
|
const taggedValueLabels = ref<string[]>([]) // 隔间组件的图元类型
|
|
@@ -236,9 +147,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
236
147
|
}
|
|
237
148
|
|
|
238
149
|
shapes.value.push(shape)
|
|
239
|
-
// 增量更新索引
|
|
240
|
-
_indexAdd(shape)
|
|
241
|
-
_triggerIndexUpdate()
|
|
242
150
|
// 通过事件总线发送事件
|
|
243
151
|
// eventBus.emit('shape-added', shape)
|
|
244
152
|
|
|
@@ -255,10 +163,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
255
163
|
const removeShape = (shapeId: string) => {
|
|
256
164
|
const index = shapes.value.findIndex(s => s.id == shapeId)
|
|
257
165
|
if (index > -1) {
|
|
258
|
-
// 增量更新索引(在删除数组元素之前)
|
|
259
|
-
_indexRemove(shapeId)
|
|
260
|
-
shapes.value.splice(index, 1)
|
|
261
|
-
_triggerIndexUpdate()
|
|
262
166
|
// 如果删除的是当前选中的形状,清除选中状态
|
|
263
167
|
if (selectedShape.value?.id === shapeId) {
|
|
264
168
|
selectedShape.value = null
|
|
@@ -293,8 +197,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
293
197
|
const updateShapeRaw = (shapeId: string, updates: Partial<Shape>, id: 'id' | 'modelId' = 'id') => {
|
|
294
198
|
const shape = shapes.value.find(s => s[id] === shapeId)
|
|
295
199
|
if (shape) {
|
|
296
|
-
// 记录旧的父节点ID,用于索引更新
|
|
297
|
-
const oldParentId = shape.parenShapeId
|
|
298
200
|
// 对 Pin 做强保护:永不允许把 parenShapeId 置空/undefined
|
|
299
201
|
if (shape.shapeType === 'pin' && 'parenShapeId' in updates) {
|
|
300
202
|
const v: any = (updates as any).parenShapeId
|
|
@@ -311,9 +213,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
311
213
|
const nextBounds = updates.bounds ? { ...shape.bounds, ...updates.bounds } : shape.bounds
|
|
312
214
|
const nextStyle = updates.style ? { ...(shape.style || {}), ...(updates.style as any) } : shape.style
|
|
313
215
|
Object.assign(shape, updates, { bounds: nextBounds, style: nextStyle })
|
|
314
|
-
// 增量更新索引(处理父节点变更)
|
|
315
|
-
_indexUpdate(shape, oldParentId)
|
|
316
|
-
_triggerIndexUpdate()
|
|
317
216
|
eventBus.emit('shape-updated', shape, updates)
|
|
318
217
|
}
|
|
319
218
|
}
|
|
@@ -372,46 +271,32 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
372
271
|
}
|
|
373
272
|
// 被框选中的多个 shape/映射 selectedIds.value中的数据
|
|
374
273
|
const marqueeShapes = computed(() => {
|
|
274
|
+
const byId = (id: string) => shapes.value.find(s => s.id === id)
|
|
275
|
+
const isShape = (x: Shape | undefined): x is Shape => !!x
|
|
375
276
|
const pending = new Set(pendingNestedIds.value)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const shape = shapeMap.value.get(id)
|
|
381
|
-
if (shape) result.push(shape)
|
|
382
|
-
}
|
|
383
|
-
return result
|
|
277
|
+
return selectedIds.value
|
|
278
|
+
.map(byId)
|
|
279
|
+
.filter(isShape)
|
|
280
|
+
.filter(s => !pending.has(s.id))
|
|
384
281
|
})
|
|
385
282
|
//获取元素拖动预览框
|
|
386
|
-
// 性能优化:只返回"直接被拖动"的图元的 ghost,不包含后代
|
|
387
|
-
// 后代图元会跟随父元素移动,不需要单独显示 ghost
|
|
388
283
|
const ghostShadow = computed<Shape[]>(() => {
|
|
389
|
-
// 如果没有在拖动,直接返回空数组
|
|
390
|
-
if (!isDragging.value || draggingIds.value.length === 0) return []
|
|
391
|
-
|
|
392
284
|
const out: Shape[] = []
|
|
393
285
|
// 兜底:防止被持久化还原成非数组
|
|
394
286
|
const pendingList = Array.isArray(pendingNestedIds.value)
|
|
395
287
|
? pendingNestedIds.value
|
|
396
288
|
: []
|
|
397
289
|
const pendingSet = new Set(pendingList)
|
|
398
|
-
//
|
|
399
|
-
// 这样可以大幅减少 ghost 数量(从 3000 减少到用户实际选中的数量)
|
|
400
|
-
const directDragSet = new Set(dragSelectionSnapshot.value.length > 0
|
|
401
|
-
? dragSelectionSnapshot.value
|
|
402
|
-
: draggingIds.value.slice(0, 1)) // 至少保留主拖动项
|
|
403
|
-
|
|
290
|
+
// 用 shapeMap O(1) 查找原始图元,避免 find O(N)
|
|
404
291
|
const map = shapeMap.value
|
|
405
|
-
for (const id
|
|
292
|
+
for (const id in dragGhost) {
|
|
406
293
|
if (pendingSet.has(id)) continue
|
|
407
|
-
const ghostRect = dragGhost[id]
|
|
408
|
-
if (!ghostRect) continue
|
|
409
294
|
const orig = map.get(id)
|
|
410
295
|
if (!orig) continue
|
|
411
296
|
|
|
412
297
|
out.push({
|
|
413
298
|
...orig,
|
|
414
|
-
bounds: { ...
|
|
299
|
+
bounds: { ...dragGhost[id] },
|
|
415
300
|
meta: { ...(orig as any).meta, isGhost: true } as any,
|
|
416
301
|
} as Shape)
|
|
417
302
|
}
|
|
@@ -420,22 +305,18 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
420
305
|
// 选择一组(用于框选/Shift 叠加)
|
|
421
306
|
const selectMany = (ids: string[]) => {
|
|
422
307
|
selectedIds.value = Array.from(new Set(ids))
|
|
423
|
-
// 使用 shapeMap 进行 O(1) 查找,避免 O(n) 的 find
|
|
424
308
|
selectedShape.value = ids.length
|
|
425
|
-
? (
|
|
309
|
+
? (shapes.value.find(s => s.id === ids[0]) ?? null)
|
|
426
310
|
: null
|
|
427
311
|
}
|
|
428
312
|
// 清空选择
|
|
429
313
|
const clearSelection = () => selectShape(null)
|
|
430
314
|
// 全选图元
|
|
431
315
|
const selectAll = () => {
|
|
432
|
-
//
|
|
433
|
-
const allShapeIds
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
allShapeIds.push(shape.id)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
316
|
+
// 获取所有非diagram类型的图元
|
|
317
|
+
const allShapeIds = shapes.value
|
|
318
|
+
.filter(shape => shape.shapeType?.toLowerCase() !== 'diagram')
|
|
319
|
+
.map(shape => shape.id)
|
|
439
320
|
selectMany(allShapeIds)
|
|
440
321
|
}
|
|
441
322
|
// 设置图表标题
|
|
@@ -449,7 +330,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
449
330
|
/**
|
|
450
331
|
* 批量操作图元(新增 / 修改 / 删除)
|
|
451
332
|
* @param payload - 新增/修改时传 Shape[];删除时可传 Shape[](只需要 id)或 id 数组
|
|
452
|
-
* @param op - 操作类型:add | update | delete | upsert
|
|
333
|
+
* @param op - 操作类型:add | update | delete | upsert(默认)
|
|
453
334
|
* @param options - 额外选项(保持你原来的 autoExpandParents)
|
|
454
335
|
*/
|
|
455
336
|
const updateShapes = (
|
|
@@ -459,44 +340,11 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
459
340
|
) => {
|
|
460
341
|
// 先把 comparents[*].comparentShapes 铺平同步到运行时
|
|
461
342
|
// const expanded = syncComparentShapesIntoRuntime(newShapes)
|
|
462
|
-
const { changedShapes
|
|
343
|
+
const { changedShapes } = applyShapeOp({
|
|
463
344
|
list: shapes.value,
|
|
464
345
|
payload: payload as any, // delete 时是 id[],其余是 Shape[]
|
|
465
346
|
op,
|
|
466
347
|
})
|
|
467
|
-
|
|
468
|
-
// 根据操作类型选择索引更新策略
|
|
469
|
-
if (op === 'replace' || changeDetails.length === 0) {
|
|
470
|
-
// replace 操作或无变更详情时,全量重建索引
|
|
471
|
-
_rebuildIndex()
|
|
472
|
-
} else {
|
|
473
|
-
// 增量更新索引
|
|
474
|
-
for (const detail of changeDetails) {
|
|
475
|
-
if (detail.type === 'add') {
|
|
476
|
-
_indexAdd(detail.shape)
|
|
477
|
-
} else if (detail.type === 'delete') {
|
|
478
|
-
// 删除时 shape 已经从数组移除,需要手动清理索引
|
|
479
|
-
_shapeMapRef.value.delete(detail.shape.id)
|
|
480
|
-
const parentId = (detail.shape as any).parenShapeId
|
|
481
|
-
if (parentId) {
|
|
482
|
-
const children = _parentChildMapRef.value.get(parentId)
|
|
483
|
-
if (children) {
|
|
484
|
-
const idx = children.indexOf(detail.shape.id)
|
|
485
|
-
if (idx !== -1) children.splice(idx, 1)
|
|
486
|
-
if (children.length === 0) {
|
|
487
|
-
_parentChildMapRef.value.delete(parentId)
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
_parentChildMapRef.value.delete(detail.shape.id)
|
|
492
|
-
} else if (detail.type === 'update') {
|
|
493
|
-
const oldParentId = (detail.oldShape as any)?.parenShapeId
|
|
494
|
-
_indexUpdate(detail.shape, oldParentId)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
_triggerIndexUpdate()
|
|
498
|
-
}
|
|
499
|
-
|
|
500
348
|
pendingNestedIds.value = []
|
|
501
349
|
// eventBus.emit('shapes-updated', newShapes)
|
|
502
350
|
// hydrateAllComparents()
|
|
@@ -516,11 +364,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
516
364
|
//清空图元数据
|
|
517
365
|
const clearAll = () => {
|
|
518
366
|
shapes.value = []
|
|
519
|
-
// 清空索引
|
|
520
|
-
_shapeMapRef.value = new Map()
|
|
521
|
-
_parentChildMapRef.value = new Map()
|
|
522
|
-
triggerRef(_shapeMapRef)
|
|
523
|
-
triggerRef(_parentChildMapRef)
|
|
524
367
|
diagrams.value = []
|
|
525
368
|
selectedShape.value = null
|
|
526
369
|
pendingNestedIds.value = []
|
|
@@ -533,13 +376,17 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
533
376
|
startDrag([shapeId], pointer)
|
|
534
377
|
}
|
|
535
378
|
const startDrag = (ids: string[], pointer: { x: number; y: number }) => {
|
|
536
|
-
//
|
|
379
|
+
// 记录“当前真正被用户选中”的快照 —— 结束时恢复
|
|
537
380
|
dragSelectionSnapshot.value = selectedIds.value.length ? [...selectedIds.value] : [ids[0]]
|
|
538
381
|
primaryDragId.value = ids[0] || null
|
|
539
382
|
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
383
|
+
// 展开“选中项 + 后代”的集合(你现有的逻辑保留)
|
|
384
|
+
const expanded: string[] = []
|
|
385
|
+
ids.forEach(id => {
|
|
386
|
+
expanded.push(id)
|
|
387
|
+
collectDescendantIds(shapes.value, id).forEach(cid => expanded.push(cid))
|
|
388
|
+
})
|
|
389
|
+
const validIds = Array.from(new Set(expanded)).filter(id => {
|
|
543
390
|
const s = byId(id); return s && getPolicy(s!).draggable
|
|
544
391
|
})
|
|
545
392
|
if (!validIds.length) return
|
|
@@ -553,32 +400,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
553
400
|
draggingIds.value = validIds
|
|
554
401
|
dragAnchor.value = { x: pointer.x, y: pointer.y }
|
|
555
402
|
|
|
556
|
-
// 构建后代坐标快照(只计算一次,供预览框渲染使用)
|
|
557
|
-
const newSnapshot = new Map<string, { id: string; x: number; y: number; width: number; height: number }[]>()
|
|
558
|
-
for (const parentId of validIds) {
|
|
559
|
-
const descendants: { id: string; x: number; y: number; width: number; height: number }[] = []
|
|
560
|
-
// 递归收集所有后代
|
|
561
|
-
const collectDescendants = (pid: string) => {
|
|
562
|
-
const childIds = _parentChildMapRef.value.get(pid) || []
|
|
563
|
-
for (const childId of childIds) {
|
|
564
|
-
const childShape = _shapeMapRef.value.get(childId)
|
|
565
|
-
if (childShape?.bounds) {
|
|
566
|
-
descendants.push({
|
|
567
|
-
id: childId,
|
|
568
|
-
x: childShape.bounds.x ?? 0,
|
|
569
|
-
y: childShape.bounds.y ?? 0,
|
|
570
|
-
width: childShape.bounds.width ?? 0,
|
|
571
|
-
height: childShape.bounds.height ?? 0,
|
|
572
|
-
})
|
|
573
|
-
}
|
|
574
|
-
collectDescendants(childId) // 递归
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
collectDescendants(parentId)
|
|
578
|
-
newSnapshot.set(parentId, descendants)
|
|
579
|
-
}
|
|
580
|
-
dragDescendantsSnapshot.value = newSnapshot
|
|
581
|
-
|
|
582
403
|
const currentDiagram = shapes.value.find(s => s.shapeType === 'diagram')
|
|
583
404
|
if (currentDiagram) {
|
|
584
405
|
dragBaseCanvasSize.value = {
|
|
@@ -834,7 +655,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
834
655
|
// 在生成 payloads 之前,把父扩容先做掉
|
|
835
656
|
const shapeId = ownerPayload?.shapeId
|
|
836
657
|
if (shapeId != null) {
|
|
837
|
-
const child = shapes.value.find(s => s.id
|
|
658
|
+
const child = shapes.value.find(s => s.id === shapeId)
|
|
838
659
|
if (child) {
|
|
839
660
|
expandParentByChild({
|
|
840
661
|
shapes: shapes.value,
|
|
@@ -931,7 +752,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
931
752
|
dragSelectionSnapshot.value = []
|
|
932
753
|
primaryDragId.value = null
|
|
933
754
|
dragBaseCanvasSize.value = null
|
|
934
|
-
dragDescendantsSnapshot.value = new Map() // 清理后代快照
|
|
935
755
|
for (const k in dragGhost) delete dragGhost[k]
|
|
936
756
|
}
|
|
937
757
|
//设置当前打开的画布id
|
|
@@ -1087,8 +907,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
1087
907
|
shapeMap, // 图元ID索引映射 (性能优化)
|
|
1088
908
|
marqueeShapes,
|
|
1089
909
|
ghostShadow,
|
|
1090
|
-
dragDescendantsSnapshot, // 拖动时的后代坐标快照
|
|
1091
|
-
dragBase, // 拖动开始时的坐标快照
|
|
1092
910
|
scales,
|
|
1093
911
|
activeDiagramId,
|
|
1094
912
|
// 当前活动画布的缩放比例
|
package/src/utils/containers.ts
CHANGED
|
@@ -709,11 +709,9 @@ const pointInRect = (pt: { x: number; y: number }, r: Rect, margin = 0) =>
|
|
|
709
709
|
pt.y <= r.y + r.height - margin
|
|
710
710
|
|
|
711
711
|
const depthOf = (shapes: Shape[], id: string): number => {
|
|
712
|
-
const graphStore = useGraphStore();
|
|
713
|
-
const byId = graphStore.shapeMap;
|
|
714
712
|
let d = 0
|
|
715
|
-
let p =
|
|
716
|
-
while (p) { d++; p =
|
|
713
|
+
let p = shapes.find(s => s.id === id)?.parenShapeId ?? null
|
|
714
|
+
while (p) { d++; p = shapes.find(s => s.id === p)?.parenShapeId ?? null }
|
|
717
715
|
return d
|
|
718
716
|
}
|
|
719
717
|
|