Files
waveform-analysis/.claude/plan.md
李启源 ea832229da
Some checks failed
CI / verify (push) Has been cancelled
first commit
2026-07-20 10:21:30 +08:00

6.8 KiB
Raw Blame History

WaveformChart 组件拆分计划

目标

WaveformChart.vue1743 行)拆分为更小的、职责单一的子组件:

  1. WaveformTooltip.vue - 悬浮提示组件
  2. WaveformTrack.vue - 单个波形轨道组件
  3. WaveformAnnotationLayer.vue - 标注层组件

当前代码分析

主组件职责(过多)

  • 数据管理和状态协调
  • 缩放和交互事件处理
  • 🔴 渲染波形轨道(网格、轴、波形线、十字线)
  • 🔴 渲染标注和图形
  • 🔴 渲染悬浮提示
  • 工具栏管理(已拆分)
  • 编辑器管理(已拆分)

模板结构1055-1743 行)

<div class="waveform-chart">
  <svg>
    <g transform="translate(margin)">
      <!-- 1. 轨道循环100+ 包含网格标签波形线十字线 -->
      <g v-for="track in trackLayouts">...</g>
      
      <!-- 2. 标注和图形层135  -->
      <g class="waveform-chart__markup-layer">
        <g v-for="shape in renderedShapes">...</g>
        <g v-for="annotation in renderedAnnotations">...</g>
        <g v-for="preview in renderedRangePreview">...</g>
      </g>
    </g>
  </svg>
  
  <!-- 3. Tooltip15  -->
  <div v-if="showTooltip && hoveredPoint" class="waveform-chart__tooltip">...</div>
  
  <!-- 4. 已拆分组件 -->
  <WaveformToolbar />
  <WaveformEditor />
</div>

拆分策略

组件 1: WaveformTooltip.vue (~80 行)

职责:显示鼠标悬浮时的数据点信息

Props

interface Props {
  visible: boolean
  position: { x: number, y: number }
  timeUnit: 's' | 'ms'
  hoveredPoint: WaveformPoint | null
  seriesPoints: Array<{
    trackIndex: number
    name: string
    color: string
    unit?: string
    point: WaveformPoint
  }>
}

提取内容

  • 模板1463-1478 行16 行)
  • 样式1682-1742 行61 行)
  • 计算属性:tooltipStyle471-477 行)

组件 2: WaveformTrack.vue (~300 行)

职责渲染单个波形轨道网格、坐标轴、波形线、十字线、overlay

Props

interface Props {
  track: TrackLayout
  clipPathId: string
  margin: { top: number, right: number, bottom: number, left: number }
  innerWidth: number
  showTooltip: boolean
  zoomable: boolean
  displayMode: WaveformDisplayMode
  activeInteractionMode: WaveformInteractionMode
  frameNumber?: string | number
  timeUnit: 's' | 'ms'
  hoveredPoint?: HoveredSeriesPoint  // 用于显示十字线
}

Emits

interface Emits {
  (e: 'pointer-move', event: PointerEvent): void
  (e: 'pointer-leave'): void
  (e: 'pointer-down', event: PointerEvent): void
  (e: 'pointer-up', event: PointerEvent): void
  (e: 'pointer-cancel', event: PointerEvent): void
  (e: 'click', event: PointerEvent): void
}

提取内容

  • 模板1099-1265 行166 行)
  • 相关函数:
    • shouldShowYAxisLabel (247-262)
    • resolveYAxisLabel (239-241)
    • resolveFrameNumber (804-810)
    • crosshairX (794-797)
    • crosshairY (799-802)
    • trackHoverPoint (790-792)
  • 样式:部分轨道相关样式

注意事项

  • 轨道组件需要在父组件中接收 D3 渲染的坐标轴
  • 或者在 onMounted 中自己调用 D3 渲染坐标轴

组件 3: WaveformAnnotationLayer.vue (~250 行)

