This is an automated email from the ASF dual-hosted git repository.
fanjia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git
The following commit(s) were added to refs/heads/main by this push:
new 2c8736f06 [Improve] improve UI (#302)
2c8736f06 is described below
commit 2c8736f06c20e8dd630dd66133b24b86c173705c
Author: Jast <[email protected]>
AuthorDate: Fri Jul 18 11:59:18 2025 +0800
[Improve] improve UI (#302)
---
.../dag/canvas/index.module.scss | 112 ++++-
.../synchronization-definition/dag/canvas/node.tsx | 44 +-
.../detail/dag/canvas-animations.scss | 302 +++++++++++
.../detail/dag/canvas-background.scss | 345 +++++++++++++
.../detail/dag/canvas-variables.scss | 192 +++++++
.../detail/dag/design-tokens.ts | 226 +++++++++
.../detail/dag/edge-styles.scss | 305 ++++++++++++
.../detail/dag/index.module.scss | 398 ++++++++++++++-
.../detail/dag/minimap-styles.scss | 382 ++++++++++++++
.../synchronization-instance/detail/dag/node.tsx | 234 ++++++++-
.../detail/dag/responsive-mixins.scss | 308 ++++++++++++
.../detail/dag/theme-manager.ts | 175 +++++++
.../detail/dag/use-dag-add-shape.ts | 49 +-
.../detail/dag/use-dag-edge.ts | 287 ++++++++++-
.../detail/dag/use-dag-graph.ts | 143 +++++-
.../detail/dag/use-dag-layout.ts | 40 +-
.../detail/dag/use-dag-node.ts | 550 ++++++++++++++++++++-
.../detail/task-definition.tsx | 4 +
18 files changed, 4013 insertions(+), 83 deletions(-)
diff --git
a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss
b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss
index e154ba0bf..75398da0b 100644
---
a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss
+++
b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss
@@ -18,42 +18,140 @@
.container {
height: 100%;
width: 100%;
+ position: relative;
+ background: #fafafa;
+ border: 1px solid #e5e7eb;
+ border-radius: 4px;
}
.dag-container {
height: 100%;
width: 100%;
+ background: transparent;
}
.minimap {
position: absolute;
right: 20px;
bottom: 60px;
- border: dashed 1px #e4e4e4;
+ border: 1px solid #e2e8f0;
+ border-radius: 6px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(8px);
+
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+ }
+}
+
+[data-theme="dark"] {
+ .minimap {
+ background: rgba(17, 24, 39, 0.95);
+ border-color: #374151;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
+
+ &:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ }
+ }
}
.dag-node {
display: flex;
align-items: center;
height: 100%;
- background-color: #fff;
- border: 1px solid #c2c8d5;
- border-radius: 4px;
- box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
+ position: relative;
+
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 6px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+ transition: box-shadow 0.2s ease, transform 0.2s ease;
+
+ &:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+ transform: translateY(-1px);
+ }
.dag-node-icon {
width: 20px;
height: 20px;
margin: 0 10px;
+ flex-shrink: 0;
}
.dag-node-label {
- width: 90px;
+ flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
- color: #666;
+ color: #374151;
font-size: 12px;
+ font-weight: 500;
+ line-height: 1.2;
+ }
+}
+
+.dag-node-status {
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ border: 2px solid #fff;
+ z-index: 10;
+
+
+ animation: status-pulse 2s ease-in-out infinite;
+}
+
+
+@keyframes node-entrance {
+ 0% {
+ opacity: 0;
+ transform: scale(0.8) translateY(10px);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+}
+
+@keyframes status-pulse {
+ 0%, 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.7;
+ transform: scale(1.1);
+ }
+}
+
+
+[data-theme="dark"] {
+ .dag-node {
+ background: linear-gradient(135deg, rgba(31, 41, 55, 0.8) 0%, rgba(17, 24,
39, 0.9) 100%);
+ border-color: #374151;
+ backdrop-filter: blur(8px);
+
+ .dag-node-label {
+ color: #d1d5db;
+ }
+
+ &:hover .dag-node-label {
+ color: #f3f4f6;
+ }
+ }
+
+ .dag-node-status {
+ border-color: #111827;
}
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx
b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx
index 998759ca3..253653d4b 100644
--- a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx
+++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx
@@ -57,11 +57,45 @@ const Node = defineComponent({
icon.value = JsonPathImg
}
+
+ const getBorderStyle = () => {
+ if (isError) {
+ return '4px solid #F87171'
+ } else if (unsaved) {
+ return '4px solid #FBBF24'
+ } else if (type === 'source') {
+ return '4px solid #34D399'
+ } else if (type === 'sink') {
+ return '4px solid #60A5FA'
+ } else if (type === 'transform') {
+ return '4px solid #A78BFA'
+ } else {
+ return '4px solid #60A5FA'
+ }
+ }
+
+ const getBackgroundStyle = () => {
+ if (isError) {
+ return 'linear-gradient(135deg, #FEF2F2 0%, #FEE2E2 100%)'
+ } else if (unsaved) {
+ return 'linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%)'
+ } else if (type === 'source') {
+ return 'linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%)'
+ } else if (type === 'sink') {
+ return 'linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%)'
+ } else if (type === 'transform') {
+ return 'linear-gradient(135deg, #F5F3FF 0%, #EDE9FE 100%)'
+ } else {
+ return 'linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%)'
+ }
+ }
+
return () => (
<div
class={styles['dag-node']}
style={{
- borderLeft: isError ? '4px solid #ff4d4f' : (unsaved ? '4px solid
#faad14' : '4px solid #1890ff')
+ borderLeft: getBorderStyle(),
+ background: getBackgroundStyle()
}}
>
<img src={icon.value} class={styles['dag-node-icon']} />
@@ -71,6 +105,14 @@ const Node = defineComponent({
default: () => name
}}
</NTooltip>
+
+ {/* 状态指示器 */}
+ {isError && (
+ <div class={styles['dag-node-status']} style={{ background:
'#EF4444' }} />
+ )}
+ {unsaved && !isError && (
+ <div class={styles['dag-node-status']} style={{ background:
'#F59E0B' }} />
+ )}
</div>
)
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-animations.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-animations.scss
new file mode 100644
index 000000000..8c781ddd1
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-animations.scss
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+@keyframes canvas-node-entrance {
+ 0% {
+ opacity: 0;
+ transform: scale(0.8) translateY(10px);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+}
+
+
+@keyframes canvas-node-exit {
+ 0% {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+ 100% {
+ opacity: 0;
+ transform: scale(0.8) translateY(-10px);
+ }
+}
+
+
+@keyframes canvas-node-pulse {
+ 0%, 100% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 0 rgba(59, 130, 246, 0.4);
+ }
+ 50% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 8px rgba(59, 130, 246, 0);
+ }
+}
+
+
+@keyframes canvas-node-pulse-success {
+ 0%, 100% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 0 rgba(16, 185, 129, 0.4);
+ }
+ 50% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 8px rgba(16, 185, 129, 0);
+ }
+}
+
+
+@keyframes canvas-node-pulse-error {
+ 0%, 100% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 0 rgba(239, 68, 68, 0.4);
+ }
+ 50% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 8px rgba(239, 68, 68, 0);
+ }
+}
+
+
+@keyframes canvas-node-pulse-warning {
+ 0%, 100% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 0 rgba(245, 158, 11, 0.4);
+ }
+ 50% {
+ box-shadow: var(--canvas-shadow-node), 0 0 0 8px rgba(245, 158, 11, 0);
+ }
+}
+
+
+@keyframes canvas-node-glow {
+ 0% {
+ filter: drop-shadow(0 0 0 transparent);
+ }
+ 100% {
+ filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.3));
+ }
+}
+
+
+@keyframes canvas-data-flow {
+ 0% {
+ stroke-dashoffset: 20;
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+
+@keyframes canvas-connection-pulse {
+ 0%, 100% {
+ stroke-width: var(--canvas-connection-stroke-width);
+ opacity: 1;
+ }
+ 50% {
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 1.5);
+ opacity: 0.8;
+ }
+}
+
+
+@keyframes canvas-connection-active {
+ 0% {
+ stroke-dasharray: 0, 10;
+ }
+ 50% {
+ stroke-dasharray: 5, 5;
+ }
+ 100% {
+ stroke-dasharray: 10, 0;
+ }
+}
+
+
+@keyframes canvas-minimap-fade-in {
+ 0% {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+
+@keyframes canvas-tooltip-fade-in {
+ 0% {
+ opacity: 0;
+ transform: translateY(5px) scale(0.95);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+
+@keyframes canvas-zoom-in {
+ 0% {
+ transform: scale(0.9);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+
+@keyframes canvas-selection-breathe {
+ 0%, 100% {
+ box-shadow: var(--canvas-shadow-node-selected);
+ }
+ 50% {
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2);
+ }
+}
+
+
+@keyframes canvas-drag-shadow {
+ 0% {
+ box-shadow: var(--canvas-shadow-node);
+ }
+ 100% {
+ box-shadow: var(--canvas-shadow-node-active);
+ }
+}
+
+
+@keyframes canvas-loading-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+
+@keyframes canvas-theme-transition {
+ 0% {
+ opacity: 0.8;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+
+@keyframes canvas-grid-scale {
+ 0% {
+ opacity: 0.3;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+
+@keyframes canvas-port-highlight {
+ 0%, 100% {
+ background-color: transparent;
+ transform: scale(1);
+ }
+ 50% {
+ background-color: var(--canvas-connection-selected);
+ transform: scale(1.2);
+ }
+}
+
+
+.canvas-animate-entrance {
+ animation: canvas-node-entrance var(--canvas-animation-slow)
var(--canvas-easing-bounce) forwards;
+}
+
+.canvas-animate-exit {
+ animation: canvas-node-exit var(--canvas-animation-normal)
var(--canvas-easing-default) forwards;
+}
+
+.canvas-animate-pulse {
+ animation: canvas-node-pulse 2s var(--canvas-easing-default) infinite;
+}
+
+.canvas-animate-pulse-success {
+ animation: canvas-node-pulse-success 2s var(--canvas-easing-default)
infinite;
+}
+
+.canvas-animate-pulse-error {
+ animation: canvas-node-pulse-error 2s var(--canvas-easing-default) infinite;
+}
+
+.canvas-animate-pulse-warning {
+ animation: canvas-node-pulse-warning 2s var(--canvas-easing-default)
infinite;
+}
+
+.canvas-animate-glow {
+ animation: canvas-node-glow var(--canvas-animation-normal)
var(--canvas-easing-default) forwards;
+}
+
+.canvas-animate-data-flow {
+ stroke-dasharray: 5, 5;
+ animation: canvas-data-flow 2s linear infinite;
+}
+
+.canvas-animate-connection-pulse {
+ animation: canvas-connection-pulse 1.5s var(--canvas-easing-default)
infinite;
+}
+
+.canvas-animate-selection-breathe {
+ animation: canvas-selection-breathe 2s var(--canvas-easing-default) infinite;
+}
+
+.canvas-animate-drag-shadow {
+ animation: canvas-drag-shadow var(--canvas-animation-fast)
var(--canvas-easing-default) forwards;
+}
+
+.canvas-animate-loading {
+ animation: canvas-loading-spin 1s linear infinite;
+}
+
+
+.canvas-transition-all {
+ transition: all var(--canvas-animation-normal) var(--canvas-easing-default);
+}
+
+.canvas-transition-transform {
+ transition: transform var(--canvas-animation-normal)
var(--canvas-easing-default);
+}
+
+.canvas-transition-colors {
+ transition: background-color var(--canvas-animation-normal)
var(--canvas-easing-default),
+ border-color var(--canvas-animation-normal)
var(--canvas-easing-default),
+ color var(--canvas-animation-normal)
var(--canvas-easing-default);
+}
+
+.canvas-transition-shadow {
+ transition: box-shadow var(--canvas-animation-normal)
var(--canvas-easing-default);
+}
+
+.canvas-transition-opacity {
+ transition: opacity var(--canvas-animation-fast)
var(--canvas-easing-default);
+}
+
+
+.canvas-theme-transition {
+ transition: background-color var(--canvas-animation-normal)
var(--canvas-easing-default),
+ border-color var(--canvas-animation-normal)
var(--canvas-easing-default),
+ color var(--canvas-animation-normal)
var(--canvas-easing-default),
+ box-shadow var(--canvas-animation-normal)
var(--canvas-easing-default);
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-background.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-background.scss
new file mode 100644
index 000000000..aa1ae9ef4
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-background.scss
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import './canvas-variables.scss';
+@import './canvas-animations.scss';
+
+
+.canvas-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background: var(--canvas-bg);
+
+
+ transition: background-color var(--canvas-animation-normal)
var(--canvas-easing-default);
+}
+
+
+.canvas-grid-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-canvas);
+
+
+ background-color: var(--canvas-bg);
+ background-image:
+
+ radial-gradient(circle at 1px 1px, var(--canvas-grid) 1px, transparent 0),
+
+ radial-gradient(circle at 10px 10px, var(--canvas-grid-secondary) 0.5px,
transparent 0);
+ background-size: 20px 20px, 100px 100px;
+ background-position: 0 0, 0 0;
+
+
+ opacity: 1;
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default),
+ background-size var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+
+ &.zoom-small {
+ background-size: 10px 10px, 50px 50px;
+ opacity: 0.6;
+ }
+
+ &.zoom-large {
+ background-size: 40px 40px, 200px 200px;
+ opacity: 0.8;
+ }
+
+ &.zoom-extra-large {
+ background-size: 80px 80px, 400px 400px;
+ opacity: 0.4;
+ }
+}
+
+
+.canvas-grid-dots {
+ background-image:
+ radial-gradient(circle, var(--canvas-grid) 1px, transparent 1px);
+ background-size: 20px 20px;
+}
+
+
+.canvas-grid-lines {
+ background-image:
+ linear-gradient(var(--canvas-grid) 1px, transparent 1px),
+ linear-gradient(90deg, var(--canvas-grid) 1px, transparent 1px);
+ background-size: 20px 20px;
+}
+
+
+.canvas-grid-cross {
+ background-image:
+ linear-gradient(var(--canvas-grid) 1px, transparent 1px),
+ linear-gradient(90deg, var(--canvas-grid) 1px, transparent 1px),
+ radial-gradient(circle at 10px 10px, var(--canvas-grid-secondary) 2px,
transparent 2px);
+ background-size: 20px 20px, 20px 20px, 100px 100px;
+}
+
+
+.canvas-guide-lines {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: calc(var(--canvas-z-index-nodes) + 1);
+ opacity: 0;
+ transition: opacity var(--canvas-animation-fast)
var(--canvas-easing-default);
+
+ &.is-active {
+ opacity: 1;
+ }
+
+ .guide-line {
+ position: absolute;
+ background: var(--canvas-guide-line);
+ opacity: 0.8;
+
+ &.horizontal {
+ width: 100%;
+ height: 1px;
+ box-shadow: 0 0 2px rgba(59, 130, 246, 0.3);
+ }
+
+ &.vertical {
+ width: 1px;
+ height: 100%;
+ box-shadow: 0 0 2px rgba(59, 130, 246, 0.3);
+ }
+
+
+ animation: canvas-guide-line-pulse 1s ease-in-out;
+ }
+}
+
+
+@keyframes canvas-guide-line-pulse {
+ 0%, 100% {
+ opacity: 0.8;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.02);
+ }
+}
+
+
+.canvas-watermark {
+ position: absolute;
+ bottom: var(--canvas-spacing-lg);
+ right: var(--canvas-spacing-lg);
+ font-size: 10px;
+ color: var(--canvas-grid);
+ opacity: 0.5;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-canvas);
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &:hover {
+ opacity: 0.8;
+ }
+}
+
+
+.canvas-boundary {
+ position: absolute;
+ border: 2px dashed var(--canvas-grid);
+ opacity: 0;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-canvas);
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-visible {
+ opacity: 0.3;
+ }
+
+ &.boundary-content {
+
+ border-color: var(--canvas-node-state-success);
+ }
+
+ &.boundary-viewport {
+
+ border-color: var(--canvas-node-state-warning);
+ }
+}
+
+
+.canvas-center-indicator {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 20px;
+ height: 20px;
+ margin: -10px 0 0 -10px;
+ border: 2px solid var(--canvas-guide-line);
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-canvas);
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-visible {
+ opacity: 0.6;
+ }
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 6px;
+ height: 6px;
+ margin: -3px 0 0 -3px;
+ background: var(--canvas-guide-line);
+ border-radius: 50%;
+ }
+}
+
+
+.canvas-zoom-indicator {
+ position: absolute;
+ top: var(--canvas-spacing-lg);
+ left: var(--canvas-spacing-lg);
+ padding: var(--canvas-spacing-sm) var(--canvas-spacing-md);
+ background: rgba(255, 255, 255, 0.9);
+ border: 1px solid var(--canvas-grid);
+ border-radius: var(--canvas-border-radius-tooltip);
+ font-size: 11px;
+ color: var(--canvas-connection-default);
+ opacity: 0;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-tooltip);
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-visible {
+ opacity: 1;
+ }
+
+ [data-theme="dark"] & {
+ background: rgba(17, 24, 39, 0.9);
+ border-color: var(--canvas-grid);
+ color: var(--canvas-connection-default);
+ }
+}
+
+
+.canvas-performance-mode {
+ .canvas-grid-background {
+ background-image: none;
+ background-color: var(--canvas-bg);
+ }
+
+ .canvas-guide-lines {
+ display: none;
+ }
+
+ .canvas-center-indicator,
+ .canvas-zoom-indicator {
+ display: none;
+ }
+}
+
+
+.canvas-debug-mode {
+ .canvas-boundary {
+ opacity: 0.5 !important;
+ }
+
+ .canvas-center-indicator {
+ opacity: 1 !important;
+ }
+
+ .canvas-zoom-indicator {
+ opacity: 1 !important;
+ }
+
+ .canvas-grid-background {
+ opacity: 1 !important;
+ }
+}
+
+
+@media (max-width: 768px) {
+ .canvas-grid-background {
+ background-size: 15px 15px, 75px 75px;
+ }
+
+ .canvas-watermark {
+ display: none;
+ }
+
+ .canvas-zoom-indicator {
+ font-size: 10px;
+ padding: var(--canvas-spacing-xs) var(--canvas-spacing-sm);
+ }
+}
+
+@media (max-width: 480px) {
+ .canvas-grid-background {
+ background-size: 12px 12px, 60px 60px;
+ opacity: 0.6;
+ }
+
+ .canvas-guide-lines {
+ display: none;
+ }
+}
+
+
+@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
+ .canvas-grid-background {
+ background-size: 20px 20px, 100px 100px;
+ }
+
+ .guide-line {
+ &.horizontal,
+ &.vertical {
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ }
+ }
+}
+
+
+[data-theme="dark"] {
+ .canvas-grid-background {
+
+ opacity: 0.8;
+ }
+
+ .canvas-watermark {
+ color: var(--canvas-grid-secondary);
+ }
+
+ .canvas-zoom-indicator {
+ backdrop-filter: blur(8px);
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-variables.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-variables.scss
new file mode 100644
index 000000000..7323a2d0f
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/canvas-variables.scss
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+:root {
+
+ --canvas-node-source-primary: #10B981;
+ --canvas-node-source-secondary: #D1FAE5;
+ --canvas-node-source-border: #059669;
+ --canvas-node-source-text: #065F46;
+ --canvas-node-source-gradient: linear-gradient(135deg, #10B981 0%, #059669
100%);
+
+
+ --canvas-node-sink-primary: #3B82F6;
+ --canvas-node-sink-secondary: #DBEAFE;
+ --canvas-node-sink-border: #1D4ED8;
+ --canvas-node-sink-text: #1E3A8A;
+ --canvas-node-sink-gradient: linear-gradient(135deg, #3B82F6 0%, #1D4ED8
100%);
+
+
+ --canvas-node-transform-primary: #8B5CF6;
+ --canvas-node-transform-secondary: #EDE9FE;
+ --canvas-node-transform-border: #7C3AED;
+ --canvas-node-transform-text: #5B21B6;
+ --canvas-node-transform-gradient: linear-gradient(135deg, #8B5CF6 0%,
#7C3AED 100%);
+
+
+ --canvas-node-state-idle: #6B7280;
+ --canvas-node-state-running: #F59E0B;
+ --canvas-node-state-success: #10B981;
+ --canvas-node-state-error: #EF4444;
+ --canvas-node-state-warning: #F59E0B;
+
+
+ --canvas-connection-default: #6B7280;
+ --canvas-connection-hover: #374151;
+ --canvas-connection-active: #1F2937;
+ --canvas-connection-selected: #3B82F6;
+ --canvas-connection-animated: #3B82F6;
+ --canvas-connection-gradient: linear-gradient(90deg, #6B7280 0%, #374151
100%);
+
+
+ --canvas-bg: #FAFAFA;
+ --canvas-grid: #E5E7EB;
+ --canvas-grid-secondary: #F3F4F6;
+ --canvas-guide-line: #3B82F6;
+
+
+ --canvas-minimap-bg: rgba(255, 255, 255, 0.95);
+ --canvas-minimap-border: #E5E7EB;
+ --canvas-minimap-viewport: rgba(59, 130, 246, 0.2);
+ --canvas-minimap-viewport-border: #3B82F6;
+
+
+ --canvas-shadow-node: 0 4px 12px rgba(0, 0, 0, 0.08);
+ --canvas-shadow-node-hover: 0 8px 24px rgba(0, 0, 0, 0.12);
+ --canvas-shadow-node-active: 0 12px 32px rgba(0, 0, 0, 0.16);
+ --canvas-shadow-node-selected: 0 0 0 2px rgba(59, 130, 246, 0.3);
+ --canvas-shadow-minimap: 0 4px 16px rgba(0, 0, 0, 0.1);
+ --canvas-shadow-tooltip: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+
+ --canvas-border-radius-node: 8px;
+ --canvas-border-radius-minimap: 6px;
+ --canvas-border-radius-tooltip: 4px;
+ --canvas-border-radius-button: 4px;
+
+
+ --canvas-spacing-xs: 4px;
+ --canvas-spacing-sm: 8px;
+ --canvas-spacing-md: 12px;
+ --canvas-spacing-lg: 16px;
+ --canvas-spacing-xl: 24px;
+ --canvas-spacing-xxl: 32px;
+
+
+ --canvas-font-size-node: 12px;
+ --canvas-font-weight-node: 500;
+ --canvas-line-height-node: 1.2;
+ --canvas-font-size-tooltip: 11px;
+ --canvas-font-weight-tooltip: 400;
+ --canvas-line-height-tooltip: 1.4;
+ --canvas-font-size-minimap: 10px;
+ --canvas-font-weight-minimap: 500;
+ --canvas-line-height-minimap: 1.2;
+
+
+ --canvas-animation-fast: 150ms;
+ --canvas-animation-normal: 250ms;
+ --canvas-animation-slow: 350ms;
+ --canvas-animation-slower: 500ms;
+ --canvas-easing-default: cubic-bezier(0.4, 0, 0.2, 1);
+ --canvas-easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
+ --canvas-easing-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
+
+
+ --canvas-node-width: 150px;
+ --canvas-node-height: 36px;
+ --canvas-node-min-width: 120px;
+ --canvas-node-max-width: 200px;
+ --canvas-minimap-width: 200px;
+ --canvas-minimap-height: 120px;
+ --canvas-connection-stroke-width: 2px;
+ --canvas-connection-stroke-width-hover: 3px;
+ --canvas-connection-arrow-size: 8px;
+
+
+ --canvas-z-index-canvas: 1;
+ --canvas-z-index-connections: 5;
+ --canvas-z-index-nodes: 10;
+ --canvas-z-index-minimap: 1000;
+ --canvas-z-index-tooltip: 2000;
+ --canvas-z-index-modal: 3000;
+}
+
+
+[data-theme="dark"] {
+
+ --canvas-bg: #111827;
+ --canvas-grid: #374151;
+ --canvas-grid-secondary: #1F2937;
+ --canvas-guide-line: #60A5FA;
+
+
+ --canvas-minimap-bg: rgba(17, 24, 39, 0.95);
+ --canvas-minimap-border: #374151;
+
+
+ --canvas-node-source-secondary: rgba(16, 185, 129, 0.1);
+ --canvas-node-sink-secondary: rgba(59, 130, 246, 0.1);
+ --canvas-node-transform-secondary: rgba(139, 92, 246, 0.1);
+
+
+ --canvas-connection-default: #9CA3AF;
+ --canvas-connection-hover: #D1D5DB;
+ --canvas-connection-active: #F3F4F6;
+}
+
+
+:root {
+ --canvas-breakpoint-mobile: 768px;
+ --canvas-breakpoint-tablet: 1024px;
+ --canvas-breakpoint-desktop: 1440px;
+ --canvas-breakpoint-wide: 1920px;
+}
+
+
+@media (max-width: 768px) {
+ :root {
+ --canvas-node-width: 120px;
+ --canvas-node-height: 32px;
+ --canvas-minimap-width: 150px;
+ --canvas-minimap-height: 90px;
+ --canvas-font-size-node: 11px;
+ --canvas-spacing-md: 8px;
+ --canvas-spacing-lg: 12px;
+ }
+}
+
+
+@media (min-width: 769px) and (max-width: 1024px) {
+ :root {
+ --canvas-node-width: 140px;
+ --canvas-node-height: 34px;
+ --canvas-minimap-width: 180px;
+ --canvas-minimap-height: 108px;
+ }
+}
+
+
+@media (min-width: 1920px) {
+ :root {
+ --canvas-node-width: 160px;
+ --canvas-node-height: 40px;
+ --canvas-minimap-width: 220px;
+ --canvas-minimap-height: 132px;
+ --canvas-font-size-node: 13px;
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/design-tokens.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/design-tokens.ts
new file mode 100644
index 000000000..0540ce891
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/design-tokens.ts
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const CanvasDesignTokens = {
+
+ colors: {
+
+ nodes: {
+ source: {
+ primary: '#10B981',
+ secondary: '#D1FAE5',
+ gradient: 'linear-gradient(135deg, #10B981 0%, #059669 100%)',
+ border: '#059669',
+ text: '#065F46'
+ },
+ sink: {
+ primary: '#3B82F6',
+ secondary: '#DBEAFE',
+ gradient: 'linear-gradient(135deg, #3B82F6 0%, #1D4ED8 100%)',
+ border: '#1D4ED8',
+ text: '#1E3A8A'
+ },
+ transform: {
+ primary: '#8B5CF6',
+ secondary: '#EDE9FE',
+ gradient: 'linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%)',
+ border: '#7C3AED',
+ text: '#5B21B6'
+ },
+
+ states: {
+ idle: '#6B7280',
+ running: '#F59E0B',
+ success: '#10B981',
+ error: '#EF4444',
+ warning: '#F59E0B'
+ }
+ },
+
+
+ connections: {
+ default: '#6B7280',
+ hover: '#374151',
+ active: '#1F2937',
+ selected: '#3B82F6',
+ gradient: 'linear-gradient(90deg, #6B7280 0%, #374151 100%)',
+ animated: '#3B82F6'
+ },
+
+
+ canvas: {
+ light: {
+ background: '#FAFAFA',
+ grid: '#E5E7EB',
+ gridSecondary: '#F3F4F6',
+ guideLine: '#3B82F6'
+ },
+ dark: {
+ background: '#111827',
+ grid: '#374151',
+ gridSecondary: '#1F2937',
+ guideLine: '#60A5FA'
+ }
+ },
+
+
+ minimap: {
+ background: 'rgba(255, 255, 255, 0.95)',
+ backgroundDark: 'rgba(17, 24, 39, 0.95)',
+ border: '#E5E7EB',
+ borderDark: '#374151',
+ viewport: 'rgba(59, 130, 246, 0.2)',
+ viewportBorder: '#3B82F6'
+ }
+ },
+
+
+ shadows: {
+ node: '0 4px 12px rgba(0, 0, 0, 0.08)',
+ nodeHover: '0 8px 24px rgba(0, 0, 0, 0.12)',
+ nodeActive: '0 12px 32px rgba(0, 0, 0, 0.16)',
+ nodeSelected: '0 0 0 2px rgba(59, 130, 246, 0.3)',
+ minimap: '0 4px 16px rgba(0, 0, 0, 0.1)',
+ tooltip: '0 2px 8px rgba(0, 0, 0, 0.1)'
+ },
+
+
+ borderRadius: {
+ node: '8px',
+ minimap: '6px',
+ tooltip: '4px',
+ button: '4px'
+ },
+
+
+ spacing: {
+ xs: '4px',
+ sm: '8px',
+ md: '12px',
+ lg: '16px',
+ xl: '24px',
+ xxl: '32px'
+ },
+
+
+ typography: {
+ node: {
+ fontSize: '12px',
+ fontWeight: '500',
+ lineHeight: '1.2'
+ },
+ tooltip: {
+ fontSize: '11px',
+ fontWeight: '400',
+ lineHeight: '1.4'
+ },
+ minimap: {
+ fontSize: '10px',
+ fontWeight: '500',
+ lineHeight: '1.2'
+ }
+ },
+
+
+ animations: {
+
+ duration: {
+ fast: '150ms',
+ normal: '250ms',
+ slow: '350ms',
+ slower: '500ms'
+ },
+
+
+ easing: {
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
+ },
+
+
+ configs: {
+ nodeEntrance: {
+ duration: '350ms',
+ easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
+ delay: '0ms'
+ },
+ nodeHover: {
+ duration: '150ms',
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
+ },
+ edgeFlow: {
+ duration: '2000ms',
+ easing: 'linear',
+ iterationCount: 'infinite'
+ },
+ themeTransition: {
+ duration: '250ms',
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
+ }
+ }
+ },
+
+
+ sizes: {
+ node: {
+ width: 150,
+ height: 36,
+ minWidth: 120,
+ maxWidth: 200
+ },
+ minimap: {
+ width: 200,
+ height: 120,
+ minWidth: 150,
+ minHeight: 90
+ },
+ connection: {
+ strokeWidth: 2,
+ strokeWidthHover: 3,
+ arrowSize: 8
+ }
+ },
+
+
+ zIndex: {
+ canvas: 1,
+ nodes: 10,
+ connections: 5,
+ minimap: 1000,
+ tooltip: 2000,
+ modal: 3000
+ }
+} as const
+
+
+export type NodeType = 'source' | 'sink' | 'transform'
+export type NodeState = 'idle' | 'running' | 'success' | 'error' | 'warning'
+export type Theme = 'light' | 'dark'
+
+
+export const getNodeColors = (type: NodeType) => {
+ return CanvasDesignTokens.colors.nodes[type]
+}
+
+export const getNodeStateColor = (state: NodeState) => {
+ return CanvasDesignTokens.colors.nodes.states[state]
+}
+
+export const getCanvasColors = (theme: Theme) => {
+ return CanvasDesignTokens.colors.canvas[theme]
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/edge-styles.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/edge-styles.scss
new file mode 100644
index 000000000..1ba37fe9c
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/edge-styles.scss
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+@import './canvas-variables.scss';
+@import './canvas-animations.scss';
+@import './responsive-mixins.scss';
+
+
+.modern-edge {
+
+ stroke: var(--canvas-connection-default);
+ stroke-width: var(--canvas-connection-stroke-width);
+ fill: none;
+ cursor: pointer;
+
+
+ transition: stroke var(--canvas-animation-normal)
var(--canvas-easing-default),
+ stroke-width var(--canvas-animation-normal)
var(--canvas-easing-default),
+ opacity var(--canvas-animation-fast)
var(--canvas-easing-default);
+
+
+ @include canvas-connection-responsive;
+
+
+ &:hover {
+ stroke: var(--canvas-connection-hover);
+ stroke-width: var(--canvas-connection-stroke-width-hover);
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+ }
+
+
+ &.is-selected {
+ stroke: var(--canvas-connection-selected);
+ stroke-width: var(--canvas-connection-stroke-width-hover);
+ @extend .canvas-animate-connection-pulse;
+ }
+
+
+ &.is-active {
+ @extend .canvas-animate-data-flow;
+ }
+
+
+ &.is-error {
+ stroke: var(--canvas-node-state-error);
+ stroke-dasharray: 5, 5;
+ animation: canvas-connection-pulse 1s ease-in-out infinite;
+ }
+
+
+ &.is-warning {
+ stroke: var(--canvas-node-state-warning);
+ stroke-dasharray: 3, 3;
+ }
+
+
+ &.is-success {
+ stroke: var(--canvas-node-state-success);
+ }
+}
+
+
+.modern-edge-arrow {
+ fill: var(--canvas-connection-default);
+ stroke: none;
+ transition: fill var(--canvas-animation-normal) var(--canvas-easing-default);
+
+ .modern-edge:hover & {
+ fill: var(--canvas-connection-hover);
+ }
+
+ .modern-edge.is-selected & {
+ fill: var(--canvas-connection-selected);
+ }
+
+ .modern-edge.is-error & {
+ fill: var(--canvas-node-state-error);
+ }
+
+ .modern-edge.is-warning & {
+ fill: var(--canvas-node-state-warning);
+ }
+
+ .modern-edge.is-success & {
+ fill: var(--canvas-node-state-success);
+ }
+}
+
+
+.modern-edge-label {
+ font-size: var(--canvas-font-size-tooltip);
+ font-weight: var(--canvas-font-weight-tooltip);
+ fill: var(--canvas-connection-default);
+ text-anchor: middle;
+ dominant-baseline: middle;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ .modern-edge:hover & {
+ opacity: 1;
+ fill: var(--canvas-connection-hover);
+ }
+
+ .modern-edge.is-selected & {
+ opacity: 1;
+ fill: var(--canvas-connection-selected);
+ }
+}
+
+
+.modern-edge-tooltip-bg {
+ fill: rgba(255, 255, 255, 0.95);
+ stroke: var(--canvas-connection-default);
+ stroke-width: 1;
+ rx: var(--canvas-border-radius-tooltip);
+ ry: var(--canvas-border-radius-tooltip);
+ filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
+ opacity: 0;
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ .modern-edge:hover & {
+ opacity: 1;
+ }
+
+ [data-theme="dark"] & {
+ fill: rgba(17, 24, 39, 0.95);
+ stroke: var(--canvas-grid);
+ }
+}
+
+
+.modern-edge-gradient {
+ stroke: url(#connection-gradient);
+
+ &:hover {
+ stroke: url(#connection-gradient-hover);
+ }
+
+ &.is-selected {
+ stroke: url(#connection-gradient-selected);
+ }
+}
+
+
+.modern-edge-animated {
+ stroke-dasharray: 8, 4;
+ animation: canvas-data-flow 2s linear infinite;
+
+ &.flow-fast {
+ animation-duration: 1s;
+ }
+
+ &.flow-slow {
+ animation-duration: 4s;
+ }
+
+ &.flow-reverse {
+ animation-direction: reverse;
+ }
+}
+
+
+.modern-edge-type {
+ &.type-data {
+ stroke: var(--canvas-connection-default);
+ }
+
+ &.type-control {
+ stroke: var(--canvas-node-state-warning);
+ stroke-dasharray: 4, 2;
+ }
+
+ &.type-error {
+ stroke: var(--canvas-node-state-error);
+ stroke-dasharray: 2, 2;
+ }
+}
+
+
+.modern-edge-weight {
+ &.weight-thin {
+ stroke-width: 1px;
+ }
+
+ &.weight-normal {
+ stroke-width: var(--canvas-connection-stroke-width);
+ }
+
+ &.weight-thick {
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 1.5);
+ }
+
+ &.weight-bold {
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 2);
+ }
+}
+
+
+.modern-edge-opacity {
+ &.opacity-ghost {
+ opacity: 0.3;
+ }
+
+ &.opacity-faded {
+ opacity: 0.6;
+ }
+
+ &.opacity-normal {
+ opacity: 1;
+ }
+}
+
+
+.modern-edge-path {
+ &.path-straight {
+
+ }
+
+ &.path-curved {
+
+ }
+
+ &.path-orthogonal {
+
+ stroke-linejoin: round;
+ stroke-linecap: round;
+ }
+
+ &.path-smooth {
+
+ stroke-linecap: round;
+ }
+}
+
+
+.modern-edge-interaction {
+ stroke: transparent;
+ stroke-width: 12px;
+ fill: none;
+ cursor: pointer;
+
+
+ @include canvas-touch-device {
+ stroke-width: 16px;
+ }
+}
+
+
+.modern-edge-highlight {
+ stroke: var(--canvas-connection-selected);
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 2);
+ opacity: 0.3;
+ pointer-events: none;
+
+ animation: canvas-connection-pulse 1.5s ease-in-out infinite;
+}
+
+
+.modern-edge-group {
+ .modern-edge {
+ stroke-width: 1px;
+ opacity: 0.7;
+ }
+
+ &:hover .modern-edge {
+ stroke-width: var(--canvas-connection-stroke-width);
+ opacity: 1;
+ }
+
+ .modern-edge.is-primary {
+ stroke-width: var(--canvas-connection-stroke-width);
+ opacity: 1;
+ }
+}
+
+
+[data-theme="dark"] {
+ .modern-edge {
+ filter: brightness(1.2);
+ }
+
+ .modern-edge-label {
+ fill: var(--canvas-connection-default);
+ }
+
+ .modern-edge:hover {
+ filter: brightness(1.4) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss
index 1ebd2fa0e..fb421c55c 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss
@@ -15,22 +15,400 @@
* limitations under the License.
*/
+
.dag-node {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: flex-start !important;
+ height: 100% !important;
+ width: 100% !important;
+ position: relative !important;
+ padding: 12px 18px !important;
+ min-height: 44px !important;
+ min-width: 180px !important;
+ box-sizing: border-box !important;
+
+
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%) !important;
+ border: 1px solid #e2e8f0 !important;
+ border-radius: 16px !important;
+ box-shadow:
+ 0 6px 20px rgba(0, 0, 0, 0.06),
+ 0 2px 6px rgba(0, 0, 0, 0.04),
+ inset 0 1px 0 rgba(255, 255, 255, 0.8) !important;
+
+
+ color: #1f2937 !important;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Helvetica Neue', Arial, sans-serif !important;
+ font-size: 13px !important;
+ font-weight: 500 !important;
+ line-height: 1.4 !important;
+
+
+ visibility: visible !important;
+ opacity: 1 !important;
+ z-index: 100 !important;
+ overflow: visible !important;
+
+
+ transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important;
+
+
+ &:hover {
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important;
+ box-shadow:
+ 0 12px 32px rgba(0, 0, 0, 0.12),
+ 0 6px 16px rgba(0, 0, 0, 0.08),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9) !important;
+ transform: translateY(-4px) scale(1.02) !important;
+ filter: brightness(1.05);
+ }
+
+
+ &.selected,
+ &:focus {
+ border-color: #6366f1 !important;
+ box-shadow:
+ 0 12px 32px rgba(0, 0, 0, 0.1),
+ 0 0 0 4px rgba(99, 102, 241, 0.15) !important;
+ }
+}
+
+
+@keyframes node-entrance {
+ 0% {
+ opacity: 0;
+ transform: scale(0.8) translateY(10px);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+}
+
+@keyframes pulse-running {
+ 0%, 100% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 0 rgba(245, 158, 11,
0.4);
+ }
+ 50% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 8px rgba(245, 158, 11,
0);
+ }
+}
+
+@keyframes pulse-success {
+ 0%, 100% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 0 rgba(16, 185, 129,
0.4);
+ }
+ 50% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 8px rgba(16, 185, 129,
0);
+ }
+}
+
+@keyframes pulse-error {
+ 0%, 100% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 0 rgba(239, 68, 68, 0.4);
+ }
+ 50% {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 8px rgba(239, 68, 68, 0);
+ }
+}
+
+
+.dag-node-icon {
+ width: 24px;
+ height: 24px;
+ margin-right: 12px;
+ flex-shrink: 0;
display: flex;
align-items: center;
+ justify-content: center;
+ font-size: 18px;
+
+ span {
+ display: block;
+ line-height: 1;
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
+ }
+}
+
+
+.dag-node-label {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ overflow: hidden;
+
+ span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: left;
+ color: #1f2937 !important;
+ font-size: 14px !important;
+ font-weight: 600 !important;
+ line-height: 1.3 !important;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
sans-serif !important;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ }
+}
+
+
+.dag-node-port {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: #6366f1;
+ border: 2px solid #fff;
+ opacity: 0;
+ transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 200;
+
+ &.port-input {
+ left: -6px;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+
+ &.port-output {
+ right: -6px;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+
+ &:hover,
+ &.is-active {
+ opacity: 1;
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3);
+ }
+}
+
+
+.container {
height: 100%;
- background-color: #fff;
- border: 1px solid #c2c8d5;
- border-left: 4px solid #5F95FF;
+ width: 100%;
+ position: relative;
+ background: #fafafa;
+ border: 1px solid #e5e7eb;
border-radius: 4px;
- box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
}
-.dag-node-label {
- display: inline-block;
- flex-shrink: 0;
+.dag-container {
+ height: 100%;
width: 100%;
- text-align: center;
- color: #666;
- font-size: 12px;
+ background: transparent;
+}
+
+
+.minimap {
+ position: absolute;
+ right: 20px;
+ bottom: 60px;
+ border: 1px solid #e2e8f0;
+ border-radius: 6px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(8px);
+
+
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+
+
+ &:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+ }
+}
+
+
+:global {
+ /* 连接线创建时的样式 */
+ .x6-edge.x6-edge-creating .x6-edge-path {
+ stroke: #6366f1 !important;
+ stroke-width: 2px !important;
+ stroke-dasharray: 5, 5 !important;
+ animation: edge-creating 0.5s linear infinite !important;
+ }
+
+ @keyframes edge-creating {
+ to {
+ stroke-dashoffset: -10;
+ }
+ }
+
+
+ .x6-node-tool-remove,
+ .x6-node-tool-button {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
+ }
+
+
+ .x6-node-tools {
+ visibility: visible !important;
+ opacity: 1 !important;
+ display: block !important;
+ }
+
+ .x6-node-tool {
+ visibility: visible !important;
+ opacity: 1 !important;
+ display: block !important;
+ }
+
+
+ .x6-node-tool text {
+ visibility: visible !important;
+ opacity: 1 !important;
+ display: block !important;
+ font-size: 12px !important;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
sans-serif !important;
+ }
+
+
+ .x6-port,
+ .x6-port-body {
+ opacity: 0 !important;
+ visibility: hidden !important;
+ }
+
+
+ .x6-node {
+ .x6-node-content {
+ visibility: visible !important;
+ opacity: 1 !important;
+ display: block !important;
+ }
+ }
+
+
+ .x6-edge {
+ .x6-edge-path {
+ stroke: #9CA3AF !important;
+ stroke-width: 2px !important;
+ stroke-linecap: round !important;
+ stroke-linejoin: round !important;
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)) !important;
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
+ }
+
+
+ .x6-edge-marker {
+ fill: #9CA3AF !important;
+ stroke: #9CA3AF !important;
+ filter: none !important;
+ }
+
+
+ &:hover {
+ .x6-edge-path {
+ stroke: #4B5563 !important;
+ stroke-width: 2.5px !important;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15)) !important;
+ }
+
+ .x6-edge-marker {
+ fill: #4B5563 !important;
+ stroke: #4B5563 !important;
+ }
+ }
+
+
+ &.selected {
+ .x6-edge-path {
+ stroke: #3b82f6 !important;
+ stroke-width: 4px !important;
+ stroke-dasharray: 8 4 !important;
+ animation: edge-flow 2s linear infinite !important;
+ }
+ }
+ }
+
+
+ @keyframes edge-flow {
+ 0% {
+ stroke-dashoffset: 0;
+ }
+ 100% {
+ stroke-dashoffset: 12;
+ }
+ }
+
+
+ .x6-edge-label {
+ background: rgba(255, 255, 255, 0.95) !important;
+ border: 1px solid #e2e8f0 !important;
+ border-radius: 6px !important;
+ padding: 4px 8px !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ color: #374151 !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
+ backdrop-filter: blur(8px) !important;
+ }
+}
+
+
+[data-theme="dark"] {
+ .container {
+ background: #111827;
+ border-color: #374151;
+ }
+
+ .minimap {
+ background: rgba(17, 24, 39, 0.95);
+ border-color: #374151;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
+
+ &:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ }
+ }
+
+ .dag-node {
+ background: linear-gradient(135deg, rgba(31, 41, 55, 0.8) 0%, rgba(17, 24,
39, 0.9) 100%);
+ border-color: #374151;
+ backdrop-filter: blur(8px);
+
+ .dag-node-label {
+ color: #d1d5db;
+ }
+
+ &:hover .dag-node-label {
+ color: #f3f4f6;
+ }
+ }
+
+ .dag-node-port {
+ border-color: #111827;
+ }
+
+
+ :global {
+ .x6-edge {
+ .x6-edge-path {
+ stroke: #8b5cf6 !important;
+ filter: drop-shadow(0 2px 4px rgba(139, 92, 246, 0.3)) !important;
+ }
+
+ .x6-edge-marker {
+ fill: #8b5cf6 !important;
+ stroke: #8b5cf6 !important;
+ }
+
+ &:hover {
+ .x6-edge-path {
+ stroke: #a78bfa !important;
+ filter: drop-shadow(0 4px 8px rgba(167, 139, 250, 0.4)) !important;
+ }
+ }
+ }
+
+ .x6-edge-label {
+ background: rgba(17, 24, 39, 0.95) !important;
+ border-color: #374151 !important;
+ color: #d1d5db !important;
+ }
+ }
}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/minimap-styles.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/minimap-styles.scss
new file mode 100644
index 000000000..22cc71084
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/minimap-styles.scss
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import './canvas-variables.scss';
+@import './canvas-animations.scss';
+@import './responsive-mixins.scss';
+
+
+.modern-minimap {
+ position: absolute;
+ right: var(--canvas-spacing-lg);
+ bottom: var(--canvas-spacing-lg);
+ width: var(--canvas-minimap-width);
+ height: var(--canvas-minimap-height);
+ background: var(--canvas-minimap-bg);
+ border: 1px solid var(--canvas-minimap-border);
+ border-radius: var(--canvas-border-radius-minimap);
+ box-shadow: var(--canvas-shadow-minimap);
+ backdrop-filter: blur(8px);
+ z-index: var(--canvas-z-index-minimap);
+
+
+ @include canvas-minimap-responsive;
+
+
+ transition: all var(--canvas-animation-normal) var(--canvas-easing-default);
+
+
+ &:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+ }
+
+
+ animation: canvas-minimap-fade-in var(--canvas-animation-slow)
var(--canvas-easing-bounce);
+}
+
+
+.modern-minimap__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--canvas-spacing-sm) var(--canvas-spacing-md);
+ border-bottom: 1px solid var(--canvas-minimap-border);
+ background: rgba(255, 255, 255, 0.5);
+ border-radius: var(--canvas-border-radius-minimap)
var(--canvas-border-radius-minimap) 0 0;
+
+ [data-theme="dark"] & {
+ background: rgba(17, 24, 39, 0.5);
+ border-bottom-color: var(--canvas-grid);
+ }
+}
+
+
+.modern-minimap__title {
+ font-size: var(--canvas-font-size-minimap);
+ font-weight: var(--canvas-font-weight-minimap);
+ color: var(--canvas-connection-default);
+ margin: 0;
+
+
+ @include canvas-typography-responsive;
+}
+
+
+.modern-minimap__controls {
+ display: flex;
+ gap: var(--canvas-spacing-xs);
+}
+
+
+.modern-minimap__zoom-in,
+.modern-minimap__zoom-out,
+.modern-minimap__reset,
+.modern-minimap__toggle {
+ width: 20px;
+ height: 20px;
+ border: none;
+ background: transparent;
+ color: var(--canvas-connection-default);
+ border-radius: var(--canvas-border-radius-button);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 500;
+
+
+ transition: all var(--canvas-animation-fast) var(--canvas-easing-default);
+
+ &:hover {
+ background: var(--canvas-minimap-viewport);
+ color: var(--canvas-connection-hover);
+ transform: scale(1.1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+
+ @include canvas-respond-to-max('mobile') {
+ width: 18px;
+ height: 18px;
+ font-size: 11px;
+ }
+}
+
+
+.modern-minimap__viewport {
+ position: relative;
+ width: 100%;
+ height: calc(100% - 40px);
+ overflow: hidden;
+ border-radius: 0 0 var(--canvas-border-radius-minimap)
var(--canvas-border-radius-minimap);
+}
+
+
+.modern-minimap__content {
+ width: 100%;
+ height: 100%;
+ position: relative;
+
+
+ .x6-graph-minimap {
+ border: none !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ background: transparent !important;
+ }
+
+
+ .x6-node {
+ opacity: 0.8;
+
+ rect {
+ fill: var(--canvas-grid-secondary) !important;
+ stroke: var(--canvas-grid) !important;
+ stroke-width: 1 !important;
+ rx: 2 !important;
+ ry: 2 !important;
+ }
+
+
+ &[data-node-type="source"] rect {
+ fill: var(--canvas-node-source-secondary) !important;
+ stroke: var(--canvas-node-source-primary) !important;
+ }
+
+ &[data-node-type="sink"] rect {
+ fill: var(--canvas-node-sink-secondary) !important;
+ stroke: var(--canvas-node-sink-primary) !important;
+ }
+
+ &[data-node-type="transform"] rect {
+ fill: var(--canvas-node-transform-secondary) !important;
+ stroke: var(--canvas-node-transform-primary) !important;
+ }
+ }
+
+
+ .x6-edge {
+ opacity: 0.6;
+
+ path {
+ stroke: var(--canvas-connection-default) !important;
+ stroke-width: 1 !important;
+ }
+ }
+}
+
+
+.modern-minimap__indicator {
+ position: absolute;
+ border: 2px solid var(--canvas-minimap-viewport-border);
+ background: var(--canvas-minimap-viewport);
+ border-radius: 2px;
+ pointer-events: none;
+ z-index: 10;
+
+
+ transition: all var(--canvas-animation-fast) var(--canvas-easing-default);
+
+
+ &.is-moving {
+ transition: none;
+ }
+
+ &.is-resizing {
+ border-style: dashed;
+ animation: canvas-minimap-indicator-pulse 1s ease-in-out infinite;
+ }
+}
+
+
+@keyframes canvas-minimap-indicator-pulse {
+ 0%, 100% {
+ opacity: 0.6;
+ }
+ 50% {
+ opacity: 1;
+ }
+}
+
+
+.modern-minimap__tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: var(--canvas-spacing-sm);
+ padding: var(--canvas-spacing-xs) var(--canvas-spacing-sm);
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ font-size: 10px;
+ border-radius: var(--canvas-border-radius-tooltip);
+ white-space: nowrap;
+ opacity: 0;
+ pointer-events: none;
+ z-index: var(--canvas-z-index-tooltip);
+
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-visible {
+ opacity: 1;
+ }
+
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border: 4px solid transparent;
+ border-top-color: rgba(0, 0, 0, 0.8);
+ }
+}
+
+
+.modern-minimap__status {
+ position: absolute;
+ top: var(--canvas-spacing-sm);
+ right: var(--canvas-spacing-sm);
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ z-index: 10;
+
+ &.status-loading {
+ background: var(--canvas-node-state-warning);
+ animation: canvas-loading-spin 1s linear infinite;
+ }
+
+ &.status-ready {
+ background: var(--canvas-node-state-success);
+ }
+
+ &.status-error {
+ background: var(--canvas-node-state-error);
+ animation: canvas-node-pulse-error 2s ease-in-out infinite;
+ }
+}
+
+
+.modern-minimap.is-collapsed {
+ width: 40px;
+ height: 40px;
+
+ .modern-minimap__header {
+ padding: var(--canvas-spacing-xs);
+ border-bottom: none;
+ }
+
+ .modern-minimap__title {
+ display: none;
+ }
+
+ .modern-minimap__viewport {
+ display: none;
+ }
+
+ .modern-minimap__toggle {
+ transform: rotate(180deg);
+ }
+}
+
+
+.modern-minimap.is-dragging {
+ cursor: grabbing;
+ transform: rotate(2deg);
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
+}
+
+
+.modern-minimap.is-fullscreen {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 60vw;
+ height: 60vh;
+ max-width: 800px;
+ max-height: 600px;
+ z-index: var(--canvas-z-index-modal);
+
+ .modern-minimap__header {
+ padding: var(--canvas-spacing-md) var(--canvas-spacing-lg);
+ }
+
+ .modern-minimap__title {
+ font-size: 14px;
+ }
+
+ .modern-minimap__controls {
+ gap: var(--canvas-spacing-sm);
+ }
+
+ .modern-minimap__zoom-in,
+ .modern-minimap__zoom-out,
+ .modern-minimap__reset,
+ .modern-minimap__toggle {
+ width: 24px;
+ height: 24px;
+ font-size: 14px;
+ }
+}
+
+
+.modern-minimap-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: calc(var(--canvas-z-index-modal) - 1);
+ opacity: 0;
+
+ transition: opacity var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-visible {
+ opacity: 1;
+ }
+}
+
+
+[data-theme="dark"] {
+ .modern-minimap {
+ border-color: var(--canvas-grid);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
+ }
+
+ .modern-minimap__tooltip {
+ background: rgba(255, 255, 255, 0.9);
+ color: var(--canvas-connection-default);
+
+ &::after {
+ border-top-color: rgba(255, 255, 255, 0.9);
+ }
+ }
+
+ .modern-minimap-overlay {
+ background: rgba(0, 0, 0, 0.7);
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx
index b1ccc0615..161dd7047 100644
--- a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx
+++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx
@@ -15,18 +15,244 @@
* limitations under the License.
*/
-import { defineComponent, inject } from 'vue'
+import { defineComponent, inject, computed } from 'vue'
+import { NTooltip, NProgress } from 'naive-ui'
import styles from './index.module.scss'
+import { ModernNodeData, determineNodeType } from './use-dag-node'
+import { CanvasDesignTokens, getNodeStateColor, NodeType } from
'./design-tokens'
const Node = defineComponent({
name: 'Node',
setup() {
const getNode = inject('getNode') as any
+
+ if (!getNode) {
+ console.error('getNode function not found in inject')
+ return () => <div class={styles['dag-node']}>Error: Node data not
available</div>
+ }
+
const node = getNode()
- const { name } = node.getData()
+ const nodeData = node?.getData() || {} as ModernNodeData
+
+
+ const {
+ name = 'Unknown',
+ nodeType,
+ connectorType,
+ status = 'idle',
+ theme,
+ isSelected = false,
+ isHovered = false,
+ isDragging = false,
+ isDisabled = false,
+ animations = {},
+ style = {},
+ progress = 0,
+ statusMessage,
+ metadata = {}
+ } = nodeData
+
+
+ const actualNodeType = computed(() =>
+ determineNodeType(nodeType, connectorType, name)
+ )
+
+
+ const getNodeIcon = (type: NodeType) => {
+
+ if (metadata.icon) {
+ return metadata.icon
+ }
+
+ switch (type) {
+ case 'source':
+ return '📊'
+ case 'sink':
+ return '🎯'
+ case 'transform':
+ return '⚙️'
+ default:
+ return null
+ }
+ }
+
+
+ const nodeClass = computed(() => {
+ return {
+ [styles['dag-node']]: true,
+ [styles[`dag-node--${actualNodeType.value}`]]: true,
+ [styles['dag-node--selected']]: isSelected,
+ [styles['dag-node--hovered']]: isHovered,
+ [styles['dag-node--dragging']]: isDragging,
+ [styles['dag-node--disabled']]: isDisabled,
+ [styles[`dag-node--${status}`]]: true,
+ [styles['dag-node--entrance']]: animations.entrance,
+ [styles['dag-node--pulse']]: animations.pulse,
+ [styles['dag-node--glow']]: animations.glow,
+ [styles['dag-node--shake']]: animations.shake
+ }
+ })
+
+
+ const getBorderStyle = () => {
+
+ if (theme) {
+ if (status === 'error') {
+ return `4px solid ${getNodeStateColor('error')}`
+ } else if (status === 'warning') {
+ return `4px solid ${getNodeStateColor('warning')}`
+ } else {
+ return `4px solid ${theme.borderColor}`
+ }
+ }
+
+
+ if (status === 'error') {
+ return '4px solid #F87171'
+ } else if (status === 'warning') {
+ return '4px solid #FBBF24'
+ } else if (actualNodeType.value === 'source') {
+ return '4px solid #34D399'
+ } else if (actualNodeType.value === 'sink') {
+ return '4px solid #60A5FA'
+ } else if (actualNodeType.value === 'transform') {
+ return '4px solid #A78BFA'
+ } else {
+ return '4px solid #60A5FA'
+ }
+ }
+
+
+ const getBackgroundStyle = () => {
+
+ if (theme) {
+ if (status === 'error') {
+ return 'linear-gradient(135deg, #FEF2F2 0%, #FEE2E2 100%)'
+ } else if (status === 'warning') {
+ return 'linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%)'
+ } else {
+ return theme.gradient
+ }
+ }
+
+
+ if (status === 'error') {
+ return 'linear-gradient(135deg, #FEF2F2 0%, #FEE2E2 100%)'
+ } else if (status === 'warning') {
+ return 'linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%)'
+ } else if (actualNodeType.value === 'source') {
+ return 'linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%)'
+ } else if (actualNodeType.value === 'sink') {
+ return 'linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%)'
+ } else if (actualNodeType.value === 'transform') {
+ return 'linear-gradient(135deg, #F5F3FF 0%, #EDE9FE 100%)'
+ } else {
+ return 'linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%)'
+ }
+ }
+
+
+ const getShadowStyle = () => {
+ if (isSelected) {
+ return CanvasDesignTokens.shadows.nodeSelected
+ } else if (isHovered) {
+ return CanvasDesignTokens.shadows.nodeHover
+ } else if (isDragging) {
+ return CanvasDesignTokens.shadows.nodeActive
+ } else {
+ return style.shadow || CanvasDesignTokens.shadows.node
+ }
+ }
+
+
+ const getStatusColor = () => {
+ return getNodeStateColor(status)
+ }
+
+
+ const nodeStyle = computed(() => ({
+ borderLeft: getBorderStyle(),
+ background: getBackgroundStyle(),
+ boxShadow: getShadowStyle(),
+ borderRadius: style.borderRadius || CanvasDesignTokens.borderRadius.node,
+ opacity: isDisabled ? 0.6 : (style.opacity || 1),
+ width: style.width ? `${style.width}px` : 'auto',
+ height: style.height ? `${style.height}px` : 'auto',
+ zIndex: style.zIndex || CanvasDesignTokens.zIndex.nodes
+ }))
+
+
+ const getTextColor = () => {
+ return theme?.textColor || '#374151'
+ }
+
return () => (
- <div class={styles['dag-node']}>
- <span class={styles['dag-node-label']}>{name}</span>
+ <div
+ class={nodeClass.value}
+ style={nodeStyle.value}
+ >
+ {/* 节点图标 */}
+
+ {/* 节点标签 */}
+ <NTooltip trigger='hover' placement='top'>
+ {{
+ trigger: () => (
+ <div class={styles['dag-node-label']} style={{ color:
getTextColor() }}>
+ <span>{name}</span>
+ </div>
+ ),
+ default: () => (
+ <div>
+ <div><strong>{name}</strong></div>
+ <div style={{ fontSize: '12px', opacity: 0.8 }}>
+ 类型: {actualNodeType.value === 'source' ? '数据源' :
actualNodeType.value === 'sink' ? '数据目标' : '数据转换'}
+ </div>
+ {status !== 'idle' && (
+ <div style={{ fontSize: '12px', opacity: 0.8 }}>
+ 状态: {status === 'running' ? '运行中' : status === 'success' ?
'成功' : status === 'error' ? '错误' : status === 'warning' ? '警告' : status}
+ </div>
+ )}
+ {statusMessage && (
+ <div style={{ fontSize: '12px', opacity: 0.8 }}>
+ 信息: {statusMessage}
+ </div>
+ )}
+ {metadata.description && (
+ <div style={{ fontSize: '12px', opacity: 0.8 }}>
+ 描述: {metadata.description}
+ </div>
+ )}
+ {metadata.tags && metadata.tags.length > 0 && (
+ <div style={{ fontSize: '12px', opacity: 0.8 }}>
+ 标签: {metadata.tags.join(', ')}
+ </div>
+ )}
+ </div>
+ )
+ }}
+ </NTooltip>
+
+ {/* 状态指示器 */}
+
+ {/* 进度条 - 仅在运行状态显示 */}
+ {status === 'running' && progress > 0 && (
+ <div class={styles['dag-node-progress']}>
+ <NProgress
+ type="line"
+ percentage={progress}
+ height={4}
+ color={getNodeStateColor('running')}
+ railColor="rgba(100, 116, 139, 0.2)"
+ />
+ </div>
+ )}
+
+ {/* 发光效果 - 用于成功状态 */}
+ {animations.glow && (
+ <div class={styles['dag-node-glow']} />
+ )}
+
+ {/* 不再使用自定义连接点,改用X6原生连接点 */}
</div>
)
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/responsive-mixins.scss
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/responsive-mixins.scss
new file mode 100644
index 000000000..d4ed0d1b2
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/responsive-mixins.scss
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+$canvas-breakpoints: (
+ 'mobile': 768px,
+ 'tablet': 1024px,
+ 'desktop': 1440px,
+ 'wide': 1920px
+) !default;
+
+
+@mixin canvas-respond-to($breakpoint) {
+ @if map-has-key($canvas-breakpoints, $breakpoint) {
+ @media (min-width: map-get($canvas-breakpoints, $breakpoint)) {
+ @content;
+ }
+ } @else {
+ @warn "Unknown breakpoint: #{$breakpoint}";
+ }
+}
+
+
+@mixin canvas-respond-to-max($breakpoint) {
+ @if map-has-key($canvas-breakpoints, $breakpoint) {
+ @media (max-width: map-get($canvas-breakpoints, $breakpoint) - 1px) {
+ @content;
+ }
+ } @else {
+ @warn "Unknown breakpoint: #{$breakpoint}";
+ }
+}
+
+
+@mixin canvas-respond-between($min-breakpoint, $max-breakpoint) {
+ @if map-has-key($canvas-breakpoints, $min-breakpoint) and
map-has-key($canvas-breakpoints, $max-breakpoint) {
+ @media (min-width: map-get($canvas-breakpoints, $min-breakpoint)) and
(max-width: map-get($canvas-breakpoints, $max-breakpoint) - 1px) {
+ @content;
+ }
+ } @else {
+ @warn "Unknown breakpoints: #{$min-breakpoint} or #{$max-breakpoint}";
+ }
+}
+
+
+@mixin canvas-high-dpi {
+ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
+ @content;
+ }
+}
+
+
+@mixin canvas-touch-device {
+ @media (hover: none) and (pointer: coarse) {
+ @content;
+ }
+}
+
+
+@mixin canvas-no-touch {
+ @media (hover: hover) and (pointer: fine) {
+ @content;
+ }
+}
+
+
+@mixin canvas-node-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ width: var(--canvas-node-width);
+ height: var(--canvas-node-height);
+ font-size: var(--canvas-font-size-node);
+ padding: var(--canvas-spacing-sm);
+
+ .canvas-node-icon {
+ width: 16px;
+ height: 16px;
+ }
+ }
+
+
+ @include canvas-respond-between('mobile', 'desktop') {
+ width: calc(var(--canvas-node-width) * 0.9);
+ height: calc(var(--canvas-node-height) * 0.95);
+ }
+
+
+ @include canvas-respond-to('desktop') {
+ width: var(--canvas-node-width);
+ height: var(--canvas-node-height);
+ }
+
+
+ @include canvas-respond-to('wide') {
+ width: calc(var(--canvas-node-width) * 1.1);
+ height: calc(var(--canvas-node-height) * 1.1);
+ font-size: calc(var(--canvas-font-size-node) * 1.1);
+ }
+}
+
+
+@mixin canvas-minimap-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ width: calc(var(--canvas-minimap-width) * 0.75);
+ height: calc(var(--canvas-minimap-height) * 0.75);
+ right: var(--canvas-spacing-md);
+ bottom: var(--canvas-spacing-md);
+
+
+ @media (max-width: 480px) {
+ display: none;
+ }
+ }
+
+
+ @include canvas-respond-between('mobile', 'desktop') {
+ width: calc(var(--canvas-minimap-width) * 0.9);
+ height: calc(var(--canvas-minimap-height) * 0.9);
+ }
+
+
+ @include canvas-respond-to('desktop') {
+ width: var(--canvas-minimap-width);
+ height: var(--canvas-minimap-height);
+ }
+
+
+ @include canvas-respond-to('wide') {
+ width: calc(var(--canvas-minimap-width) * 1.1);
+ height: calc(var(--canvas-minimap-height) * 1.1);
+ }
+}
+
+
+@mixin canvas-connection-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 1.5);
+
+ &:hover {
+ stroke-width: calc(var(--canvas-connection-stroke-width-hover) * 1.5);
+ }
+ }
+
+
+ @include canvas-touch-device {
+ stroke-width: calc(var(--canvas-connection-stroke-width) * 1.2);
+
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: -10px;
+ bottom: -10px;
+ left: -10px;
+ right: -10px;
+ pointer-events: all;
+ background: transparent;
+ }
+ }
+}
+
+
+@mixin canvas-container-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ padding: var(--canvas-spacing-sm);
+
+ .canvas-toolbar {
+ flex-direction: column;
+ gap: var(--canvas-spacing-sm);
+ }
+
+ .canvas-sidebar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 40vh;
+ transform: translateY(100%);
+ transition: transform var(--canvas-animation-normal)
var(--canvas-easing-default);
+
+ &.is-open {
+ transform: translateY(0);
+ }
+ }
+ }
+
+
+ @include canvas-respond-between('mobile', 'desktop') {
+ .canvas-sidebar {
+ width: 280px;
+ }
+ }
+
+
+ @include canvas-respond-to('desktop') {
+ .canvas-sidebar {
+ width: 320px;
+ }
+ }
+}
+
+
+@mixin canvas-typography-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ font-size: calc(var(--canvas-font-size-node) * 0.9);
+ line-height: calc(var(--canvas-line-height-node) * 1.1);
+ }
+
+
+ @include canvas-high-dpi {
+ font-weight: calc(var(--canvas-font-weight-node) - 100);
+ }
+
+
+ @include canvas-respond-to('wide') {
+ font-size: calc(var(--canvas-font-size-node) * 1.1);
+ }
+}
+
+
+@mixin canvas-spacing-responsive($property: 'padding') {
+
+ @include canvas-respond-to-max('mobile') {
+ #{$property}: calc(var(--canvas-spacing-md) * 0.75);
+ }
+
+
+ @include canvas-respond-between('mobile', 'desktop') {
+ #{$property}: calc(var(--canvas-spacing-md) * 0.9);
+ }
+
+
+ @include canvas-respond-to('desktop') {
+ #{$property}: var(--canvas-spacing-md);
+ }
+
+
+ @include canvas-respond-to('wide') {
+ #{$property}: calc(var(--canvas-spacing-md) * 1.2);
+ }
+}
+
+
+@mixin canvas-shadow-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+ &:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+ }
+ }
+
+
+ @include canvas-respond-to('desktop') {
+ box-shadow: var(--canvas-shadow-node);
+
+ &:hover {
+ box-shadow: var(--canvas-shadow-node-hover);
+ }
+ }
+
+
+ @include canvas-high-dpi {
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
+ }
+ }
+}
+
+
+@mixin canvas-animation-responsive {
+
+ @include canvas-respond-to-max('mobile') {
+ transition-duration: calc(var(--canvas-animation-normal) * 0.8);
+ }
+
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none !important;
+ animation: none !important;
+ }
+
+
+ @media (update: slow) {
+ transition-duration: calc(var(--canvas-animation-fast) * 0.5);
+ animation-duration: calc(var(--canvas-animation-normal) * 0.5);
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/theme-manager.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/theme-manager.ts
new file mode 100644
index 000000000..30dfa26c7
--- /dev/null
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/theme-manager.ts
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ref, computed, watch } from 'vue'
+import { CanvasDesignTokens, type Theme, type NodeType } from './design-tokens'
+
+
+export class CanvasThemeManager {
+ private currentTheme = ref<Theme>('light')
+
+ constructor() {
+
+ this.initTheme()
+
+
+ watch(this.currentTheme, (newTheme) => {
+ this.applyTheme(newTheme)
+ })
+ }
+
+
+ private initTheme() {
+
+ const savedTheme = localStorage.getItem('canvas-theme') as Theme
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme:
dark)').matches
+
+ this.currentTheme.value = savedTheme || (systemPrefersDark ? 'dark' :
'light')
+
+
+ window.matchMedia('(prefers-color-scheme:
dark)').addEventListener('change', (e) => {
+ if (!localStorage.getItem('canvas-theme')) {
+ this.currentTheme.value = e.matches ? 'dark' : 'light'
+ }
+ })
+ }
+
+
+ private applyTheme(theme: Theme) {
+ const root = document.documentElement
+
+
+ root.setAttribute('data-theme', theme)
+
+
+ localStorage.setItem('canvas-theme', theme)
+
+
+ window.dispatchEvent(new CustomEvent('canvas-theme-change', {
+ detail: { theme }
+ }))
+ }
+
+
+ toggleTheme() {
+ this.currentTheme.value = this.currentTheme.value === 'light' ? 'dark' :
'light'
+ }
+
+
+ setTheme(theme: Theme) {
+ this.currentTheme.value = theme
+ }
+
+
+ get theme() {
+ return this.currentTheme.value
+ }
+
+
+ get themeRef() {
+ return this.currentTheme
+ }
+
+
+ get canvasColors() {
+ return computed(() =>
CanvasDesignTokens.colors.canvas[this.currentTheme.value])
+ }
+
+
+ getNodeColors(type: NodeType) {
+ return CanvasDesignTokens.colors.nodes[type]
+ }
+
+
+ get connectionColors() {
+ return CanvasDesignTokens.colors.connections
+ }
+
+
+ get minimapColors() {
+ return computed(() => ({
+ background: this.currentTheme.value === 'light'
+ ? CanvasDesignTokens.colors.minimap.background
+ : CanvasDesignTokens.colors.minimap.backgroundDark,
+ border: this.currentTheme.value === 'light'
+ ? CanvasDesignTokens.colors.minimap.border
+ : CanvasDesignTokens.colors.minimap.borderDark,
+ viewport: CanvasDesignTokens.colors.minimap.viewport,
+ viewportBorder: CanvasDesignTokens.colors.minimap.viewportBorder
+ }))
+ }
+
+
+ getCSSVariable(variableName: string): string {
+ return getComputedStyle(document.documentElement)
+ .getPropertyValue(`--canvas-${variableName}`)
+ .trim()
+ }
+
+
+ setCSSVariable(variableName: string, value: string) {
+ document.documentElement.style.setProperty(`--canvas-${variableName}`,
value)
+ }
+
+
+ get animations() {
+ return CanvasDesignTokens.animations
+ }
+
+
+ get sizes() {
+ return CanvasDesignTokens.sizes
+ }
+
+
+ get spacing() {
+ return CanvasDesignTokens.spacing
+ }
+
+
+ get isDark() {
+ return computed(() => this.currentTheme.value === 'dark')
+ }
+
+
+ get isLight() {
+ return computed(() => this.currentTheme.value === 'light')
+ }
+}
+
+
+export const canvasThemeManager = new CanvasThemeManager()
+
+
+export function useCanvasTheme() {
+ return {
+ theme: canvasThemeManager.themeRef,
+ isDark: canvasThemeManager.isDark,
+ isLight: canvasThemeManager.isLight,
+ canvasColors: canvasThemeManager.canvasColors,
+ minimapColors: canvasThemeManager.minimapColors,
+ connectionColors: canvasThemeManager.connectionColors,
+ animations: canvasThemeManager.animations,
+ sizes: canvasThemeManager.sizes,
+ spacing: canvasThemeManager.spacing,
+ toggleTheme: () => canvasThemeManager.toggleTheme(),
+ setTheme: (theme: Theme) => canvasThemeManager.setTheme(theme),
+ getNodeColors: (type: NodeType) => canvasThemeManager.getNodeColors(type),
+ getCSSVariable: (name: string) => canvasThemeManager.getCSSVariable(name),
+ setCSSVariable: (name: string, value: string) =>
canvasThemeManager.setCSSVariable(name, value)
+ }
+}
\ No newline at end of file
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts
index ada1c6d67..d31eb3e1a 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts
@@ -114,28 +114,33 @@ export function useDagAddShape(
})
nodes[i].child.forEach((n: any) => {
+
+ const nodeType = (n.nodeType && n.nodeType.toLowerCase()) ||
+ (n.label.toLowerCase().includes('source') ? 'source' :
+ n.label.toLowerCase().includes('sink') ? 'sink' : 'transform');
+
+
+ const portItems = [];
+
+
group.addChild(
graph.addNode({
id: n.id,
- // x: 50,
- // y: 50,
- // width: 120,
- // height: 40,
+ x: 50,
+ y: 50,
+ width: 180,
+ height: 44,
shape: DagNodeName,
- // label: n.label,
zIndex: 10,
- // attrs: {
- // body: {
- // stroke: 'none',
- // fill: '#858585'
- // },
- // label: {
- // fill: '#fff',
- // fontSize: 12
- // }
- // },
+ ports: {
+ items: portItems
+ },
data: {
- name: n.label
+ name: n.label,
+ nodeType: nodeType,
+ connectorType: n.label,
+ status: 'idle',
+ vertexId: n.vertexId
}
})
)
@@ -146,12 +151,18 @@ export function useDagAddShape(
graph.addEdge({
shape: DagEdgeName,
source: {
- cell: e.source
+ cell: e.source,
+ port: 'output'
},
target: {
- cell: e.target
+ cell: e.target,
+ port: 'input'
},
- id: e.id
+ id: e.id,
+ zIndex: 5,
+ data: {
+ animated: true
+ }
})
})
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts
index 15a3f4f41..52e42ed40 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts
@@ -15,13 +15,294 @@
* limitations under the License.
*/
-export function useDagEdge() {
+import { CanvasDesignTokens } from './design-tokens'
+
+
+export interface ModernEdgeData {
+ id: string
+ source: string
+ target: string
+
+
+ style?: {
+ stroke?: string
+ strokeWidth?: number
+ strokeDasharray?: string
+ animated?: boolean
+ }
+
+
+ status?: 'normal' | 'active' | 'error' | 'warning' | 'success'
+
+
+ dataFlow?: {
+ direction?: 'forward' | 'backward' | 'bidirectional'
+ speed?: 'slow' | 'normal' | 'fast'
+ volume?: number
+ }
+
+
+ isSelected?: boolean
+ isHovered?: boolean
+
+
+ label?: string
+
+
+ metadata?: {
+ [key: string]: any
+ }
+}
+
+
+export interface EdgeOptions {
+ router?: 'normal' | 'smooth' | 'orthogonal'
+ connector?: 'normal' | 'rounded' | 'smooth'
+ animated?: boolean
+ showArrow?: boolean
+ interactive?: boolean
+}
+
+export function useDagEdge(options: EdgeOptions = {}) {
+ const defaultOptions: EdgeOptions = {
+ router: 'smooth',
+ connector: 'smooth',
+ animated: false,
+ showArrow: true,
+ interactive: true
+ }
+
+ const finalOptions = { ...defaultOptions, ...options }
+
return {
+ inherit: 'edge',
+
+
+ router: {
+ name: 'orth'
+ },
+ connector: {
+ name: 'normal'
+ },
attrs: {
line: {
- stroke: '#C2C8D5',
- strokeWidth: 1
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: CanvasDesignTokens.sizes.connection.strokeWidth,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ fill: 'none',
+ class: 'modern-edge',
+
+
+ ...(finalOptions.animated && {
+ strokeDasharray: '8,4',
+ class: 'modern-edge modern-edge-animated'
+ })
+ },
+
+
+ ...(finalOptions.showArrow && {
+ targetMarker: {
+ name: 'block',
+ size: CanvasDesignTokens.sizes.connection.arrowSize,
+ fill: CanvasDesignTokens.colors.connections.default,
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 1,
+ class: 'modern-edge-arrow'
+ }
+ }),
+
+
+ ...(finalOptions.interactive && {
+ wrap: {
+ stroke: 'transparent',
+ strokeWidth: 12,
+ fill: 'none',
+ cursor: 'pointer',
+ class: 'modern-edge-interaction'
+ }
+ })
+ },
+
+
+ defaultLabel: {
+ markup: [
+ {
+ tagName: 'rect',
+ selector: 'body'
+ },
+ {
+ tagName: 'text',
+ selector: 'label'
+ }
+ ],
+ attrs: {
+ body: {
+ ref: 'label',
+ fill: 'rgba(255, 255, 255, 0.95)',
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 1,
+ rx: 4,
+ ry: 4,
+ refWidth: '100%',
+ refHeight: '100%',
+ refX: '-50%',
+ refY: '-50%',
+ class: 'modern-edge-tooltip-bg'
+ },
+ label: {
+ fontSize: CanvasDesignTokens.typography.tooltip.fontSize,
+ fontWeight: CanvasDesignTokens.typography.tooltip.fontWeight,
+ fill: CanvasDesignTokens.colors.connections.default,
+ textAnchor: 'middle',
+ textVerticalAnchor: 'middle',
+ class: 'modern-edge-label'
+ }
+ },
+ position: {
+ distance: 0.5,
+ offset: 0
+ }
+ },
+
+
+ events: {
+ 'edge:mouseenter': ({ edge }: any) => {
+ const data = edge.getData() || {}
+ edge.setData({ ...data, isHovered: true })
+
+
+ edge.attr('line/class', 'modern-edge modern-edge:hover')
+ edge.attr('line/stroke', CanvasDesignTokens.colors.connections.hover)
+ edge.attr('line/strokeWidth',
CanvasDesignTokens.sizes.connection.strokeWidthHover)
+ },
+
+ 'edge:mouseleave': ({ edge }: any) => {
+ const data = edge.getData() || {}
+ edge.setData({ ...data, isHovered: false })
+
+
+ const isSelected = data.isSelected
+ edge.attr('line/class', isSelected ? 'modern-edge is-selected' :
'modern-edge')
+ edge.attr('line/stroke', isSelected
+ ? CanvasDesignTokens.colors.connections.selected
+ : CanvasDesignTokens.colors.connections.default)
+ edge.attr('line/strokeWidth', isSelected
+ ? CanvasDesignTokens.sizes.connection.strokeWidthHover
+ : CanvasDesignTokens.sizes.connection.strokeWidth)
+ },
+
+ 'edge:selected': ({ edge }: any) => {
+ const data = edge.getData() || {}
+ edge.setData({ ...data, isSelected: true })
+
+
+ edge.attr('line/class', 'modern-edge is-selected')
+ edge.attr('line/stroke',
CanvasDesignTokens.colors.connections.selected)
+ edge.attr('line/strokeWidth',
CanvasDesignTokens.sizes.connection.strokeWidthHover)
+ },
+
+ 'edge:unselected': ({ edge }: any) => {
+ const data = edge.getData() || {}
+ edge.setData({ ...data, isSelected: false })
+
+
+ edge.attr('line/class', 'modern-edge')
+ edge.attr('line/stroke', CanvasDesignTokens.colors.connections.default)
+ edge.attr('line/strokeWidth',
CanvasDesignTokens.sizes.connection.strokeWidth)
}
}
}
}
+
+
+export function createEdgeData(
+ id: string,
+ source: string,
+ target: string,
+ options: Partial<ModernEdgeData> = {}
+): ModernEdgeData {
+ return {
+ id,
+ source,
+ target,
+ status: 'normal',
+ isSelected: false,
+ isHovered: false,
+ style: {
+ animated: false
+ },
+ dataFlow: {
+ direction: 'forward',
+ speed: 'normal'
+ },
+ metadata: {},
+ ...options
+ }
+}
+
+
+export function updateEdgeStatus(
+ edge: any,
+ status: 'normal' | 'active' | 'error' | 'warning' | 'success'
+) {
+ const data = edge.getData() || {}
+ edge.setData({ ...data, status })
+
+
+ const statusColors = {
+ normal: CanvasDesignTokens.colors.connections.default,
+ active: CanvasDesignTokens.colors.connections.animated,
+ error: CanvasDesignTokens.colors.nodes.states.error,
+ warning: CanvasDesignTokens.colors.nodes.states.warning,
+ success: CanvasDesignTokens.colors.nodes.states.success
+ }
+
+ const statusClasses = {
+ normal: 'modern-edge',
+ active: 'modern-edge is-active',
+ error: 'modern-edge is-error',
+ warning: 'modern-edge is-warning',
+ success: 'modern-edge is-success'
+ }
+
+ edge.attr('line/stroke', statusColors[status])
+ edge.attr('line/class', statusClasses[status])
+
+
+ if (status === 'error') {
+ edge.attr('line/strokeDasharray', '5,5')
+ } else if (status === 'warning') {
+ edge.attr('line/strokeDasharray', '3,3')
+ } else {
+ edge.attr('line/strokeDasharray', 'none')
+ }
+}
+
+
+export function enableDataFlowAnimation(
+ edge: any,
+ speed: 'slow' | 'normal' | 'fast' = 'normal'
+) {
+ const data = edge.getData() || {}
+ edge.setData({
+ ...data,
+ style: {
+ ...data.style,
+ animated: true
+ },
+ dataFlow: {
+ ...data.dataFlow,
+ speed
+ }
+ })
+
+ const speedClasses = {
+ slow: 'modern-edge modern-edge-animated flow-slow',
+ normal: 'modern-edge modern-edge-animated',
+ fast: 'modern-edge modern-edge-animated flow-fast'
+ }
+
+ edge.attr('line/class', speedClasses[speed])
+ edge.attr('line/strokeDasharray', '8,4')
+}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts
index ebf95d8a3..9b1bc84c6 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts
@@ -16,33 +16,146 @@
*/
import { Graph } from '@antv/x6'
-import { DagEdgeName } from './dag-setting'
+import { useCanvasTheme } from './theme-manager'
+
+
+export interface CanvasOptions {
+ enableGrid?: boolean
+ enableMinimap?: boolean
+ enableScroller?: boolean
+ enableSelection?: boolean
+ enableKeyboard?: boolean
+ enableClipboard?: boolean
+ enableHistory?: boolean
+ gridSize?: number
+ minimapSize?: { width: number; height: number }
+ background?: {
+ color?: string
+ image?: string
+ size?: string
+ position?: string
+ }
+}
export function useDagGraph(
graph: any,
dagContainer: HTMLElement,
- minimapContainer: HTMLElement
+ minimapContainer: HTMLElement,
+ options: CanvasOptions = {}
) {
- return new Graph({
+
+ const graphInstance = new Graph({
container: dagContainer,
- scroller: true,
+ autoResize: true,
+
+
grid: {
- size: 10,
- visible: true
+ size: 20,
+ visible: true,
+ type: 'dot'
},
- connecting: {
- // router: 'orth',
- allowBlank: false,
- allowLoop: false,
- createEdge() {
- return graph.value?.createEdge({ shape: DagEdgeName })
- }
+
+
+ background: {
+ color: '#FAFAFA'
},
- minimap: {
+
+
+ minimap: minimapContainer ? {
enabled: true,
+ container: minimapContainer,
width: 200,
height: 120,
- container: minimapContainer
+ padding: 10
+ } : false,
+
+
+ selecting: {
+ enabled: true,
+ multiple: true,
+ rubberband: true,
+ movable: true
+ },
+
+
+ connecting: {
+ allowBlank: false,
+ allowLoop: false,
+ allowNode: false,
+ allowEdge: false,
+ allowPort: true
}
})
+
+ console.log('Graph instance created:', graphInstance)
+ console.log('Container element:', dagContainer)
+
+
+ const { canvasColors } = useCanvasTheme()
+ const updateTheme = () => {
+ const colors = canvasColors.value
+
+
+ graphInstance.drawBackground({
+ color: colors.background
+ })
+ }
+
+
+ window.addEventListener('canvas-theme-change', updateTheme)
+
+
+ graphInstance.on('scale', ({ sx }: any) => {
+ const container = dagContainer
+ const gridElement = container.querySelector('.x6-graph-grid')
+
+ if (gridElement) {
+
+ let opacity = 1
+ if (sx < 0.5) {
+ opacity = 0.3
+ gridElement.classList.add('zoom-small')
+ } else if (sx > 2) {
+ opacity = 0.6
+ gridElement.classList.add('zoom-large')
+ } else if (sx > 4) {
+ opacity = 0.4
+ gridElement.classList.add('zoom-extra-large')
+ } else {
+ gridElement.classList.remove('zoom-small', 'zoom-large',
'zoom-extra-large')
+ }
+
+ ;(gridElement as HTMLElement).style.opacity = opacity.toString()
+ }
+ })
+
+
+ let frameCount = 0
+ let lastTime = performance.now()
+
+ const monitorPerformance = () => {
+ frameCount++
+ const currentTime = performance.now()
+
+ if (currentTime - lastTime >= 1000) {
+ const fps = Math.round((frameCount * 1000) / (currentTime - lastTime))
+
+
+ if (fps < 30) {
+ dagContainer.classList.add('canvas-performance-mode')
+ } else {
+ dagContainer.classList.remove('canvas-performance-mode')
+ }
+
+ frameCount = 0
+ lastTime = currentTime
+ }
+
+ requestAnimationFrame(monitorPerformance)
+ }
+
+
+ requestAnimationFrame(monitorPerformance)
+
+ return graphInstance
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts
index 5dc800bfb..e55d95c92 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts
@@ -22,25 +22,31 @@ import { DagEdgeName, DagNodeName } from './dag-setting'
const updateParentNodePosition = (nodes: any, node: any) => {
if (node.children && node.children.length) {
const children = node.children
- let minX = Number.MAX_VALUE
- let maxX = 0
- let minY = Number.MAX_VALUE
- let maxY = 0
- nodes
- .filter((node: any) => children.includes(node.id))
- .map((node: any) => {
- minX = Math.min(minX, node.x)
- maxX = Math.max(maxX, node.x)
- minY = Math.min(minY, node.y)
- maxY = Math.max(maxY, node.y)
- })
+ const childNodes = nodes.filter((n: any) => children.includes(n.id));
+ if (childNodes.length === 0) return;
- node.x = minX - 20
- node.y = minY - 20
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
+
+ childNodes.forEach((child: any) => {
+ const childX = child.x;
+ const childY = child.y;
+ const childWidth = child.size?.width || 180;
+ const childHeight = child.size?.height || 44;
+
+ minX = Math.min(minX, childX);
+ minY = Math.min(minY, childY);
+ maxX = Math.max(maxX, childX + childWidth);
+ maxY = Math.max(maxY, childY + childHeight);
+ });
+
+ const padding = 40;
+
+ node.x = minX - padding;
+ node.y = minY - padding;
node.size = {
- width: maxX - minX + 200,
- height: maxY - minY + 80
- }
+ width: (maxX - minX) + (2 * padding),
+ height: (maxY - minY) + (2 * padding)
+ };
}
}
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts
index 9aeaff364..08f22e938 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts
@@ -17,17 +17,553 @@
import '@antv/x6-vue-shape'
import Node from './node'
-import { createVNode } from 'vue'
+import { CanvasDesignTokens, getNodeColors, getNodeStateColor, NodeType,
NodeState } from './design-tokens'
-export function useDagNode() {
+
+export interface ModernNodeData {
+
+ id: string
+ name: string
+ nodeType?: NodeType
+ connectorType?: string
+
+
+ theme?: {
+ primaryColor: string
+ secondaryColor: string
+ gradient: string
+ borderColor: string
+ textColor: string
+ iconColor?: string
+ }
+
+
+ status?: NodeState
+ progress?: number
+ statusMessage?: string
+
+
+ isSelected?: boolean
+ isHovered?: boolean
+ isDragging?: boolean
+ isDisabled?: boolean
+
+
+ animations?: {
+ entrance?: boolean
+ pulse?: boolean
+ glow?: boolean
+ shake?: boolean
+ }
+
+
+ style?: {
+ width?: number
+ height?: number
+ borderRadius?: string
+ shadow?: string
+ opacity?: number
+ zIndex?: number
+ }
+
+
+ metadata?: {
+ vertexId?: string
+ description?: string
+ tags?: string[]
+ icon?: string
+ createdAt?: string
+ updatedAt?: string
+ [key: string]: any
+ }
+}
+
+
+export interface NodeOptions {
+ width?: number
+ height?: number
+ minWidth?: number
+ maxWidth?: number
+ resizable?: boolean
+ rotatable?: boolean
+ selectable?: boolean
+ movable?: boolean
+ portVisible?: boolean
+ animationEnabled?: boolean
+ themeEnabled?: boolean
+}
+
+
+export function useDagNode(options: NodeOptions = {}) {
+ const defaultOptions: NodeOptions = {
+ width: CanvasDesignTokens.sizes.node.width,
+ height: CanvasDesignTokens.sizes.node.height,
+ minWidth: CanvasDesignTokens.sizes.node.minWidth,
+ maxWidth: CanvasDesignTokens.sizes.node.maxWidth,
+ resizable: false,
+ rotatable: false,
+ selectable: true,
+ movable: true,
+ portVisible: true,
+ animationEnabled: true,
+ themeEnabled: true
+ }
+
+ const finalOptions = { ...defaultOptions, ...options }
+
return {
inherit: 'vue-shape',
- width: 150,
- height: 36,
- component: {
- render: () => {
- return createVNode(Node)
+ width: finalOptions.width,
+ height: finalOptions.height,
+ resizing: finalOptions.resizable ? {
+ enabled: true,
+ minWidth: finalOptions.minWidth,
+ maxWidth: finalOptions.maxWidth,
+ preserveAspectRatio: false
+ } : false,
+ rotating: finalOptions.rotatable,
+ selecting: finalOptions.selectable,
+ moving: finalOptions.movable,
+
+
+ attrs: {
+ body: {
+ stroke: 'transparent',
+ fill: 'transparent',
+ rx: parseInt(CanvasDesignTokens.borderRadius.node),
+ ry: parseInt(CanvasDesignTokens.borderRadius.node)
+ }
+ },
+
+
+ ports: {
+ groups: {
+ input: {
+ position: 'left',
+ attrs: {
+ circle: {
+ r: 5,
+ magnet: true,
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 2,
+ fill: '#fff',
+ opacity: 0
+ }
+ }
+ },
+ output: {
+ position: 'right',
+ attrs: {
+ circle: {
+ r: 5,
+ magnet: true,
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 2,
+ fill: '#fff',
+ opacity: 0
+ }
+ }
+ }
+ },
+
+ items: []
+ },
+
+
+ component: Node,
+
+
+ events: {
+
+ 'node:mouseenter': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+ if (data.isDisabled) return
+
+
+ node.setData({
+ ...data,
+ isHovered: true,
+
+ animations: finalOptions.animationEnabled ? {
+ ...data.animations,
+ glow: data.status === 'success' ? true : data.animations?.glow
+ } : data.animations
+ })
+
+
+ if (finalOptions.portVisible) {
+
+ const ports = node.getPorts() || [];
+ const hasInputPort = ports.some((port: any) => port.id === 'input');
+ const hasOutputPort = ports.some((port: any) => port.id ===
'output');
+
+ if (hasInputPort) {
+ node.setPortProp('input', 'attrs/circle/opacity', 1);
+ node.setPortProp('input', 'attrs/circle/magnet', true);
+ }
+ if (hasOutputPort) {
+ node.setPortProp('output', 'attrs/circle/opacity', 1);
+ node.setPortProp('output', 'attrs/circle/magnet', true);
+ }
+ }
+ },
+
+
+ 'node:mouseleave': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+ if (data.isDisabled) return
+
+
+ node.setData({
+ ...data,
+ isHovered: false,
+
+ animations: finalOptions.animationEnabled ? {
+ ...data.animations,
+ glow: data.status === 'success'
+ } : data.animations
+ })
+
+
+ if (finalOptions.portVisible && !data.isSelected) {
+
+ const ports = node.getPorts() || [];
+ const hasInputPort = ports.some((port: any) => port.id === 'input');
+ const hasOutputPort = ports.some((port: any) => port.id ===
'output');
+
+ if (hasInputPort) {
+ node.setPortProp('input', 'attrs/circle/opacity', 0);
+ }
+ if (hasOutputPort) {
+ node.setPortProp('output', 'attrs/circle/opacity', 0);
+ }
+ }
+ },
+
+
+ 'node:selected': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+ if (data.isDisabled) return
+
+
+ node.setData({ ...data, isSelected: true })
+
+
+ if (finalOptions.portVisible) {
+
+ const ports = node.getPorts() || [];
+ const hasInputPort = ports.some((port: any) => port.id === 'input');
+ const hasOutputPort = ports.some((port: any) => port.id ===
'output');
+
+ if (hasInputPort) {
+ node.setPortProp('input', 'attrs/circle/opacity', 1);
+ node.setPortProp('input', 'attrs/circle/magnet', true);
+ }
+ if (hasOutputPort) {
+ node.setPortProp('output', 'attrs/circle/opacity', 1);
+ node.setPortProp('output', 'attrs/circle/magnet', true);
+ }
+ }
+ },
+
+
+ 'node:unselected': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+
+
+ node.setData({ ...data, isSelected: false })
+
+
+ if (finalOptions.portVisible && !data.isHovered) {
+
+ const ports = node.getPorts() || [];
+ const hasInputPort = ports.some((port: any) => port.id === 'input');
+ const hasOutputPort = ports.some((port: any) => port.id ===
'output');
+
+ if (hasInputPort) {
+ node.setPortProp('input', 'attrs/circle/opacity', 0);
+ }
+ if (hasOutputPort) {
+ node.setPortProp('output', 'attrs/circle/opacity', 0);
+ }
+ }
+ },
+
+
+ 'node:move': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+ if (data.isDisabled) return
+
+
+ node.setData({
+ ...data,
+ isDragging: true,
+
+ animations: finalOptions.animationEnabled ? {
+ ...data.animations,
+ entrance: false
+ } : data.animations
+ })
+ },
+
+
+ 'node:moved': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+
+
+ node.setData({
+ ...data,
+ isDragging: false,
+
+ metadata: {
+ ...data.metadata,
+ position: node.getPosition(),
+ updatedAt: new Date().toISOString()
+ }
+ })
+ },
+
+
+ 'node:resize': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+ if (data.isDisabled) return
+
+
+ const size = node.getSize()
+ node.setData({
+ ...data,
+ style: {
+ ...data.style,
+ width: size.width,
+ height: size.height
+ }
+ })
+ },
+
+
+ 'node:added': ({ node }: any) => {
+ const data = node.getData() as ModernNodeData
+
+
+ if (finalOptions.animationEnabled) {
+ node.setData({
+ ...data,
+ animations: {
+ ...data.animations,
+ entrance: true
+ }
+ })
+ }
+
+
+ if (finalOptions.themeEnabled && data.nodeType && !data.theme) {
+ updateNodeTheme(node, data.nodeType)
+ }
+
+
+ const nodeType = data.nodeType || determineNodeType(undefined,
data.connectorType, data.name);
+
+
+ node.removePorts();
+
+
+ if (nodeType !== 'sink') {
+ node.addPort({
+ id: 'output',
+ group: 'output',
+ attrs: {
+ circle: {
+ magnet: true,
+ r: 5,
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 2,
+ fill: '#fff'
+ }
+ }
+ });
+ }
+
+ if (nodeType !== 'source') {
+ node.addPort({
+ id: 'input',
+ group: 'input',
+ attrs: {
+ circle: {
+ magnet: true,
+ r: 5,
+ stroke: CanvasDesignTokens.colors.connections.default,
+ strokeWidth: 2,
+ fill: '#fff'
+ }
+ }
+ });
+ }
}
}
}
}
+
+
+export function generateNodeTheme(type: NodeType) {
+ const colors = getNodeColors(type)
+ return {
+ primaryColor: colors.primary,
+ secondaryColor: colors.secondary,
+ gradient: colors.gradient,
+ borderColor: colors.border,
+ textColor: colors.text,
+ iconColor: colors.primary
+ }
+}
+
+
+export function getStatusColor(status: NodeState) {
+ return getNodeStateColor(status)
+}
+
+
+export function determineNodeType(
+ type?: string,
+ connector?: string,
+ nodeName?: string
+): NodeType {
+ if (type && ['source', 'sink', 'transform'].includes(type)) {
+ return type as NodeType;
+ }
+
+ const lowerText = [(connector || ''), (nodeName || '')].join('
').toLowerCase()
+
+ if (lowerText.includes('source') || lowerText.includes('input')) {
+ return 'source'
+ } else if (lowerText.includes('sink') || lowerText.includes('output')) {
+ return 'sink'
+ } else {
+ return 'transform'
+ }
+}
+
+export function createNodeData(
+ id: string,
+ name: string,
+ type: NodeType,
+ options: Partial<ModernNodeData> = {}
+): ModernNodeData {
+
+ const theme = generateNodeTheme(type)
+
+
+ const defaultStyle = {
+ width: CanvasDesignTokens.sizes.node.width,
+ height: CanvasDesignTokens.sizes.node.height,
+ borderRadius: CanvasDesignTokens.borderRadius.node,
+ shadow: CanvasDesignTokens.shadows.node,
+ opacity: 1,
+ zIndex: CanvasDesignTokens.zIndex.nodes
+ }
+
+ return {
+ id,
+ name,
+ nodeType: type,
+ theme,
+ status: 'idle',
+ progress: 0,
+ isSelected: false,
+ isHovered: false,
+ isDragging: false,
+ isDisabled: false,
+ animations: {
+ entrance: true,
+ pulse: false,
+ glow: false,
+ shake: false
+ },
+ style: {
+ ...defaultStyle,
+ ...(options.style || {})
+ },
+ metadata: {
+ icon: getNodeTypeIcon(type),
+ ...(options.metadata || {})
+ },
+ ...options
+ }
+}
+
+
+function getNodeTypeIcon(type: NodeType): string {
+ switch (type) {
+ case 'source':
+ return 'data-source'
+ case 'sink':
+ return 'data-target'
+ case 'transform':
+ return 'data-transform'
+ default:
+ return 'default-node'
+ }
+}
+
+export function updateNodeStatus(
+ node: any,
+ status: NodeState,
+ progress?: number,
+ statusMessage?: string
+) {
+ const data = node.getData() as ModernNodeData
+
+
+ const animations = {
+ ...data.animations,
+ pulse: status === 'running',
+ glow: status === 'success',
+ shake: status === 'error'
+ }
+
+ node.setData({
+ ...data,
+ status,
+ progress,
+ statusMessage,
+ animations
+ })
+}
+
+
+export function updateNodeTheme(node: any, type: NodeType) {
+ const data = node.getData() as ModernNodeData
+ const theme = generateNodeTheme(type)
+
+ node.setData({
+ ...data,
+ nodeType: type,
+ theme
+ })
+}
+
+export function updateNodeMetadata(node: any, metadata:
Partial<ModernNodeData['metadata']>) {
+ const data = node.getData() as ModernNodeData
+
+ node.setData({
+ ...data,
+ metadata: {
+ ...data.metadata,
+ ...metadata,
+ updatedAt: new Date().toISOString()
+ }
+ })
+}
+
+
+export function updateNodeStyle(node: any, style:
Partial<ModernNodeData['style']>) {
+ const data = node.getData() as ModernNodeData
+
+ node.setData({
+ ...data,
+ style: {
+ ...data.style,
+ ...style
+ }
+ })
+}
+
diff --git
a/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx
b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx
index c4376bbaf..a74e41cc4 100644
---
a/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx
+++
b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx
@@ -27,6 +27,7 @@ import { useDagEdge } from './dag/use-dag-edge'
import { useTaskDefinition } from './use-task-definition'
import styles from './task-definition.module.scss'
import { useDagNode } from './dag/use-dag-node'
+import { useCanvasTheme } from './dag/theme-manager'
interface IJobConfig {
name: string
@@ -46,6 +47,9 @@ const TaskDefinition = defineComponent({
const graph = ref<Graph>()
const { getJobConfig, getJobDag } = useTaskDefinition(t)
+
+ const { } = useCanvasTheme()
+
const initGraph = () => {
graph.value = useDagGraph(
graph,