diff --git a/src/components/flow/ComponentsPanel.vue b/src/components/flow/ComponentsPanel.vue index dcbe388..524d394 100644 --- a/src/components/flow/ComponentsPanel.vue +++ b/src/components/flow/ComponentsPanel.vue @@ -51,6 +51,24 @@ const componentGroups = [ width: 200, height: 120 } + }, + { + id: 'vector', + name: '矢量图块', + type: 'vectorNode', + description: '可平铺的矢量图形,用于边框装饰', + data: { + vector: { + kind: 'rect', + tileWidth: 50, + tileHeight: 50, + fill: '#409EFF', + stroke: '#303133', + strokeWidth: 1 + }, + width: 200, + height: 200 + } } ] }, diff --git a/src/components/flow/FlowEditor.vue b/src/components/flow/FlowEditor.vue index abda04c..4562ef4 100644 --- a/src/components/flow/FlowEditor.vue +++ b/src/components/flow/FlowEditor.vue @@ -74,6 +74,8 @@ import ImageNode from './nodes/common/ImageNode.vue'; import AssetSelectorNode from './nodes/common/AssetSelectorNode.vue'; import TextNode from './nodes/common/TextNode.vue'; import TextNodeModel from './nodes/common/TextNodeModel'; +import VectorNode from './nodes/common/VectorNode.vue'; +import VectorNodeModel from './nodes/common/VectorNodeModel'; import PropertyPanel from './PropertyPanel.vue'; import { useGlobalMessage } from '@/ts/useGlobalMessage'; import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow'; @@ -669,6 +671,7 @@ function registerNodes(lfInstance: LogicFlow) { register({ type: 'imageNode', component: ImageNode }, lfInstance); register({ type: 'assetSelector', component: AssetSelectorNode }, lfInstance); register({ type: 'textNode', component: TextNode, model: TextNodeModel }, lfInstance); + register({ type: 'vectorNode', component: VectorNode, model: VectorNodeModel }, lfInstance); } // 初始化 LogicFlow diff --git a/src/components/flow/PropertyPanel.vue b/src/components/flow/PropertyPanel.vue index 4e841ce..3c286af 100644 --- a/src/components/flow/PropertyPanel.vue +++ b/src/components/flow/PropertyPanel.vue @@ -5,6 +5,7 @@ import ImagePanel from './panels/ImagePanel.vue'; import TextPanel from './panels/TextPanel.vue'; import StylePanel from './panels/StylePanel.vue'; import AssetSelectorPanel from './panels/AssetSelectorPanel.vue'; +import VectorPanel from './panels/VectorPanel.vue'; import { ASSET_LIBRARIES } from '@/types/nodeTypes'; import { getLogicFlowInstance } from '@/ts/useLogicFlow'; @@ -33,7 +34,8 @@ const panelMap: Record = { propertySelect: PropertyRulePanel, imageNode: ImagePanel, textNode: TextPanel, - assetSelector: AssetSelectorPanel + assetSelector: AssetSelectorPanel, + vectorNode: VectorPanel }; const panelComponent = computed(() => panelMap[nodeType.value] || null); diff --git a/src/components/flow/nodes/common/VectorNode.vue b/src/components/flow/nodes/common/VectorNode.vue new file mode 100644 index 0000000..316e9b3 --- /dev/null +++ b/src/components/flow/nodes/common/VectorNode.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/flow/nodes/common/VectorNodeModel.ts b/src/components/flow/nodes/common/VectorNodeModel.ts new file mode 100644 index 0000000..38c1a2e --- /dev/null +++ b/src/components/flow/nodes/common/VectorNodeModel.ts @@ -0,0 +1,44 @@ +import { HtmlNodeModel } from '@logicflow/core'; + +class VectorNodeModel extends HtmlNodeModel { + initNodeData(data: any) { + super.initNodeData(data); + + // 从 properties 读取宽高 + if (data.properties?.width) { + this.width = data.properties.width; + } else { + this.width = 200; + } + + if (data.properties?.height) { + this.height = data.properties.height; + } else { + this.height = 200; + } + + // 初始化默认矢量配置 + if (!data.properties?.vector) { + this.setProperty('vector', { + kind: 'rect', + tileWidth: 50, + tileHeight: 50, + fill: '#409EFF', + stroke: '#303133', + strokeWidth: 1 + }); + } + } + + resize(deltaX: number, deltaY: number) { + const result = super.resize?.(deltaX, deltaY); + + // 持久化宽高到 properties + this.setProperty('width', this.width); + this.setProperty('height', this.height); + + return result; + } +} + +export default VectorNodeModel; diff --git a/src/components/flow/panels/VectorPanel.vue b/src/components/flow/panels/VectorPanel.vue new file mode 100644 index 0000000..c1c623c --- /dev/null +++ b/src/components/flow/panels/VectorPanel.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/stores/files.ts b/src/stores/files.ts new file mode 100644 index 0000000..f631de5 --- /dev/null +++ b/src/stores/files.ts @@ -0,0 +1,35 @@ +import { defineStore } from 'pinia'; + +export const useFilesStore = defineStore('files', { + state: () => ({ + fileList: [{ label: 'File 1', name: 1, visible: false }, { label: 'File 2', name: 2, visible: false }], + activeFile: -1, + }), + getters: { + visibleFiles: (state) => state.fileList.filter(file => file.visible), + }, + actions: { + addFile(file: { label: string; name: number }) { + this.fileList.push({ ...file, visible: false }); + }, + setActiveFile(fileId: number) { + this.activeFile = fileId; + }, + setVisible(fileId: number, visibility: boolean) { + const file = this.fileList.find(file => file.name === fileId); + if (file) { + file.visible = visibility; + } + }, + closeTab(fileId: number) { + const file = this.fileList.find(file => file.name === fileId); + if (file) { + file.visible = false; + if (this.activeFile === fileId) { + const nextVisibleFile = this.visibleFiles[0]; + this.activeFile = nextVisibleFile ? nextVisibleFile.name : -1; + } + } + }, + }, +}); \ No newline at end of file diff --git a/src/ts/schema.ts b/src/ts/schema.ts index 0f9517d..abdb0e3 100644 --- a/src/ts/schema.ts +++ b/src/ts/schema.ts @@ -58,7 +58,17 @@ export interface NodeProperties { meta?: NodeMeta; image?: { url: string; fit?: 'fill'|'contain'|'cover' }; text?: { content: string; rich?: boolean }; - vector?: { kind: 'path'|'rect'|'ellipse'|'polygon'; path?: string; points?: Array<[number, number]> }; + vector?: { + kind: 'path' | 'rect' | 'ellipse' | 'polygon' | 'svg'; + svgContent?: string; + path?: string; + points?: Array<[number, number]>; + tileWidth: number; + tileHeight: number; + fill?: string; + stroke?: string; + strokeWidth?: number; + }; shikigami?: { name: string; avatar: string; rarity: string }; yuhun?: { name: string; type: string; avatar: string; shortName?: string }; property?: Record;