@logicflow/vue-node-registry 1.2.0-alpha.2 → 1.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$dev.log +2 -2
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +8 -0
- package/dist/index.css +122 -0
- package/es/components/container.d.ts +19 -0
- package/es/components/container.js +27 -0
- package/es/components/container.js.map +1 -0
- package/es/components/titleBar.d.ts +24 -0
- package/es/components/titleBar.js +160 -0
- package/es/components/titleBar.js.map +1 -0
- package/es/index.css +122 -0
- package/es/index.less +1 -0
- package/es/model.d.ts +18 -1
- package/es/model.js +45 -5
- package/es/model.js.map +1 -1
- package/es/style/index.css +122 -0
- package/es/style/index.less +140 -0
- package/es/style/raw.d.ts +4 -0
- package/es/style/raw.js +6 -0
- package/es/style/raw.js.map +1 -0
- package/es/utils/size.d.ts +2 -0
- package/es/utils/size.js +14 -0
- package/es/utils/size.js.map +1 -0
- package/es/view.d.ts +9 -0
- package/es/view.js +137 -12
- package/es/view.js.map +1 -1
- package/lib/components/container.d.ts +19 -0
- package/lib/components/container.js +30 -0
- package/lib/components/container.js.map +1 -0
- package/lib/components/titleBar.d.ts +24 -0
- package/lib/components/titleBar.js +163 -0
- package/lib/components/titleBar.js.map +1 -0
- package/lib/index.css +122 -0
- package/lib/index.less +1 -0
- package/lib/model.d.ts +18 -1
- package/lib/model.js +43 -3
- package/lib/model.js.map +1 -1
- package/lib/style/index.css +122 -0
- package/lib/style/index.less +140 -0
- package/lib/style/raw.d.ts +4 -0
- package/lib/style/raw.js +9 -0
- package/lib/style/raw.js.map +1 -0
- package/lib/utils/size.d.ts +2 -0
- package/lib/utils/size.js +19 -0
- package/lib/utils/size.js.map +1 -0
- package/lib/view.d.ts +9 -0
- package/lib/view.js +136 -11
- package/lib/view.js.map +1 -1
- package/package.json +3 -3
- package/rollup.config.js +52 -0
- package/src/assets/arrow.svg +10 -0
- package/src/components/container.ts +36 -0
- package/src/components/titleBar.ts +189 -0
- package/src/index.less +1 -0
- package/src/model.ts +81 -3
- package/src/style/index.less +140 -0
- package/src/style/raw.ts +129 -0
- package/src/utils/size.ts +22 -0
- package/src/view.ts +138 -10
package/src/model.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import LogicFlow, {
|
|
2
|
-
|
|
1
|
+
import LogicFlow, {
|
|
2
|
+
GraphModel,
|
|
3
|
+
BaseNodeModel,
|
|
4
|
+
HtmlNodeModel,
|
|
5
|
+
IHtmlNodeProperties,
|
|
6
|
+
} from '@logicflow/core'
|
|
7
|
+
import { cloneDeep, isNil, isArray, isEmpty } from 'lodash-es'
|
|
8
|
+
|
|
9
|
+
import NodeConfig = LogicFlow.NodeConfig
|
|
10
|
+
|
|
11
|
+
export type NodeAction = {
|
|
12
|
+
name: string
|
|
13
|
+
callback?: (node: BaseNodeModel, graph: GraphModel) => void
|
|
14
|
+
}
|
|
3
15
|
|
|
4
16
|
export interface VueCustomProperties extends IHtmlNodeProperties {
|
|
5
17
|
// 形状属性
|
|
@@ -14,14 +26,69 @@ export interface VueCustomProperties extends IHtmlNodeProperties {
|
|
|
14
26
|
// 样式属性
|
|
15
27
|
style?: LogicFlow.CommonTheme
|
|
16
28
|
textStyle?: LogicFlow.TextNodeTheme
|
|
29
|
+
|
|
30
|
+
// 标题配置
|
|
31
|
+
_showTitle?: boolean
|
|
32
|
+
_title?: string
|
|
33
|
+
_icon?: string
|
|
34
|
+
_titleHeight?: number
|
|
35
|
+
_expanded?: boolean
|
|
17
36
|
}
|
|
18
37
|
|
|
19
38
|
export class VueNodeModel<
|
|
20
39
|
P extends VueCustomProperties = VueCustomProperties,
|
|
21
40
|
> extends HtmlNodeModel<P> {
|
|
41
|
+
private __baseHeight?: number
|
|
42
|
+
public __actions?: {
|
|
43
|
+
name: string
|
|
44
|
+
callback?: (node: BaseNodeModel, graph: GraphModel) => void
|
|
45
|
+
}[]
|
|
46
|
+
constructor(data: NodeConfig<P>, graphModel: GraphModel) {
|
|
47
|
+
super(data, graphModel)
|
|
48
|
+
const { properties } = data
|
|
49
|
+
// 如果需要展示标题,则重新设置一个能把节点内容都展示出来的最小宽高
|
|
50
|
+
if (properties) {
|
|
51
|
+
const { _showTitle = false, style = {} } = properties
|
|
52
|
+
if (_showTitle) {
|
|
53
|
+
this.minWidth = 160
|
|
54
|
+
this.minHeight = 80
|
|
55
|
+
this.text.editable = false
|
|
56
|
+
// 判断当前节点宽高是否小于最小宽高,如果是,强制设置为最小宽高
|
|
57
|
+
const newWidth = this.width < this.minWidth ? this.minWidth : this.width
|
|
58
|
+
const newHeight =
|
|
59
|
+
this.height < this.minHeight ? this.minHeight : this.height
|
|
60
|
+
|
|
61
|
+
this.setProperties({
|
|
62
|
+
_expanded: false,
|
|
63
|
+
...properties,
|
|
64
|
+
style: {
|
|
65
|
+
overflow: 'visible',
|
|
66
|
+
...cloneDeep(style),
|
|
67
|
+
},
|
|
68
|
+
width: newWidth + 8,
|
|
69
|
+
height: newHeight + 8,
|
|
70
|
+
})
|
|
71
|
+
this.setNodeActions([
|
|
72
|
+
{
|
|
73
|
+
name: '复制',
|
|
74
|
+
callback: (nodeModel, graphModel) => {
|
|
75
|
+
graphModel.cloneNode(nodeModel.id)
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: '删除',
|
|
80
|
+
callback: (nodeModel, graphModel) => {
|
|
81
|
+
graphModel.deleteNode(nodeModel.id)
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.log('nodeModel', this)
|
|
88
|
+
}
|
|
22
89
|
setAttributes() {
|
|
23
90
|
// DONE: 解决 width、height、radius 为 0 时的问题
|
|
24
|
-
const { width, height, radius } = this.properties
|
|
91
|
+
const { width, height, radius, _showTitle, _titleHeight } = this.properties
|
|
25
92
|
if (!isNil(width)) {
|
|
26
93
|
this.width = width
|
|
27
94
|
}
|
|
@@ -31,6 +98,12 @@ export class VueNodeModel<
|
|
|
31
98
|
if (!isNil(radius)) {
|
|
32
99
|
this.radius = radius
|
|
33
100
|
}
|
|
101
|
+
if (this.__baseHeight === undefined) {
|
|
102
|
+
this.__baseHeight = isNil(height) ? this.height : height
|
|
103
|
+
}
|
|
104
|
+
const extra = _showTitle ? (_titleHeight ?? 28) : 0
|
|
105
|
+
const base = isNil(height) ? (this.__baseHeight as number) : height
|
|
106
|
+
this.height = base + extra
|
|
34
107
|
}
|
|
35
108
|
|
|
36
109
|
getTextStyle(): LogicFlow.TextNodeTheme {
|
|
@@ -59,6 +132,11 @@ export class VueNodeModel<
|
|
|
59
132
|
// ry: radius,
|
|
60
133
|
}
|
|
61
134
|
}
|
|
135
|
+
|
|
136
|
+
setNodeActions(actions: NodeAction[]) {
|
|
137
|
+
this.__actions =
|
|
138
|
+
isArray(actions) && !isEmpty(actions) ? cloneDeep(actions) : []
|
|
139
|
+
}
|
|
62
140
|
}
|
|
63
141
|
|
|
64
142
|
export default VueNodeModel
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
.lf-vue-node-container {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
padding: 6px;
|
|
7
|
+
color: #474747;
|
|
8
|
+
border-radius: 12px;
|
|
9
|
+
box-shadow: 0 0 10px #cad2e15f;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.lf-vue-node-content-wrap {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex: 1 1 auto;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.lf-vue-node-title {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: flex-start;
|
|
21
|
+
justify-content: space-between;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
margin-bottom: 4px;
|
|
24
|
+
padding: 0 8px;
|
|
25
|
+
backdrop-filter: saturate(180%) blur(4px);
|
|
26
|
+
|
|
27
|
+
&-expanded {
|
|
28
|
+
margin-bottom: 6px;
|
|
29
|
+
padding-bottom: 8px;
|
|
30
|
+
border-bottom: 1px solid #eaeaea;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
35
|
+
.lf-vue-node-title {
|
|
36
|
+
backdrop-filter: none;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.lf-vue-node-title-left {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: 6px;
|
|
43
|
+
align-items: center;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lf-vue-node-title-icon {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
color: #666;
|
|
52
|
+
font-style: normal;
|
|
53
|
+
line-height: 16px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.lf-vue-node-title-text {
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
color: #333;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.lf-vue-node-title-actions {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 6px;
|
|
69
|
+
align-items: center;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.lf-vue-node-title-expand,
|
|
73
|
+
.lf-vue-node-title-more {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
width: 20px;
|
|
78
|
+
height: 20px;
|
|
79
|
+
padding: 2px;
|
|
80
|
+
background: transparent;
|
|
81
|
+
border: none;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: background 0.15s ease;
|
|
85
|
+
appearance: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.lf-vue-node-title-expand:hover,
|
|
89
|
+
.lf-vue-node-title-more:hover {
|
|
90
|
+
background: rgb(0 0 0 / 6%);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.lf-vue-node-title-expand-icon {
|
|
94
|
+
color: #666;
|
|
95
|
+
font-style: normal;
|
|
96
|
+
transition: transform 0.3s ease;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.lf-vue-node-title-more-icon {
|
|
100
|
+
color: #666;
|
|
101
|
+
font-style: normal;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.lf-vue-node-title-tooltip {
|
|
105
|
+
position: absolute;
|
|
106
|
+
top: -50px;
|
|
107
|
+
right: -135px;
|
|
108
|
+
min-width: 120px;
|
|
109
|
+
max-width: 240px;
|
|
110
|
+
padding: 6px 8px;
|
|
111
|
+
background: #fff;
|
|
112
|
+
border: 1px solid rgb(0 0 0 / 10%);
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
|
|
115
|
+
transform: translateY(calc(100% + 4px));
|
|
116
|
+
transition:
|
|
117
|
+
opacity 0.15s ease,
|
|
118
|
+
transform 0.15s ease;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.lf-vue-node-title-tooltip-list {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.lf-vue-node-title-tooltip-item {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: flex-start;
|
|
131
|
+
padding: 6px;
|
|
132
|
+
color: #333;
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
border-radius: 4px;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.lf-vue-node-title-tooltip-item:hover {
|
|
139
|
+
background: rgb(0 0 0 / 5%);
|
|
140
|
+
}
|
package/src/style/raw.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auto generated file, do not modify it!
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const content = `.lf-vue-node-container {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
padding: 6px;
|
|
13
|
+
color: #474747;
|
|
14
|
+
border-radius: 12px;
|
|
15
|
+
box-shadow: 0 0 10px #cad2e15f;
|
|
16
|
+
}
|
|
17
|
+
.lf-vue-node-content-wrap {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex: 1 1 auto;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
}
|
|
22
|
+
.lf-vue-node-title {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
margin-bottom: 4px;
|
|
28
|
+
padding: 0 8px;
|
|
29
|
+
backdrop-filter: saturate(180%) blur(4px);
|
|
30
|
+
}
|
|
31
|
+
.lf-vue-node-title-expanded {
|
|
32
|
+
margin-bottom: 6px;
|
|
33
|
+
padding-bottom: 8px;
|
|
34
|
+
border-bottom: 1px solid #eaeaea;
|
|
35
|
+
}
|
|
36
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
37
|
+
.lf-vue-node-title {
|
|
38
|
+
backdrop-filter: none;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
.lf-vue-node-title-left {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 6px;
|
|
44
|
+
align-items: center;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
.lf-vue-node-title-icon {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
color: #666;
|
|
52
|
+
font-style: normal;
|
|
53
|
+
line-height: 16px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
.lf-vue-node-title-text {
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
color: #333;
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
text-overflow: ellipsis;
|
|
63
|
+
}
|
|
64
|
+
.lf-vue-node-title-actions {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 6px;
|
|
67
|
+
align-items: center;
|
|
68
|
+
}
|
|
69
|
+
.lf-vue-node-title-expand,
|
|
70
|
+
.lf-vue-node-title-more {
|
|
71
|
+
display: inline-flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
width: 20px;
|
|
75
|
+
height: 20px;
|
|
76
|
+
padding: 2px;
|
|
77
|
+
background: transparent;
|
|
78
|
+
border: none;
|
|
79
|
+
border-radius: 4px;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
transition: background 0.15s ease;
|
|
82
|
+
appearance: none;
|
|
83
|
+
}
|
|
84
|
+
.lf-vue-node-title-expand:hover,
|
|
85
|
+
.lf-vue-node-title-more:hover {
|
|
86
|
+
background: rgba(0, 0, 0, 0.06);
|
|
87
|
+
}
|
|
88
|
+
.lf-vue-node-title-expand-icon {
|
|
89
|
+
color: #666;
|
|
90
|
+
font-style: normal;
|
|
91
|
+
transition: transform 0.3s ease;
|
|
92
|
+
}
|
|
93
|
+
.lf-vue-node-title-more-icon {
|
|
94
|
+
color: #666;
|
|
95
|
+
font-style: normal;
|
|
96
|
+
}
|
|
97
|
+
.lf-vue-node-title-tooltip {
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: -50px;
|
|
100
|
+
right: -135px;
|
|
101
|
+
min-width: 120px;
|
|
102
|
+
max-width: 240px;
|
|
103
|
+
padding: 6px 8px;
|
|
104
|
+
background: #fff;
|
|
105
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
106
|
+
border-radius: 6px;
|
|
107
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
|
108
|
+
transform: translateY(calc(100% + 4px));
|
|
109
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
110
|
+
}
|
|
111
|
+
.lf-vue-node-title-tooltip-list {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
gap: 4px;
|
|
115
|
+
}
|
|
116
|
+
.lf-vue-node-title-tooltip-item {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: flex-start;
|
|
120
|
+
padding: 6px;
|
|
121
|
+
color: #333;
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
.lf-vue-node-title-tooltip-item:hover {
|
|
127
|
+
background: rgba(0, 0, 0, 0.05);
|
|
128
|
+
}
|
|
129
|
+
`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function computeBaseHeight(
|
|
2
|
+
measuredHeight: number,
|
|
3
|
+
_showTitle?: boolean,
|
|
4
|
+
_titleHeight?: number,
|
|
5
|
+
) {
|
|
6
|
+
const extra = _showTitle
|
|
7
|
+
? typeof _titleHeight === 'number'
|
|
8
|
+
? _titleHeight
|
|
9
|
+
: 28
|
|
10
|
+
: 0
|
|
11
|
+
return Math.max(1, Math.round(measuredHeight) - extra)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function shouldUpdateSize(
|
|
15
|
+
prevW?: number,
|
|
16
|
+
prevH?: number,
|
|
17
|
+
w?: number,
|
|
18
|
+
h?: number,
|
|
19
|
+
) {
|
|
20
|
+
if (!w || !h) return false
|
|
21
|
+
return !(prevW === Math.round(w) && prevH === Math.round(h))
|
|
22
|
+
}
|
package/src/view.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import { isVue2, isVue3, createApp, h, Vue2 } from 'vue-demi'
|
|
1
|
+
import { isVue2, isVue3, createApp, h, Vue2, defineComponent } from 'vue-demi'
|
|
2
|
+
// Vue2/3 兼容 API;使用 vue-demi 保持两端一致
|
|
3
|
+
import {
|
|
4
|
+
throttle,
|
|
5
|
+
round,
|
|
6
|
+
get,
|
|
7
|
+
isFunction,
|
|
8
|
+
isArray,
|
|
9
|
+
clamp,
|
|
10
|
+
isNumber,
|
|
11
|
+
} from 'lodash-es'
|
|
2
12
|
import { HtmlNode } from '@logicflow/core'
|
|
3
13
|
import { vueNodesMap } from './registry'
|
|
4
14
|
import { isActive, connect, disconnect } from './teleport'
|
|
15
|
+
import { Container } from './components/container'
|
|
5
16
|
|
|
6
17
|
export class VueNodeView extends HtmlNode {
|
|
7
18
|
root?: any
|
|
8
19
|
private vm: any
|
|
20
|
+
// 尺寸监听相关状态
|
|
21
|
+
private __resizeObserver?: ResizeObserver
|
|
22
|
+
private __resizeRafId?: number
|
|
23
|
+
private __lastWidth?: number
|
|
24
|
+
private __lastHeight?: number
|
|
25
|
+
private __fallbackUnlisten?: () => void
|
|
26
|
+
private __throttledUpdate = throttle(() => this.measureAndUpdate(), 80)
|
|
27
|
+
// private isMounted: boolean = false
|
|
9
28
|
|
|
10
29
|
getComponentContainer() {
|
|
11
30
|
return this.root
|
|
@@ -21,34 +40,62 @@ export class VueNodeView extends HtmlNode {
|
|
|
21
40
|
}
|
|
22
41
|
|
|
23
42
|
setHtml(rootEl: SVGForeignObjectElement) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
// 创建节点内容容器并注入到 foreignObject(若已存在则复用)
|
|
44
|
+
const existed = rootEl.querySelector('.custom-vue-node-content')
|
|
45
|
+
if (!existed) {
|
|
46
|
+
const el = document.createElement('div')
|
|
47
|
+
el.className = 'custom-vue-node-content'
|
|
48
|
+
this.root = el
|
|
49
|
+
rootEl.appendChild(el)
|
|
50
|
+
}
|
|
51
|
+
// 渲染 Vue 组件并启用尺寸监听
|
|
29
52
|
this.renderVueComponent()
|
|
53
|
+
this.startResizeObserver()
|
|
30
54
|
}
|
|
31
55
|
|
|
32
56
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
33
57
|
confirmUpdate(_rootEl: SVGForeignObjectElement) {
|
|
34
58
|
// TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
|
|
35
|
-
|
|
59
|
+
const { model } = this.props
|
|
60
|
+
const { _showTitle = false } = model.properties || {}
|
|
61
|
+
if (_showTitle) {
|
|
62
|
+
this.setHtml(_rootEl)
|
|
63
|
+
}
|
|
36
64
|
}
|
|
37
65
|
|
|
38
66
|
protected renderVueComponent() {
|
|
39
67
|
this.unmountVueComponent()
|
|
40
68
|
const root = this.getComponentContainer()
|
|
41
69
|
const { model, graphModel } = this.props
|
|
70
|
+
const { _showTitle = false } = model.properties || {}
|
|
71
|
+
const wrapWithContainer = (child: any) =>
|
|
72
|
+
defineComponent({
|
|
73
|
+
name: 'LFVueNodeContainerWrapper',
|
|
74
|
+
props: {
|
|
75
|
+
node: { type: Object, required: true },
|
|
76
|
+
graph: { type: Object, required: true },
|
|
77
|
+
},
|
|
78
|
+
render(this: any) {
|
|
79
|
+
// 根据 _showTitle 决定是否用 Container 包裹,避免无标题时额外结构
|
|
80
|
+
if (!_showTitle) {
|
|
81
|
+
return h(child, { node: this.node, graph: this.graph })
|
|
82
|
+
}
|
|
83
|
+
return h(Container, { node: this.node, graph: this.graph }, [
|
|
84
|
+
h(child, { node: this.node, graph: this.graph }),
|
|
85
|
+
])
|
|
86
|
+
},
|
|
87
|
+
})
|
|
42
88
|
|
|
43
89
|
if (root) {
|
|
44
90
|
const { component } = vueNodesMap[model.type]
|
|
45
91
|
if (component) {
|
|
46
92
|
if (isVue2) {
|
|
47
93
|
const Vue = Vue2 as any
|
|
94
|
+
const Composed = wrapWithContainer(component)
|
|
48
95
|
this.vm = new Vue({
|
|
49
96
|
el: root,
|
|
50
97
|
render(h: any) {
|
|
51
|
-
return h(
|
|
98
|
+
return h(Composed, {
|
|
52
99
|
node: model,
|
|
53
100
|
graph: graphModel,
|
|
54
101
|
})
|
|
@@ -61,12 +108,15 @@ export class VueNodeView extends HtmlNode {
|
|
|
61
108
|
},
|
|
62
109
|
})
|
|
63
110
|
} else if (isVue3) {
|
|
111
|
+
console.log('isActive', isActive())
|
|
64
112
|
if (isActive()) {
|
|
65
|
-
|
|
113
|
+
const Composed = wrapWithContainer(component)
|
|
114
|
+
connect(this.targetId(), Composed, root, model, graphModel)
|
|
66
115
|
} else {
|
|
116
|
+
const Composed = wrapWithContainer(component)
|
|
67
117
|
this.vm = createApp({
|
|
68
118
|
render() {
|
|
69
|
-
return h(
|
|
119
|
+
return h(Composed, {
|
|
70
120
|
node: model,
|
|
71
121
|
graph: graphModel,
|
|
72
122
|
})
|
|
@@ -79,14 +129,91 @@ export class VueNodeView extends HtmlNode {
|
|
|
79
129
|
},
|
|
80
130
|
})
|
|
81
131
|
this.vm?.mount(root)
|
|
132
|
+
// this.isMounted = true
|
|
82
133
|
}
|
|
83
134
|
}
|
|
84
135
|
}
|
|
85
136
|
}
|
|
86
137
|
}
|
|
87
138
|
|
|
139
|
+
private measureAndUpdate = () => {
|
|
140
|
+
try {
|
|
141
|
+
// 读取子组件(或容器本身)的实际尺寸并更新模型属性
|
|
142
|
+
const root = this.getComponentContainer() as HTMLElement
|
|
143
|
+
if (!root) return
|
|
144
|
+
const target = (root.firstElementChild as HTMLElement) || root
|
|
145
|
+
const rect = target.getBoundingClientRect()
|
|
146
|
+
const width = round(rect.width)
|
|
147
|
+
const height = round(rect.height)
|
|
148
|
+
if (width <= 0 || height <= 0) return
|
|
149
|
+
if (width === this.__lastWidth && height === this.__lastHeight) return
|
|
150
|
+
this.__lastWidth = width
|
|
151
|
+
this.__lastHeight = height
|
|
152
|
+
const props = this.props.model.properties as any
|
|
153
|
+
const extra = get(props, '_showTitle')
|
|
154
|
+
? isNumber(get(props, '_titleHeight'))
|
|
155
|
+
? get(props, '_titleHeight')
|
|
156
|
+
: 28
|
|
157
|
+
: 0
|
|
158
|
+
// 去掉标题占用的高度,保证内容区域与模型高度一致
|
|
159
|
+
const baseHeight = clamp(height - extra, 1, Number.MAX_SAFE_INTEGER)
|
|
160
|
+
this.props.model.setProperties({ width, height: baseHeight })
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// swallow error
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private startResizeObserver() {
|
|
167
|
+
// 启动尺寸监听:优先使用 ResizeObserver,退化到 window.resize
|
|
168
|
+
const root = this.getComponentContainer() as HTMLElement
|
|
169
|
+
if (!root) return
|
|
170
|
+
try {
|
|
171
|
+
if (isFunction((window as any).ResizeObserver)) {
|
|
172
|
+
this.__resizeObserver = new (window as any).ResizeObserver(
|
|
173
|
+
(entries: any[]) => {
|
|
174
|
+
if (!isArray(entries) || !entries.length) return
|
|
175
|
+
if (this.__resizeRafId) cancelAnimationFrame(this.__resizeRafId)
|
|
176
|
+
// 使用 RAF 对齐绘制帧,再用节流函数合并频繁变更
|
|
177
|
+
this.__resizeRafId = requestAnimationFrame(this.__throttledUpdate)
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
const target = (root.firstElementChild as HTMLElement) || root
|
|
181
|
+
this.__resizeObserver?.observe(target)
|
|
182
|
+
} else {
|
|
183
|
+
// 退化监听:在窗口尺寸变化时尝试更新
|
|
184
|
+
window.addEventListener('resize', () => this.__throttledUpdate())
|
|
185
|
+
this.__fallbackUnlisten = () =>
|
|
186
|
+
window.removeEventListener('resize', () => this.__throttledUpdate())
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
// swallow error
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private stopResizeObserver() {
|
|
194
|
+
try {
|
|
195
|
+
// 停止所有监听与异步回调,避免内存泄漏
|
|
196
|
+
if (this.__resizeObserver) {
|
|
197
|
+
this.__resizeObserver.disconnect()
|
|
198
|
+
this.__resizeObserver = undefined
|
|
199
|
+
}
|
|
200
|
+
if (this.__resizeRafId) {
|
|
201
|
+
cancelAnimationFrame(this.__resizeRafId)
|
|
202
|
+
this.__resizeRafId = undefined
|
|
203
|
+
}
|
|
204
|
+
if (this.__fallbackUnlisten) {
|
|
205
|
+
this.__fallbackUnlisten()
|
|
206
|
+
this.__fallbackUnlisten = undefined
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
// swallow error
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
88
213
|
protected unmountVueComponent() {
|
|
89
214
|
const root = this.getComponentContainer()
|
|
215
|
+
// 在卸载 Vue 实例前先停止尺寸监听
|
|
216
|
+
this.stopResizeObserver()
|
|
90
217
|
if (this.vm) {
|
|
91
218
|
isVue2 && this.vm.$destroy()
|
|
92
219
|
isVue3 && this.vm.unmount()
|
|
@@ -99,6 +226,7 @@ export class VueNodeView extends HtmlNode {
|
|
|
99
226
|
}
|
|
100
227
|
|
|
101
228
|
unmount() {
|
|
229
|
+
// Teleport 模式下断开连接,并清理视图与监听
|
|
102
230
|
if (isActive()) {
|
|
103
231
|
disconnect(this.targetId(), this.props.graphModel.flowId as string)
|
|
104
232
|
}
|