@mx-sose-front/mx-sose-graph 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +103 -10
- package/dist/index.esm.js +6620 -5599
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +7 -7
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/{ContextMenu.vue → ContextMenu/ContextMenu.vue} +243 -70
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +138 -0
- package/src/components/Edge/Edge.vue +38 -49
- package/src/components/InteractionLayer.vue +432 -838
- package/src/components/Shape/Block.vue +8 -8
- package/src/components/ZoomSlider/ZoomSlider.vue +229 -0
- package/src/constants/index.ts +12 -0
- package/src/statics/icons/childIcons//351/241/271/347/233/256/351/241/272/345/272/217@3x.png +0 -0
- package/src/store/graphStore.ts +98 -21
- package/src/types/index.ts +14 -1
- package/src/types/interactionLayer.ts +35 -0
- package/src/utils/contextMenuUtils.ts +264 -0
- package/src/utils/diagram.ts +93 -4
- package/src/utils/edgeUtils.ts +228 -0
- package/src/utils/geom.ts +34 -0
- package/src/utils/graphDragService.ts +14 -17
- package/src/utils/keyboardUtils.ts +229 -0
- package/src/utils/license-guard.ts +50 -0
- package/src/utils/nameEditUtils.ts +132 -0
- package/src/utils/resizeUtils.ts +463 -0
- package/src/view/graph.vue +102 -136
- package/src/components/Label.vue +0 -0
|
@@ -252,11 +252,12 @@ export const collectAffectedShapeIds = (shapes: Shape[], changedIds: string[], c
|
|
|
252
252
|
|
|
253
253
|
for (const id of allChanged) {
|
|
254
254
|
affected.add(id)
|
|
255
|
+
// 只算自己子树(组拖动需要)
|
|
256
|
+
collectDescendantIds(shapes, id).forEach(cid => affected.add(cid))
|
|
257
|
+
|
|
258
|
+
// 父节点本身可能受影响(容器/连线)
|
|
255
259
|
const parentId = shapes.find(s => s.id === id)?.parenShapeId ?? null
|
|
256
|
-
if (parentId)
|
|
257
|
-
affected.add(parentId)
|
|
258
|
-
collectDescendantIds(shapes, parentId).forEach(cid => affected.add(cid))
|
|
259
|
-
}
|
|
260
|
+
if (parentId) affected.add(parentId)
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
for (const id of Array.from(affected)) {
|
|
@@ -305,7 +306,9 @@ export const createOnNestDoneCallback = (options: {
|
|
|
305
306
|
|
|
306
307
|
const parent = shapes.find(s => s.id === childShape.parenShapeId)
|
|
307
308
|
if (!parent) return
|
|
308
|
-
|
|
309
|
+
// 只记录 parent 的前后 bounds
|
|
310
|
+
const beforeParent = JSON.stringify(parent.bounds ?? {})
|
|
311
|
+
const { expanded } = expandParentToFitChildBounds(
|
|
309
312
|
shapes,
|
|
310
313
|
parent,
|
|
311
314
|
childShape.id,
|
|
@@ -313,17 +316,11 @@ export const createOnNestDoneCallback = (options: {
|
|
|
313
316
|
updateShape,
|
|
314
317
|
)
|
|
315
318
|
if (!expanded) return
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
id: s.id,
|
|
323
|
-
bounds: JSON.stringify(s.bounds ?? {}),
|
|
324
|
-
}))
|
|
325
|
-
|
|
326
|
-
// 交给外面单独的监听去调 updateShapeSize 接口
|
|
327
|
-
eventBus.emit('shape-size-update', sizePayload)
|
|
319
|
+
const afterParent = JSON.stringify(parent.bounds ?? {})
|
|
320
|
+
if (afterParent == beforeParent) return
|
|
321
|
+
// 只更新当前嵌套的父图元(parent)
|
|
322
|
+
eventBus.emit('shape-size-update', [
|
|
323
|
+
{ id: parent.id, bounds: afterParent },
|
|
324
|
+
])
|
|
328
325
|
}
|
|
329
326
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { useGraphStore } from '../store/graphStore';
|
|
2
|
+
import { eventBus } from '../store';
|
|
3
|
+
// 定义快捷键处理函数类型
|
|
4
|
+
type KeyboardEventHandler = (e: KeyboardEvent) => void;
|
|
5
|
+
|
|
6
|
+
// 定义快捷键配置类型
|
|
7
|
+
interface KeyboardConfig {
|
|
8
|
+
onDelete?: () => void;
|
|
9
|
+
onEditProperty?: () => void;
|
|
10
|
+
onCancelConnection?: () => void;
|
|
11
|
+
onShapesRemove?: (items: Array<{ modelId: string; shapeId: string; shapeType: string; isRemoveModelTree: boolean }>) => void;
|
|
12
|
+
isEditingName?: () => boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 创建键盘事件处理器
|
|
17
|
+
* @param config 快捷键配置
|
|
18
|
+
* @returns 键盘事件处理函数
|
|
19
|
+
*/
|
|
20
|
+
export const createKeyboardHandler = (config: KeyboardConfig): KeyboardEventHandler => {
|
|
21
|
+
const graphStore = useGraphStore();
|
|
22
|
+
|
|
23
|
+
return (e: KeyboardEvent) => {
|
|
24
|
+
// 按下Esc且正在连线时,取消连线
|
|
25
|
+
if (e.key === 'Escape' && config.onCancelConnection) {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
config.onCancelConnection();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 处理Delete键移除图元
|
|
31
|
+
if (e.key === 'Delete') {
|
|
32
|
+
const target = e.target as HTMLElement | null;
|
|
33
|
+
if (target) {
|
|
34
|
+
const tag = target.tagName;
|
|
35
|
+
// 如果在原生输入类控件里(input / textarea)
|
|
36
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
37
|
+
return; // 让浏览器自己处理 Delete,删文字,不动图元
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 如果鼠标不在图元区域,不处理删除
|
|
42
|
+
if (!graphStore.museInGraphView) return;
|
|
43
|
+
e.preventDefault(); // 避免浏览器默认行为
|
|
44
|
+
|
|
45
|
+
// 选中的图元中如果有画布(diagram),则不允许删除
|
|
46
|
+
const selectedShapes = graphStore.marqueeShapes ?? [];
|
|
47
|
+
// 判断是否包含画布
|
|
48
|
+
const hasDiagram = selectedShapes.some(s =>
|
|
49
|
+
s.shapeType?.toLowerCase?.() === 'diagram'
|
|
50
|
+
);
|
|
51
|
+
if (hasDiagram) {
|
|
52
|
+
// 选中了画布,直接不处理删除
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const items = selectedShapes
|
|
57
|
+
.map(s => ({
|
|
58
|
+
modelId: s.modelId,
|
|
59
|
+
shapeId: s.id,
|
|
60
|
+
shapeType: s.shapeType,
|
|
61
|
+
isRemoveModelTree: false
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
if (items.length) {
|
|
65
|
+
eventBus.emit('shapes-remove', items);
|
|
66
|
+
if (config.onShapesRemove) {
|
|
67
|
+
config.onShapesRemove(items);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 按下Alt+B时触发树上高亮功能
|
|
73
|
+
if (e.altKey && e.key === 'b') {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
if (graphStore.selectedShape) {
|
|
76
|
+
eventBus.emit('highlight-shape', graphStore.selectedShape);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 按下Ctrl+A时选中所有图元
|
|
81
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
|
|
82
|
+
// 如果正在编辑名称,不执行全选功能,让输入框正常处理Ctrl+A
|
|
83
|
+
if (config.isEditingName && config.isEditingName()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
if (graphStore.museInGraphView) {
|
|
89
|
+
graphStore.selectAll();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 检查是否在输入元素中
|
|
94
|
+
const activeElement = document.activeElement;
|
|
95
|
+
const isInputElement = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.hasAttribute('contenteditable'));
|
|
96
|
+
|
|
97
|
+
// 按下Enter键时触发属性配置功能(不在输入框或文本域中时)
|
|
98
|
+
if (e.key === 'Enter' && graphStore.selectedShape && graphStore.museInGraphView && !isInputElement) {
|
|
99
|
+
// 如果正在编辑名称,不执行属性配置功能
|
|
100
|
+
if (config.isEditingName && config.isEditingName()) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
if (graphStore.museInGraphView && config.onEditProperty) {
|
|
106
|
+
config.onEditProperty();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 按下Ctrl+D时触发删除功能(不在输入框或文本域中时)
|
|
111
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'd' && graphStore.museInGraphView && !isInputElement) {
|
|
112
|
+
// 如果正在编辑名称,不执行删除功能
|
|
113
|
+
if (config.isEditingName && config.isEditingName()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
if (graphStore.selectedIds.length === 1 && graphStore.selectedShape && graphStore.selectedShape.shapeType?.toLowerCase?.() !== 'diagram' && graphStore.museInGraphView) {
|
|
119
|
+
// 单选情况下发送删除事件
|
|
120
|
+
eventBus.emit('shapes-delete', graphStore.selectedShape);
|
|
121
|
+
if (config.onDelete) {
|
|
122
|
+
config.onDelete();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 按下方向键时移动选中的图元(不在输入框或文本域中时)
|
|
128
|
+
if ((e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') && graphStore.museInGraphView && !isInputElement) {
|
|
129
|
+
// 如果正在编辑名称,不执行移动功能
|
|
130
|
+
if (config.isEditingName && config.isEditingName()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
const step = 1; // 移动距离
|
|
136
|
+
|
|
137
|
+
// 获取画布信息(diagram类型的shape)
|
|
138
|
+
const canvas = graphStore.shapes.find(shape => shape.shapeType === 'diagram');
|
|
139
|
+
const canvasX = canvas?.bounds?.x || 0;
|
|
140
|
+
const canvasY = canvas?.bounds?.y || 0;
|
|
141
|
+
const canvasWidth = canvas?.bounds?.width || 300; // 最小画布宽度
|
|
142
|
+
const canvasHeight = canvas?.bounds?.height || 200; // 最小画布高度
|
|
143
|
+
|
|
144
|
+
// 计算画布的边界约束
|
|
145
|
+
const canvasMinX = canvasX;
|
|
146
|
+
const canvasMinY = canvasY;
|
|
147
|
+
const canvasMaxX = canvasWidth + canvasX;
|
|
148
|
+
const canvasMaxY = canvasHeight + canvasY;
|
|
149
|
+
|
|
150
|
+
// 获取所有选中的图元
|
|
151
|
+
const selectedShapes = graphStore.selectedIds.map(id => graphStore.shapes.find(s => s.id === id)).filter(Boolean);
|
|
152
|
+
|
|
153
|
+
selectedShapes.forEach(shape => {
|
|
154
|
+
if (shape && shape.bounds) {
|
|
155
|
+
const { x = 0, y = 0, width = 0, height = 0 } = shape.bounds;
|
|
156
|
+
let newX = x;
|
|
157
|
+
let newY = y;
|
|
158
|
+
|
|
159
|
+
// 根据按键方向调整位置
|
|
160
|
+
switch (e.key) {
|
|
161
|
+
case 'ArrowUp':
|
|
162
|
+
newY -= step;
|
|
163
|
+
break;
|
|
164
|
+
case 'ArrowDown':
|
|
165
|
+
newY += step;
|
|
166
|
+
break;
|
|
167
|
+
case 'ArrowLeft':
|
|
168
|
+
newX -= step;
|
|
169
|
+
break;
|
|
170
|
+
case 'ArrowRight':
|
|
171
|
+
newX += step;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 添加边界约束:确保图元不会超出画布的边界
|
|
176
|
+
newX = Math.max(newX, canvasMinX);
|
|
177
|
+
newY = Math.max(newY, canvasMinY);
|
|
178
|
+
newX = Math.min(newX, canvasMaxX - width);
|
|
179
|
+
newY = Math.min(newY, canvasMaxY - height);
|
|
180
|
+
|
|
181
|
+
// 计算位置变化量
|
|
182
|
+
const deltaX = newX - x;
|
|
183
|
+
const deltaY = newY - y;
|
|
184
|
+
|
|
185
|
+
// 更新父图元位置
|
|
186
|
+
graphStore.updateShape(shape.id, {
|
|
187
|
+
bounds: {
|
|
188
|
+
x: newX,
|
|
189
|
+
y: newY,
|
|
190
|
+
width,
|
|
191
|
+
height
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 查找并移动所有子图元
|
|
196
|
+
const childShapes = graphStore.shapes.filter(child => child.parenShapeId === shape.id);
|
|
197
|
+
childShapes.forEach(child => {
|
|
198
|
+
if (child && child.bounds) {
|
|
199
|
+
const childX = child.bounds.x || 0;
|
|
200
|
+
const childY = child.bounds.y || 0;
|
|
201
|
+
const childWidth = child.bounds.width || 0;
|
|
202
|
+
const childHeight = child.bounds.height || 0;
|
|
203
|
+
|
|
204
|
+
// 计算子图元的新位置
|
|
205
|
+
const childNewX = childX + deltaX;
|
|
206
|
+
const childNewY = childY + deltaY;
|
|
207
|
+
|
|
208
|
+
// 为子图元添加边界约束
|
|
209
|
+
const constrainedChildX = Math.max(childNewX, canvasMinX);
|
|
210
|
+
const constrainedChildY = Math.max(childNewY, canvasMinY);
|
|
211
|
+
const constrainedChildXWithWidth = Math.min(constrainedChildX, canvasMaxX - childWidth);
|
|
212
|
+
const constrainedChildYWithHeight = Math.min(constrainedChildY, canvasMaxY - childHeight);
|
|
213
|
+
|
|
214
|
+
// 更新子图元位置
|
|
215
|
+
graphStore.updateShape(child.id, {
|
|
216
|
+
bounds: {
|
|
217
|
+
x: constrainedChildXWithWidth,
|
|
218
|
+
y: constrainedChildYWithHeight,
|
|
219
|
+
width: childWidth,
|
|
220
|
+
height: childHeight
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ElMessage, type MessageHandler } from "element-plus"
|
|
2
|
+
import { useGraphStore } from "../store/graphStore"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ElMessage.warning 互斥锁:同一时间只显示一个 warning
|
|
6
|
+
* - warning 显示期间:不会重复弹
|
|
7
|
+
* - warning 关闭后:允许下一次再弹
|
|
8
|
+
*/
|
|
9
|
+
let warningLock: Promise<void> | null = null
|
|
10
|
+
let warningHandler: MessageHandler | null = null
|
|
11
|
+
|
|
12
|
+
function showWarningOnce(message?: string) {
|
|
13
|
+
// 如果上一次 warning 还在显示,就直接复用“锁”,不再重复弹
|
|
14
|
+
if (warningLock) return warningLock
|
|
15
|
+
|
|
16
|
+
warningLock = new Promise<void>((resolve) => {
|
|
17
|
+
warningHandler = ElMessage.warning({
|
|
18
|
+
message,
|
|
19
|
+
onClose: () => {
|
|
20
|
+
warningHandler = null
|
|
21
|
+
warningLock = null
|
|
22
|
+
resolve()
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return warningLock
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 操作拦截器:
|
|
32
|
+
* - canOperate=false 时:提示并阻止执行(warning 同一时间只显示一个)
|
|
33
|
+
* - canOperate=true 时:执行传入的函数
|
|
34
|
+
*/
|
|
35
|
+
export async function guardOperate<T>(
|
|
36
|
+
fn: () => T | Promise<T>,
|
|
37
|
+
options?: { message?: string; silent?: boolean }
|
|
38
|
+
): Promise<T | undefined> {
|
|
39
|
+
const licenseStore = useGraphStore()
|
|
40
|
+
|
|
41
|
+
if (!licenseStore.canOperate) {
|
|
42
|
+
return
|
|
43
|
+
if (!options?.silent) {
|
|
44
|
+
// showWarningOnce(options?.message ?? "当前软件未激活License,请激活后使用!")
|
|
45
|
+
}
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return await fn()
|
|
50
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ref, nextTick, type Ref } from 'vue';
|
|
2
|
+
import type { Shape } from '../types';
|
|
3
|
+
|
|
4
|
+
export interface NameEditOptions {
|
|
5
|
+
onNameChange?: (oldName: string, newName: string) => void;
|
|
6
|
+
validateName?: (name: string) => string | null; // 返回错误信息,null表示验证通过
|
|
7
|
+
onEditStart?: () => void;
|
|
8
|
+
onEditEnd?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class NameEditManager {
|
|
12
|
+
private isEditing: Ref<boolean>;
|
|
13
|
+
private editingName: Ref<string>;
|
|
14
|
+
private nameInput: Ref<HTMLInputElement | undefined>;
|
|
15
|
+
private options: NameEditOptions;
|
|
16
|
+
|
|
17
|
+
constructor(options: NameEditOptions = {}) {
|
|
18
|
+
this.isEditing = ref(false);
|
|
19
|
+
this.editingName = ref('');
|
|
20
|
+
this.nameInput = ref<HTMLInputElement>();
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 获取响应式状态
|
|
25
|
+
get editingState() {
|
|
26
|
+
return {
|
|
27
|
+
isEditingName: this.isEditing,
|
|
28
|
+
editingName: this.editingName,
|
|
29
|
+
nameInput: this.nameInput
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 开始编辑名称
|
|
34
|
+
async startEdit(shape: Shape | null): Promise<void> {
|
|
35
|
+
if (!shape || this.isEditing.value) return;
|
|
36
|
+
|
|
37
|
+
this.isEditing.value = true;
|
|
38
|
+
this.editingName.value = shape.name;
|
|
39
|
+
|
|
40
|
+
// 触发开始编辑回调
|
|
41
|
+
this.options.onEditStart?.();
|
|
42
|
+
|
|
43
|
+
await nextTick();
|
|
44
|
+
|
|
45
|
+
// 聚焦并选中文本
|
|
46
|
+
this.nameInput.value?.focus();
|
|
47
|
+
this.nameInput.value?.select();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 完成编辑
|
|
51
|
+
finishEdit(shape: Shape | null): void {
|
|
52
|
+
if (!shape || !this.isEditing.value) return;
|
|
53
|
+
|
|
54
|
+
const newName = this.editingName.value.trim();
|
|
55
|
+
const oldName = shape.name;
|
|
56
|
+
|
|
57
|
+
if (newName && newName !== oldName) {
|
|
58
|
+
// 验证名称
|
|
59
|
+
const validationError = this.options.validateName?.(newName);
|
|
60
|
+
if (validationError) {
|
|
61
|
+
// 如果验证失败,可以显示错误提示
|
|
62
|
+
console.warn('名称验证失败:', validationError);
|
|
63
|
+
this.cancelEdit();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 调用名称变更回调
|
|
68
|
+
this.options.onNameChange?.(oldName, newName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.isEditing.value = false;
|
|
72
|
+
this.editingName.value = '';
|
|
73
|
+
this.options.onEditEnd?.();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 取消编辑
|
|
77
|
+
cancelEdit(): void {
|
|
78
|
+
this.isEditing.value = false;
|
|
79
|
+
this.editingName.value = '';
|
|
80
|
+
this.options.onEditEnd?.();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 处理键盘事件
|
|
84
|
+
handleKeyUp(event: KeyboardEvent, shape: Shape | null): void {
|
|
85
|
+
if (event.key === 'Enter') {
|
|
86
|
+
this.finishEdit(shape);
|
|
87
|
+
} else if (event.key === 'Escape') {
|
|
88
|
+
this.cancelEdit();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 处理失焦事件
|
|
93
|
+
handleBlur(shape: Shape | null): void {
|
|
94
|
+
this.finishEdit(shape);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 判断是否可以编辑
|
|
98
|
+
canEdit(shape: Shape | null): boolean {
|
|
99
|
+
return !!shape &&
|
|
100
|
+
shape.shapeKey !== 'ConceptRole' &&
|
|
101
|
+
!this.isEditing.value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 获取名称显示文本
|
|
105
|
+
getDisplayName(shape: Shape | null): string {
|
|
106
|
+
return shape?.name || '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 重置状态
|
|
110
|
+
reset(): void {
|
|
111
|
+
this.isEditing.value = false;
|
|
112
|
+
this.editingName.value = '';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 便捷创建函数
|
|
117
|
+
export function createNameEditManager(options?: NameEditOptions): NameEditManager {
|
|
118
|
+
return new NameEditManager(options);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 默认验证函数
|
|
122
|
+
export function defaultNameValidator(name: string): string | null {
|
|
123
|
+
if (!name.trim()) {
|
|
124
|
+
return '名称不能为空';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (name.length > 100) {
|
|
128
|
+
return '名称长度不能超过100个字符';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|