diff --git a/.claude/plan-system-refactoring.md b/.claude/plan-system-refactoring.md deleted file mode 100644 index 30997f2..0000000 --- a/.claude/plan-system-refactoring.md +++ /dev/null @@ -1,663 +0,0 @@ -# WaveformChart 按系统拆分重构计划 - -## 🎯 目标 - -将现有的组件按功能系统重新组织,每个系统有独立的目录,提高代码的可维护性和可理解性。 - -## 📊 当前结构分析 - -### 当前组件列表 - -``` -src/components/ -├── WaveformChart.vue # 主容器组件 (~1143 行) -├── WaveformToolbar.vue # 工具栏 (174 行) -├── WaveformEditor.vue # 编辑器 (149 行) -├── WaveformTooltip.vue # 悬浮提示 (111 行) -├── WaveformTrack.vue # 波形轨道 (317 行) -├── WaveformAnnotationLayer.vue # 标注层 (281 行) -├── waveform.ts # 类型定义 -├── waveform-markup.ts # 标注相关类型和工具 -└── index.ts # 导出 -``` - -### 功能系统识别 - -通过分析代码,可以识别出以下核心系统: - -1. **基础绘制系统 (Rendering System)** - - 波形轨道渲染(网格、坐标轴、波形线) - - SVG 基础图形绘制 - - D3 图表渲染 - -2. **缩放系统 (Zoom System)** - - 缩放行为管理 - - 变换状态管理 - - 独立/共享缩放模式 - -3. **交互系统 (Interaction System)** - - 鼠标事件处理 - - 悬浮检测 - - 十字线显示 - - 工具栏和模式切换 - -4. **标注系统 (Annotation System)** - - 标注管理(创建、编辑、删除) - - 图形管理(垂直线、时间区间) - - 标注渲染 - - 编辑器 - -5. **数据系统 (Data System)** - - 数据规范化 - - 类型定义 - - 轨道布局计算 - -## 🏗️ 目标目录结构 - -``` -src/components/ -├── WaveformChart.vue # 主容器(协调各系统) -├── index.ts # 公共导出 -│ -├── core/ # 核心系统 -│ ├── types.ts # 共享类型定义 -│ ├── constants.ts # 常量(颜色、尺寸等) -│ └── index.ts -│ -├── data/ # 数据系统 -│ ├── types.ts # 数据相关类型 -│ ├── normalize.ts # 数据规范化 -│ ├── layout.ts # 轨道布局计算 -│ └── index.ts -│ -├── rendering/ # 基础绘制系统 -│ ├── WaveformTrack.vue # 波形轨道组件 -│ ├── Grid.vue # 网格组件(可选) -│ ├── Axis.vue # 坐标轴组件(可选) -│ ├── types.ts # 渲染相关类型 -│ └── index.ts -│ -├── zoom/ # 缩放系统 -│ ├── useZoom.ts # 缩放组合式函数 -│ ├── types.ts # 缩放相关类型 -│ └── index.ts -│ -├── interaction/ # 交互系统 -│ ├── WaveformToolbar.vue # 工具栏 -│ ├── WaveformTooltip.vue # 悬浮提示 -│ ├── useInteraction.ts # 交互组合式函数 -│ ├── useHover.ts # 悬浮逻辑 -│ ├── types.ts # 交互相关类型 -│ └── index.ts -│ -└── annotation/ # 标注系统 - ├── WaveformAnnotationLayer.vue # 标注渲染层 - ├── WaveformEditor.vue # 标注编辑器 - ├── useAnnotation.ts # 标注管理逻辑 - ├── markup.ts # 标注工具函数 - ├── types.ts # 标注相关类型 - └── index.ts -``` - -## 📦 系统划分详情 - -### 1. Core System (核心系统) - -**职责**:提供共享的类型、常量和工具 - -**文件**: - -- `core/types.ts` - 基础类型定义 - - ```typescript - export interface DisplaySeries { ... } - export interface TrackLayout { ... } - export interface WaveformPoint { ... } - ``` - -- `core/constants.ts` - 常量 - ```typescript - export const channelColors = [...] - export const margin = { top: 18, right: 24, bottom: 52, left: 64 } - export const minimumHeight = 180 - ``` - -**来源**:从 `WaveformChart.vue` 和 `waveform.ts` 提取 - ---- - -### 2. Data System (数据系统) - -**职责**:数据规范化、轨道布局计算 - -**文件**: - -- `data/types.ts` - 数据类型 - - ```typescript - export type WaveformData = ... - export type WaveformDisplayMode = ... - export interface WaveformSeries { ... } - ``` - -- `data/normalize.ts` - 数据规范化 - - ```typescript - export function normalizeWaveformData(data: WaveformData): WaveformSeries[] - export function normalizeWaveformSeries(data: WaveformData): DisplaySeries[] - ``` - -- `data/layout.ts` - 轨道布局计算 - ```typescript - export function computeTrackLayouts( - series: DisplaySeries[], - displayMode: WaveformDisplayMode, - innerWidth: number, - innerHeight: number, - ... - ): TrackLayout[] - ``` - -**来源**: - -- `waveform.ts` → `data/types.ts` + `data/normalize.ts` -- `WaveformChart.vue` 中的 `trackLayouts` 计算逻辑 → `data/layout.ts` - ---- - -### 3. Rendering System (基础绘制系统) - -**职责**:渲染波形轨道、网格、坐标轴、波形线 - -**文件**: - -- `rendering/WaveformTrack.vue` - 波形轨道组件(已存在) -- `rendering/types.ts` - 渲染相关类型 - ```typescript - export interface RenderingProps { ... } - export interface AxisConfig { ... } - ``` - -**可选优化**: - -- `rendering/Grid.vue` - 独立网格组件 -- `rendering/Axis.vue` - 独立坐标轴组件 - -**来源**: - -- `WaveformTrack.vue` → `rendering/WaveformTrack.vue` - ---- - -### 4. Zoom System (缩放系统) - -**职责**:管理缩放行为、变换状态 - -**文件**: - -- `zoom/useZoom.ts` - 缩放组合式函数 - - ```typescript - export function useZoom(options: ZoomOptions) { - const sharedTransform = shallowRef(zoomIdentity) - const independentTransforms = shallowRef([]) - - function configureZoom() { ... } - function resetViewport() { ... } - function handleSharedZoom(event: D3ZoomEvent) { ... } - function handleIndependentZoom(event: D3ZoomEvent, trackIndex: number) { ... } - - return { - sharedTransform, - independentTransforms, - configureZoom, - resetViewport, - handleSharedZoom, - handleIndependentZoom, - } - } - ``` - -- `zoom/types.ts` - 缩放相关类型 - ```typescript - export interface ZoomOptions { ... } - export interface ZoomState { ... } - ``` - -**来源**:从 `WaveformChart.vue` 提取缩放相关逻辑 - ---- - -### 5. Interaction System (交互系统) - -**职责**:处理用户交互(悬浮、点击、工具栏) - -**文件**: - -- `interaction/WaveformToolbar.vue` - 工具栏(已存在) -- `interaction/WaveformTooltip.vue` - 悬浮提示(已存在) - -- `interaction/useInteraction.ts` - 交互管理 - - ```typescript - export function useInteraction(options: InteractionOptions) { - const interactionMode = ref('zoom') - - function setInteractionMode(mode: WaveformInteractionMode) { ... } - function handleOverlayClick(event: PointerEvent, trackIndex?: number) { ... } - - return { - interactionMode, - setInteractionMode, - handleOverlayClick, - } - } - ``` - -- `interaction/useHover.ts` - 悬浮逻辑 - - ```typescript - export function useHover(options: HoverOptions) { - const hoveredSeriesPoints = ref([]) - const hoveredTrackIndex = ref(null) - const hoverPosition = ref({ x: 0, y: 0 }) - - function handlePointerMove(event: PointerEvent, trackIndex?: number) { ... } - function clearHover() { ... } - function nearestPoint(series: DisplaySeries, xValue: number) { ... } - - return { - hoveredSeriesPoints, - hoveredTrackIndex, - hoverPosition, - handlePointerMove, - clearHover, - nearestPoint, - } - } - ``` - -- `interaction/types.ts` - 交互相关类型 - ```typescript - export type WaveformInteractionMode = 'zoom' | 'select' | 'annotation' | ... - export interface InteractionOptions { ... } - export interface HoverOptions { ... } - ``` - -**来源**: - -- `WaveformToolbar.vue` → `interaction/WaveformToolbar.vue` -- `WaveformTooltip.vue` → `interaction/WaveformTooltip.vue` -- `WaveformChart.vue` 中的交互逻辑 → `interaction/useInteraction.ts` + `interaction/useHover.ts` - ---- - -### 6. Annotation System (标注系统) - -**职责**:管理标注和图形(创建、编辑、删除、渲染) - -**文件**: - -- `annotation/WaveformAnnotationLayer.vue` - 标注渲染层(已存在) -- `annotation/WaveformEditor.vue` - 标注编辑器(已存在) - -- `annotation/useAnnotation.ts` - 标注管理逻辑 - - ```typescript - export function useAnnotation(options: AnnotationOptions) { - const selection = ref(null) - const editingDraft = ref(null) - const rangeDraft = ref(null) - - function createAnnotation(point: WaveformPoint, seriesId: string) { ... } - function editAnnotation(id: string) { ... } - function deleteAnnotation(id: string) { ... } - function selectMarkup(kind: 'annotation' | 'shape', id: string) { ... } - - return { - selection, - editingDraft, - rangeDraft, - createAnnotation, - editAnnotation, - deleteAnnotation, - selectMarkup, - } - } - ``` - -- `annotation/markup.ts` - 标注工具函数 - - ```typescript - export function layoutAnnotationBox(...) { ... } - export function resolveAnnotationStyle(...) { ... } - export function resolveShapeStyle(...) { ... } - export function normalizeRangeShape(...) { ... } - ``` - -- `annotation/types.ts` - 标注相关类型 - ```typescript - export interface WaveformAnnotation { ... } - export interface WaveformShape { ... } - export interface RenderedAnnotation { ... } - export interface RenderedShape { ... } - ``` - -**来源**: - -- `WaveformAnnotationLayer.vue` → `annotation/WaveformAnnotationLayer.vue` -- `WaveformEditor.vue` → `annotation/WaveformEditor.vue` -- `waveform-markup.ts` → `annotation/markup.ts` + `annotation/types.ts` -- `WaveformChart.vue` 中的标注管理逻辑 → `annotation/useAnnotation.ts` - ---- - -## 🔄 重构策略 - -### 阶段 1: 创建新目录结构(不破坏现有代码) - -1. 创建新的目录结构 -2. 复制现有文件到新位置 -3. 不修改任何逻辑 - -### 阶段 2: 提取共享类型和常量 - -1. 创建 `core/types.ts` 和 `core/constants.ts` -2. 从各个文件中提取共享类型 -3. 更新导入路径 - -### 阶段 3: 重构数据系统 - -1. 创建 `data/` 目录 -2. 将 `waveform.ts` 拆分为 `data/types.ts` 和 `data/normalize.ts` -3. 从 `WaveformChart.vue` 提取布局计算逻辑到 `data/layout.ts` - -### 阶段 4: 重构缩放系统 - -1. 创建 `zoom/useZoom.ts` -2. 从 `WaveformChart.vue` 提取缩放相关逻辑 -3. 在主组件中使用组合式函数 - -### 阶段 5: 重构交互系统 - -1. 移动 `WaveformToolbar.vue` 和 `WaveformTooltip.vue` 到 `interaction/` -2. 创建 `interaction/useInteraction.ts` 和 `interaction/useHover.ts` -3. 从 `WaveformChart.vue` 提取交互逻辑 - -### 阶段 6: 重构标注系统 - -1. 移动 `WaveformAnnotationLayer.vue` 和 `WaveformEditor.vue` 到 `annotation/` -2. 将 `waveform-markup.ts` 拆分为 `annotation/markup.ts` 和 `annotation/types.ts` -3. 创建 `annotation/useAnnotation.ts` -4. 从 `WaveformChart.vue` 提取标注管理逻辑 - -### 阶段 7: 重构渲染系统 - -1. 移动 `WaveformTrack.vue` 到 `rendering/` -2. 更新所有导入路径 - -### 阶段 8: 更新主组件和公共导出 - -1. 简化 `WaveformChart.vue`,使用各系统的组合式函数 -2. 更新 `components/index.ts` 导出 -3. 确保向后兼容 - -### 阶段 9: 测试和验证 - -1. 运行所有测试 -2. 验证功能完整性 -3. 检查性能 - ---- - -## 📝 重构后的主组件结构 - -```vue - - - -``` - ---- - -## ✅ 收益 - -### 1. 可维护性 ⭐⭐⭐⭐⭐ - -- **按功能定位**:需要修改缩放功能?直接到 `zoom/` 目录 -- **职责清晰**:每个系统有独立的目录和文件 -- **减少耦合**:系统之间通过明确的接口通信 - -### 2. 可测试性 ⭐⭐⭐⭐⭐ - -- **独立测试**:每个系统可以独立测试 -- **组合式函数**:易于单元测试,不需要挂载组件 - -```typescript -// 测试缩放系统 -describe('useZoom', () => { - it('should handle zoom transform', () => { - const { sharedTransform, handleSharedZoom } = useZoom(options) - // 测试逻辑 - }) -}) -``` - -### 3. 可扩展性 ⭐⭐⭐⭐⭐ - -- **添加新功能**:在对应系统目录下添加 -- **替换实现**:可以替换整个系统而不影响其他部分 -- **插件化**:各系统可以作为独立插件使用 - -### 4. 可理解性 ⭐⭐⭐⭐⭐ - -- **目录即文档**:从目录结构就能理解系统功能 -- **代码组织**:相关代码放在一起,容易理解上下文 -- **新人友好**:新开发者可以快速定位到相关代码 - ---- - -## ⚠️ 风险和注意事项 - -### 1. 大规模重构风险 - -**风险**:移动文件可能导致测试失败、功能损坏 - -**缓解措施**: - -- 分阶段重构,每个阶段运行测试 -- 保持向后兼容 -- 使用 Git 分支,可以随时回滚 - -### 2. 导入路径变更 - -**风险**:大量文件的导入路径需要更新 - -**缓解措施**: - -- 使用 IDE 的重构功能 -- 在新目录的 `index.ts` 中保持导出一致 -- 逐步迁移,保留旧路径的重导出 - -### 3. 类型依赖复杂 - -**风险**:类型定义分散后可能产生循环依赖 - -**缓解措施**: - -- 明确类型依赖关系 -- 共享类型放在 `core/types.ts` -- 避免系统之间直接依赖类型 - -### 4. 性能影响 - -**风险**:拆分可能影响打包体积和加载性能 - -**缓解措施**: - -- 使用 Tree-shaking 优化 -- 合理使用动态导入 -- 监控打包体积变化 - ---- - -## 🎯 实施建议 - -### 渐进式重构 - -**推荐方案**:不是一次性重构所有内容,而是: - -1. **先提取组合式函数**(最小影响) - - 创建 `zoom/useZoom.ts` - - 创建 `interaction/useHover.ts` - - 创建 `annotation/useAnnotation.ts` - - 在主组件中使用,不移动其他文件 - -2. **再按系统移动组件**(中等影响) - - 移动 `WaveformToolbar.vue` 到 `interaction/` - - 移动 `WaveformAnnotationLayer.vue` 到 `annotation/` - - 更新导入路径 - -3. **最后重构类型定义**(最大影响) - - 拆分 `waveform.ts` 和 `waveform-markup.ts` - - 创建 `core/types.ts` - - 更新所有类型导入 - -### 向后兼容 - -在 `components/index.ts` 保持原有导出: - -```typescript -// 新的导出路径 -export { default as WaveformChart } from './WaveformChart.vue' -export { default as WaveformToolbar } from './interaction/WaveformToolbar.vue' -export { default as WaveformTooltip } from './interaction/WaveformTooltip.vue' - -// 类型导出 -export type * from './core/types' -export type * from './data/types' -export type * from './annotation/types' -``` - ---- - -## 📅 预估时间 - -- **阶段 1-2**(目录结构 + 类型提取):2-3 小时 -- **阶段 3-4**(数据系统 + 缩放系统):3-4 小时 -- **阶段 5-6**(交互系统 + 标注系统):3-4 小时 -- **阶段 7-8**(渲染系统 + 主组件更新):2-3 小时 -- **阶段 9**(测试和验证):1-2 小时 - -**总计**:11-16 小时 - ---- - -## 🤔 用户决策点 - -1. **是否采用组合式函数?** - - ✅ 推荐:更易测试,逻辑复用 - - ❌ 备选:保持当前结构,只移动文件 - -2. **是否进一步拆分组件?** - - 例如:将 `WaveformTrack` 拆分为 `Grid` + `Axis` + `Line` - - ✅ 更细粒度,更灵活 - - ❌ 可能过度工程化 - -3. **是否一次性重构?** - - ✅ 推荐:渐进式重构,每个阶段测试 - - ❌ 备选:一次性完成(风险较大) - -4. **公共导出路径?** - - 选项 A:`import { WaveformChart } from '@/components'`(保持现状) - - 选项 B:`import { WaveformChart } from '@/components/WaveformChart.vue'`(显式路径) - - 选项 C:系统级导出 `import { WaveformAnnotationLayer } from '@/components/annotation'` diff --git a/.claude/plan.md b/.claude/plan.md deleted file mode 100644 index c0dfe15..0000000 --- a/.claude/plan.md +++ /dev/null @@ -1,294 +0,0 @@ -# WaveformChart 组件拆分计划 - -## 目标 - -将 `WaveformChart.vue`(1743 行)拆分为更小的、职责单一的子组件: - -1. **WaveformTooltip.vue** - 悬浮提示组件 -2. **WaveformTrack.vue** - 单个波形轨道组件 -3. **WaveformAnnotationLayer.vue** - 标注层组件 - -## 当前代码分析 - -### 主组件职责(过多) - -- ✅ 数据管理和状态协调 -- ✅ 缩放和交互事件处理 -- 🔴 渲染波形轨道(网格、轴、波形线、十字线) -- 🔴 渲染标注和图形 -- 🔴 渲染悬浮提示 -- ✅ 工具栏管理(已拆分) -- ✅ 编辑器管理(已拆分) - -### 模板结构(1055-1743 行) - -```vue -
- - - - ... - - - - ... - ... - ... - - - - - -
...
- - - - -
-``` - -## 拆分策略 - -### 组件 1: WaveformTooltip.vue (~80 行) - -**职责**:显示鼠标悬浮时的数据点信息 - -**Props**: - -```typescript -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 行) -- 计算属性:`tooltipStyle`(471-477 行) - ---- - -### 组件 2: WaveformTrack.vue (~300 行) - -**职责**:渲染单个波形轨道(网格、坐标轴、波形线、十字线、overlay) - -**Props**: - -```typescript -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**: - -```typescript -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**: - -```typescript -interface Props { - renderedAnnotations: RenderedAnnotation[] - renderedShapes: RenderedShape[] - renderedRangePreview: RenderedShape[] - activeInteractionMode: WaveformInteractionMode - selection: WaveformMarkupSelection - innerWidth: number - clipPathId: string -} -``` - -**Emits**: - -```typescript -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. 样式隔离 - -每个组件使用 ` -``` - -### 4. 可复用性 - -组件可以在其他场景中使用: - -```vue - - - - - -``` - ---- - -## 🧪 测试验证 - -### 测试结果 - -```bash -✅ 所有测试通过 (24/24) -✅ TypeScript 类型检查通过 -✅ ESLint 代码规范通过 -``` - -### 测试覆盖场景 - -#### 工具栏测试 - -- [x] 缩放模式切换 -- [x] 选择模式切换 -- [x] 标注工具按钮 -- [x] 编辑按钮禁用状态 -- [x] 删除按钮禁用状态 - -#### 编辑器测试 - -- [x] 文本输入 -- [x] 确认按钮点击 -- [x] 取消按钮点击 -- [x] Ctrl+Enter 快捷键(组件内部已实现) -- [x] Escape 快捷键(组件内部已实现) - -#### 集成测试 - -- [x] 创建标注流程 -- [x] 编辑标注流程 -- [x] 删除标注流程 -- [x] 创建图形流程 - ---- - -## 📈 收益分析 - -### 1. 可维护性提升 - -**组件定位更快**: - -- 重构前: 在 1913 行文件中找工具栏代码 -- 重构后: 直接打开 WaveformToolbar.vue (174 行) -- **效率提升**: 10x ✅ - -**修改影响范围更小**: - -- 重构前: 修改工具栏可能影响主组件其他部分 -- 重构后: 修改工具栏只影响 WaveformToolbar.vue -- **风险降低**: 90% ✅ - ---- - -### 2. 可测试性提升 - -**独立单元测试**: - -```typescript -// 可以单独测试工具栏 -describe('WaveformToolbar', () => { - it('emits update:interaction-mode when button clicked', async () => { - const wrapper = mount(WaveformToolbar, { - props: { interactionMode: 'zoom', canEditSelection: false }, - }) - await wrapper.find('[aria-label="选择标注"]').trigger('click') - expect(wrapper.emitted('update:interaction-mode')).toBeTruthy() - }) -}) - -// 可以单独测试编辑器 -describe('WaveformEditor', () => { - it('emits confirm with trimmed text', async () => { - const wrapper = mount(WaveformEditor, { - props: { kind: 'annotation', initialText: '', style: {} }, - }) - await wrapper.find('textarea').setValue(' 测试文本 ') - await wrapper.find('.is-primary').trigger('click') - expect(wrapper.emitted('confirm')?.[0]).toEqual(['测试文本']) - }) -}) -``` - ---- - -### 3. 可复用性提升 - -**跨组件使用**: - -```vue - - - - - - - - - - - - -``` - ---- - -### 4. 代码质量提升 - -**职责单一**: - -- 每个组件只负责一件事 -- 工具栏 = UI 展示 + 事件分发 -- 编辑器 = 文本输入 + 快捷键 -- 主组件 = 业务逻辑协调 - -**接口清晰**: - -```typescript -// 工具栏接口清晰 -interface WaveformToolbarProps { - interactionMode: WaveformInteractionMode // 当前模式 - canEditSelection: boolean // 是否可编辑 -} - -// 编辑器接口清晰 -interface WaveformEditorProps { - kind: 'annotation' | 'shape' // 类型 - initialText: string // 初始文本 - style: CSSProperties // 位置样式 -} -``` - ---- - -## 🔄 与整体重构的关系 - -### 已完成的模块化 - -``` -src/ -├── types/ # ✅ 类型定义(阶段 1) -├── core/ # ✅ 核心数据处理(阶段 1) -├── utils/ # ✅ 工具函数(阶段 1) -├── components/ -│ ├── WaveformChart.vue # 主组件(~1823 行) -│ ├── WaveformToolbar.vue # ✨ 工具栏(174 行) -│ ├── WaveformEditor.vue # ✨ 编辑器(143 行) -│ ├── waveform-markup.ts -│ └── waveform.ts -``` - -### 下一步规划 - -``` -components/ -├── WaveformChart.vue # 主容器 -├── WaveformToolbar.vue # ✅ 已完成 -├── WaveformEditor.vue # ✅ 已完成 -├── WaveformTooltip.vue # 📝 待拆分 -├── WaveformTrack.vue # 📝 待拆分 -└── WaveformAnnotationLayer.vue # 📝 待拆分 -``` - ---- - -## 📚 API 文档 - -### WaveformToolbar API - -#### Props - -| 属性 | 类型 | 必填 | 默认值 | 说明 | -| ------------------ | ------------------------- | ---- | ------ | ----------------------- | -| `interactionMode` | `WaveformInteractionMode` | ✅ | - | 当前激活的交互模式 | -| `canEditSelection` | `boolean` | ✅ | - | 是否可以编辑/删除选中项 | - -#### Events - -| 事件名 | 参数 | 说明 | -| ------------------------- | ------------------------------- | ------------------ | -| `update:interaction-mode` | `mode: WaveformInteractionMode` | 交互模式变更时触发 | -| `edit` | - | 点击编辑按钮时触发 | -| `delete` | - | 点击删除按钮时触发 | - -#### 使用示例 - -```vue - -``` - ---- - -### WaveformEditor API - -#### Props - -| 属性 | 类型 | 必填 | 默认值 | 说明 | -| ------------- | ------------------------- | ---- | ------ | -------------- | -| `kind` | `'annotation' \| 'shape'` | ✅ | - | 编辑器类型 | -| `initialText` | `string` | ✅ | - | 初始文本内容 | -| `style` | `CSSProperties` | ✅ | - | 编辑器位置样式 | - -#### Events - -| 事件名 | 参数 | 说明 | -| --------- | -------------- | ---------------------------------- | -| `confirm` | `text: string` | 确认编辑时触发,返回 trim 后的文本 | -| `cancel` | - | 取消编辑时触发 | - -#### 使用示例 - -```vue - -``` - ---- - -## ✅ 验收清单 - -- [x] 创建 WaveformToolbar.vue 组件 -- [x] 创建 WaveformEditor.vue 组件 -- [x] 更新 WaveformChart.vue 使用新组件 -- [x] 删除主组件中的旧代码和样式 -- [x] 更新测试文件 -- [x] 所有测试通过 (24/24) -- [x] TypeScript 类型检查通过 -- [x] ESLint 代码规范通过 -- [x] 功能验证通过 -- [x] 向后兼容性验证 -- [x] 编写完整文档 - ---- - -## 🎉 总结 - -### 完成情况 - -✅ **工具栏和编辑器组件拆分成功** - -- 新增 2 个独立组件 -- 主组件减少 ~90 行代码 -- 所有测试通过 -- 代码质量提升 - -### 核心价值 - -| 维度 | 改善 | -| -------- | --------------------------- | -| 可维护性 | ✅ 组件独立,易于定位和修改 | -| 可测试性 | ✅ 支持独立单元测试 | -| 可复用性 | ✅ 可在其他组件中使用 | -| 代码质量 | ✅ 职责单一,接口清晰 | - -### 后续建议 - -1. **继续拆分 Tooltip 组件** - 将悬浮提示抽取为独立组件 -2. **拆分 Track 组件** - 单个波形轨道作为独立组件 -3. **拆分 AnnotationLayer 组件** - 标注层作为独立组件 -4. **编写组件单元测试** - 为新组件添加专门的测试文件 - ---- - -**完成时间**: 2026-07-18 -**影响范围**: `WaveformChart.vue`, 新增 2 个组件 -**破坏性变更**: 无 -**向后兼容**: ✅ 完全兼容 diff --git a/Y_AXIS_LABEL_FIX.md b/Y_AXIS_LABEL_FIX.md deleted file mode 100644 index 06a2abe..0000000 --- a/Y_AXIS_LABEL_FIX.md +++ /dev/null @@ -1,293 +0,0 @@ -# Y 轴标签重叠问题修复报告 - -## 🐛 问题描述 - -在"多道紧凑"(compact)模式下,当多个波形轨道叠加显示时,Y 轴标签会出现重叠现象,导致标签无法阅读。 - -### 问题截图位置 - -- 红色标记处:Y 轴标签 "BT2_2M" 和 "BT1_2M" 重叠 - -### 根本原因 - -1. 紧凑模式下,每个轨道的高度被压缩以容纳更多波形 -2. Y 轴标签是垂直旋转放置的,每个标签需要约 80px 的高度空间 -3. 当轨道高度 < 80px 时,相邻轨道的标签会发生重叠 - ---- - -## ✅ 解决方案 - -### 策略:智能间隔显示 - -采用**自适应间隔显示策略**:根据轨道高度动态决定显示哪些标签,避免重叠。 - -#### 核心逻辑 - -```typescript -/** - * 判断是否应该显示 Y 轴标签 - * 在紧凑模式下,当轨道高度太小时隐藏标签避免重叠 - */ -function shouldShowYAxisLabel(trackHeight: number, trackIndex: number): boolean { - // 标签需要的最小高度(像素) - const MIN_HEIGHT_FOR_LABEL = 80 - - if (trackHeight >= MIN_HEIGHT_FOR_LABEL) { - // 轨道高度足够,显示所有标签 - return true - } - - // 轨道高度不足时,使用间隔显示策略 - // 计算应该显示的轨道间隔 - const labelSpacing = Math.ceil(MIN_HEIGHT_FOR_LABEL / trackHeight) - - // 只显示间隔位置的标签 - return trackIndex % labelSpacing === 0 -} -``` - -#### 显示规则 - -| 轨道高度 | 显示策略 | 示例 | -| -------- | -------------- | ---------------------- | -| ≥ 80px | 显示所有标签 | 轨道 0, 1, 2, 3 都显示 | -| 40-79px | 每隔 1 个显示 | 轨道 0, 2, 4 显示 | -| 27-39px | 每隔 2 个显示 | 轨道 0, 3, 6 显示 | -| < 27px | 每隔 3+ 个显示 | 轨道 0, 4, 8 显示 | - ---- - -## 🎨 视觉增强 - -### 添加标签背景 - -为提高标签可读性,添加了半透明白色背景: - -```vue - - - - - - {{ resolveYAxisLabel(track.series) }} - - -``` - -### CSS 样式 - -```css -.waveform-chart__y-axis-label-bg { - fill: white; - opacity: 0.9; - pointer-events: none; -} - -.waveform-chart__y-axis-label { - font-size: 12px; - font-weight: 500; - pointer-events: none; -} -``` - ---- - -## 📊 效果对比 - -### 修复前 - -``` -轨道 0: BT2_2M ← 标签 -轨道 1: BT1_2M ← 标签 ⚠️ 与轨道 0 重叠 -轨道 2: BT3_2M ← 标签 ⚠️ 与轨道 1 重叠 -``` - -### 修复后(轨道高度 40px) - -``` -轨道 0: BT2_2M ← 显示标签 ✅ -轨道 1: ← 隐藏标签 ✅ -轨道 2: BT3_2M ← 显示标签 ✅ -``` - ---- - -## 🧪 测试验证 - -### 测试结果 - -```bash -✅ 所有测试通过 (24/24) -✅ TypeScript 类型检查通过 -✅ ESLint 代码规范通过 -``` - -### 手动测试场景 - -#### 场景 1:独立坐标模式 - -- **预期**: 所有标签都显示(轨道高度通常 > 80px) -- **结果**: ✅ 符合预期 - -#### 场景 2:多道分离模式 - -- **预期**: 所有标签都显示(轨道间有间隔) -- **结果**: ✅ 符合预期 - -#### 场景 3:多道紧凑模式 - 2 个轨道 - -- **轨道高度**: ~200px -- **预期**: 两个标签都显示 -- **结果**: ✅ 符合预期 - -#### 场景 4:多道紧凑模式 - 5 个轨道 - -- **轨道高度**: ~60px -- **预期**: 显示轨道 0, 2, 4 的标签 -- **结果**: ✅ 符合预期,无重叠 - -#### 场景 5:多道紧凑模式 - 10 个轨道 - -- **轨道高度**: ~30px -- **预期**: 显示轨道 0, 3, 6, 9 的标签 -- **结果**: ✅ 符合预期,无重叠 - ---- - -## 💡 设计考量 - -### 为什么不直接缩小字体? - -- ❌ 字体太小难以阅读 -- ❌ 仍然会重叠(只是延迟问题) -- ✅ 间隔显示更清晰 - -### 为什么不使用横向布局? - -- ❌ 横向标签占用更多水平空间 -- ❌ 会与波形图重叠 -- ✅ 垂直标签是行业标准 - -### 为什么使用间隔显示而不是全部隐藏? - -- ❌ 全部隐藏用户无法识别波形 -- ✅ 间隔显示保留关键信息 -- ✅ 用户可以通过显示的标签推断其他波形 - -### 为什么添加背景? - -- ✅ 提高标签与网格线的对比度 -- ✅ 防止标签与波形线重叠时难以阅读 -- ✅ 视觉层次更清晰 - ---- - -## 🚀 未来优化方向 - -### 短期(可选) - -1. **悬浮显示完整信息** - - 鼠标悬浮在轨道上时,显示该轨道的完整标签 - - 使用 Tooltip 或临时文本 - -2. **标签缩写** - - 当空间不足时,显示缩写版本(如 "BT2_2M" → "BT2") - - 完整名称通过 title 属性提供 - -### 中期(可选) - -3. **可配置阈值** - - 允许用户自定义 `MIN_HEIGHT_FOR_LABEL` - - 添加 props: `minLabelHeight?: number` - -4. **智能字体缩放** - - 根据轨道高度动态调整字体大小 - - 保持在可读范围内(10-14px) - -### 长期(可选) - -5. **外部标签面板** - - 在图表右侧添加独立的标签列表 - - 点击标签高亮对应波形 - - 类似于图例功能 - ---- - -## 📝 代码变更 - -### 文件:`src/components/WaveformChart.vue` - -#### 1. 新增函数(+26 行) - -```typescript -function shouldShowYAxisLabel(trackHeight: number, trackIndex: number): boolean { - const MIN_HEIGHT_FOR_LABEL = 80 - if (trackHeight >= MIN_HEIGHT_FOR_LABEL) return true - - const labelSpacing = Math.ceil(MIN_HEIGHT_FOR_LABEL / trackHeight) - return trackIndex % labelSpacing === 0 -} -``` - -#### 2. 更新模板(修改 15 行) - -- 添加条件判断 `shouldShowYAxisLabel(track.height, track.index)` -- 使用 `` 包裹标签和背景 -- 添加标签背景 `` - -#### 3. 新增样式(+5 行) - -```css -.waveform-chart__y-axis-label-bg { - fill: white; - opacity: 0.9; - pointer-events: none; -} -``` - -### 总代码变更 - -- **新增**: 46 行 -- **修改**: 15 行 -- **删除**: 10 行 -- **净增**: 41 行 - ---- - -## ✅ 验收标准 - -- [x] 紧凑模式下标签不重叠 -- [x] 所有单元测试通过 -- [x] TypeScript 类型检查通过 -- [x] 代码规范检查通过 -- [x] 不同轨道数量场景测试通过 -- [x] 标签可读性良好 -- [x] 性能无明显影响 - ---- - -## 📚 相关文档 - -- [WaveformChart 组件文档](../doc/04-API设计.md) -- [项目架构文档](../ARCHITECTURE.md) - ---- - -**修复时间**: 2026-07-18 -**影响范围**: `WaveformChart.vue` 组件 -**破坏性变更**: 无 -**向后兼容**: ✅ 完全兼容