v-nuxt-ui 0.1.36 → 0.2.1
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/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/components/Watermark.d.vue.ts +3 -3
- package/dist/runtime/components/Watermark.vue.d.ts +3 -3
- package/dist/runtime/components/button/CircleColor.d.vue.ts +17 -0
- package/dist/runtime/components/button/CircleColor.vue +37 -0
- package/dist/runtime/components/button/CircleColor.vue.d.ts +17 -0
- package/dist/runtime/components/flow/FlowEdge.client.vue +170 -41
- package/dist/runtime/components/flow/FlowEditor.client.d.vue.ts +51 -0
- package/dist/runtime/components/flow/FlowEditor.client.vue +294 -0
- package/dist/runtime/components/flow/FlowEditor.client.vue.d.ts +51 -0
- package/dist/runtime/components/flow/FlowNode.client.d.vue.ts +13 -2
- package/dist/runtime/components/flow/FlowNode.client.vue +44 -48
- package/dist/runtime/components/flow/FlowNode.client.vue.d.ts +13 -2
- package/dist/runtime/components/flow/FlowToolbar.d.vue.ts +41 -5
- package/dist/runtime/components/flow/FlowToolbar.vue +554 -88
- package/dist/runtime/components/flow/FlowToolbar.vue.d.ts +41 -5
- package/dist/runtime/components/flow/FlowToolbarItemWrapper.d.vue.ts +17 -0
- package/dist/runtime/components/flow/FlowToolbarItemWrapper.vue +16 -0
- package/dist/runtime/components/flow/FlowToolbarItemWrapper.vue.d.ts +17 -0
- package/dist/runtime/components/sys/flow/CreateModal.d.vue.ts +13 -0
- package/dist/runtime/components/sys/flow/CreateModal.vue +32 -0
- package/dist/runtime/components/sys/flow/CreateModal.vue.d.ts +13 -0
- package/dist/runtime/components/sys/flow/EditNodeModal.d.vue.ts +13 -0
- package/dist/runtime/components/sys/flow/EditNodeModal.vue +30 -0
- package/dist/runtime/components/sys/flow/EditNodeModal.vue.d.ts +13 -0
- package/dist/runtime/components/sys/flow/Table.d.vue.ts +3 -0
- package/dist/runtime/components/sys/flow/Table.vue +98 -0
- package/dist/runtime/components/sys/flow/Table.vue.d.ts +3 -0
- package/dist/runtime/components/sys/table/CreateModal.vue +9 -191
- package/dist/runtime/components/sys/table/Table.vue +0 -11
- package/dist/runtime/components/sys/table/TableColumnList.d.vue.ts +54 -0
- package/dist/runtime/components/sys/table/TableColumnList.vue +196 -0
- package/dist/runtime/components/sys/table/TableColumnList.vue.d.ts +54 -0
- package/dist/runtime/components/sys/table/TableColumnModal.d.vue.ts +3 -13
- package/dist/runtime/components/sys/table/TableColumnModal.vue +32 -100
- package/dist/runtime/components/sys/table/TableColumnModal.vue.d.ts +3 -13
- package/dist/runtime/components/table/query/order/Item.d.vue.ts +2 -2
- package/dist/runtime/components/table/query/order/Item.vue.d.ts +2 -2
- package/dist/runtime/composables/api/sys/index.d.ts +3 -0
- package/dist/runtime/composables/api/sys/index.js +3 -0
- package/dist/runtime/composables/api/sys/useFlowApi.d.ts +2 -0
- package/dist/runtime/composables/api/sys/useFlowApi.js +5 -0
- package/dist/runtime/composables/api/sys/useFlowEdgeApi.d.ts +2 -0
- package/dist/runtime/composables/api/sys/useFlowEdgeApi.js +3 -0
- package/dist/runtime/composables/api/sys/useFlowNodeApi.d.ts +2 -0
- package/dist/runtime/composables/api/sys/useFlowNodeApi.js +3 -0
- package/dist/runtime/composables/flow/index.d.ts +3 -0
- package/dist/runtime/composables/flow/index.js +3 -0
- package/dist/runtime/composables/flow/useFlow.d.ts +33 -0
- package/dist/runtime/composables/flow/useFlow.js +401 -0
- package/dist/runtime/composables/flow/useFlowNode.d.ts +17 -0
- package/dist/runtime/composables/flow/useFlowNode.js +106 -0
- package/dist/runtime/composables/flow/useFlowResize.d.ts +21 -0
- package/dist/runtime/composables/flow/useFlowResize.js +84 -0
- package/dist/runtime/composables/flow/useFlowStyles.d.ts +62 -9
- package/dist/runtime/composables/flow/useFlowStyles.js +127 -23
- package/dist/runtime/composables/table/useTableColumnPermission.d.ts +36 -0
- package/dist/runtime/composables/useSidebarMenu.js +0 -2
- package/dist/runtime/composables/useTheme.d.ts +1 -1
- package/dist/runtime/composables/useTheme.js +0 -1
- package/dist/runtime/constants/flow.d.ts +166 -0
- package/dist/runtime/constants/flow.js +171 -0
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/types/models/flow.d.ts +61 -0
- package/dist/runtime/types/models/flow.js +0 -0
- package/dist/runtime/types/models/index.d.ts +1 -0
- package/dist/runtime/types/models/index.js +1 -0
- package/dist/runtime/types/models/table.d.ts +1 -0
- package/dist/runtime/types/storage.d.ts +3 -4
- package/dist/runtime/types/storage.js +3 -4
- package/package.json +3 -2
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { shallowRef, ref, watch } from "vue";
|
|
2
|
+
import { GRID_SIZE } from "#v/constants";
|
|
3
|
+
export function useFlow(options) {
|
|
4
|
+
const { flow, onUpdateModel, api } = options;
|
|
5
|
+
const nodes = shallowRef([]);
|
|
6
|
+
const edges = shallowRef([]);
|
|
7
|
+
const _pendingCount = ref(0);
|
|
8
|
+
const loading = ref(false);
|
|
9
|
+
const pendingEdgeDeletes = /* @__PURE__ */ new Map();
|
|
10
|
+
const withLoading = async (fn) => {
|
|
11
|
+
_pendingCount.value++;
|
|
12
|
+
loading.value = true;
|
|
13
|
+
try {
|
|
14
|
+
return await fn();
|
|
15
|
+
} finally {
|
|
16
|
+
_pendingCount.value--;
|
|
17
|
+
if (_pendingCount.value <= 0) {
|
|
18
|
+
_pendingCount.value = 0;
|
|
19
|
+
loading.value = false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const getErrorMessage = (error) => {
|
|
24
|
+
return error instanceof Error ? error.message : String(error);
|
|
25
|
+
};
|
|
26
|
+
const deleteEdgeById = async (edgeId) => {
|
|
27
|
+
if (!edges.value.some((e) => e.id === edgeId)) return;
|
|
28
|
+
const existingDelete = pendingEdgeDeletes.get(edgeId);
|
|
29
|
+
if (existingDelete) {
|
|
30
|
+
await existingDelete;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const deletePromise = (async () => {
|
|
34
|
+
if (api?.value?.deleteEdge) {
|
|
35
|
+
if (!Number.isNaN(Number(edgeId))) {
|
|
36
|
+
try {
|
|
37
|
+
await api.value.deleteEdge(Number(edgeId));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`\u5220\u9664\u8FDE\u7EBF ${edgeId} \u5931\u8D25: ${getErrorMessage(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
edges.value = edges.value.filter((e) => e.id !== edgeId);
|
|
44
|
+
})();
|
|
45
|
+
pendingEdgeDeletes.set(edgeId, deletePromise);
|
|
46
|
+
try {
|
|
47
|
+
await deletePromise;
|
|
48
|
+
} finally {
|
|
49
|
+
pendingEdgeDeletes.delete(edgeId);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const toVueFlowNode = (node) => ({
|
|
53
|
+
id: String(node.id),
|
|
54
|
+
type: "custom",
|
|
55
|
+
position: { x: node.positionX ?? 0, y: node.positionY ?? 0 },
|
|
56
|
+
data: {
|
|
57
|
+
...node,
|
|
58
|
+
id: node.id,
|
|
59
|
+
name: node.name,
|
|
60
|
+
width: node.width,
|
|
61
|
+
height: node.height
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const toVueFlowEdge = (edge) => ({
|
|
65
|
+
id: String(edge.id),
|
|
66
|
+
type: "custom",
|
|
67
|
+
source: String(edge.parentId),
|
|
68
|
+
target: String(edge.childId),
|
|
69
|
+
sourceHandle: edge.parentHandlePos ?? void 0,
|
|
70
|
+
targetHandle: edge.childHandlePos ?? void 0,
|
|
71
|
+
label: edge.label
|
|
72
|
+
});
|
|
73
|
+
watch(
|
|
74
|
+
() => flow.value,
|
|
75
|
+
(newFlow) => {
|
|
76
|
+
if (!newFlow) return;
|
|
77
|
+
nodes.value = (newFlow.nodes ?? []).map(toVueFlowNode);
|
|
78
|
+
edges.value = (newFlow.edges ?? []).map(toVueFlowEdge);
|
|
79
|
+
},
|
|
80
|
+
{ immediate: true }
|
|
81
|
+
);
|
|
82
|
+
const emitUpdate = () => {
|
|
83
|
+
if (!onUpdateModel?.value) return;
|
|
84
|
+
const updatedFlow = {
|
|
85
|
+
id: flow.value?.id ?? 0,
|
|
86
|
+
...flow.value,
|
|
87
|
+
nodes: nodes.value.map((n) => ({
|
|
88
|
+
id: n.data.id ?? Number(n.id),
|
|
89
|
+
name: n.data.name ?? "",
|
|
90
|
+
positionX: n.position.x,
|
|
91
|
+
positionY: n.position.y,
|
|
92
|
+
width: n.data.width,
|
|
93
|
+
height: n.data.height
|
|
94
|
+
})),
|
|
95
|
+
edges: edges.value.map((e) => ({
|
|
96
|
+
id: Number(e.id) || void 0,
|
|
97
|
+
parentId: Number(e.source),
|
|
98
|
+
childId: Number(e.target),
|
|
99
|
+
parentHandlePos: e.sourceHandle ?? void 0,
|
|
100
|
+
childHandlePos: e.targetHandle ?? void 0,
|
|
101
|
+
label: typeof e.label === "string" ? e.label : void 0
|
|
102
|
+
}))
|
|
103
|
+
};
|
|
104
|
+
onUpdateModel.value(updatedFlow);
|
|
105
|
+
};
|
|
106
|
+
const deleteNode = async (nodeId) => {
|
|
107
|
+
const doDelete = async () => {
|
|
108
|
+
const relatedEdgeIds = [...new Set(edges.value.filter((e) => e.source === nodeId || e.target === nodeId).map((e) => e.id))];
|
|
109
|
+
try {
|
|
110
|
+
await Promise.all(relatedEdgeIds.map((edgeId) => deleteEdgeById(edgeId)));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
emitUpdate();
|
|
113
|
+
throw new Error(`\u5220\u9664\u8282\u70B9 ${nodeId} \u7684\u5173\u8054\u8FDE\u7EBF\u5931\u8D25: ${getErrorMessage(error)}`);
|
|
114
|
+
}
|
|
115
|
+
if (api?.value?.deleteNode) {
|
|
116
|
+
try {
|
|
117
|
+
await api.value.deleteNode(Number(nodeId));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
emitUpdate();
|
|
120
|
+
throw new Error(`\u5220\u9664\u8282\u70B9 ${nodeId} \u5931\u8D25: ${getErrorMessage(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
nodes.value = nodes.value.filter((n) => n.id !== nodeId);
|
|
124
|
+
emitUpdate();
|
|
125
|
+
};
|
|
126
|
+
if (api?.value) await withLoading(doDelete);
|
|
127
|
+
else await doDelete();
|
|
128
|
+
};
|
|
129
|
+
const deleteEdge = async (edgeId) => {
|
|
130
|
+
const doDelete = async () => {
|
|
131
|
+
await deleteEdgeById(edgeId);
|
|
132
|
+
emitUpdate();
|
|
133
|
+
};
|
|
134
|
+
if (api?.value) await withLoading(doDelete);
|
|
135
|
+
else await doDelete();
|
|
136
|
+
};
|
|
137
|
+
const createEdge = async (params) => {
|
|
138
|
+
const doCreate = async () => {
|
|
139
|
+
let edgeId = `e-${params.source}-${params.sourceHandle}-${params.target}-${params.targetHandle}-${Date.now()}`;
|
|
140
|
+
let label;
|
|
141
|
+
if (api?.value?.createEdge) {
|
|
142
|
+
const { data } = await api.value.createEdge({
|
|
143
|
+
id: 0,
|
|
144
|
+
flowId: flow.value?.id,
|
|
145
|
+
parentId: Number(params.source),
|
|
146
|
+
childId: Number(params.target),
|
|
147
|
+
parentHandlePos: params.sourceHandle ?? void 0,
|
|
148
|
+
childHandlePos: params.targetHandle ?? void 0
|
|
149
|
+
});
|
|
150
|
+
if (data.value.data) {
|
|
151
|
+
edgeId = String(data.value.data.id);
|
|
152
|
+
label = data.value.data.label;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const newEdge = {
|
|
156
|
+
id: edgeId,
|
|
157
|
+
type: "custom",
|
|
158
|
+
source: params.source,
|
|
159
|
+
target: params.target,
|
|
160
|
+
sourceHandle: params.sourceHandle ?? void 0,
|
|
161
|
+
targetHandle: params.targetHandle ?? void 0,
|
|
162
|
+
label
|
|
163
|
+
};
|
|
164
|
+
edges.value = [...edges.value, newEdge];
|
|
165
|
+
emitUpdate();
|
|
166
|
+
};
|
|
167
|
+
if (api?.value) await withLoading(doCreate);
|
|
168
|
+
else await doCreate();
|
|
169
|
+
};
|
|
170
|
+
const reconnectEdge = async (oldEdgeId, connection) => {
|
|
171
|
+
if (!api?.value) {
|
|
172
|
+
edges.value = edges.value.filter((e) => e.id !== oldEdgeId);
|
|
173
|
+
const newEdge = {
|
|
174
|
+
id: `e-${connection.source}-${connection.sourceHandle}-${connection.target}-${connection.targetHandle}-${Date.now()}`,
|
|
175
|
+
type: "custom",
|
|
176
|
+
source: connection.source,
|
|
177
|
+
target: connection.target,
|
|
178
|
+
sourceHandle: connection.sourceHandle ?? void 0,
|
|
179
|
+
targetHandle: connection.targetHandle ?? void 0
|
|
180
|
+
};
|
|
181
|
+
edges.value = [...edges.value, newEdge];
|
|
182
|
+
emitUpdate();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const oldEdge = edges.value.find((e) => e.id === oldEdgeId);
|
|
186
|
+
const oldEdgeBackup = oldEdge ? { ...oldEdge } : void 0;
|
|
187
|
+
const tempId = `e-pending-${Date.now()}`;
|
|
188
|
+
const pendingEdge = {
|
|
189
|
+
id: tempId,
|
|
190
|
+
type: "custom",
|
|
191
|
+
source: connection.source,
|
|
192
|
+
target: connection.target,
|
|
193
|
+
sourceHandle: connection.sourceHandle ?? void 0,
|
|
194
|
+
targetHandle: connection.targetHandle ?? void 0,
|
|
195
|
+
label: oldEdge?.label,
|
|
196
|
+
style: { ...oldEdge?.style ?? {}, opacity: 0.5 }
|
|
197
|
+
};
|
|
198
|
+
edges.value = [...edges.value.filter((e) => e.id !== oldEdgeId), pendingEdge];
|
|
199
|
+
emitUpdate();
|
|
200
|
+
await withLoading(async () => {
|
|
201
|
+
try {
|
|
202
|
+
if (api.value?.deleteEdge) {
|
|
203
|
+
if (!Number.isNaN(Number(oldEdgeId))) {
|
|
204
|
+
await api.value.deleteEdge(Number(oldEdgeId));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let finalId = tempId;
|
|
208
|
+
let label = typeof pendingEdge.label === "string" ? pendingEdge.label : void 0;
|
|
209
|
+
if (api.value?.createEdge) {
|
|
210
|
+
const { data } = await api.value.createEdge({
|
|
211
|
+
id: 0,
|
|
212
|
+
flowId: flow.value?.id,
|
|
213
|
+
parentId: Number(connection.source),
|
|
214
|
+
childId: Number(connection.target),
|
|
215
|
+
parentHandlePos: connection.sourceHandle ?? void 0,
|
|
216
|
+
childHandlePos: connection.targetHandle ?? void 0
|
|
217
|
+
});
|
|
218
|
+
if (data.value.data) {
|
|
219
|
+
finalId = String(data.value.data.id);
|
|
220
|
+
label = data.value.data.label;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const idx = edges.value.findIndex((e) => e.id === tempId);
|
|
224
|
+
if (idx !== -1) {
|
|
225
|
+
const { opacity: _, ...restStyle } = edges.value[idx].style ?? {};
|
|
226
|
+
edges.value[idx] = {
|
|
227
|
+
...edges.value[idx],
|
|
228
|
+
id: finalId,
|
|
229
|
+
label,
|
|
230
|
+
style: Object.keys(restStyle).length > 0 ? restStyle : void 0
|
|
231
|
+
};
|
|
232
|
+
edges.value = [...edges.value];
|
|
233
|
+
emitUpdate();
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
edges.value = edges.value.filter((e) => e.id !== tempId);
|
|
237
|
+
if (oldEdgeBackup) {
|
|
238
|
+
edges.value = [...edges.value, oldEdgeBackup];
|
|
239
|
+
}
|
|
240
|
+
emitUpdate();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
const updateNodePosition = async (nodeId, x, y) => {
|
|
245
|
+
const doUpdate = async () => {
|
|
246
|
+
const node = nodes.value.find((n) => n.id === nodeId);
|
|
247
|
+
if (node) {
|
|
248
|
+
if (api?.value?.updateNode) {
|
|
249
|
+
await api.value.updateNode({
|
|
250
|
+
...node.data,
|
|
251
|
+
id: node.data.id ?? Number(nodeId),
|
|
252
|
+
positionX: x,
|
|
253
|
+
positionY: y
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
node.position = { x, y };
|
|
257
|
+
emitUpdate();
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
if (api?.value) await withLoading(doUpdate);
|
|
261
|
+
else await doUpdate();
|
|
262
|
+
};
|
|
263
|
+
const updateNodeDimensions = async (nodeId, dimensions) => {
|
|
264
|
+
const doUpdate = async () => {
|
|
265
|
+
const node = nodes.value.find((n) => n.id === nodeId);
|
|
266
|
+
if (node) {
|
|
267
|
+
if (api?.value?.updateNode) {
|
|
268
|
+
await api.value.updateNode({
|
|
269
|
+
...node.data,
|
|
270
|
+
id: node.data.id ?? Number(nodeId),
|
|
271
|
+
width: dimensions.width,
|
|
272
|
+
height: dimensions.height
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
node.data = {
|
|
276
|
+
...node.data,
|
|
277
|
+
width: dimensions.width,
|
|
278
|
+
height: dimensions.height
|
|
279
|
+
};
|
|
280
|
+
emitUpdate();
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
if (api?.value) await withLoading(doUpdate);
|
|
284
|
+
else await doUpdate();
|
|
285
|
+
};
|
|
286
|
+
const updateEdgeLabel = async (edgeId, label) => {
|
|
287
|
+
const doUpdate = async () => {
|
|
288
|
+
const edge = edges.value.find((e) => e.id === edgeId);
|
|
289
|
+
if (edge) {
|
|
290
|
+
if (api?.value?.updateEdge) {
|
|
291
|
+
if (!Number.isNaN(Number(edgeId))) {
|
|
292
|
+
await api.value.updateEdge({
|
|
293
|
+
id: Number(edgeId),
|
|
294
|
+
parentId: Number(edge.source),
|
|
295
|
+
childId: Number(edge.target),
|
|
296
|
+
parentHandlePos: edge.sourceHandle ?? void 0,
|
|
297
|
+
childHandlePos: edge.targetHandle ?? void 0,
|
|
298
|
+
label: label || void 0
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
edge.label = label || void 0;
|
|
303
|
+
emitUpdate();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
if (api?.value) await withLoading(doUpdate);
|
|
307
|
+
else await doUpdate();
|
|
308
|
+
};
|
|
309
|
+
const updateNode = async (updatedNode) => {
|
|
310
|
+
const doUpdate = async () => {
|
|
311
|
+
const index = nodes.value.findIndex((n) => n.id === String(updatedNode.id));
|
|
312
|
+
if (index !== -1) {
|
|
313
|
+
if (api?.value?.updateNode) {
|
|
314
|
+
await api.value.updateNode(updatedNode);
|
|
315
|
+
}
|
|
316
|
+
nodes.value[index].data = {
|
|
317
|
+
...nodes.value[index].data,
|
|
318
|
+
...updatedNode,
|
|
319
|
+
id: updatedNode.id,
|
|
320
|
+
name: updatedNode.name
|
|
321
|
+
};
|
|
322
|
+
emitUpdate();
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
if (api?.value) await withLoading(doUpdate);
|
|
326
|
+
else await doUpdate();
|
|
327
|
+
};
|
|
328
|
+
const createNode = async () => {
|
|
329
|
+
const doCreate = async () => {
|
|
330
|
+
const positionX = Math.round((200 + Math.random() * 200) / GRID_SIZE) * GRID_SIZE;
|
|
331
|
+
const positionY = Math.round((200 + Math.random() * 200) / GRID_SIZE) * GRID_SIZE;
|
|
332
|
+
const defaultName = `\u8282\u70B9 ${nodes.value.length + 1}`;
|
|
333
|
+
let nodeId = Date.now();
|
|
334
|
+
let nodeName = defaultName;
|
|
335
|
+
let nodeWidth = 120;
|
|
336
|
+
let nodeHeight = 40;
|
|
337
|
+
if (api?.value?.createNode) {
|
|
338
|
+
const { data } = await api.value.createNode({
|
|
339
|
+
id: 0,
|
|
340
|
+
flowId: flow.value?.id,
|
|
341
|
+
name: defaultName,
|
|
342
|
+
positionX,
|
|
343
|
+
positionY,
|
|
344
|
+
width: 120,
|
|
345
|
+
height: 40
|
|
346
|
+
});
|
|
347
|
+
if (data.value.data) {
|
|
348
|
+
nodeId = data.value.data.id;
|
|
349
|
+
nodeName = data.value.data.name ?? defaultName;
|
|
350
|
+
nodeWidth = data.value.data.width ?? 120;
|
|
351
|
+
nodeHeight = data.value.data.height ?? 40;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const newNode = {
|
|
355
|
+
id: String(nodeId),
|
|
356
|
+
type: "custom",
|
|
357
|
+
position: { x: positionX, y: positionY },
|
|
358
|
+
data: {
|
|
359
|
+
id: nodeId,
|
|
360
|
+
name: nodeName,
|
|
361
|
+
width: nodeWidth,
|
|
362
|
+
height: nodeHeight
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
nodes.value = [...nodes.value, newNode];
|
|
366
|
+
emitUpdate();
|
|
367
|
+
};
|
|
368
|
+
if (api?.value) await withLoading(doCreate);
|
|
369
|
+
else await doCreate();
|
|
370
|
+
};
|
|
371
|
+
const syncNodes = (createHandlers) => {
|
|
372
|
+
nodes.value.forEach((node) => {
|
|
373
|
+
const handlers = createHandlers(node.id);
|
|
374
|
+
Object.assign(node.data, handlers);
|
|
375
|
+
});
|
|
376
|
+
};
|
|
377
|
+
const syncEdges = (createHandlers) => {
|
|
378
|
+
edges.value.forEach((edge) => {
|
|
379
|
+
const handlers = createHandlers(edge.id);
|
|
380
|
+
if (!edge.data) edge.data = {};
|
|
381
|
+
Object.assign(edge.data, handlers);
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
return {
|
|
385
|
+
nodes,
|
|
386
|
+
edges,
|
|
387
|
+
GRID_SIZE,
|
|
388
|
+
loading,
|
|
389
|
+
deleteNode,
|
|
390
|
+
deleteEdge,
|
|
391
|
+
createEdge,
|
|
392
|
+
reconnectEdge,
|
|
393
|
+
updateNodePosition,
|
|
394
|
+
updateNodeDimensions,
|
|
395
|
+
updateNode,
|
|
396
|
+
createNode,
|
|
397
|
+
syncNodes,
|
|
398
|
+
updateEdgeLabel,
|
|
399
|
+
syncEdges
|
|
400
|
+
};
|
|
401
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { FlowHandle } from '#v/constants';
|
|
3
|
+
interface UseFlowNodeOptions {
|
|
4
|
+
/** 节点 data (reactive) */
|
|
5
|
+
data: Ref<any>;
|
|
6
|
+
/** 是否选中 (reactive) */
|
|
7
|
+
selected: Ref<boolean>;
|
|
8
|
+
}
|
|
9
|
+
export declare function useFlowNode(options: UseFlowNodeOptions): {
|
|
10
|
+
nodeRef: Ref<HTMLElement | null, HTMLElement | null>;
|
|
11
|
+
isHoveredLocal: Ref<boolean, boolean>;
|
|
12
|
+
borderColor: import("vue").ComputedRef<any>;
|
|
13
|
+
showHandles: import("vue").ComputedRef<boolean>;
|
|
14
|
+
activeHandles: import("vue").ComputedRef<FlowHandle[]>;
|
|
15
|
+
handleStyle: (handle: FlowHandle) => Record<string, string | number | undefined>;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ref, computed, inject, onMounted, onBeforeUnmount } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
FLOW_HANDLES,
|
|
4
|
+
FLOW_HANDLE_TIER_THRESHOLDS,
|
|
5
|
+
FLOW_MOUSE_POSITION_KEY,
|
|
6
|
+
FLOW_NODE_PROXIMITY_THRESHOLD
|
|
7
|
+
} from "#v/constants";
|
|
8
|
+
import { handleSizePreview } from "./useFlowStyles.js";
|
|
9
|
+
export function useFlowNode(options) {
|
|
10
|
+
const { data, selected } = options;
|
|
11
|
+
const nodeRef = ref(null);
|
|
12
|
+
const isHoveredLocal = ref(false);
|
|
13
|
+
const injectedMousePosition = inject(FLOW_MOUSE_POSITION_KEY, null);
|
|
14
|
+
const localMousePosition = ref({ x: 0, y: 0 });
|
|
15
|
+
const mousePosition = computed(() => injectedMousePosition?.value ?? localMousePosition.value);
|
|
16
|
+
const borderColor = computed(() => {
|
|
17
|
+
if (selected.value) return "var(--ui-primary)";
|
|
18
|
+
if (data.value.borderColor) return data.value.borderColor;
|
|
19
|
+
return "var(--ui-border-default)";
|
|
20
|
+
});
|
|
21
|
+
const isNearby = computed(() => {
|
|
22
|
+
if (!nodeRef.value) return false;
|
|
23
|
+
const rect = nodeRef.value.getBoundingClientRect();
|
|
24
|
+
const mouseX = mousePosition.value.x;
|
|
25
|
+
const mouseY = mousePosition.value.y;
|
|
26
|
+
const dx = Math.max(rect.left - mouseX, 0, mouseX - rect.right);
|
|
27
|
+
const dy = Math.max(rect.top - mouseY, 0, mouseY - rect.bottom);
|
|
28
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
29
|
+
return distance <= FLOW_NODE_PROXIMITY_THRESHOLD;
|
|
30
|
+
});
|
|
31
|
+
const showHandles = computed(() => isHoveredLocal.value || isNearby.value || handleSizePreview.value);
|
|
32
|
+
const activeHandles = computed(() => {
|
|
33
|
+
const w = data.value.width ?? 120;
|
|
34
|
+
const h = data.value.height ?? 40;
|
|
35
|
+
const t = FLOW_HANDLE_TIER_THRESHOLDS;
|
|
36
|
+
const hTier = w < t.small.maxWidth ? 0 : w < t.medium.maxWidth ? 1 : 2;
|
|
37
|
+
const vTier = h < t.small.maxHeight ? 0 : h < t.medium.maxHeight ? 1 : 2;
|
|
38
|
+
return FLOW_HANDLES.filter((handle) => {
|
|
39
|
+
const id = handle.id;
|
|
40
|
+
if (id === "t2" || id === "b2") return true;
|
|
41
|
+
if (id === "r2" || id === "l2") return true;
|
|
42
|
+
if (id === "tl" || id === "tr" || id === "bl" || id === "br") return true;
|
|
43
|
+
if (id === "t1" || id === "t3" || id === "b1" || id === "b3") return hTier >= 2;
|
|
44
|
+
if (id === "r1" || id === "r3" || id === "l1" || id === "l3") return vTier >= 2;
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
const handleStyle = (handle) => {
|
|
49
|
+
const bw = data.value.borderWidth ?? 2;
|
|
50
|
+
const handleSize = data.value.handleSize ?? 6;
|
|
51
|
+
const handleColor = data.value.handleColor ?? "";
|
|
52
|
+
const offset = -(bw / 2);
|
|
53
|
+
const base = {
|
|
54
|
+
pointerEvents: "all",
|
|
55
|
+
opacity: showHandles.value ? 1 : 0,
|
|
56
|
+
transition: "opacity 0.2s",
|
|
57
|
+
width: `${handleSize}px`,
|
|
58
|
+
height: `${handleSize}px`,
|
|
59
|
+
zIndex: 20,
|
|
60
|
+
borderRadius: "50%",
|
|
61
|
+
...handleColor ? { backgroundColor: handleColor } : {}
|
|
62
|
+
};
|
|
63
|
+
const pos = handle.position;
|
|
64
|
+
if (pos === "top") {
|
|
65
|
+
base.top = `${offset}px`;
|
|
66
|
+
} else if (pos === "bottom") {
|
|
67
|
+
base.bottom = `${offset}px`;
|
|
68
|
+
} else if (pos === "left") {
|
|
69
|
+
base.left = `${offset}px`;
|
|
70
|
+
} else if (pos === "right") {
|
|
71
|
+
base.right = `${offset}px`;
|
|
72
|
+
}
|
|
73
|
+
if (handle.offsetPercent?.x !== void 0) {
|
|
74
|
+
const pct = handle.offsetPercent.x;
|
|
75
|
+
const shift = (pct / 50 - 1) * (bw / 2);
|
|
76
|
+
base.left = `calc(${pct}% + ${shift}px)`;
|
|
77
|
+
}
|
|
78
|
+
if (handle.offsetPercent?.y !== void 0) {
|
|
79
|
+
const pct = handle.offsetPercent.y;
|
|
80
|
+
const shift = (pct / 50 - 1) * (bw / 2);
|
|
81
|
+
base.top = `calc(${pct}% + ${shift}px)`;
|
|
82
|
+
}
|
|
83
|
+
return base;
|
|
84
|
+
};
|
|
85
|
+
const handleGlobalMouseMove = (e) => {
|
|
86
|
+
localMousePosition.value = { x: e.clientX, y: e.clientY };
|
|
87
|
+
};
|
|
88
|
+
onMounted(() => {
|
|
89
|
+
if (!injectedMousePosition) {
|
|
90
|
+
window.addEventListener("mousemove", handleGlobalMouseMove);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
onBeforeUnmount(() => {
|
|
94
|
+
if (!injectedMousePosition) {
|
|
95
|
+
window.removeEventListener("mousemove", handleGlobalMouseMove);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
nodeRef,
|
|
100
|
+
isHoveredLocal,
|
|
101
|
+
borderColor,
|
|
102
|
+
showHandles,
|
|
103
|
+
activeHandles,
|
|
104
|
+
handleStyle
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ShallowRef } from 'vue';
|
|
2
|
+
import type { Node } from '@vue-flow/core';
|
|
3
|
+
import type { FlowNode, UseFlowResizeDimensions } from '#v/types';
|
|
4
|
+
import type { ResizeEdge } from '#v/constants';
|
|
5
|
+
interface UseFlowResizeOptions {
|
|
6
|
+
gridSize: number;
|
|
7
|
+
nodes: ShallowRef<Node[]>;
|
|
8
|
+
getViewport: () => {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
zoom: number;
|
|
12
|
+
};
|
|
13
|
+
onResizeEnd: (nodeId: string, dimensions: UseFlowResizeDimensions) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function useFlowResize(options: UseFlowResizeOptions): {
|
|
16
|
+
isResizing: import("vue").Ref<boolean, boolean>;
|
|
17
|
+
startResize: (event: MouseEvent, nodeId: string, node: FlowNode, edge: ResizeEdge) => void;
|
|
18
|
+
handleMouseMove: (event: MouseEvent) => void;
|
|
19
|
+
handleMouseUp: () => void;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
export function useFlowResize(options) {
|
|
3
|
+
const { gridSize, nodes, getViewport, onResizeEnd } = options;
|
|
4
|
+
const isResizing = ref(false);
|
|
5
|
+
const resizingNodeId = ref(null);
|
|
6
|
+
const resizeEdge = ref(null);
|
|
7
|
+
const resizeStartX = ref(0);
|
|
8
|
+
const resizeStartY = ref(0);
|
|
9
|
+
const resizeStartWidth = ref(0);
|
|
10
|
+
const resizeStartHeight = ref(0);
|
|
11
|
+
const resizeStartNodeX = ref(0);
|
|
12
|
+
const resizeStartNodeY = ref(0);
|
|
13
|
+
const startResize = (event, nodeId, node, edge) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
isResizing.value = true;
|
|
17
|
+
resizingNodeId.value = nodeId;
|
|
18
|
+
resizeEdge.value = edge;
|
|
19
|
+
resizeStartX.value = event.clientX;
|
|
20
|
+
resizeStartY.value = event.clientY;
|
|
21
|
+
resizeStartWidth.value = node.width ?? 120;
|
|
22
|
+
resizeStartHeight.value = node.height ?? 40;
|
|
23
|
+
resizeStartNodeX.value = node.positionX ?? 0;
|
|
24
|
+
resizeStartNodeY.value = node.positionY ?? 0;
|
|
25
|
+
document.body.style.userSelect = "none";
|
|
26
|
+
};
|
|
27
|
+
const handleMouseMove = (event) => {
|
|
28
|
+
if (!isResizing.value || !resizingNodeId.value || !resizeEdge.value) return;
|
|
29
|
+
const viewport = getViewport();
|
|
30
|
+
const zoom = viewport.zoom;
|
|
31
|
+
const dx = (event.clientX - resizeStartX.value) / zoom;
|
|
32
|
+
const dy = (event.clientY - resizeStartY.value) / zoom;
|
|
33
|
+
const node = nodes.value.find((n) => n.id === resizingNodeId.value);
|
|
34
|
+
if (!node) return;
|
|
35
|
+
let newWidth = resizeStartWidth.value;
|
|
36
|
+
let newHeight = resizeStartHeight.value;
|
|
37
|
+
let newX = resizeStartNodeX.value;
|
|
38
|
+
let newY = resizeStartNodeY.value;
|
|
39
|
+
const edge = resizeEdge.value;
|
|
40
|
+
if (edge.includes("right")) {
|
|
41
|
+
newWidth = Math.max(60, resizeStartWidth.value + dx);
|
|
42
|
+
}
|
|
43
|
+
if (edge.includes("left")) {
|
|
44
|
+
const deltaWidth = Math.min(dx, resizeStartWidth.value - 60);
|
|
45
|
+
newWidth = resizeStartWidth.value - deltaWidth;
|
|
46
|
+
newX = resizeStartNodeX.value + deltaWidth;
|
|
47
|
+
}
|
|
48
|
+
if (edge.includes("bottom")) {
|
|
49
|
+
newHeight = Math.max(40, resizeStartHeight.value + dy);
|
|
50
|
+
}
|
|
51
|
+
if (edge === "top" || edge === "top-left" || edge === "top-right") {
|
|
52
|
+
const deltaHeight = Math.min(dy, resizeStartHeight.value - 40);
|
|
53
|
+
newHeight = resizeStartHeight.value - deltaHeight;
|
|
54
|
+
newY = resizeStartNodeY.value + deltaHeight;
|
|
55
|
+
}
|
|
56
|
+
newWidth = Math.round(newWidth / gridSize) * gridSize;
|
|
57
|
+
newHeight = Math.round(newHeight / gridSize) * gridSize;
|
|
58
|
+
newX = Math.round(newX / gridSize) * gridSize;
|
|
59
|
+
newY = Math.round(newY / gridSize) * gridSize;
|
|
60
|
+
node.data = { ...node.data, width: newWidth, height: newHeight };
|
|
61
|
+
node.position = { x: newX, y: newY };
|
|
62
|
+
};
|
|
63
|
+
const handleMouseUp = () => {
|
|
64
|
+
if (isResizing.value && resizingNodeId.value) {
|
|
65
|
+
const node = nodes.value.find((n) => n.id === resizingNodeId.value);
|
|
66
|
+
if (node) {
|
|
67
|
+
onResizeEnd(resizingNodeId.value, {
|
|
68
|
+
width: node.data.width ?? 120,
|
|
69
|
+
height: node.data.height ?? 40
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
isResizing.value = false;
|
|
74
|
+
resizingNodeId.value = null;
|
|
75
|
+
resizeEdge.value = null;
|
|
76
|
+
document.body.style.userSelect = "";
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
isResizing,
|
|
80
|
+
startResize,
|
|
81
|
+
handleMouseMove,
|
|
82
|
+
handleMouseUp
|
|
83
|
+
};
|
|
84
|
+
}
|