@arronqzy/vue-blueprint 0.1.0

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.
Files changed (57) hide show
  1. package/README.md +50 -0
  2. package/package.json +44 -0
  3. package/src/BlueprintCanvasContext.ts +71 -0
  4. package/src/BlueprintNodeConfigSidebar.vue +338 -0
  5. package/src/blueprint.css +327 -0
  6. package/src/blueprintNodeTypes.ts +20 -0
  7. package/src/components/BluePrintVueRoot.vue +73 -0
  8. package/src/components/BlueprintCanvas.vue +220 -0
  9. package/src/components/BlueprintContextMenu.vue +114 -0
  10. package/src/components/BlueprintExecutionLogPanel.vue +294 -0
  11. package/src/components/BlueprintMetaDialog.vue +80 -0
  12. package/src/components/BlueprintNodeSwitchTaskDialog.vue +41 -0
  13. package/src/components/ClockNodeConfigPanel.vue +124 -0
  14. package/src/components/FetchNodeConfigPanel.vue +559 -0
  15. package/src/components/FetchUrlAutocomplete.vue +174 -0
  16. package/src/components/JsonNodeConfigPanel.vue +73 -0
  17. package/src/components/LogicNodeConfigPanel.vue +73 -0
  18. package/src/components/ViewElementMultiSelect.vue +50 -0
  19. package/src/composables/useBlueprintDebugSession.ts +441 -0
  20. package/src/composables/useBlueprintFlowState.ts +486 -0
  21. package/src/composables/useBlueprintFlowViewport.ts +65 -0
  22. package/src/composables/useBlueprintNodeSelectionGuard.ts +41 -0
  23. package/src/composables/useBlueprintPageLifecycle.ts +244 -0
  24. package/src/createBlueprintEdgeTypes.ts +10 -0
  25. package/src/edges/BlueprintSmoothEdge.vue +31 -0
  26. package/src/env.d.ts +7 -0
  27. package/src/fetch-config-task-store.ts +206 -0
  28. package/src/flowCoordinates.ts +19 -0
  29. package/src/flowDefaults.ts +9 -0
  30. package/src/graph/blueprint-graph.ts +265 -0
  31. package/src/graph/document.ts +422 -0
  32. package/src/graph/index.ts +7 -0
  33. package/src/graph/node-summary.ts +88 -0
  34. package/src/graph/node-types.ts +9 -0
  35. package/src/graph/sync-edges.ts +69 -0
  36. package/src/graph/sync-nodes.ts +110 -0
  37. package/src/graph/vue-flow-adapter.ts +127 -0
  38. package/src/index.ts +37 -0
  39. package/src/library/blueprint-io.ts +108 -0
  40. package/src/library/blueprint-library-db.ts +112 -0
  41. package/src/library/execution-log-db.ts +171 -0
  42. package/src/library/execution-log-settings.ts +50 -0
  43. package/src/library/swagger-docs.ts +56 -0
  44. package/src/library/types.ts +35 -0
  45. package/src/nodes/AndFlowNode.vue +60 -0
  46. package/src/nodes/BlueprintFlowNode.vue +26 -0
  47. package/src/nodes/BlueprintNodeCard.vue +155 -0
  48. package/src/nodes/BlueprintNodeShell.vue +70 -0
  49. package/src/nodes/ClockFlowNode.vue +60 -0
  50. package/src/nodes/FetchFlowNode.vue +26 -0
  51. package/src/nodes/JsonFlowNode.vue +26 -0
  52. package/src/nodes/LifecycleFlowNode.vue +45 -0
  53. package/src/nodes/LogicFlowNode.vue +26 -0
  54. package/src/runtime/document-to-runnable-graph.ts +51 -0
  55. package/src/runtime/execution-overlay.ts +169 -0
  56. package/src/types.ts +1 -0
  57. package/src/utils/cn.ts +3 -0