职责渲染所有标注和图形annotations + shapes

Props

interface Props {
  renderedAnnotations: RenderedAnnotation[]
  renderedShapes: RenderedShape[]
  renderedRangePreview: RenderedShape[]
  activeInteractionMode: WaveformInteractionMode
  selection: WaveformMarkupSelection
  innerWidth: number
  clipPathId: string
}

Emits

interface Emits {
  (e: 'select-markup', kind: 'annotation' | 'shape', id: string): void
  (e: 'edit-markup', kind: 'annotation' | 'shape', id: string): void
}

提取内容

  • 模板1285-1419 行135 行)
  • 相关函数:
    • isSelected (491-493)
    • safeDomId (503-505)
    • arrowMarkerId (507-509)
    • annotationBoxStyle (511-518)
    • shapeLabelWidth (520-522)
    • shapeLabelX (524-531)
    • shapeLabelStyle (533-538)
  • 样式标注相关样式1581-1660 行)

实施步骤

阶段 1: 创建 WaveformTooltip.vue

  1. 创建组件文件
  2. 提取模板和样式
  3. 实现计算属性 tooltipStyle
  4. 更新主组件使用新组件

阶段 2: 创建 WaveformAnnotationLayer.vue

  1. 创建组件文件
  2. 提取标注层模板
  3. 提取相关工具函数
  4. 提取样式
  5. 更新主组件

阶段 3: 创建 WaveformTrack.vue

  1. 创建组件文件
  2. 提取轨道渲染逻辑
  3. 处理 D3 坐标轴渲染(使用 ref + onMounted
  4. 提取相关样式
  5. 更新主组件

阶段 4: 验证和测试

  1. 运行类型检查 pnpm typecheck
  2. 运行代码规范检查 pnpm lint
  3. 运行单元测试 pnpm test
  4. 手动测试功能完整性

设计决策

1. 类型共享

DisplaySeries, HoveredSeriesPoint, TrackLayout, RenderedAnnotation, RenderedShape 等接口移到独立的类型文件中,供多个组件使用。

创建 src/components/waveform-chart-types.ts

2. D3 渲染策略

对于 WaveformTrack 中的坐标轴渲染:

  • 方案 A推荐:在 Track 组件内部使用 ref + onMounted 调用 D3
  • 方案 B:父组件渲染后通知子组件
  • 选择 A:更符合组件封装原则

3. 事件冒泡

所有交互事件click, pointer-move 等)通过 emit 向上传递,保持主组件的事件协调职责。

4. 样式隔离

每个组件使用 <style scoped>,但共享的样式变量可以提取到 CSS 变量中。


预期收益

代码规模

  • 主组件1743 → ~1100 行(-37%
  • 新组件
    • WaveformTooltip: ~80 行
    • WaveformAnnotationLayer: ~250 行
    • WaveformTrack: ~300 行

可维护性

  • 每个组件职责清晰
  • 修改轨道渲染不影响标注层
  • Tooltip 可独立测试和复用

可测试性

  • 每个子组件可独立单元测试
  • 减少主组件测试的复杂度

风险和注意事项

1. D3 上下文问题

坐标轴渲染依赖 D3 操作 DOM需要确保 ref 正确传递和挂载时机。

解决方案:在 Track 组件中使用 watch 监听 track prop 变化,触发重新渲染。

2. 性能影响

拆分组件可能增加 Vue 的更新开销。

解决方案

  • 使用 shallowRef 存储 D3 对象
  • 对于大数组tracks, annotations使用稳定的 :key
  • 如有性能问题,可使用 v-memo 指令

3. 向后兼容

确保 props 和 emits 接口不变。

验证方法:运行现有的 24 个单元测试。


实施时间估算

  • 阶段 1Tooltip30 分钟
  • 阶段 2AnnotationLayer1 小时
  • 阶段 3Track1.5 小时
  • 阶段 4测试30 分钟

总计:约 3.5 小时