Skip to content

Commit f8b9c51

Browse files
committed
feat: Workflow node search
1 parent c61e823 commit f8b9c51

File tree

4 files changed

+194
-55
lines changed

4 files changed

+194
-55
lines changed

ui/src/workflow/common/NodeContainer.vue

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,34 @@ const closeNodeMenu = () => {
432432
showAnchor.value = false
433433
anchorData.value = undefined
434434
}
435+
/**
436+
* 检索选中时候触发
437+
* @param kw
438+
*/
439+
const selectOn = (kw: string) => {
440+
props.nodeModel.setSelected(true)
441+
console.log('selectOn', kw)
442+
}
443+
/**
444+
* 定位时触发
445+
* @param kw
446+
*/
447+
const focusOn = (kw: string) => {
448+
console.log('focusOn', kw)
449+
}
450+
/**
451+
* 清除时触发
452+
*/
453+
const clearSelectOn = () => {
454+
console.log('onClearSearchSelect')
455+
}
435456
onMounted(() => {
436457
set(props.nodeModel, 'openNodeMenu', (anchorData: any) => {
437458
showAnchor.value ? closeNodeMenu() : openNodeMenu(anchorData)
438459
})
460+
set(props.nodeModel, 'selectOn', selectOn)
461+
set(props.nodeModel, 'focusOn', focusOn)
462+
set(props.nodeModel, 'clearSelectOn', clearSelectOn)
439463
})
440464
</script>
441465
<style lang="scss" scoped>
@@ -455,5 +479,4 @@ onMounted(() => {
455479
}
456480
}
457481
}
458-
459482
</style>

ui/src/workflow/common/NodeSearch.vue

Lines changed: 127 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,27 @@
1010
<div class="workflow-search-container flex-between">
1111
<el-input
1212
ref="searchInputRef"
13-
v-model="searchText"
13+
v-bind:modelValue="searchText"
14+
@update:modelValue="handleSearch"
1415
:placeholder="$t('workflow.tip.searchPlaceholder')"
1516
clearable
16-
@keyup.enter="handleSearch"
17+
@keyup.enter="next"
1718
@keyup.esc="closeSearch"
1819
>
1920
</el-input>
2021
<span>
2122
<el-space :size="4">
22-
<span class="lighter"> 2/3 </span>
23+
<span class="lighter" v-if="selectedCount && selectedCount > 0">
24+
{{ currentIndex + 1 }}/{{ selectedCount }}
25+
</span>
26+
<span class="lighter" v-else-if="searchText.length > 0"> 无结果 </span>
2327
<el-divider direction="vertical" />
2428

2529
<el-button text>
26-
<el-icon><ArrowUp /></el-icon>
30+
<el-icon @click="up"><ArrowUp /></el-icon>
2731
</el-button>
2832
<el-button text>
29-
<el-icon><ArrowDown /></el-icon>
33+
<el-icon @click="next"><ArrowDown /></el-icon>
3034
</el-button>
3135
<el-button text @click="closeSearch()">
3236
<el-icon><Close /></el-icon>
@@ -43,15 +47,13 @@
4347
</template>
4448