@@ -0,0 +1,174 @@
1
+ <script setup lang="ts">
2
+ import { computed, onUnmounted, ref, watch } from "vue";
3
+ import {
4
+ buildEndpointSuggestions,
5
+ filterEndpointSuggestions,
6
+ type SwaggerApiEndpoint,
7
+ } from "@arronqzy/blueprint-dsl";
8
+
9
+ import { cn } from "../utils/cn";
10
+
11
+ const props = withDefaults(
12
+ defineProps<{
13
+ value: string;
14
+ apiBaseUrl: string;
15
+ endpoints: SwaggerApiEndpoint[];
16
+ placeholder?: string;
17
+ selectOnly?: boolean;
18
+ }>(),
19
+ {
20
+ placeholder: "",
21
+ selectOnly: false,
22
+ }
23
+ );
24
+
25
+ const emit = defineEmits<{
26
+ change: [value: string];
27
+ selectEndpoint: [endpoint: SwaggerApiEndpoint, fullUrl: string];
28
+ }>();
29
+
30
+ const open = ref(false);
31
+ const activeIndex = ref(0);
32
+ const filterQuery = ref("");
33
+ const containerRef = ref<HTMLDivElement | null>(null);
34
+
35
+ const suggestions = computed(() =>
36
+ buildEndpointSuggestions(props.apiBaseUrl, props.endpoints)
37
+ );
38
+
39
+ const query = computed(() => (props.selectOnly ? filterQuery.value : props.value));
40
+
41
+ const filtered = computed(() =>
42
+ filterEndpointSuggestions(suggestions.value, query.value)
43
+ );
44
+
45
+ const selectedSuggestion = computed(() =>
46
+ suggestions.value.find((item) => item.path === props.value)
47
+ );
48
+
49
+ const inputValue = computed(() => {
50
+ if (!props.selectOnly) return props.value;
51
+ if (open.value) return filterQuery.value;
52
+ return selectedSuggestion.value?.label ?? props.value;
53
+ });
54
+
55
+ watch([query, () => filtered.value.length], () => {
56
+ activeIndex.value = 0;
57
+ });
58
+
59
+ let pointerCleanup: (() => void) | null = null;
60
+
61
+ watch(open, (isOpen) => {
62
+ pointerCleanup?.();
63
+ pointerCleanup = null;
64
+ if (!isOpen) return;
65
+
66
+ const onPointerDown = (event: MouseEvent) => {
67
+ if (!containerRef.value?.contains(event.target as globalThis.Node)) {
68
+ open.value = false;
69
+ filterQuery.value = "";
70
+ }
71
+ };
72
+ window.addEventListener("pointerdown", onPointerDown);
73
+ pointerCleanup = () => window.removeEventListener("pointerdown", onPointerDown);
74
+ });
75
+
76
+ onUnmounted(() => {
77
+ pointerCleanup?.();
78
+ });
79
+
80
+ const showSuggestions = computed(() => open.value && props.endpoints.length > 0);
81
+
82
+ function selectSuggestion(index: number) {
83
+ const item = filtered.value[index];
84
+ if (!item) return;
85
+ const nextValue = props.apiBaseUrl.trim() ? item.path : item.fullUrl;
86
+ emit("change", nextValue);
87
+ emit("selectEndpoint", item, item.fullUrl);
88
+ filterQuery.value = "";
89
+ open.value = false;
90
+ }
91
+
92
+ function openDropdown() {
93
+ open.value = true;
94
+ if (props.selectOnly) {
95
+ filterQuery.value = "";
96
+ }
97
+ }
98
+
99
+ function closeDropdown() {
100
+ open.value = false;
101
+ filterQuery.value = "";
102
+ }
103
+
104
+ function handleInput(event: Event) {
105
+ const next = (event.target as HTMLInputElement).value;
106
+ if (props.selectOnly) {
107
+ filterQuery.value = next;
108
+ open.value = true;
109
+ return;
110
+ }
111
+ emit("change", next);
112
+ open.value = true;
113
+ }
114
+
115
+ function handleKeyDown(event: KeyboardEvent) {
116
+ if (!showSuggestions.value || filtered.value.length === 0) {
117
+ if (props.selectOnly && event.key === "Escape") {
118
+ closeDropdown();
119
+ }
120
+ return;
121
+ }
122
+ if (event.key === "ArrowDown") {
123
+ event.preventDefault();
124
+ activeIndex.value = Math.min(activeIndex.value + 1, filtered.value.length - 1);
125
+ } else if (event.key === "ArrowUp") {
126
+ event.preventDefault();
127
+ activeIndex.value = Math.max(activeIndex.value - 1, 0);
128
+ } else if (event.key === "Enter") {
129
+ event.preventDefault();
130
+ selectSuggestion(activeIndex.value);
131
+ } else if (event.key === "Escape") {
132
+ closeDropdown();
133
+ }
134
+ }
135
+ </script>
136
+
137
+ <template>
138
+ <div ref="containerRef" class="relative">
139
+ <input
140
+ :value="inputValue"
141
+ :readOnly="selectOnly && !open"
142
+ :placeholder="placeholder"
143
+ class="flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 text-[11px] text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
144
+ :class="selectOnly && !open ? 'cursor-pointer font-medium' : 'font-mono'"
145
+ @input="handleInput"
146
+ @focus="openDropdown"
147
+ @blur="selectOnly ? closeDropdown() : undefined"
148
+ @keydown="handleKeyDown"
149
+ />
150
+ <div
151
+ v-if="showSuggestions"
152
+ class="absolute z-[10150] mt-1 max-h-52 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md"
153
+ >
154
+ <div
155
+ v-if="filtered.length === 0"
156
+ class="px-2 py-1.5 text-[11px] text-muted-foreground"
157
+ >
158
+ 无匹配接口
159
+ </div>
160
+ <button
161
+ v-for="(item, index) in filtered"
162
+ :key="`${item.method}:${item.path}:${item.operationId ?? index}`"
163
+ type="button"
164
+ class="flex w-full flex-col items-start gap-0.5 px-2 py-1.5 text-left text-[11px] hover:bg-accent"
165
+ :class="cn(index === activeIndex && 'bg-accent')"
166
+ @mousedown.prevent
167
+ @click="selectSuggestion(index)"
168
+ >
169
+ <span class="font-medium text-foreground">{{ item.label }}</span>
170
+ <span class="truncate font-mono text-muted-foreground">{{ item.fullUrl }}</span>
171
+ </button>
172
+ </div>
173
+ </div>
174
+ </template>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from "vue";
3
+ import { validateJsonString } from "@arronqzy/blueprint-dsl";
4
+ import type { JsonNodeConfig } from "@arronqzy/blueprint-dsl";
5
+
6
+ import type { BlueprintGraphNode } from "../graph/document";
7
+ import { resolveNodeJsonConfig } from "../graph/document";
8
+
9
+ export type JsonNodeConfigPanelProps = {
10
+ node: BlueprintGraphNode;
11
+ onUpdateNode: (
12
+ nodeId: string,
13
+ patch: Partial<Pick<BlueprintGraphNode, "jsonConfig" | "configSource">>
14
+ ) => void;
15
+ };
16
+
17
+ const props = defineProps<JsonNodeConfigPanelProps>();
18
+
19
+ const draftError = ref<string | null>(null);
20
+
21
+ function patchJsonConfig(node: BlueprintGraphNode, patch: Partial<JsonNodeConfig>) {
22
+ return {
23
+ jsonConfig: { ...resolveNodeJsonConfig(node), ...patch },
24
+ configSource: "json" as const,
25
+ };
26
+ }
27
+
28
+ const jsonConfig = computed(() => resolveNodeJsonConfig(props.node));
29
+
30
+ const storedValidation = computed(() =>
31
+ validateJsonString(jsonConfig.value.jsonString)
32
+ );
33
+
34
+ const parseError = computed(
35
+ () => draftError.value ?? (storedValidation.value.ok ? null : storedValidation.value.error)
36
+ );
37
+
38
+ function handleChange(event: Event) {
39
+ const jsonString = (event.target as HTMLTextAreaElement).value;
40
+ const result = validateJsonString(jsonString);
41
+ draftError.value = result.ok ? null : result.error;
42
+ props.onUpdateNode(props.node.id, patchJsonConfig(props.node, { jsonString }));
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <div class="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
48
+ <div class="font-medium text-foreground">JSON 节点</div>
49
+ <p class="text-[11px] text-muted-foreground">
50
+ 收到<strong>真信号</strong>后,将下方 JSON 解析为 JavaScript object(或
51
+ array)并从输出口发出<strong>真信号</strong>;解析失败则发出
52
+ <strong>假信号</strong>。
53
+ </p>
54
+
55
+ <label class="block space-y-1">
56
+ <span class="text-muted-foreground">JSON 内容</span>
57
+ <textarea
58
+ :value="jsonConfig.jsonString"
59
+ rows="12"
60
+ spellcheck="false"
61
+ class="w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
62
+ placeholder='{
63
+ "key": "value"
64
+ }'
65
+ @input="handleChange"
66
+ />
67
+ <p v-if="parseError" class="text-[11px] text-destructive">
68
+ JSON 格式错误:{{ parseError }}
69
+ </p>
70
+ <p v-else class="text-[11px] text-muted-foreground">JSON 格式正确</p>
71
+ </label>
72
+ </div>
73
+ </template>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from "vue";
3
+ import { validateLogicSourceCode } from "@arronqzy/blueprint-dsl";
4
+ import type { LogicNodeConfig } from "@arronqzy/blueprint-dsl";
5
+
6
+ import type { BlueprintGraphNode } from "../graph/document";
7
+ import { resolveNodeLogicConfig } from "../graph/document";
8
+
9
+ export type LogicNodeConfigPanelProps = {
10
+ node: BlueprintGraphNode;
11
+ onUpdateNode: (
12
+ nodeId: string,
13
+ patch: Partial<Pick<BlueprintGraphNode, "logicConfig" | "configSource">>
14
+ ) => void;
15
+ };
16
+
17
+ const props = defineProps<LogicNodeConfigPanelProps>();
18
+
19
+ const draftError = ref<string | null>(null);
20
+
21
+ function patchLogicConfig(node: BlueprintGraphNode, patch: Partial<LogicNodeConfig>) {
22
+ return {
23
+ logicConfig: { ...resolveNodeLogicConfig(node), ...patch },
24
+ configSource: "logic" as const,
25
+ };
26
+ }
27
+
28
+ const logicConfig = computed(() => resolveNodeLogicConfig(props.node));
29
+
30
+ const storedValidation = computed(() =>
31
+ validateLogicSourceCode(logicConfig.value.sourceCode)
32
+ );
33
+
34
+ const parseError = computed(
35
+ () => draftError.value ?? (storedValidation.value.ok ? null : storedValidation.value.error)
36
+ );
37
+
38
+ function handleChange(event: Event) {
39
+ const sourceCode = (event.target as HTMLTextAreaElement).value;
40
+ const result = validateLogicSourceCode(sourceCode);
41
+ draftError.value = result.ok ? null : result.error;
42
+ props.onUpdateNode(props.node.id, patchLogicConfig(props.node, { sourceCode }));
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <div class="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
48
+ <div class="font-medium text-foreground">逻辑节点</div>
49
+ <p class="text-[11px] text-muted-foreground">
50
+ 收到<strong>真信号</strong>后,将输入数据传入下方 <code>update</code>
51
+ 函数并执行;返回值作为<strong>真信号</strong>输出。代码语法错误或运行时报错则发出
52
+ <strong>假信号</strong>(错误信息为输出值)。
53
+ </p>
54
+
55
+ <label class="block space-y-1">
56
+ <span class="text-muted-foreground">JavaScript 代码</span>
57
+ <textarea
58
+ :value="logicConfig.sourceCode"
59
+ rows="14"
60
+ spellcheck="false"
61
+ class="w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
62
+ placeholder="function update(input) {
63
+ return input;
64
+ }"
65
+ @input="handleChange"
66
+ />
67
+ <p v-if="parseError" class="text-[11px] text-destructive">
68
+ JavaScript 错误:{{ parseError }}
69
+ </p>
70
+ <p v-else class="text-[11px] text-muted-foreground">JavaScript 语法正确</p>
71
+ </label>
72
+ </div>
73
+ </template>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { Select } from "ant-design-vue";
4
+
5
+ export type ViewElementMultiSelectOption = {
6
+ id: string;
7
+ label: string;
8
+ };
9
+
10
+ const props = withDefaults(
11
+ defineProps<{
12
+ options: ViewElementMultiSelectOption[];
13
+ value: string[];
14
+ placeholder?: string;
15
+ disabled?: boolean;
16
+ }>(),
17
+ {
18
+ placeholder: "选择视图节点",
19
+ disabled: false,
20
+ }
21
+ );
22
+
23
+ const emit = defineEmits<{
24
+ change: [next: string[]];
25
+ }>();
26
+
27
+ const selectOptions = computed(() =>
28
+ props.options.map((opt) => ({ value: opt.id, label: opt.label }))
29
+ );
30
+
31
+ const emptyOptions = computed(() => props.options.length === 0);
32
+ </script>
33
+
34
+ <template>
35
+ <Select
36
+ mode="multiple"
37
+ show-search
38
+ :value="value"
39
+ :disabled="disabled || emptyOptions"
40
+ :placeholder="emptyOptions ? '视图画布暂无节点' : placeholder"
41
+ :options="selectOptions"
42
+ :filter-option="
43
+ (input, option) =>
44
+ (option?.label ?? '').toString().toLowerCase().includes(input.toLowerCase())
45
+ "
46
+ class="w-full"
47
+ size="small"
48
+ @change="(next) => emit('change', next as string[])"
49
+ />
50
+ </template>