From 1f45f6216176fca1e02d4ce766286c1927d362b7 Mon Sep 17 00:00:00 2001 From: rookie4show Date: Tue, 24 Feb 2026 21:04:36 +0800 Subject: [PATCH] feat: add flow capability levels and plugin injection API --- src/YysEditorEmbed.vue | 91 ++++++++++++++++++++++++------------------ src/flowRuntime.ts | 63 +++++++++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 src/flowRuntime.ts diff --git a/src/YysEditorEmbed.vue b/src/YysEditorEmbed.vue index 4730b82..eb72b46 100644 --- a/src/YysEditorEmbed.vue +++ b/src/YysEditorEmbed.vue @@ -41,7 +41,6 @@ import { ref, computed, watch, onMounted, onBeforeUnmount, provide } from 'vue' import { createPinia } from 'pinia' import LogicFlow from '@logicflow/core' import '@logicflow/core/lib/style/index.css' -import { Snapshot, MiniMap, Control } from '@logicflow/extension' import '@logicflow/extension/lib/style/index.css' import FlowEditor from './components/flow/FlowEditor.vue' @@ -49,13 +48,13 @@ import Toolbar from './components/Toolbar.vue' import ComponentsPanel from './components/flow/ComponentsPanel.vue' import { useFilesStore } from '@/ts/useStore' import { setLogicFlowInstance, destroyLogicFlowInstance, getLogicFlowInstance } from '@/ts/useLogicFlow' -import { register } from '@logicflow/vue-node-registry' -import ImageNode from './components/flow/nodes/common/ImageNode.vue' -import AssetSelectorNode from './components/flow/nodes/common/AssetSelectorNode.vue' -import TextNode from './components/flow/nodes/common/TextNode.vue' -import TextNodeModel from './components/flow/nodes/common/TextNodeModel' -import VectorNode from './components/flow/nodes/common/VectorNode.vue' -import VectorNodeModel from './components/flow/nodes/common/VectorNodeModel' +import { + registerFlowNodes, + resolveFlowPlugins, + type FlowCapabilityLevel, + type FlowNodeRegistration, + type FlowPlugin +} from './flowRuntime' // 类型定义 export interface GraphData { @@ -92,12 +91,15 @@ export interface EditorConfig { const props = withDefaults(defineProps<{ data?: GraphData mode?: 'preview' | 'edit' + capability?: FlowCapabilityLevel width?: string | number height?: string | number showToolbar?: boolean showPropertyPanel?: boolean showComponentPanel?: boolean config?: EditorConfig + plugins?: FlowPlugin[] + nodeRegistrations?: FlowNodeRegistration[] }>(), { mode: 'edit', width: '100%', @@ -132,6 +134,13 @@ const previewContainerRef = ref(null) const previewLf = ref(null) // Computed +const effectiveCapability = computed(() => { + if (props.capability) { + return props.capability + } + return props.mode === 'preview' ? 'render-only' : 'interactive' +}) + const containerStyle = computed(() => ({ width: typeof props.width === 'number' ? `${props.width}px` : props.width, height: typeof props.height === 'number' ? `${props.height}px` : props.height @@ -150,10 +159,20 @@ const contentHeight = computed(() => { return containerHeight.value }) +const destroyPreviewMode = () => { + if (previewLf.value) { + previewLf.value.destroy() + previewLf.value = null + } +} + // 初始化预览模式的 LogicFlow const initPreviewMode = () => { if (!previewContainerRef.value) return + destroyPreviewMode() + const isRenderOnly = effectiveCapability.value === 'render-only' + // 创建 LogicFlow 实例(只读模式) previewLf.value = new LogicFlow({ container: previewContainerRef.value, @@ -161,36 +180,19 @@ const initPreviewMode = () => { height: previewContainerRef.value.offsetHeight, grid: false, keyboard: { - enabled: false + enabled: !isRenderOnly }, - // 禁用所有交互 - isSilentMode: true, - stopScrollGraph: true, - stopZoomGraph: true, - stopMoveGraph: true, - adjustNodePosition: false, - plugins: [Snapshot, MiniMap, Control] + // render-only 模式禁用所有交互能力 + isSilentMode: isRenderOnly, + stopScrollGraph: isRenderOnly, + stopZoomGraph: isRenderOnly, + stopMoveGraph: isRenderOnly, + adjustNodePosition: !isRenderOnly, + plugins: resolveFlowPlugins(effectiveCapability.value, props.plugins) }) - // 注册自定义节点(必须在 LogicFlow 实例创建后) - register({ - type: 'imageNode', - component: ImageNode - }, previewLf.value) - register({ - type: 'assetSelector', - component: AssetSelectorNode - }, previewLf.value) - register({ - type: 'textNode', - component: TextNode, - model: TextNodeModel - }, previewLf.value) - register({ - type: 'vectorNode', - component: VectorNode, - model: VectorNodeModel - }, previewLf.value) + // 注册节点(支持外部注入) + registerFlowNodes(previewLf.value, props.nodeRegistrations) // 渲染数据 if (props.data) { @@ -257,9 +259,23 @@ watch(() => props.mode, (newMode) => { setTimeout(() => { initPreviewMode() }, 100) + } else { + destroyPreviewMode() } }) +watch( + [() => props.capability, () => props.plugins, () => props.nodeRegistrations], + () => { + if (props.mode === 'preview') { + setTimeout(() => { + initPreviewMode() + }, 0) + } + }, + { deep: true } +) + // 初始化 onMounted(() => { if (props.mode === 'preview') { @@ -277,10 +293,7 @@ onMounted(() => { // 清理 onBeforeUnmount(() => { - if (previewLf.value) { - previewLf.value.destroy() - previewLf.value = null - } + destroyPreviewMode() destroyLogicFlowInstance() }) diff --git a/src/flowRuntime.ts b/src/flowRuntime.ts new file mode 100644 index 0000000..5af7027 --- /dev/null +++ b/src/flowRuntime.ts @@ -0,0 +1,63 @@ +import type LogicFlow from '@logicflow/core' +import { Menu, Label, Snapshot, SelectionSelect, MiniMap, Control } from '@logicflow/extension' +import { register } from '@logicflow/vue-node-registry' + +import ImageNode from './components/flow/nodes/common/ImageNode.vue' +import AssetSelectorNode from './components/flow/nodes/common/AssetSelectorNode.vue' +import TextNode from './components/flow/nodes/common/TextNode.vue' +import TextNodeModel from './components/flow/nodes/common/TextNodeModel' +import VectorNode from './components/flow/nodes/common/VectorNode.vue' +import VectorNodeModel from './components/flow/nodes/common/VectorNodeModel' + +export type FlowCapabilityLevel = 'render-only' | 'interactive' + +export interface FlowNodeRegistration { + type: string + component?: any + model?: any +} + +export type FlowPlugin = any + +const DEFAULT_FLOW_NODES: FlowNodeRegistration[] = [ + { type: 'imageNode', component: ImageNode }, + { type: 'assetSelector', component: AssetSelectorNode }, + { type: 'textNode', component: TextNode, model: TextNodeModel }, + { type: 'vectorNode', component: VectorNode, model: VectorNodeModel } +] + +const FLOW_PLUGIN_PRESETS: Record = { + 'render-only': [Snapshot], + interactive: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control] +} + +export function getFlowPluginsByCapability(capability: FlowCapabilityLevel): FlowPlugin[] { + return [...FLOW_PLUGIN_PRESETS[capability]] +} + +export function resolveFlowPlugins( + capability: FlowCapabilityLevel, + plugins?: FlowPlugin[] +): FlowPlugin[] { + if (Array.isArray(plugins) && plugins.length > 0) { + return plugins + } + return getFlowPluginsByCapability(capability) +} + +export function getDefaultFlowNodes(): FlowNodeRegistration[] { + return [...DEFAULT_FLOW_NODES] +} + +export function resolveFlowNodes(nodes?: FlowNodeRegistration[]): FlowNodeRegistration[] { + if (Array.isArray(nodes) && nodes.length > 0) { + return nodes + } + return getDefaultFlowNodes() +} + +export function registerFlowNodes(lfInstance: LogicFlow, nodes?: FlowNodeRegistration[]) { + const registrations = resolveFlowNodes(nodes) + registrations.forEach((registration) => register(registration, lfInstance)) +} + diff --git a/src/index.js b/src/index.js index d5b1822..a1810b9 100644 --- a/src/index.js +++ b/src/index.js @@ -10,3 +10,4 @@ export default YysEditorEmbed // 类型导出 export * from './YysEditorEmbed.vue' +export * from './flowRuntime'