4549
<script setup lang="ts">
46-
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
47-
50+
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
51+
import { MsgSuccess, MsgWarning } from '@/utils/message'
4852
// Props定义
4953
interface Props {
50-
onSearch?: (keyword: string) => void // 搜索回调
54+
lf?: any
5155
}
52-
const props = withDefaults(defineProps<Props>(), {
53-
onSearch: undefined,
54-
})
56+
const props = withDefaults(defineProps<Props>(), {})
5557
5658
// 状态
5759
const showSearch = ref(false)
@@ -72,6 +74,99 @@ const handleKeyDown = (e: KeyboardEvent) => {
7274
}
7375
}
7476
77+
const focusOn = (node: any) => {
78+
console.log(node)
79+
props.lf?.graphModel.transformModel.focusOn(
80+
node.x,
81+
node.y,
82+
props.lf?.container.clientWidth,
83+
props.lf?.container.clientHeight,
84+
)
85+
}
86+
const selectedNodes = ref<Array<any>>()
87+
const currentIndex = ref<number>(0)
88+
const selectedCount = computed(() => {
89+
return selectedNodes.value?.length
90+
})
91+
92+
const getSelectNodes = (kw: string) => {
93+
const result: Array<any> = []
94+
const graph_data = props.lf?.getGraphData()
95+
graph_data.nodes.filter((node: any) => {
96+
if (node.properties.stepName.includes(kw)) {
97+
result.push({
98+
...node,
99+
order: 1,
100+
focusOn: () => {
101+
focusOn(node)
102+
props.lf?.graphModel.getNodeModelById(node.id).focusOn(searchText.value)
103+
},
104+
selectOn: () => {
105+
props.lf?.graphModel.getNodeModelById(node.id).selectOn(searchText.value)
106+
},
107+
clearSelectOn: () => {
108+
props.lf?.graphModel.getNodeModelById(node.id).clearSelectOn(searchText.value)
109+
},
110+
})
111+
}
112+
if (node.type == 'loop-body-node') {
113+
const nodeModel = props.lf?.graphModel
114+
const childNodeModel = nodeModel.getNodeModelById(node.id)
115+
childNodeModel.getSelectNodes(searchText.value).map((childNode: any) => {
116+
result.push({
117+
...childNode,
118+
order: 2,
119+
focusOn: () => {
120+
focusOn(node)
121+
childNodeModel.focusOn({ node: childNode, kw: searchText.value })
122+
},
123+
selectOn: () => {
124+
childNodeModel.selectOn({ node: childNode, kw: searchText.value })
125+
},
126+
clearSelectOn: () => {
127+
childNodeModel.clearSelectOn({ node: childNode, kw: searchText.value })
128+
},
129+
})
130+
})
131+
}
132+
})
133+
result.sort((a, b) => a.order - b.order || a.y - b.y || a.x - b.x)
134+
return result
135+
}
136+
const selectNodes = (nodes: Array<any>) => {
137+
nodes.forEach((node) => node.selectOn())
138+
}
139+
const next = () => {
140+
if (selectedNodes.value && selectedNodes.value.length > 0) {
141+
if (selectedNodes.value.length - 1 >= currentIndex.value + 1) {
142+
currentIndex.value++
143+
} else {
144+
currentIndex.value = 0
145+
}
146+
selectedNodes.value[currentIndex.value].focusOn()
147+
}
148+
}
149+
const up = () => {
150+
if (selectedNodes.value && selectedNodes.value.length > 0) {
151+
if (currentIndex.value - 1 <= 0) {
152+
currentIndex.value = selectedNodes.value.length - 1
153+
} else {
154+
currentIndex.value--
155+
}
156+
selectedNodes.value[currentIndex.value].focusOn()
157+
}
158+
}
159+
160+
const onSearch = (kw: string) => {
161+
if (selectedNodes.value === undefined) {
162+
const selected = getSelectNodes(kw)
163+
if (selected && selected.length > 0) {
164+
selectedNodes.value = selected
165+
selectNodes(selected)
166+
selected[currentIndex.value].focusOn()
167+
}
168+
}
169+
}
75170
// 打开搜索
76171
const openSearch = () => {
77172
showSearch.value = true
@@ -84,14 +179,31 @@ const openSearch = () => {
84179
85180
// 关闭搜索
86181
const closeSearch = () => {
182+
clearSelect()
87183
showSearch.value = false
88184
searchText.value = ''
89185
}
90-
186+
const clearSelect = () => {
187+
if (selectedNodes.value) {
188+
selectedNodes.value[currentIndex.value].clearSelectOn()
189+
}
190+
selectedNodes.value = undefined
191+
currentIndex.value = 0
192+
props.lf?.graphModel.clearSelectElements()
193+
const graph_data = props.lf?.getGraphData()
194+
graph_data.nodes.forEach((node: any) => {
195+
if (node.type == 'loop-body-node') {
196+
props.lf?.graphModel.getNodeModelById(node.id).clearSelectElements()
197+
}
198+
})
199+
}
91200
// 执行搜索
92-
const handleSearch = () => {
201+
const handleSearch = (kw: string) => {
202+
searchText.value = kw
203+
clearSelect()
204+
93205
if (searchText.value.trim()) {
94-
props.onSearch?.(searchText.value)
206+
onSearch?.(searchText.value)
95207
}
96208
}
97209
@@ -123,7 +235,7 @@ onUnmounted(() => {
123235
width: 360px;
124236
:deep(.el-input__wrapper) {
125237
box-shadow: none;
126-
padding: 0 8px 0 1px!important;
238+
padding: 0 8px 0 1px !important;
127239
}
128240
}
129241
</style>

ui/src/workflow/index.vue

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- 辅助工具栏 -->
44
<Control class="workflow-control" v-if="lf" :lf="lf"></Control>
55
<TeleportContainer :flow-id="flowId" />
6-
<NodeSearch class="workflow-search" :on-search="onSearch"></NodeSearch>
6+
<NodeSearch class="workflow-search" :lf="lf"></NodeSearch>
77
</template>
88
<script setup lang="ts">
99
import LogicFlow from '@logicflow/core'
@@ -18,7 +18,7 @@ import { initDefaultShortcut } from '@/workflow/common/shortcut'
1818
import Dagre from '@/workflow/plugins/dagre'
1919
import { disconnectAll, getTeleport } from '@/workflow/common/teleport'
2020
import { WorkflowMode } from '@/enums/application'
21-
import { MsgSuccess, MsgWarning } from '@/utils/message'
21+
2222
import NodeSearch from '@/workflow/common/NodeSearch.vue'
2323
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
2424
const workflow_mode = inject('workflowMode') || WorkflowMode.Application
@@ -52,43 +52,6 @@ onUnmounted(() => {
5252
const render = (data: any) => {
5353
lf.value.render(data)
5454
}
55-
const searchQueue: Array<string> = []
56-
const selectNode = (node: any) => {
57-
lf.value.graphModel.selectNodeById(node.id)
58-
lf.value.graphModel.transformModel.focusOn(
59-
node.x,
60-
node.y,
61-
lf.value.container.clientWidth,
62-
lf.value.container.clientHeight,
63-
)
64-
searchQueue.push(node.id)
65-
}
66-
const onSearch = (kw: string) => {
67-
const graph_data = lf.value.getGraphData()
68-
for (let index = 0; index < graph_data.nodes.length; index++) {
69-
const node = graph_data.nodes[index]
70-
let firstNode = null
71-
if (node.properties.stepName.includes(kw)) {
72-
if (!firstNode) {
73-
firstNode = node
74-
}
75-
76-
if (!searchQueue.includes(node.id)) {
77-
selectNode(node)
78-
break
79-
}
80-
}
81-
if (index === graph_data.nodes.length - 1) {
82-
searchQueue.length = 0
83-
if (firstNode) {
84-
selectNode(firstNode)
85-
} else {
86-
lf.value.graphModel.clearSelectElements()
87-
MsgWarning('不存在的节点')
88-
}
89-
}
90-
}
91-
}
9255
9356
const renderGraphData = (data?: any) => {
9457
const container: any = document.querySelector('#container')

ui/src/workflow/nodes/loop-body-node/index.vue

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,53 @@ const loopLayout = () => {
166166
LoopBodyContainerRef.value?.zoom()
167167
lf.value?.extension?.dagre.layout()
168168
}
169+
const selectOn = (node: any, kw: string) => {
170+
lf.value?.graphModel.getNodeModelById(node.id).selectOn(kw)
171+
}
172+
const focusOn = (node: any, kw: string) => {
173+
lf.value?.graphModel.transformModel.focusOn(
174+
node.x,
175+
node.y,
176+
lf.value?.container.clientWidth,
177+
lf.value?.container.clientHeight,
178+
)
179+
lf.value?.graphModel.getNodeModelById(node.id).focusOn(kw)
180+
}
181+
182+
const getSelectNodes = (kw: string) => {
183+
const graph_data = lf.value?.getGraphData()
184+
return graph_data.nodes.filter((node: any) => node.properties.stepName.includes(kw))
185+
}
186+
const onSearchSelect = (node: any, kw: string) => {
187+
lf.value?.graphModel.getNodeModelById(node.id).selectOn(kw)
188+
}
189+
const onClearSearchSelect = (node: any, kw: string) => {
190+
lf.value?.graphModel.getNodeModelById(node.id).clearSelectOn(kw)
191+
}
192+
const clearSelectElements = () => {
193+
lf.value.graphModel.clearSelectElements()
194+
}
169195
onMounted(() => {
170196
renderGraphData(cloneDeep(props.nodeModel.properties.workflow))
171197
set(props.nodeModel, 'validate', validate)
172198
set(props.nodeModel, 'set_loop_body', set_loop_body)
173199
set(props.nodeModel, 'loopLayout', loopLayout)
200+
set(props.nodeModel, 'getSelectNodes', getSelectNodes)
201+
set(props.nodeModel, 'focusOn', (event: any) => {
202+
focusOn(event.node, event.kw)
203+
})
204+
set(props.nodeModel, 'selectOn', (event: any) => {
205+
selectOn(event.node, event.kw)
206+
})
207+
set(props.nodeModel, 'clearSelectOn', (event: any) => {
208+
onSearchSelect(event.node, event.kw)
209+
})
210+
set(props.nodeModel, 'clearSelectElements', clearSelectElements)
211+
set(props.nodeModel, 'onClearSearchSelect', (event: any) => {
212+
onClearSearchSelect(event.node, event.kw)
213+
})
174214
})
215+
175216
onUnmounted(() => {
176217
disconnectByFlow(lf.value.graphModel.flowId)
177218
lf.value = null

0 commit comments

Comments
 (0)