feat: 简化 TextNode 实现,完全依赖 LogicFlow 原生文本节点能力

- 移除自定义文本编辑逻辑,交由 LogicFlow 和 TextNodeModel 处理
- TextNode.vue 简化为空容器,LogicFlow 自动渲染文本内容
- 保留 TextNodeModel.ts 用于配置文本样式和编辑行为
- 采用模型-视图分离架构,符合 LogicFlow 设计模式
This commit is contained in:
2026-02-14 23:47:31 +08:00
parent 4a4a55110b
commit eb26deff72
5 changed files with 95 additions and 54 deletions

View File

@@ -45,9 +45,9 @@ const componentGroups = [
id: 'text', id: 'text',
name: '文字编辑框', name: '文字编辑框',
type: 'textNode', type: 'textNode',
description: '可编辑文本的节点', description: '可编辑文本的节点',
data: { data: {
html: '<div>双击右侧可编辑文字</div>', text: '双击编辑文字',
width: 200, width: 200,
height: 120 height: 120
} }

View File

@@ -74,7 +74,8 @@ import YuhunSelectNode from './nodes/yys/YuhunSelectNode.vue';
import PropertySelectNode from './nodes/yys/PropertySelectNode.vue'; import PropertySelectNode from './nodes/yys/PropertySelectNode.vue';
import ImageNode from './nodes/common/ImageNode.vue'; import ImageNode from './nodes/common/ImageNode.vue';
import AssetSelectorNode from './nodes/common/AssetSelectorNode.vue'; import AssetSelectorNode from './nodes/common/AssetSelectorNode.vue';
// import TextNode from './nodes/common/TextNode.vue'; import TextNode from './nodes/common/TextNode.vue';
import TextNodeModel from './nodes/common/TextNodeModel';
import PropertyPanel from './PropertyPanel.vue'; import PropertyPanel from './PropertyPanel.vue';
import { useGlobalMessage } from '@/ts/useGlobalMessage'; import { useGlobalMessage } from '@/ts/useGlobalMessage';
import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow'; import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow';
@@ -312,7 +313,7 @@ function bringForward(nodeId?: string) {
if (!targetId) return; if (!targetId) return;
const currentNode = lfInstance.getNodeModelById(targetId); const currentNode = lfInstance.getNodeModelById(targetId);
if (!currentNode) return; if (!currentNode) return;t
const currentZIndex = currentNode.zIndex; const currentZIndex = currentNode.zIndex;
currentNode.setZIndex(currentZIndex + 1); currentNode.setZIndex(currentZIndex + 1);
@@ -662,7 +663,7 @@ function registerNodes(lfInstance: LogicFlow) {
register({ type: 'imageNode', component: ImageNode }, lfInstance); register({ type: 'imageNode', component: ImageNode }, lfInstance);
register({ type: 'assetSelector', component: AssetSelectorNode }, lfInstance); register({ type: 'assetSelector', component: AssetSelectorNode }, lfInstance);
// register({ type: 'textNode', component: TextNode }, lfInstance); register({ type: 'textNode', component: TextNode, model: TextNodeModel }, lfInstance);
} }
// 初始化 LogicFlow // 初始化 LogicFlow
@@ -677,6 +678,22 @@ onMounted(() => {
keyboard: { keyboard: {
enabled: true enabled: true
}, },
style: {
text: {
color: '#333333',
fontSize: 14,
background: {
fill: '#ffffff',
stroke: '#dcdfe6',
strokeWidth: 1,
radius: 4
}
},
nodeText: {
color: '#333333',
fontSize: 14
}
},
plugins: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control], plugins: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control],
pluginsOptions: { pluginsOptions: {
label: { label: {

View File

@@ -1,51 +1,30 @@
<!--<script setup lang="ts">--> <script setup lang="ts">
<!--import { ref, watch } from 'vue';--> // LogicFlow 会自动处理文本节点的渲染和编辑
<!--import { Handle, useVueFlow } from '@vue-flow/core';--> </script>
<!--import { NodeResizer } from '@vue-flow/node-resizer';-->
<!--import '@vue-flow/node-resizer/dist/style.css';-->
<!--const props = defineProps({--> <template>
<!-- data: Object,--> <div class="text-content">
<!-- id: String,--> <!-- LogicFlow 会自动渲染 text 内容 -->
<!-- selected: Boolean--> </div>
<!--});--> </template>
<!--const nodeWidth = ref(200);--> <style scoped>
<!--const nodeHeight = ref(120);--> .text-content {
<!--const html = ref('');--> width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
outline: none;
word-break: break-word;
padding: 4px;
box-sizing: border-box;
cursor: text;
}
<!--watch(() => props.data, (newData) => {--> .text-content:focus {
<!-- if (newData && newData.html !== undefined) html.value = newData.html;--> background: rgba(64, 158, 255, 0.1);
<!-- if (newData && newData.width) nodeWidth.value = newData.width;--> border-radius: 2px;
<!-- if (newData && newData.height) nodeHeight.value = newData.height;--> }
<!--}, { immediate: true });--> </style>
<!--</script>-->
<!--<template>-->
<!-- <div class="text-node" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">-->
<!-- <NodeResizer v-if="selected" :min-width="80" :min-height="40" :max-width="400" :max-height="400" />-->
<!-- <Handle type="target" position="left" :id="`${id}-target`" />-->
<!-- <div class="text-content" v-html="html"></div>-->
<!-- <Handle type="source" position="right" :id="`${id}-source`" />-->
<!-- </div>-->
<!--</template>-->
<!--<style scoped>-->
<!--.text-node {-->
<!-- background: #fff;-->
<!-- border: 1px solid #dcdfe6;-->
<!-- border-radius: 4px;-->
<!-- display: flex;-->
<!-- flex-direction: column;-->
<!-- align-items: center;-->
<!-- justify-content: center;-->
<!-- overflow: hidden;-->
<!--}-->
<!--.text-content {-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!-- padding: 8px;-->
<!-- font-size: 15px;-->
<!-- color: #333;-->
<!-- word-break: break-all;-->
<!-- overflow: auto;-->
<!--}-->
<!--</style>-->

View File

@@ -0,0 +1,37 @@
import { HtmlNodeModel } from '@logicflow/core';
class TextNodeModel extends HtmlNodeModel {
initNodeData(data: any) {
super.initNodeData(data);
// 启用文本编辑功能,支持双击编辑
this.text.editable = true;
this.text.draggable = false;
// 如果有 text 属性,设置为文本内容
if (data.properties?.text) {
this.text.value = data.properties.text;
}
}
setAttributes() {
// 设置默认尺寸
this.width = 200;
this.height = 120;
}
// 自定义文本样式
getTextStyle() {
const style = super.getTextStyle();
style.fontSize = 14;
style.color = '#333';
return style;
}
// 当文本被编辑后,同步到 properties
updateText(value: string) {
super.updateText(value);
this.setProperty('text', value);
}
}
export default TextNodeModel;

View File

@@ -8,7 +8,15 @@ const props = defineProps<{
<div class="property-section"> <div class="property-section">
<div class="section-header">文本节点</div> <div class="section-header">文本节点</div>
<div class="property-item"> <div class="property-item">
<div class="property-value">文本编辑器待实现当前节点内容{{ props.node?.properties?.text?.content || '未设置' }}</div> <label class="property-label">内容</label>
<div class="property-value">
{{ props.node?.properties?.text || '未设置' }}
</div>
</div>
<div class="property-item">
<div class="property-note">
💡 提示双击画布中的节点即可编辑文字
</div>
</div> </div>
</div> </div>
</template> </template>