diff --git a/ui/src/views/application-workflow/index.vue b/ui/src/views/application-workflow/index.vue
index 5369d2b04..9a6eac4b6 100644
--- a/ui/src/views/application-workflow/index.vue
+++ b/ui/src/views/application-workflow/index.vue
@@ -27,11 +27,11 @@
添加组件
- {{ $t('common.debug')}}
- {{ $t('common.save')}}
+ {{ $t('common.save') }}
发布
@@ -326,7 +326,7 @@ function getDetail() {
saveTime.value = res.data?.update_time
workflowRef.value?.clearGraphData()
nextTick(() => {
- workflowRef.value?.renderGraphData(detail.value.work_flow)
+ workflowRef.value?.render(detail.value.work_flow)
})
})
}
diff --git a/ui/src/workflow/common/app-node.ts b/ui/src/workflow/common/app-node.ts
index 0a091df50..ade79beb8 100644
--- a/ui/src/workflow/common/app-node.ts
+++ b/ui/src/workflow/common/app-node.ts
@@ -3,39 +3,24 @@ import ElementPlus from 'element-plus'
import * as ElementPlusIcons from '@element-plus/icons-vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { HtmlResize } from '@logicflow/extension'
-
import { h as lh } from '@logicflow/core'
import { createApp, h } from 'vue'
import directives from '@/directives'
import i18n from '@/locales'
import { WorkflowType } from '@/enums/workflow'
import { nodeDict } from '@/workflow/common/data'
+import { isActive, connect, disconnect } from './teleport'
class AppNode extends HtmlResize.view {
isMounted
- r
- app
-
+ r?: any
+ component: any
+ app: any
+ root?: any
+ VueNode: any
constructor(props: any, VueNode: any) {
super(props)
+ this.component = VueNode
this.isMounted = false
- this.r = h(VueNode, {
- properties: props.model.properties,
- nodeModel: props.model
- })
-
- this.app = createApp({
- render: () => this.r
- })
- this.app.use(ElementPlus, {
- locale: zhCn
- })
- this.app.use(Components)
- this.app.use(directives)
- this.app.use(i18n)
- for (const [key, component] of Object.entries(ElementPlusIcons)) {
- this.app.component(key, component)
- }
-
if (props.model.properties.noRender) {
delete props.model.properties.noRender
} else {
@@ -137,13 +122,79 @@ class AppNode extends HtmlResize.view {
this.isMounted = true
const node = document.createElement('div')
rootEl.appendChild(node)
- this.app?.mount(node)
+ this.renderVueComponent(node)
} else {
if (this.r && this.r.component) {
this.r.component.props.properties = this.props.model.getProperties()
}
}
}
+ componentWillUnmount() {
+ super.componentWillUnmount()
+ this.unmount()
+ }
+ getComponentContainer() {
+ return this.root
+ }
+ protected targetId() {
+ return `${this.props.graphModel.flowId}:${this.props.model.id}`
+ }
+ protected renderVueComponent(root: any) {
+ this.unmountVueComponent()
+ this.root = root
+ const { model, graphModel } = this.props
+
+ if (root) {
+ if (isActive()) {
+ connect(this.targetId(), this.component, root, model, graphModel)
+ } else {
+ this.r = h(this.component, {
+ properties: this.props.model.properties,
+ nodeModel: this.props.model
+ })
+ this.app = createApp({
+ render() {
+ return this.r
+ },
+ provide() {
+ return {
+ getNode: () => model,
+ getGraph: () => graphModel
+ }
+ }
+ })
+
+ this.app.use(ElementPlus, {
+ locale: zhCn
+ })
+ this.app.use(Components)
+ this.app.use(directives)
+ this.app.use(i18n)
+ for (const [key, component] of Object.entries(ElementPlusIcons)) {
+ this.app.component(key, component)
+ }
+ this.app?.mount(root)
+ }
+ }
+ }
+
+ protected unmountVueComponent() {
+ if (this.app) {
+ this.app.unmount()
+ this.app = null
+ }
+ if (this.root) {
+ this.root.innerHTML = ''
+ }
+ return this.root
+ }
+
+ unmount() {
+ if (isActive()) {
+ disconnect(this.targetId())
+ }
+ this.unmountVueComponent()
+ }
}
class AppNodeModel extends HtmlResize.model {
diff --git a/ui/src/workflow/common/edge.ts b/ui/src/workflow/common/edge.ts
index 8093e0ac1..730891096 100644
--- a/ui/src/workflow/common/edge.ts
+++ b/ui/src/workflow/common/edge.ts
@@ -1,6 +1,6 @@
import { BezierEdge, BezierEdgeModel, h } from '@logicflow/core'
import { createApp, h as vh } from 'vue'
-
+import { isActive, connect, disconnect } from './teleport'
import CustomLine from './CustomLine.vue'
function isMouseInElement(element: any, e: any) {
const rect = element.getBoundingClientRect()
@@ -15,7 +15,8 @@ const DEFAULT_WIDTH = 32
const DEFAULT_HEIGHT = 32
class CustomEdge2 extends BezierEdge {
isMounted
-
+ customLineApp?: any
+ root?: any
constructor() {
super()
this.isMounted = false
@@ -28,6 +29,64 @@ class CustomEdge2 extends BezierEdge {
}
}
}
+ /**
+ * 渲染vue组件
+ * @param root
+ */
+ protected renderVueComponent(root: any) {
+ this.unmountVueComponent()
+ this.root = root
+ const { graphModel } = this.props
+ if (root) {
+ if (isActive()) {
+ connect(
+ this.targetId(),
+ CustomLine,
+ root,
+ this.props.model,
+ graphModel,
+ (node: any, graph: any) => {
+ return { model: node, graph }
+ }
+ )
+ } else {
+ this.customLineApp = createApp({
+ render: () => vh(CustomLine, { model: this.props.model })
+ })
+ this.customLineApp?.mount(root)
+ }
+ }
+ }
+ protected targetId() {
+ return `${this.props.graphModel.flowId}:${this.props.model.id}`
+ }
+ /**
+ * 组件即将卸载勾子
+ */
+ componentWillUnmount() {
+ if (super.componentWillUnmount) {
+ super.componentWillUnmount()
+ }
+ if (isActive()) {
+ console.log('unmount')
+ disconnect(this.targetId())
+ }
+ this.unmountVueComponent()
+ }
+ /**
+ * 卸载vue
+ * @returns
+ */
+ protected unmountVueComponent() {
+ if (this.customLineApp) {
+ this.customLineApp.unmount()
+ this.customLineApp = null
+ }
+ if (this.root) {
+ this.root.innerHTML = ''
+ }
+ return this.root
+ }
getEdge() {
const { model } = this.props
@@ -57,14 +116,11 @@ class CustomEdge2 extends BezierEdge {
height: customHeight
}
- const app = createApp({
- render: () => vh(CustomLine, { model: this.props.model })
- })
setTimeout(() => {
const s = document.getElementById(id)
if (s && !this.isMounted) {
- app.mount(s)
this.isMounted = true
+ this.renderVueComponent(s)
}
}, 0)
diff --git a/ui/src/workflow/common/teleport.ts b/ui/src/workflow/common/teleport.ts
new file mode 100644
index 000000000..6f88b414f
--- /dev/null
+++ b/ui/src/workflow/common/teleport.ts
@@ -0,0 +1,80 @@
+import { BaseEdgeModel, BaseNodeModel, GraphModel } from '@logicflow/core'
+import { defineComponent, h, reactive, isVue3, Teleport, markRaw, Fragment } from 'vue-demi'
+
+let active = false
+const items = reactive<{ [key: string]: any }>({})
+
+export function connect(
+ id: string,
+ component: any,
+ container: HTMLDivElement,
+ node: BaseNodeModel | BaseEdgeModel,
+ graph: GraphModel,
+ get_props?: any
+) {
+ if (!get_props) {
+ get_props = (node: BaseNodeModel | BaseEdgeModel, graph: GraphModel) => {
+ return { nodeModel: node, graph }
+ }
+ }
+ if (active) {
+ items[id] = markRaw(
+ defineComponent({
+ render: () => h(Teleport, { to: container } as any, [h(component, get_props(node, graph))]),
+ provide: () => ({
+ getNode: () => node,
+ getGraph: () => graph
+ })
+ })
+ )
+ }
+}
+
+export function disconnect(id: string) {
+ if (active) {
+ delete items[id]
+ }
+}
+
+export function isActive() {
+ return active
+}
+
+export function getTeleport(): any {
+ if (!isVue3) {
+ throw new Error('teleport is only available in Vue3')
+ }
+ active = true
+
+ return defineComponent({
+ props: {
+ flowId: {
+ type: String,
+ required: true
+ }
+ },
+ setup(props) {
+ return () => {
+ const children: Record[] = []
+ Object.keys(items).forEach((id) => {
+ // https://github.com/didi/LogicFlow/issues/1768
+ // 多个不同的VueNodeView都会connect注册到items中,因此items存储了可能有多个flowId流程图的数据
+ // 当使用多个LogicFlow时,会创建多个flowId + 同时使用KeepAlive
+ // 每一次items改变,会触发不同flowId持有的setup()执行,由于每次setup()执行就是遍历items,因此存在多次重复渲染元素的问题
+ // 即items[0]会在Page1的setup()执行,items[0]也会在Page2的setup()执行,从而生成两个items[0]
+
+ // 比对当前界面显示的flowId,只更新items[当前页面flowId:nodeId]的数据
+ // 比如items[0]属于Page1的数据,那么Page2无论active=true/false,都无法执行items[0]
+ if (id.startsWith(props.flowId)) {
+ children.push(items[id])
+ }
+ })
+ return h(
+ Fragment,
+ {},
+ children.map((item) => h(item))
+ )
+ }
+ }
+ })
+}
diff --git a/ui/src/workflow/index.vue b/ui/src/workflow/index.vue
index f2a72b3c5..3feaf2afc 100644
--- a/ui/src/workflow/index.vue
+++ b/ui/src/workflow/index.vue
@@ -2,6 +2,7 @@
+