好的,我重新梳理一下「新增矩形框标注」这一整条链路,让你对组件-数据互动的细节有更清晰的把握;同时指出目前实现方式的一些可优化点,供后续改进参考。
───────────────────────── 一、核心调用链
选中工具
• 用户点击工具栏 BBoxTool.vue,组件内部isActive → click()把自身设为激活,并监听mousedown、drag、mouseup。开始绘制
•mousedown时在 paper.js 画布上创建this.polygon.path(其实就是一个 Path)。
• 首次落点前,通过this.$parent.currentCategory.createAnnotation()在当前类别(虚拟 category 0)下 push 出一个 annotation 对象(_isCreate=true,segmentation 空)。
这里的 “currentCategory” 来自父组件 Category.vue,每一个 Category 里都混入了 Annotation.vue 子组件。结束绘制
•mouseup里调用this.$parent.uniteCurrentAnnotation(this.polygon.path, true, true, true)父级 Annotator.vue 把临时 path 与 Annotation.vue 中的
compoundPathunite,并简化。此时:- Annotation.vue 内部生成 paper 复合路径
- annotation.paperObject 写入 JSON
弹出选择标签
• Annotation.vue 的simplifyPath()最后 emit modify → Annotator.vueshowCategoryPopup()打开 category-popup.vue。
• 用户在弹窗里选一个真正标签后,回调changeCategory(data):a. 向
categoryList/categories中插入新 tag(如果还没有)
b. 调currentAnnotation.updateCategoryById(newTag):
- 实际做的是「复制新 annotation + 删除旧 annotation」,避免直接改引用带来的 reactivity 风险。UI 同步渲染
•categories数组被 Vue 定义为响应式;Annotator.vue 模板里<Category v-for="(category, index) in categories" />Category.vue 负责把每个
category.annotations再 v-for 成多个 Annotation.vue,形成右侧树。
• 因此任何对 categories / annotations 的增删,都会自动驱动组件结构变化。
───────────────────────── 二、数据结构缩影
categories: [
{
id, name, colorValue, annotations: [
{
id, tagId, tagName, paperObject, segmentation, _isCreate,
// 运行期额外:
paperCompoundPath, sessions...
}
]
},
...
]
虚拟标签 id=0 只用来“先画后选”。真正标签选好后才把 annotation 迁移到新 category。
───────────────────────── 三、值得关注 / 可改进要点
“复制后删除” 的策略
• 好处:避免直接修改嵌套对象导致 Vue diff 检测不到。
• 代价:撤销栈、label 组装、paper 对象重建都要重新跑一次。
⮕ 如果后期性能吃紧,可以考虑Vue.set+ 对深层字段使用Vue.observable,直接改 tagId/tagName,再触发一次 label 更新。currentCategory / currentAnnotation 跟 ref 依赖
• 目前通过$refs.category和$refs.category[index]查找组件实例,一旦异步删除/新增过快容易出现 undefined。
⮕ 可以在 Annotation 对象里保存一个 guid,唯一映射到 paperItem & listItem,避免多层索引查找。虚拟节点 & 快捷键
• 虚拟节点切换、快捷键注册都写在 Annotator.vue,代码量大且职责杂。
⮕ 考虑抽到 composable(Vue 3 可用组合式 API)或 mixin,提高复用度。Undo/Redo 两套逻辑并存
• libs/undo.js(动作栈) 与 Annotation.vue 内 pervious/compoundPath snapshot 都在用。
⮕ 后期可统一:只记录“分类/标注层级的 diff + paper JSON”,把图形和数据还原做成同一颗指令树。category-popup 与画布位置
• 弹窗目前绝对定位覆盖整个 middle-panel,需要 common-mask 手动遮罩;
• 如果将其做成 Element-UI Dialog 或 teleport portal,可省掉手动 mask。
───────────────────────── 四、后续若要改动,建议步骤
- 把 “创建 annotation” ...
---- 第二次总结
好的,我把「矩形框-标注层」从鼠标按下到弹窗选类型这一整条链路,再梳理一遍,帮助你彻底吃透核心调用关系与数据流。下面的编号基本对应调用先后顺序,可直接对照源码定位。
1)BBoxTool.vue(工具层,子组件)
• mousedown → startBBox():
- 若 this.$parent.currentCategory 已经选中(常用标签或虚拟标签 id=0),调用this.$parent.currentCategory.createAnnotation()
▶ 进入 Category.vue
2)Category.vue(右侧标签树,孙组件)
• createAnnotation() 内部生成一个基础 annotation 对象:
- 赋默认颜色、bbox=true、租用 uuid 作为 id(若 _isCreate 标记说明新建,尚未落库)
- this.annotations.push(annotation) 把对象塞进当前 category.annotations 数组
• Category.vue 会 v-for 渲染每条 annotation ⇒ 于是对应的 Annotation.vue 立即挂载(但此时复合路径还是空)
3)BBoxTool.vue 拖拽绘制矩形
• 实时更新临时 this.polygon.path(一个 paper.Path)
• mouseup → completeBBox() 结束绘制后执行this.$parent.uniteCurrentAnnotation(this.polygon.path, true, true, true)
4)Annotator.vue(顶层页面父组件)
• uniteCurrentAnnotation() 找到 currentAnnotation(Annotation.vue 实例),转调其 unite() 方法,把刚刚的临时 path 合并到真正的 compoundPath 上。
• 合并完成后 Annotation.vue 会 simplifyPath() → emitModify(),同时:
- 将 compoundPath.exportJSON() 写回 annotation.paperObject
- 给图形打上 data.type='BBox' 并绘制 label
- 调用 this.$parent.$parent.showCategoryPopup() 弹出选择标签类型
5)category-popup.vue(弹窗选类型)
• 用户点确定 → Annotator.changeCategory(newTag)
- 如果当前 annotation 仍在虚拟标签 id=0 内:
① Annotation.vue updateCategoryById 先删自身(deleteAnnotationLayer),返回 Annotator
② Annotator updateAnnotationCategory() 把 annotation 拷贝一份改 tagId/tagName,
插入真正的目标 Category.annotations 并做撤销栈记录
③ 同时给 categories / categoryList 添快捷键、颜色等,并刷新右侧树
6)渲染 & 交互
• Annotator 中 categories 变化 ⇒ 重新计算 <Category v-for>
• 每个 Category.vue 内部再 v-for 生成 Annotation.vue ⇒ 更新视图
• 之后所有图形编辑、撤销、删除等都以 Annotation.vue 内部逻辑为核心(paper.js 实体 + UndoAction)
数据流关键点 A. 单一真源:Annotator.vue 的 categories(数组嵌套 annotations)
B. Category.vue / Annotation.vue 都直接 Mutate 这棵树 ⇒ 视图自动响应
C. Vuex 仅保存撤销栈、复合路径剪贴板、字体大小等跨组件状态
你想查看或改造这条链可关注的文件入口
- 工具层:components/annotator/tools/BBoxTool.vue
- Category 数据层:components/annotator/Category.vue → createAnnotation / deleteAnnotation
- Annotation 图形层:components/annotator/Annotation.vue → unite / simplifyPath / updateCategoryById
- 弹窗层:components/categoryPopup.vue → 确认后调用 Annotator.updateCategory
典型改动场景建议 • 想在“画完就落默认类别”——在 BBoxTool 完成绘制后直接带类别,不弹窗。
• 想“先选类别再画”——在未选合法 currentCategory 时禁止 createAnnotation()。
• 弹窗复制-删除的实现耦合较强,若要优化可:
- 让 Annotation.vue 直接改
annotation.tagId并移动数组元素,而不是复制后删。 - 或者把标注对象抽到 Vuex / Pinia,页面组件只做展示。
如果你需要对上述流程的某一步做功能调整或遇到 bug,告诉我具体节点,我再帮你细拆实现方案。