mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-03-05 15:05:27 +00:00
feat: 实现矢量节点 MVP 功能
- 扩展 NodeProperties 接口,添加 vector 字段定义 - 创建 VectorNode.vue 组件,使用 SVG Pattern 实现自动平铺 - 创建 VectorNodeModel.ts 数据模型,处理节点初始化和 resize - 创建 VectorPanel.vue 属性面板,支持图形类型、平铺尺寸、颜色等配置 - 在 FlowEditor.vue 中注册 vectorNode - 在 ComponentsPanel.vue 中添加到组件库 - 在 PropertyPanel.vue 中注册属性面板 功能特性: - 支持 5 种图形类型(矩形/椭圆/多边形/路径/自定义SVG) - 节点缩放时矢量图自动重复平铺 - 可调整平铺尺寸(10-500px) - 支持填充和描边颜色配置 - 实时预览,属性修改立即生效
This commit is contained in:
106
src/components/flow/nodes/common/VectorNode.vue
Normal file
106
src/components/flow/nodes/common/VectorNode.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useNodeAppearance } from '@/ts/useNodeAppearance';
|
||||
|
||||
const vectorConfig = ref({
|
||||
kind: 'rect',
|
||||
svgContent: '',
|
||||
path: '',
|
||||
tileWidth: 50,
|
||||
tileHeight: 50,
|
||||
fill: '#409EFF',
|
||||
stroke: '#303133',
|
||||
strokeWidth: 1
|
||||
});
|
||||
|
||||
const nodeSize = ref({ width: 200, height: 200 });
|
||||
|
||||
const { containerStyle } = useNodeAppearance({
|
||||
onPropsChange(props, node) {
|
||||
// 同步矢量配置
|
||||
if (props.vector) {
|
||||
Object.assign(vectorConfig.value, props.vector);
|
||||
}
|
||||
// 同步节点尺寸
|
||||
if (node) {
|
||||
nodeSize.value.width = node.width;
|
||||
nodeSize.value.height = node.height;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 生成唯一的 pattern ID
|
||||
const patternId = computed(() => `vector-pattern-${Math.random().toString(36).substr(2, 9)}`);
|
||||
|
||||
// 生成 SVG 内容
|
||||
const svgContent = computed(() => {
|
||||
const { kind, path, tileWidth, tileHeight, fill, stroke, strokeWidth } = vectorConfig.value;
|
||||
|
||||
let shapeElement = '';
|
||||
switch (kind) {
|
||||
case 'rect':
|
||||
shapeElement = `<rect x="0" y="0" width="${tileWidth}" height="${tileHeight}"
|
||||
fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
||||
break;
|
||||
case 'ellipse':
|
||||
shapeElement = `<ellipse cx="${tileWidth/2}" cy="${tileHeight/2}"
|
||||
rx="${tileWidth/2 - strokeWidth}" ry="${tileHeight/2 - strokeWidth}"
|
||||
fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
||||
break;
|
||||
case 'path':
|
||||
shapeElement = `<path d="${path || 'M 0 0'}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
||||
break;
|
||||
case 'svg':
|
||||
shapeElement = vectorConfig.value.svgContent || '';
|
||||
break;
|
||||
case 'polygon':
|
||||
// 默认三角形
|
||||
const points = `0,${tileHeight} ${tileWidth/2},0 ${tileWidth},${tileHeight}`;
|
||||
shapeElement = `<polygon points="${points}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" />`;
|
||||
break;
|
||||
}
|
||||
|
||||
return `
|
||||
<svg width="${nodeSize.value.width}" height="${nodeSize.value.height}"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="${patternId.value}"
|
||||
x="0" y="0"
|
||||
width="${tileWidth}"
|
||||
height="${tileHeight}"
|
||||
patternUnits="userSpaceOnUse">
|
||||
${shapeElement}
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#${patternId.value})" />
|
||||
</svg>
|
||||
`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vector-node" :style="containerStyle">
|
||||
<div class="vector-content" v-html="svgContent"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vector-node {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.vector-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vector-content :deep(svg) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user