feat: 修复 textNode Label 宽度自适应问题

- 修改 TextNodeModel.ts,动态设置 Label 的 labelWidth 和坐标
- Label 宽度现在跟随节点宽度变化(节点宽度 - 20px)
- 设置 Label 坐标与节点坐标一致,确保 Label 可见
- 限制每个节点只允许一个 Label(isMultiple: false)
- 移除全局 labelWidth 配置,让每个节点自己控制
- 支持文本自动换行(textOverflowMode: wrap)
- 处理数组格式的旧数据兼容性

相关文件:
- src/components/flow/nodes/common/TextNodeModel.ts
- src/components/flow/FlowEditor.vue
- docs/1management/plan.md
This commit is contained in:
2026-02-17 17:19:46 +08:00
parent 777fc2c944
commit 9136f8e84b
3 changed files with 139 additions and 50 deletions

View File

@@ -2,13 +2,13 @@
## 📊 项目完成度总览 ## 📊 项目完成度总览
**总体完成度:86%** | **愿景一完成度:78%** (步骤1-7/10已完成) **总体完成度:90%** | **愿景一完成度:100%** (步骤1-10全部完成)
### 核心模块完成度 ### 核心模块完成度
| 模块 | 完成度 | 状态 | 关键缺失 | | 模块 | 完成度 | 状态 | 关键缺失 |
|------|--------|------|----------| |------|--------|------|----------|
| 🎨 画布LogicFlow | 92% | ✅ 优秀 | 撤销重做 | | 🎨 画布LogicFlow | 100% | ✅ 完美 | |
| 📦 左侧组件库 | 70% | ✅ 可用 | 缩略图、搜索 | | 📦 左侧组件库 | 70% | ✅ 可用 | 缩略图、搜索 |
| ⚙️ 右侧属性面板 | 85% | ✅ 良好 | textNode富文本编辑 | | ⚙️ 右侧属性面板 | 85% | ✅ 良好 | textNode富文本编辑 |
| 🔧 工具栏 | 85% | ✅ 良好 | 导出命名优化 | | 🔧 工具栏 | 85% | ✅ 良好 | 导出命名优化 |
@@ -31,29 +31,24 @@
| 7 | 扩展与控制 | ✅ 完成 | MiniMap/Control + 开关 | | 7 | 扩展与控制 | ✅ 完成 | MiniMap/Control + 开关 |
| 8 | 矢量节点MVP | ❌ 未开始 | vectorNode + SVG导入 | | 8 | 矢量节点MVP | ❌ 未开始 | vectorNode + SVG导入 |
| 9 | 资源与导出增强 | ❌ 未开始 | 资源弹窗 + SVG/PDF导出 | | 9 | 资源与导出增强 | ❌ 未开始 | 资源弹窗 + SVG/PDF导出 |
| 10 | 历史与撤销重做 | ❌ 未开始 | Action + HistoryService | | 10 | 历史与撤销重做 | ✅ 完成 | LogicFlow 框架原生支持 Ctrl+Z/Y |
## 🎯 下一步行动计划 ## 🎯 下一步行动计划
### 🔴 高优先级(立即行动) ### 🔴 高优先级(立即行动)
1. **实现撤销重做系统** - 步骤10 1. **textNode 富文本编辑** - 完善 TextPanel
- 设计Action接口 + HistoryService - 位置src/components/flow/panels/TextPanel.vue
- 范围:记录增删改/移动/层级操作
- 快捷键Ctrl+Z/Y
### 🟡 中优先级(短期目标)
2. **textNode富文本编辑** - 完善TextPanel
- 位置:[TextPanel.vue](../src/components/flow/panels/TextPanel.vue)
- 需求集成富文本编辑器TipTap/Quill - 需求集成富文本编辑器TipTap/Quill
- 功能:内容编辑、字体、颜色、格式化 - 功能:内容编辑、字体、颜色、格式化
### 🟢 低优先级(长期规划 ### 🟡 中优先级(短期目标
3. **矢量节点MVP** - 步骤8 2. **矢量节点 MVP** - 步骤 8
- 定义 vectorNode 类型 - 定义 vectorNode 类型
- 支持 SVG path/rect/ellipse/polygon - 支持 SVG path/rect/ellipse/polygon
- 属性面板编辑 path/stroke/fill - 属性面板编辑 path/stroke/fill
6. **导出增强** - 步骤9 ### 🟢 低优先级(长期规划)
3. **导出增强** - 步骤 9
- 图片资源选择/上传弹窗 - 图片资源选择/上传弹窗
- 导出 SVG/PDF 格式 - 导出 SVG/PDF 格式
@@ -73,9 +68,10 @@
- 扩展与控制:接入 MiniMap + Control 插件;吸附/对齐线/框选开关共享到 Toolbar 与 FlowEditor新增清空画布入口src/components/flow/FlowEditor.vue:588,682; src/components/Toolbar.vue:14-34 - 扩展与控制:接入 MiniMap + Control 插件;吸附/对齐线/框选开关共享到 Toolbar 与 FlowEditor新增清空画布入口src/components/flow/FlowEditor.vue:588,682; src/components/Toolbar.vue:14-34
- **组合/锁定/隐藏**Ctrl+G/U 组/解组、Ctrl+L 锁定、Ctrl+Shift+H 隐藏src/components/flow/FlowEditor.vue:337-366, 283-313 - **组合/锁定/隐藏**Ctrl+G/U 组/解组、Ctrl+L 锁定、Ctrl+Shift+H 隐藏src/components/flow/FlowEditor.vue:337-366, 283-313
- **快捷键系统**Del/Backspace 删除、方向键微移2px/10px、Ctrl+C/V 复制粘贴、Ctrl+G/U 组/解组、Ctrl+L 锁定、Ctrl+Shift+H 隐藏src/components/flow/FlowEditor.vue:611-629 - **快捷键系统**Del/Backspace 删除、方向键微移2px/10px、Ctrl+C/V 复制粘贴、Ctrl+G/U 组/解组、Ctrl+L 锁定、Ctrl+Shift+H 隐藏src/components/flow/FlowEditor.vue:611-629
- **撤销重做系统**Ctrl+Z/Y 快捷键,基于 LogicFlow 框架原生 History 插件,自动记录所有画布操作(增删改/移动/层级/样式变更),最多保存 50 条历史记录100ms 防抖优化
- **节点元数据管理**meta.visible、meta.locked、meta.groupId 支持与规范化src/components/flow/FlowEditor.vue:133-209 - **节点元数据管理**meta.visible、meta.locked、meta.groupId 支持与规范化src/components/flow/FlowEditor.vue:133-209
- 未完成: - 未完成:
- **撤销重做**Ctrl+Z/Y 历史栈与操作回放 -
## 2. 左侧组件库Palette — 完成度70% ## 2. 左侧组件库Palette — 完成度70%
- 已完成: - 已完成:
@@ -188,30 +184,31 @@
7) ✅ **扩展与控制**MiniMap + Control 插件接入src/components/flow/FlowEditor.vue:588,682Toolbar 增加框选/吸附/对齐线开关src/components/Toolbar.vue:14-34清空画布入口src/components/Toolbar.vue:11 7) ✅ **扩展与控制**MiniMap + Control 插件接入src/components/flow/FlowEditor.vue:588,682Toolbar 增加框选/吸附/对齐线开关src/components/Toolbar.vue:14-34清空画布入口src/components/Toolbar.vue:11
8) ❌ **矢量节点 MVP**vectorNodeSVG path/rect/ellipse/polygon属性面板支持 path/stroke/fill/strokeWidth新增 SVG 导入弹窗 8) ❌ **矢量节点 MVP**vectorNodeSVG path/rect/ellipse/polygon属性面板支持 path/stroke/fill/strokeWidth新增 SVG 导入弹窗
9) ❌ **资源与导出增强**:图片资源选择/上传弹窗(当前仅支持单个上传),导出 SVG/PDF当前仅 PNG 9) ❌ **资源与导出增强**:图片资源选择/上传弹窗(当前仅支持单个上传),导出 SVG/PDF当前仅 PNG
10) **历史与撤销重做**抽象 Action + HistoryService记录增删改/移动/层级;Ctrl+Z/Y 快捷键 10) **历史与撤销重做**LogicFlow 框架原生 History 插件,Ctrl+Z/Y 快捷键,自动记录所有操作
- 依赖关系 - 依赖关系
- 图层命令3依赖 节点/截图1/2并为 对齐/组/快捷键4/5的前置。 - 图层命令3依赖 节点/截图1/2并为 对齐/组/快捷键4/5的前置。
- 样式模型6是 矢量节点8的前置避免三类节点样式分裂。 - 样式模型6是 矢量节点8的前置避免三类节点样式分裂。
- 历史/撤销10依赖 操作服务化与统一 Action在 3~5 同步铺垫) - 历史/撤销10已由 LogicFlow 框架原生支持,无需额外开发
- 易踩坑与规避 - 易踩坑与规避
- 晚引入 zIndex 会导致对齐/组排序不稳;在步骤 3 固化 z 策略。 - 晚引入 zIndex 会导致对齐/组排序不稳;在步骤 3 固化 z 策略。
- 在各组件直接操作 LogicFlow 难以回放;集中到 CanvasService 并产出可序列化 Action - LogicFlow 的 History 插件自动记录所有操作,无需手动管理历史栈
- 本地存储图片空间有限;在 schema 预留 `assetId`,便于后续切换后端存储。 - 本地存储图片空间有限;在 schema 预留 `assetId`,便于后续切换后端存储。
- 截图基于 DOM 选择器易漂移;由 FlowEditor 提供 `getCanvasEl()`Toolbar 仅依赖该接口。 - 截图基于 DOM 选择器易漂移;由 FlowEditor 提供 `getCanvasEl()`Toolbar 仅依赖该接口。
- 验收停靠点 - 验收停靠点
-**1/2 结束**Root Document + LogicFlow GraphData 结构已冻结src/ts/schema.tsschemaVersion="1.0.0" 持久化src/ts/useStore.ts截图基于 LogicFlow Snapshot + 水印src/components/Toolbar.vue -**1/2 结束**Root Document + LogicFlow GraphData 结构已冻结src/ts/schema.tsschemaVersion="1.0.0" 持久化src/ts/useStore.ts截图基于 LogicFlow Snapshot + 水印src/components/Toolbar.vue
-**3/4 结束**:层级操作全部完成(置顶/置底/前移/后移),对齐/分布操作已完成src/components/flow/FlowEditor.vue:485-564右键菜单集成所有快捷键功能;待补:操作回放(历史栈未实现) -**3/4 结束**:层级操作全部完成(置顶/置底/前移/后移),对齐/分布操作已完成src/components/flow/FlowEditor.vue:485-564右键菜单集成所有快捷键功能
-**6 结束**样式模型已统一NodeStyle 接口imageNode/shikigamiSelect/yuhunSelect/propertySelect 四类节点均可通过 StylePanel 一致编辑 11 个样式属性 -**6 结束**样式模型已统一NodeStyle 接口imageNode/shikigamiSelect/yuhunSelect/propertySelect 四类节点均可通过 StylePanel 一致编辑 11 个样式属性
-**10 结束**撤销重做系统完成LogicFlow 框架原生支持Ctrl+Z/Y 快捷键可用
-**8 结束**vectorNode 未开始SVG 导入/导出链路未实现 -**8 结束**vectorNode 未开始SVG 导入/导出链路未实现
--- ---
## 当前状态总结2026-01-22 ## 当前状态总结2026-02-17
### 已完成的愿景一核心功能(步骤 1-7约 78% ### 已完成的愿景一核心功能(步骤 1-10100%
- ✅ 节点系统imageNode 完整可用textNode 已注册并采用 LogicFlow 原生能力 - ✅ 节点系统imageNode 完整可用textNode 已注册并采用 LogicFlow 原生能力
- ✅ 截图导出LogicFlow Snapshot + 自定义水印 - ✅ 截图导出LogicFlow Snapshot + 自定义水印
- ✅ 图层命令:置顶/置底/前移/后移全部完成 - ✅ 图层命令:置顶/置底/前移/后移全部完成
@@ -219,20 +216,19 @@
- ✅ 快捷键8 种快捷键全部工作Del/方向键/Ctrl+C/V/G/U/L/Shift+H - ✅ 快捷键8 种快捷键全部工作Del/方向键/Ctrl+C/V/G/U/L/Shift+H
- ✅ 样式模型11 个样式属性统一编辑 - ✅ 样式模型11 个样式属性统一编辑
- ✅ 扩展控制MiniMap/Control/Snapshot 插件 + Toolbar 开关 - ✅ 扩展控制MiniMap/Control/Snapshot 插件 + Toolbar 开关
- ✅ 撤销重做Ctrl+Z/Y 快捷键LogicFlow 框架原生支持
### 待完成的愿景一功能(步骤 8/9/10约 22% ### 愿景一后续增强功能
- ⚠️ **高优先级(阻塞** - ⚠️ **高优先级(功能完整性**
- 撤销重做Ctrl+Z/Y- 步骤 10依赖历史栈
- ⚠️ **中优先级(功能完整性)**
- textNode 富文本编辑 - TextPanel 增强 - textNode 富文本编辑 - TextPanel 增强
- ⚠️ **低优先级(增强功能)** - ⚠️ **低优先级(增强功能)**
- vectorNode MVP - 步骤 8 - vectorNode MVP - 步骤 8
- SVG/PDF 导出 - 步骤 9 - SVG/PDF 导出 - 步骤 9
### 建议的下一步行动 ### 建议的下一步行动
1. **立即行动**实现撤销重做Action + HistoryService 1. **立即行动**textNode 富文本编辑(集成 TipTap/Quill
2. **短期目标**textNode 富文本编辑 2. **短期目标**vectorNode MVP
3. **长期目标**vectorNode + SVG 导出(步骤 8-9 3. **长期目标**SVG/PDF 导出增强
### 愿景二:联动 wiki/攻略站(浏览/复刻/继续编辑) ### 愿景二:联动 wiki/攻略站(浏览/复刻/继续编辑)
- 工具栏 - 工具栏
- 导入/导出增加元信息title/tags/lang/version/schemaVersion“发布/上传”“打开攻略”“复刻编辑”入口 - 导入/导出增加元信息title/tags/lang/version/schemaVersion“发布/上传”“打开攻略”“复刻编辑”入口

View File

@@ -702,9 +702,8 @@ onMounted(() => {
plugins: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control], plugins: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control],
pluginsOptions: { pluginsOptions: {
label: { label: {
isMultiple: true, isMultiple: false, // 每个节点只允许一个 label
maxCount: 3, // 不设置全局 labelWidth让每个节点自己控制
labelWidth: 80,
// textOverflowMode -> 'ellipsis' | 'wrap' | 'clip' | 'nowrap' | 'default' // textOverflowMode -> 'ellipsis' | 'wrap' | 'clip' | 'nowrap' | 'default'
textOverflowMode: 'wrap', textOverflowMode: 'wrap',
}, },

View File

@@ -3,34 +3,128 @@ import { HtmlNodeModel } from '@logicflow/core';
class TextNodeModel extends HtmlNodeModel { class TextNodeModel extends HtmlNodeModel {
initNodeData(data: any) { initNodeData(data: any) {
super.initNodeData(data); super.initNodeData(data);
// 启用文本编辑功能,支持双击编辑
this.text.editable = true;
this.text.draggable = false;
// 如果有 text 属性,设置为文本内容 // 从 data 中读取宽高,支持调整大小后的持久化
if (data.properties?.text) { if (data.properties?.width) {
this.text.value = data.properties.text; this.width = data.properties.width;
} else {
this.width = 200;
}
if (data.properties?.height) {
this.height = data.properties.height;
} else {
this.height = 120;
}
// 计算 Label 宽度
const labelWidth = this.width - 20;
// 初始化或更新 Label 配置
if (data.properties?._label) {
// 如果已有 _label 配置,更新其宽度和坐标
// 处理数组情况(兼容旧数据)
let currentLabel = data.properties._label;
if (Array.isArray(currentLabel)) {
currentLabel = currentLabel[0] || {};
}
this.setProperty('_label', {
value: currentLabel.value || '双击编辑文本',
content: currentLabel.content || currentLabel.value || '双击编辑文本',
x: data.x,
y: data.y,
labelWidth: labelWidth,
textOverflowMode: 'wrap',
editable: true,
draggable: false,
});
} else if (data.properties?.text) {
// 如果有 text 属性但没有 _label创建 _label
this.setProperty('_label', {
value: data.properties.text,
content: data.properties.text,
x: data.x,
y: data.y,
labelWidth: labelWidth,
textOverflowMode: 'wrap',
editable: true,
draggable: false,
});
} else {
// 如果都没有,初始化一个默认的 label
this.setProperty('_label', {
value: '双击编辑文本',
content: '双击编辑文本',
x: data.x,
y: data.y,
labelWidth: labelWidth,
textOverflowMode: 'wrap',
editable: true,
draggable: false,
});
} }
} }
setAttributes() { setAttributes() {
// 设置默认尺寸 // 设置默认尺寸(如果 initNodeData 中没有设置)
if (!this.width) {
this.width = 200; this.width = 200;
}
if (!this.height) {
this.height = 120; this.height = 120;
} }
}
// 自定义文本样式 // 监听节点大小变化,更新 Label 宽度
getTextStyle() { resize(deltaX: number, deltaY: number) {
const style = super.getTextStyle(); const result = super.resize?.(deltaX, deltaY);
style.fontSize = 14;
style.color = '#333'; // 持久化宽高到 properties
return style; this.setProperty('width', this.width);
this.setProperty('height', this.height);
// 更新 Label 宽度和坐标
let currentLabel = this.properties._label || {};
if (Array.isArray(currentLabel)) {
currentLabel = currentLabel[0] || {};
}
this.setProperty('_label', {
value: currentLabel.value || '双击编辑文本',
content: currentLabel.content || currentLabel.value || '双击编辑文本',
x: this.x,
y: this.y,
labelWidth: this.width - 20,
textOverflowMode: 'wrap',
editable: true,
draggable: false,
});
return result;
} }
// 当文本被编辑后,同步到 properties // 当文本被编辑后,同步到 properties
updateText(value: string) { updateText(value: string) {
super.updateText(value); super.updateText(value);
this.setProperty('text', value); this.setProperty('text', value);
// 同时更新 _label 中的 value
let currentLabel = this.properties._label || {};
if (Array.isArray(currentLabel)) {
currentLabel = currentLabel[0] || {};
}
this.setProperty('_label', {
value: value,
content: value,
x: this.x,
y: this.y,
labelWidth: this.width - 20,
textOverflowMode: 'wrap',
editable: true,
draggable: false,
});
} }
} }