|
@@ -6,6 +6,7 @@ const props = defineProps({
|
|
|
elements: { type: Array, default: () => [] }
|
|
elements: { type: Array, default: () => [] }
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+const activeTab = ref('source')
|
|
|
const searchQuery = ref('')
|
|
const searchQuery = ref('')
|
|
|
const expandedGroups = ref(['source', 'action', 'element'])
|
|
const expandedGroups = ref(['source', 'action', 'element'])
|
|
|
|
|
|
|
@@ -88,6 +89,34 @@ function onDragStartElement(event, element) {
|
|
|
<span class="panel-title">节点面板</span>
|
|
<span class="panel-title">节点面板</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- Tab 切换 -->
|
|
|
|
|
+ <div class="panel-tabs">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="tab-item"
|
|
|
|
|
+ :class="{ active: activeTab === 'source' }"
|
|
|
|
|
+ @click="activeTab = 'source'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="tab-icon">📦</span>
|
|
|
|
|
+ <span class="tab-label">来源</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="tab-item"
|
|
|
|
|
+ :class="{ active: activeTab === 'action' }"
|
|
|
|
|
+ @click="activeTab = 'action'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="tab-icon">⚡</span>
|
|
|
|
|
+ <span class="tab-label">动作</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="tab-item"
|
|
|
|
|
+ :class="{ active: activeTab === 'element' }"
|
|
|
|
|
+ @click="activeTab = 'element'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="tab-icon">🏷️</span>
|
|
|
|
|
+ <span class="tab-label">要素</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div class="panel-search">
|
|
<div class="panel-search">
|
|
|
<el-input
|
|
<el-input
|
|
|
v-model="searchQuery"
|
|
v-model="searchQuery"
|
|
@@ -99,14 +128,10 @@ function onDragStartElement(event, element) {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="panel-content">
|
|
<div class="panel-content">
|
|
|
- <!-- 来源节点组 -->
|
|
|
|
|
- <div class="node-group">
|
|
|
|
|
- <div class="group-header" @click="toggleGroup('source')">
|
|
|
|
|
- <span class="group-icon">{{ expandedGroups.includes('source') ? '▼' : '▶' }}</span>
|
|
|
|
|
- <span class="group-title">来源节点</span>
|
|
|
|
|
- <span class="group-count">{{ sourceNodes.length }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="group-content" v-show="expandedGroups.includes('source')">
|
|
|
|
|
|
|
+ <!-- 来源节点 Tab -->
|
|
|
|
|
+ <div v-show="activeTab === 'source'" class="tab-content">
|
|
|
|
|
+ <div class="node-section">
|
|
|
|
|
+ <div class="section-title">基础来源</div>
|
|
|
<div
|
|
<div
|
|
|
v-for="node in sourceNodes"
|
|
v-for="node in sourceNodes"
|
|
|
:key="node.subType"
|
|
:key="node.subType"
|
|
@@ -122,16 +147,28 @@ function onDragStartElement(event, element) {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="node-section" v-if="attachments.length > 0">
|
|
|
|
|
+ <div class="section-title">项目附件 <span class="section-count">{{ filteredAttachments.length }}</span></div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="att in filteredAttachments"
|
|
|
|
|
+ :key="att.id"
|
|
|
|
|
+ class="node-item attachment-item"
|
|
|
|
|
+ draggable="true"
|
|
|
|
|
+ @dragstart="onDragStartAttachment($event, att)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="node-icon">📎</span>
|
|
|
|
|
+ <div class="node-info">
|
|
|
|
|
+ <span class="node-label">{{ att.displayName || att.fileName }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 动作节点组 -->
|
|
|
|
|
- <div class="node-group">
|
|
|
|
|
- <div class="group-header" @click="toggleGroup('action')">
|
|
|
|
|
- <span class="group-icon">{{ expandedGroups.includes('action') ? '▼' : '▶' }}</span>
|
|
|
|
|
- <span class="group-title">动作节点</span>
|
|
|
|
|
- <span class="group-count">{{ actionNodes.length }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="group-content" v-show="expandedGroups.includes('action')">
|
|
|
|
|
|
|
+ <!-- 动作节点 Tab -->
|
|
|
|
|
+ <div v-show="activeTab === 'action'" class="tab-content">
|
|
|
|
|
+ <div class="node-section">
|
|
|
|
|
+ <div class="section-title">可用动作</div>
|
|
|
<div
|
|
<div
|
|
|
v-for="node in actionNodes"
|
|
v-for="node in actionNodes"
|
|
|
:key="node.subType"
|
|
:key="node.subType"
|
|
@@ -149,37 +186,10 @@ function onDragStartElement(event, element) {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 附件列表 -->
|
|
|
|
|
- <div class="node-group" v-if="attachments.length > 0">
|
|
|
|
|
- <div class="group-header" @click="toggleGroup('attachments')">
|
|
|
|
|
- <span class="group-icon">{{ expandedGroups.includes('attachments') ? '▼' : '▶' }}</span>
|
|
|
|
|
- <span class="group-title">项目附件</span>
|
|
|
|
|
- <span class="group-count">{{ filteredAttachments.length }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="group-content" v-show="expandedGroups.includes('attachments')">
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="att in filteredAttachments"
|
|
|
|
|
- :key="att.id"
|
|
|
|
|
- class="node-item attachment-item"
|
|
|
|
|
- draggable="true"
|
|
|
|
|
- @dragstart="onDragStartAttachment($event, att)"
|
|
|
|
|
- >
|
|
|
|
|
- <span class="node-icon">📎</span>
|
|
|
|
|
- <div class="node-info">
|
|
|
|
|
- <span class="node-label">{{ att.displayName || att.fileName }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 要素列表 -->
|
|
|
|
|
- <div class="node-group">
|
|
|
|
|
- <div class="group-header" @click="toggleGroup('element')">
|
|
|
|
|
- <span class="group-icon">{{ expandedGroups.includes('element') ? '▼' : '▶' }}</span>
|
|
|
|
|
- <span class="group-title">输出要素</span>
|
|
|
|
|
- <span class="group-count">{{ filteredElements.length }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="group-content" v-show="expandedGroups.includes('element')">
|
|
|
|
|
|
|
+ <!-- 要素节点 Tab -->
|
|
|
|
|
+ <div v-show="activeTab === 'element'" class="tab-content">
|
|
|
|
|
+ <div class="node-section">
|
|
|
|
|
+ <div class="section-title">输出要素 <span class="section-count">{{ filteredElements.length }}</span></div>
|
|
|
<div
|
|
<div
|
|
|
v-for="elem in filteredElements"
|
|
v-for="elem in filteredElements"
|
|
|
:key="elem.elementKey"
|
|
:key="elem.elementKey"
|
|
@@ -207,68 +217,113 @@ function onDragStartElement(event, element) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.panel-header {
|
|
.panel-header {
|
|
|
- padding: 14px 16px;
|
|
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border-bottom: 1.5px solid #e8ecf0;
|
|
|
|
|
+ background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.panel-title {
|
|
.panel-title {
|
|
|
- font-size: 14px;
|
|
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ letter-spacing: 0.3px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel-tabs {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ border-bottom: 1.5px solid #e8ecf0;
|
|
|
|
|
+ background: #fafbfc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-item {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ padding: 10px 8px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
+ border: 1.5px solid transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-item:hover {
|
|
|
|
|
+ background: linear-gradient(135deg, #f0f7ff 0%, #e6f0ff 100%);
|
|
|
|
|
+ border-color: #d9e9ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-item.active {
|
|
|
|
|
+ background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-item.active .tab-icon {
|
|
|
|
|
+ filter: brightness(0) invert(1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-item.active .tab-label {
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-icon {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ transition: filter 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- color: #303133;
|
|
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ transition: color 0.2s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.panel-search {
|
|
.panel-search {
|
|
|
padding: 12px;
|
|
padding: 12px;
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
|
|
+ border-bottom: 1.5px solid #e8ecf0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.panel-content {
|
|
.panel-content {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
- padding: 8px 0;
|
|
|
|
|
|
|
+ padding: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.node-group {
|
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
|
|
|
+.tab-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 16px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.group-header {
|
|
|
|
|
|
|
+.node-section {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
|
|
|
|
+ flex-direction: column;
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
- padding: 10px 16px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- user-select: none;
|
|
|
|
|
- transition: background 0.2s;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.group-header:hover {
|
|
|
|
|
- background: #f5f7fa;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.group-icon {
|
|
|
|
|
- font-size: 10px;
|
|
|
|
|
- color: #909399;
|
|
|
|
|
- width: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.group-title {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
color: #606266;
|
|
color: #606266;
|
|
|
|
|
+ padding: 0 4px;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ letter-spacing: 0.5px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.group-count {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+.section-count {
|
|
|
|
|
+ font-size: 11px;
|
|
|
color: #909399;
|
|
color: #909399;
|
|
|
background: #f0f2f5;
|
|
background: #f0f2f5;
|
|
|
padding: 2px 8px;
|
|
padding: 2px 8px;
|
|
|
border-radius: 10px;
|
|
border-radius: 10px;
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.group-content {
|
|
|
|
|
- padding: 4px 8px;
|
|
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.node-item {
|
|
.node-item {
|