lishihuan大约 5 分钟

好的,我重新梳理一下「新增矩形框标注」这一整条链路,让你对组件-数据互动的细节有更清晰的把握;同时指出目前实现方式的一些可优化点,供后续改进参考。

───────────────────────── 一、核心调用链

  1. 选中工具
    • 用户点击工具栏 BBoxTool.vue,组件内部 isActive → click() 把自身设为激活,并监听 mousedowndragmouseup

  2. 开始绘制
    mousedown 时在 paper.js 画布上创建 this.polygon.path(其实就是一个 Path)。
    • 首次落点前,通过

    this.$parent.currentCategory.createAnnotation()
    

    在当前类别(虚拟 category 0)下 push 出一个 annotation 对象(_isCreate=true,segmentation 空)。
    这里的 “currentCategory” 来自父组件 Category.vue,每一个 Category 里都混入了 Annotation.vue 子组件。

  3. 结束绘制
    mouseup 里调用

    this.$parent.uniteCurrentAnnotation(this.polygon.path, true, true, true)
    

    父级 Annotator.vue 把临时 path 与 Annotation.vue 中的 compoundPath unite,并简化。此时:

    • Annotation.vue 内部生成 paper 复合路径
    • annotation.paperObject 写入 JSON
  4. 弹出选择标签
    • Annotation.vue 的 simplifyPath() 最后 emit modify → Annotator.vue showCategoryPopup() 打开 category-popup.vue。
    • 用户在弹窗里选一个真正标签后,回调 changeCategory(data)

    a. 向 categoryList / categories 中插入新 tag(如果还没有)
    b. 调 currentAnnotation.updateCategoryById(newTag)
    - 实际做的是「复制新 annotation + 删除旧 annotation」,避免直接改引用带来的 reactivity 风险。

  5. 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。

───────────────────────── 三、值得关注 / 可改进要点

  1. “复制后删除” 的策略
    • 好处:避免直接修改嵌套对象导致 Vue diff 检测不到。
    • 代价:撤销栈、label 组装、paper 对象重建都要重新跑一次。
    ⮕ 如果后期性能吃紧,可以考虑 Vue.set + 对深层字段使用 Vue.observable,直接改 tagId/tagName,再触发一次 label 更新。

  2. currentCategory / currentAnnotation 跟 ref 依赖
    • 目前通过 $refs.category$refs.category[index] 查找组件实例,一旦异步删除/新增过快容易出现 undefined。
    ⮕ 可以在 Annotation 对象里保存一个 guid,唯一映射到 paperItem & listItem,避免多层索引查找。

  3. 虚拟节点 & 快捷键
    • 虚拟节点切换、快捷键注册都写在 Annotator.vue,代码量大且职责杂。
    ⮕ 考虑抽到 composable(Vue 3 可用组合式 API)或 mixin,提高复用度。

  4. Undo/Redo 两套逻辑并存
    • libs/undo.js(动作栈) 与 Annotation.vue 内 pervious/compoundPath snapshot 都在用。
    ⮕ 后期可统一:只记录“分类/标注层级的 diff + paper JSON”,把图形和数据还原做成同一颗指令树。

  5. category-popup 与画布位置
    • 弹窗目前绝对定位覆盖整个 middle-panel,需要 common-mask 手动遮罩;
    • 如果将其做成 Element-UI Dialog 或 teleport portal,可省掉手动 mask。

───────────────────────── 四、后续若要改动,建议步骤

  1. 把 “创建 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 仅保存撤销栈、复合路径剪贴板、字体大小等跨组件状态

你想查看或改造这条链可关注的文件入口

  1. 工具层:components/annotator/tools/BBoxTool.vue
  2. Category 数据层:components/annotator/Category.vue → createAnnotation / deleteAnnotation
  3. Annotation 图形层:components/annotator/Annotation.vue → unite / simplifyPath / updateCategoryById
  4. 弹窗层:components/categoryPopup.vue → 确认后调用 Annotator.updateCategory

典型改动场景建议 • 想在“画完就落默认类别”——在 BBoxTool 完成绘制后直接带类别,不弹窗。
• 想“先选类别再画”——在未选合法 currentCategory 时禁止 createAnnotation()
• 弹窗复制-删除的实现耦合较强,若要优化可:

  • 让 Annotation.vue 直接改 annotation.tagId 并移动数组元素,而不是复制后删。
  • 或者把标注对象抽到 Vuex / Pinia,页面组件只做展示。

如果你需要对上述流程的某一步做功能调整或遇到 bug,告诉我具体节点,我再帮你细拆实现方案。