chore: remove obsolete project documents
Some checks failed
Package component / package (push) Failing after 30s
Some checks failed
Package component / package (push) Failing after 30s
This commit is contained in:
@@ -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<ZoomTransform>(zoomIdentity)
|
||||
const independentTransforms = shallowRef<ZoomTransform[]>([])
|
||||
|
||||
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<WaveformInteractionMode>('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<HoveredSeriesPoint[]>([])
|
||||
const hoveredTrackIndex = ref<number | null>(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<WaveformMarkupSelection>(null)
|
||||
const editingDraft = ref<EditingDraft | null>(null)
|
||||
const rangeDraft = ref<RangeDraft | null>(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
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
// 数据系统
|
||||
import { normalizeWaveformSeries, computeTrackLayouts } from './data'
|
||||
import type { WaveformData, DisplayMode } from './data/types'
|
||||
|
||||
// 缩放系统
|
||||
import { useZoom } from './zoom'
|
||||
|
||||
// 交互系统
|
||||
import { useInteraction, useHover } from './interaction'
|
||||
import WaveformToolbar from './interaction/WaveformToolbar.vue'
|
||||
import WaveformTooltip from './interaction/WaveformTooltip.vue'
|
||||
|
||||
// 标注系统
|
||||
import { useAnnotation } from './annotation'
|
||||
import WaveformAnnotationLayer from './annotation/WaveformAnnotationLayer.vue'
|
||||
import WaveformEditor from './annotation/WaveformEditor.vue'
|
||||
|
||||
// 渲染系统
|
||||
import WaveformTrack from './rendering/WaveformTrack.vue'
|
||||
|
||||
// 核心
|
||||
import { channelColors, margin } from './core/constants'
|
||||
|
||||
const props = defineProps<{
|
||||
data: WaveformData
|
||||
displayMode?: DisplayMode
|
||||
// ...
|
||||
}>()
|
||||
|
||||
// 数据处理
|
||||
const chartSeries = computed(() => normalizeWaveformSeries(props.data))
|
||||
const trackLayouts = computed(() => computeTrackLayouts(chartSeries.value, ...))
|
||||
|
||||
// 缩放系统
|
||||
const {
|
||||
sharedTransform,
|
||||
independentTransforms,
|
||||
configureZoom,
|
||||
resetViewport,
|
||||
} = useZoom({ /* options */ })
|
||||
|
||||
// 交互系统
|
||||
const { interactionMode, setInteractionMode } = useInteraction({ /* options */ })
|
||||
const { hoveredSeriesPoints, handlePointerMove, clearHover } = useHover({ /* options */ })
|
||||
|
||||
// 标注系统
|
||||
const {
|
||||
selection,
|
||||
editingDraft,
|
||||
createAnnotation,
|
||||
editAnnotation,
|
||||
deleteAnnotation,
|
||||
} = useAnnotation({ /* options */ })
|
||||
|
||||
// 生命周期和监听
|
||||
watch(() => props.data, resetViewport)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="waveform-chart">
|
||||
<svg>
|
||||
<!-- 轨道渲染 -->
|
||||
<WaveformTrack
|
||||
v-for="track in trackLayouts"
|
||||
:key="track.index"
|
||||
:track="track"
|
||||
@pointer-move="handlePointerMove($event, track.index)"
|
||||
/>
|
||||
|
||||
<!-- 标注层 -->
|
||||
<WaveformAnnotationLayer :annotations="renderedAnnotations" :shapes="renderedShapes" />
|
||||
</svg>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<WaveformToolbar
|
||||
:interaction-mode="interactionMode"
|
||||
@update:interaction-mode="setInteractionMode"
|
||||
/>
|
||||
|
||||
<!-- 编辑器 -->
|
||||
<WaveformEditor v-if="editingDraft" :draft="editingDraft" />
|
||||
|
||||
<!-- Tooltip -->
|
||||
<WaveformTooltip
|
||||
:visible="hoveredSeriesPoints.length > 0"
|
||||
:series-points="hoveredSeriesPoints"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 收益
|
||||
|
||||
### 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'`
|
||||
294
.claude/plan.md
294
.claude/plan.md
@@ -1,294 +0,0 @@
|
||||
# WaveformChart 组件拆分计划
|
||||
|
||||
## 目标
|
||||
|
||||
将 `WaveformChart.vue`(1743 行)拆分为更小的、职责单一的子组件:
|
||||
|
||||
1. **WaveformTooltip.vue** - 悬浮提示组件
|
||||
2. **WaveformTrack.vue** - 单个波形轨道组件
|
||||
3. **WaveformAnnotationLayer.vue** - 标注层组件
|
||||
|
||||
## 当前代码分析
|
||||
|
||||
### 主组件职责(过多)
|
||||
|
||||
- ✅ 数据管理和状态协调
|
||||
- ✅ 缩放和交互事件处理
|
||||
- 🔴 渲染波形轨道(网格、轴、波形线、十字线)
|
||||
- 🔴 渲染标注和图形
|
||||
- 🔴 渲染悬浮提示
|
||||
- ✅ 工具栏管理(已拆分)
|
||||
- ✅ 编辑器管理(已拆分)
|
||||
|
||||
### 模板结构(1055-1743 行)
|
||||
|
||||
```vue
|
||||
<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. Tooltip(15 行) -->
|
||||
<div v-if="showTooltip && hoveredPoint" class="waveform-chart__tooltip">...</div>
|
||||
|
||||
<!-- 4. 已拆分组件 -->
|
||||
<WaveformToolbar />
|
||||
<WaveformEditor />
|
||||
</div>
|
||||
```
|
||||
|
||||
## 拆分策略
|
||||
|
||||
### 组件 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. 样式隔离
|
||||
|
||||
每个组件使用 `<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 个单元测试。
|
||||
|
||||
---
|
||||
|
||||
## 实施时间估算
|
||||
|
||||
- 阶段 1(Tooltip):30 分钟
|
||||
- 阶段 2(AnnotationLayer):1 小时
|
||||
- 阶段 3(Track):1.5 小时
|
||||
- 阶段 4(测试):30 分钟
|
||||
|
||||
**总计**:约 3.5 小时
|
||||
@@ -1,185 +0,0 @@
|
||||
# 标注拖动功能迁移指南
|
||||
|
||||
## 概述
|
||||
|
||||
版本更新添加了标注标签拖动功能,允许用户手动调整重叠标签的位置。此功能引入了接口变更和行为变化。
|
||||
|
||||
## 接口变更
|
||||
|
||||
### WaveformAnnotation 接口新增字段
|
||||
|
||||
```typescript
|
||||
interface WaveformAnnotation {
|
||||
// ... 现有字段
|
||||
|
||||
// 新增:标签偏移量(像素)
|
||||
labelOffsetX?: number
|
||||
labelOffsetY?: number
|
||||
}
|
||||
```
|
||||
|
||||
**影响范围**:
|
||||
|
||||
- 序列化/反序列化代码
|
||||
- 标注验证逻辑
|
||||
- 类型检查工具
|
||||
|
||||
### 迁移步骤
|
||||
|
||||
#### 1. 更新序列化代码
|
||||
|
||||
如果您有过滤已知字段的序列化代码,请添加新字段:
|
||||
|
||||
```typescript
|
||||
// 修改前
|
||||
function serializeAnnotation(annotation: WaveformAnnotation) {
|
||||
return {
|
||||
id: annotation.id,
|
||||
seriesId: annotation.seriesId,
|
||||
x: annotation.x,
|
||||
y: annotation.y,
|
||||
label: annotation.label,
|
||||
}
|
||||
}
|
||||
|
||||
// 修改后
|
||||
function serializeAnnotation(annotation: WaveformAnnotation) {
|
||||
return {
|
||||
id: annotation.id,
|
||||
seriesId: annotation.seriesId,
|
||||
x: annotation.x,
|
||||
y: annotation.y,
|
||||
label: annotation.label,
|
||||
// 添加新字段(如果存在)
|
||||
...(annotation.labelOffsetX !== undefined && { labelOffsetX: annotation.labelOffsetX }),
|
||||
...(annotation.labelOffsetY !== undefined && { labelOffsetY: annotation.labelOffsetY }),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 更新验证逻辑
|
||||
|
||||
如果使用严格的对象键检查,请允许新字段:
|
||||
|
||||
```typescript
|
||||
// 修改前
|
||||
const ALLOWED_KEYS = ['id', 'seriesId', 'x', 'y', 'label']
|
||||
|
||||
// 修改后
|
||||
const ALLOWED_KEYS = ['id', 'seriesId', 'x', 'y', 'label', 'labelOffsetX', 'labelOffsetY']
|
||||
```
|
||||
|
||||
#### 3. 更新 JSON Schema(如果使用)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"seriesId": { "type": "string" },
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"label": { "type": "string" },
|
||||
"labelOffsetX": { "type": "number" },
|
||||
"labelOffsetY": { "type": "number" }
|
||||
},
|
||||
"required": ["id", "seriesId", "x", "y"]
|
||||
}
|
||||
```
|
||||
|
||||
## 行为变更
|
||||
|
||||
### 1. 自动碰撞检测已移除
|
||||
|
||||
**之前**:标注标签会自动避免重叠,系统尝试 8 种放置位置(上/下/左/右及对角线)。
|
||||
|
||||
**现在**:标注标签默认放置在数据点上方,重叠时需要手动拖动调整。
|
||||
|
||||
**原因**:手动拖动提供了更精确的控制,避免了自动布局可能产生的意外位置。
|
||||
|
||||
**迁移建议**:
|
||||
|
||||
- 如果您的应用依赖自动避让,请在文档中告知用户现在需要手动调整
|
||||
- 可以通过监听 `@move` 事件来实现自定义的自动布局逻辑
|
||||
|
||||
### 2. 标注系列切换使用最近采样点
|
||||
|
||||
**之前**:在编辑器中切换标注所属系列时,Y 坐标通过插值计算。
|
||||
|
||||
**现在**:Y 坐标捕捉到最近的实际采样点。
|
||||
|
||||
**影响**:对于阶梯线或稀疏数据,切换系列时 Y 值可能会跳变到不同的采样点位置。
|
||||
|
||||
**用户体验建议**:
|
||||
|
||||
- 在 UI 中添加提示:"切换系列将捕捉到最近的数据点"
|
||||
- 考虑在切换前保存原始坐标,提供"恢复"功能
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
✅ **完全向后兼容**:
|
||||
|
||||
- 新字段是可选的
|
||||
- 未设置偏移量时,行为与之前相同
|
||||
- 旧数据可以无需修改直接使用
|
||||
|
||||
❌ **可能不兼容的场景**:
|
||||
|
||||
1. **严格类型检查**:使用 `Object.keys().length` 检查精确键数量
|
||||
2. **JSON Schema 验证**:`additionalProperties: false` 会拒绝新字段
|
||||
3. **序列化白名单**:只序列化已知字段会丢失偏移量
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 单元测试
|
||||
|
||||
```typescript
|
||||
describe('Annotation serialization', () => {
|
||||
it('should preserve labelOffset fields', () => {
|
||||
const annotation: WaveformAnnotation = {
|
||||
id: 'test',
|
||||
seriesId: 'series-1',
|
||||
x: 100,
|
||||
y: 50,
|
||||
label: 'Test',
|
||||
labelOffsetX: 10,
|
||||
labelOffsetY: -20,
|
||||
}
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(annotation))
|
||||
expect(serialized.labelOffsetX).toBe(10)
|
||||
expect(serialized.labelOffsetY).toBe(-20)
|
||||
})
|
||||
|
||||
it('should handle annotations without offsets', () => {
|
||||
const annotation: WaveformAnnotation = {
|
||||
id: 'test',
|
||||
seriesId: 'series-1',
|
||||
x: 100,
|
||||
y: 50,
|
||||
label: 'Test',
|
||||
}
|
||||
|
||||
// 应该不会抛出错误
|
||||
expect(() => renderAnnotation(annotation)).not.toThrow()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 集成测试
|
||||
|
||||
1. 加载旧数据文件,验证标注正常显示
|
||||
2. 拖动标注,验证偏移量正确保存
|
||||
3. 重新加载,验证偏移量持久化
|
||||
|
||||
## 支持
|
||||
|
||||
如有问题,请查看:
|
||||
|
||||
- 示例代码:`src/components/WaveformChart.test.ts` (行 942-1020)
|
||||
- 类型定义:`src/types/chart.ts` (行 928-932)
|
||||
- API 文档:`README.md`
|
||||
|
||||
## 更新日期
|
||||
|
||||
2026-07-21
|
||||
122
ARCHITECTURE.md
122
ARCHITECTURE.md
@@ -1,122 +0,0 @@
|
||||
# 波形分析组件项目架构
|
||||
|
||||
## 项目概览
|
||||
|
||||
这是一个基于 Vue 3、TypeScript 和 D3.js 的响应式波形可视化组件库,面向大规模数据集提供高性能的 SVG 波形渲染、缩放和悬浮取点能力。
|
||||
|
||||
- **技术栈**: Vue 3、TypeScript、D3.js、Vite
|
||||
- **包管理器**: pnpm
|
||||
- **测试**: Vitest、Vue Test Utils
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
src/
|
||||
├── components/
|
||||
│ ├── WaveformChart.vue # 主波形图组件
|
||||
│ ├── waveform.ts # 数据规范化兼容入口
|
||||
│ ├── data/types.ts # 组件内部数据类型入口
|
||||
│ ├── interaction/
|
||||
│ │ └── WaveformTooltip.vue # 悬浮提示
|
||||
│ ├── rendering/
|
||||
│ │ └── WaveformTrack.vue # 单轨道 SVG 渲染
|
||||
│ ├── core/ # 常量和组件核心类型
|
||||
│ └── WaveformChart.test.ts # 组件与数据测试
|
||||
├── core/ # 数据规范化、可见域裁剪和保峰降采样
|
||||
├── types/ # 公开数据和图表类型
|
||||
├── utils/ # 域、格式化和几何工具
|
||||
├── data/ # 示例波形数据
|
||||
└── App.vue # Demo 应用
|
||||
```
|
||||
|
||||
## 分层架构
|
||||
|
||||
```text
|
||||
外部 WaveformData
|
||||
│
|
||||
▼
|
||||
normalizeWaveformData / normalizeWaveformSeries
|
||||
│
|
||||
▼
|
||||
不可变数据缓存 / 坐标域元数据
|
||||
│
|
||||
▼
|
||||
WaveformChart
|
||||
├─ chartSeries / trackLayouts
|
||||
├─ D3 scales、坐标轴和可见域降采样路径
|
||||
├─ 独立或共享模式的 zoom behavior
|
||||
├─ hover 最近点与 WaveformTooltip
|
||||
└─ 受控采样点标注与自动布局
|
||||
│
|
||||
▼
|
||||
WaveformTrack(每个 series 一条 SVG 轨道)
|
||||
```
|
||||
|
||||
## 核心组件
|
||||
|
||||
### WaveformChart
|
||||
|
||||
`WaveformChart` 负责数据归一化后的布局、坐标轴、路径、缩放和跨轨道 hover 协调。
|
||||
|
||||
支持三种显示模式:
|
||||
|
||||
- `independent`: 每个波形独立坐标和缩放
|
||||
- `separated`: 波形垂直堆叠,共享 X 轴
|
||||
- `compact`: 多个波形在紧凑布局中展示
|
||||
|
||||
叠加曲线由独立的 `overlayMode` 控制 Y 轴:`single-axis` 共享一个值轴,
|
||||
`multi-axis` 最多使用四个值轴,超出的曲线复用第 4 轴。
|
||||
|
||||
公开输入包括 `data`、显示模式、尺寸、标签、颜色、tooltip、缩放、时间单位、帧号和
|
||||
受控标注状态;公开事件包括 `point-hover`、`zoom-change` 以及标注 CRUD 生命周期事件。
|
||||
|
||||
标注只保存 `seriesId` 与 `x/y` 数据坐标,渲染时根据当前轨道比例尺重新投影。新增支持
|
||||
工具模式和绘图区任意位置右键快捷创建;创建后的标注通过连接线绑定到对应的数据坐标,已有标注通过右键菜单编辑或删除。
|
||||
|
||||
### WaveformTrack
|
||||
|
||||
轨道组件渲染网格、坐标轴、端点标签、波形路径、帧号和 hover 十字线。独立模式下,
|
||||
轨道创建自己的透明交互层;共享模式下由图表创建覆盖整个绘图区的交互层。
|
||||
|
||||
### WaveformTooltip
|
||||
|
||||
根据当前轨道或共享 X 坐标显示最近数据点、系列名称、单位和格式化时间。tooltip 不
|
||||
持有数据状态,由 `WaveformChart` 通过 props 控制显示位置和内容。
|
||||
|
||||
### WaveformAnnotationLayer
|
||||
|
||||
标注层使用 SVG 圆点、箭头、矩形和多行文本渲染标注,不依赖图表库私有 DOM。布局按实际
|
||||
轨道尺寸计算,并使用确定性的候选偏移避让相邻文字框;无效标注会被忽略但不会从受控
|
||||
数组中删除。
|
||||
|
||||
X、Y 轴在显示域最大绝对值小于 `0.01` 或大于等于 `100` 时使用共享科学计数指数;刻度固定保留两位缩放值,倍率以 `E±NN` 独立显示在轴末端。X 轴按当前时间单位换算后判断,多 Y 轴分别计算。tooltip 和标注编辑器使用各自的普通十进制格式。所有格式化仅作用于展示层,受控数据与比例尺仍使用原始数值。
|
||||
标注框候选位置按上、下、右、左及四个对角方向排序,优先垂直布局并按轨道独立执行碰撞避让。
|
||||
|
||||
## 数据流
|
||||
|
||||
1. `normalizeWaveformData` 将 samples 或 points 输入转换为有序有限点。
|
||||
2. `normalizeWaveformSeries` 为多通道数据提供稳定 ID,并过滤空通道。
|
||||
3. 数据引用变化时缓存每个通道的有序点和 X/Y 域。
|
||||
4. `WaveformChart` 截取可见域,并按像素桶保留首点、末点和 Y 极值后生成 SVG path。
|
||||
5. D3 zoom 更新比例尺变换并发出 `zoom-change`,pointer move 仍基于原始点更新 hover。
|
||||
|
||||
内部时间坐标始终使用秒;`timeUnit` 只影响轴、端点和 tooltip 的显示格式。
|
||||
|
||||
## 设计约束
|
||||
|
||||
- 使用 `shallowRef` 保存 D3 行为和 ResizeObserver,避免深层响应式开销。
|
||||
- 缩放行为只由 `zoomable`、显示模式、绘图区尺寸和通道数决定。
|
||||
- 父组件通过 props 提供不可变数据;只有替换 `data` 引用才会刷新内部缓存。
|
||||
- 空数据和非法数据必须渲染明确的空状态,而不是创建无效 SVG path。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
`WaveformChart.test.ts` 覆盖数据规范化、空数据、多通道布局、坐标轴、显示模式、hover、
|
||||
tooltip、缩放和响应式尺寸变化。完成改动后运行:
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
pnpm typecheck
|
||||
pnpm lint
|
||||
pnpm build
|
||||
```
|
||||
@@ -1,639 +0,0 @@
|
||||
# WaveformChart 组件拆分完成报告
|
||||
|
||||
## 🎯 任务概述
|
||||
|
||||
将 `WaveformChart.vue` 主组件(1743 行)拆分为更小的、职责单一的子组件,降低复杂度,提高可维护性。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成的工作
|
||||
|
||||
### 1. 新建 `WaveformTooltip.vue` 组件
|
||||
|
||||
**文件**: `src/components/WaveformTooltip.vue` (111 行)
|
||||
|
||||
**职责**: 显示鼠标悬浮时的数据点信息
|
||||
|
||||
**Props**:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
visible: boolean
|
||||
position: { x: number; y: number }
|
||||
timeUnit: 's' | 'ms'
|
||||
hoveredPoint: WaveformPoint | null
|
||||
seriesPoints: SeriesPoint[]
|
||||
containerWidth: number
|
||||
containerHeight: number
|
||||
}
|
||||
```
|
||||
|
||||
**特性**:
|
||||
|
||||
- 自动计算位置避免溢出容器
|
||||
- 支持多系列数据显示
|
||||
- 响应式样式计算
|
||||
- 保持向后兼容的 CSS 类名
|
||||
|
||||
---
|
||||
|
||||
### 2. 新建 `WaveformAnnotationLayer.vue` 组件
|
||||
|
||||
**文件**: `src/components/WaveformAnnotationLayer.vue` (281 行)
|
||||
|
||||
**职责**: 渲染所有标注和图形(annotations + shapes + range preview)
|
||||
|
||||
**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
|
||||
}
|
||||
```
|
||||
|
||||
**特性**:
|
||||
|
||||
- 渲染标注箭头和文本框
|
||||
- 渲染垂直线和时间区间
|
||||
- 渲染区间拖拽预览
|
||||
- 交互模式控制(选择/编辑)
|
||||
- 保持向后兼容的 CSS 类名
|
||||
|
||||
---
|
||||
|
||||
### 3. 新建 `WaveformTrack.vue` 组件
|
||||
|
||||
**文件**: `src/components/WaveformTrack.vue` (317 行)
|
||||
|
||||
**职责**: 渲染单个波形轨道(网格、坐标轴、波形线、十字线、overlay)
|
||||
|
||||
**Props**:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
track: TrackLayout
|
||||
clipPathId: string
|
||||
innerWidth: number
|
||||
showTooltip: boolean
|
||||
zoomable: boolean
|
||||
displayMode: WaveformDisplayMode
|
||||
activeInteractionMode: WaveformInteractionMode
|
||||
frameNumber?: string | number
|
||||
timeUnit: 's' | 'ms'
|
||||
yLabel?: string
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
**特性**:
|
||||
|
||||
- 渲染网格(主要/次要刻度)
|
||||
- 渲染 X/Y 坐标轴(使用 D3.js)
|
||||
- 渲染 Y 轴标签(智能间隔显示)
|
||||
- 渲染波形线
|
||||
- 渲染十字线
|
||||
- 渲染帧编号水印
|
||||
- 独立模式下的交互覆盖层
|
||||
- 保持向后兼容的 CSS 类名
|
||||
|
||||
---
|
||||
|
||||
### 4. 重构 `WaveformChart.vue` 主组件
|
||||
|
||||
**文件**: `src/components/WaveformChart.vue`
|
||||
|
||||
**删除内容** (~600 行):
|
||||
|
||||
- ✅ Tooltip 模板和样式 (~80 行)
|
||||
- ✅ 标注层模板和样式 (~280 行)
|
||||
- ✅ 轨道渲染模板和样式 (~240 行)
|
||||
- ✅ 工具函数:`tooltipStyle`, `isSelected`, `safeDomId`, `arrowMarkerId`, `annotationBoxStyle`, `shapeLabelWidth`, `shapeLabelX`, `shapeLabelStyle`, `trackHoverPoint`, `crosshairX`, `crosshairY`, `resolveYAxisLabel`, `shouldShowYAxisLabel`, `renderAxes`
|
||||
|
||||
**添加内容** (~40 行):
|
||||
|
||||
- ✅ 导入新组件
|
||||
- ✅ 新增 `TooltipSeriesPoint` 接口
|
||||
- ✅ 新增 `tooltipSeriesPoints` 计算属性
|
||||
- ✅ 保留必要的辅助函数(`safeDomId`, `arrowMarkerId`)用于生成 SVG marker ID
|
||||
|
||||
**模板对比**:
|
||||
|
||||
**重构前** (~200 行):
|
||||
|
||||
```vue
|
||||
<g v-for="track in trackLayouts">
|
||||
<!-- 网格 -->
|
||||
<g class="waveform-chart__grid waveform-chart__grid--minor">...</g>
|
||||
<g class="waveform-chart__grid waveform-chart__grid--major">...</g>
|
||||
<!-- 水印 -->
|
||||
<text class="waveform-chart__watermark">...</text>
|
||||
<!-- 坐标轴 -->
|
||||
<g class="waveform-chart__axis waveform-chart__axis--x">...</g>
|
||||
<g class="waveform-chart__axis waveform-chart__axis--y">...</g>
|
||||
<!-- Y 轴标签 -->
|
||||
<text class="waveform-chart__y-axis-label">...</text>
|
||||
<!-- 波形线 -->
|
||||
<path class="waveform-chart__line">...</path>
|
||||
<!-- 十字线 -->
|
||||
<g class="waveform-chart__crosshair">...</g>
|
||||
<!-- 覆盖层 -->
|
||||
<rect class="waveform-chart__overlay">...</rect>
|
||||
</g>
|
||||
|
||||
<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>
|
||||
|
||||
<div v-if="showTooltip && hoveredPoint" class="waveform-chart__tooltip">...</div>
|
||||
```
|
||||
|
||||
**重构后** (~25 行):
|
||||
|
||||
```vue
|
||||
<!-- 轨道渲染 -->
|
||||
<WaveformTrack
|
||||
v-for="track in trackLayouts"
|
||||
:key="`${track.index}-${track.series.name}`"
|
||||
:track="track"
|
||||
:clip-path-id="clipPathId"
|
||||
:inner-width="innerWidth"
|
||||
:show-tooltip="showTooltip"
|
||||
:zoomable="zoomable"
|
||||
:display-mode="displayMode"
|
||||
:active-interaction-mode="activeInteractionMode"
|
||||
:frame-number="resolveFrameNumber(track.index)"
|
||||
:time-unit="timeUnit"
|
||||
:y-label="yLabel"
|
||||
:hovered-point="hoveredSeriesPoints.find((p) => p.trackIndex === track.index)"
|
||||
@pointer-move="handleIndependentPointerMove($event, track.index)"
|
||||
@pointer-leave="clearHover"
|
||||
@pointer-down="handleRangePointerDown($event, track.index)"
|
||||
@pointer-up="handleRangePointerUp"
|
||||
@pointer-cancel="handleRangePointerCancel"
|
||||
@click="handleOverlayClick($event, track.index)"
|
||||
/>
|
||||
|
||||
<!-- 标注层 -->
|
||||
<WaveformAnnotationLayer
|
||||
:rendered-annotations="renderedAnnotations"
|
||||
:rendered-shapes="renderedShapes"
|
||||
:rendered-range-preview="renderedRangePreview"
|
||||
:active-interaction-mode="activeInteractionMode"
|
||||
:selection="selection"
|
||||
:inner-width="innerWidth"
|
||||
:clip-path-id="clipPathId"
|
||||
@select-markup="selectMarkup"
|
||||
@edit-markup="editMarkup"
|
||||
/>
|
||||
|
||||
<!-- Tooltip -->
|
||||
<WaveformTooltip
|
||||
:visible="showTooltip && hoveredPoint !== null"
|
||||
:position="hoverPosition"
|
||||
:time-unit="timeUnit"
|
||||
:hovered-point="hoveredPoint"
|
||||
:series-points="tooltipSeriesPoints"
|
||||
:container-width="width"
|
||||
:container-height="chartHeight"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
| 指标 | 数值 |
|
||||
| --------------------------- | --------------- |
|
||||
| **新增组件** | 3 个 |
|
||||
| **WaveformTooltip** | 111 行 |
|
||||
| **WaveformAnnotationLayer** | 281 行 |
|
||||
| **WaveformTrack** | 317 行 |
|
||||
| **主组件减少** | ~600 行 |
|
||||
| **主组件行数** | 1743 → ~1143 行 |
|
||||
| **复杂度降低** | 34.4% |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 架构改进
|
||||
|
||||
### 组件依赖关系
|
||||
|
||||
```
|
||||
WaveformChart.vue (主组件 ~1143 行)
|
||||
├── WaveformToolbar.vue (工具栏 174 行) ✅ 已完成
|
||||
├── WaveformEditor.vue (编辑器 149 行) ✅ 已完成
|
||||
├── WaveformTooltip.vue (悬浮提示 111 行) ✅ 新增
|
||||
├── WaveformTrack.vue (波形轨道 317 行) ✅ 新增
|
||||
└── WaveformAnnotationLayer.vue (标注层 281 行) ✅ 新增
|
||||
```
|
||||
|
||||
### 职责划分
|
||||
|
||||
#### WaveformChart (主组件)
|
||||
|
||||
- 数据管理和状态协调
|
||||
- 缩放和交互事件处理
|
||||
- 计算轨道布局
|
||||
- 计算渲染数据(annotations, shapes)
|
||||
- 标注和图形的增删改逻辑
|
||||
|
||||
#### WaveformTooltip (悬浮提示)
|
||||
|
||||
- 显示数据点信息
|
||||
- 自动位置计算
|
||||
- 响应式样式
|
||||
|
||||
#### WaveformAnnotationLayer (标注层)
|
||||
|
||||
- 渲染标注(箭头、文本框)
|
||||
- 渲染图形(垂直线、时间区间)
|
||||
- 渲染区间预览
|
||||
- 交互响应(选择、编辑)
|
||||
|
||||
#### WaveformTrack (波形轨道)
|
||||
|
||||
- 渲染网格和坐标轴
|
||||
- 渲染波形线
|
||||
- 渲染十字线
|
||||
- 渲染 Y 轴标签
|
||||
- 渲染水印
|
||||
- 独立模式交互
|
||||
|
||||
---
|
||||
|
||||
## 💡 设计亮点
|
||||
|
||||
### 1. 向后兼容
|
||||
|
||||
所有新组件都保留了原始的 CSS 类名,确保现有测试和外部样式不受影响:
|
||||
|
||||
```vue
|
||||
<!-- 新组件中同时使用新旧类名 -->
|
||||
<g class="waveform-track waveform-chart__track">
|
||||
<path class="waveform-track__line waveform-chart__line">
|
||||
<div class="waveform-tooltip waveform-chart__tooltip">
|
||||
```
|
||||
|
||||
### 2. Props 最小化
|
||||
|
||||
每个子组件只接收必要的 props,避免过度耦合:
|
||||
|
||||
```typescript
|
||||
// ✅ 好的设计 - WaveformTooltip
|
||||
interface Props {
|
||||
visible: boolean
|
||||
position: { x: number; y: number }
|
||||
hoveredPoint: WaveformPoint | null
|
||||
// ... 只传递渲染所需的数据
|
||||
}
|
||||
|
||||
// ❌ 避免的设计
|
||||
interface Props {
|
||||
data: WaveformData // 传递整个数据对象
|
||||
annotations: WaveformAnnotation[] // 传递不相关的数据
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 事件向上传递
|
||||
|
||||
子组件不直接修改状态,通过事件通知父组件:
|
||||
|
||||
```typescript
|
||||
// WaveformAnnotationLayer
|
||||
emit('select-markup', 'annotation', id)
|
||||
emit('edit-markup', 'shape', id)
|
||||
|
||||
// WaveformTrack
|
||||
emit('pointer-move', $event)
|
||||
emit('click', $event)
|
||||
```
|
||||
|
||||
### 4. D3 渲染封装
|
||||
|
||||
`WaveformTrack` 组件内部封装了 D3 坐标轴渲染:
|
||||
|
||||
```typescript
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
renderAxes()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.track,
|
||||
async () => {
|
||||
await nextTick()
|
||||
renderAxes()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
```
|
||||
|
||||
### 5. 样式隔离
|
||||
|
||||
每个组件使用 `<style scoped>`,但通过双类名保持兼容性。
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### 测试结果
|
||||
|
||||
```bash
|
||||
✅ 所有测试通过 (24/24)
|
||||
✅ TypeScript 类型检查通过
|
||||
✅ ESLint 代码规范通过
|
||||
```
|
||||
|
||||
### 测试覆盖场景
|
||||
|
||||
#### 通过的测试
|
||||
|
||||
- ✅ 渲染波形路径和响应宽度变化
|
||||
- ✅ 渲染显式点数据和单点支持
|
||||
- ✅ 悬浮时发出最近点事件并在离开时清除
|
||||
- ✅ 渲染参考网格样式和可选帧水印
|
||||
- ✅ 默认使用毫秒并支持秒和自定义标签
|
||||
- ✅ 将精确的可见范围值固定到两个 x 轴端点
|
||||
- ✅ 保持 zoom-change 域为源秒
|
||||
- ✅ 默认将命名的多通道路径渲染为独立轨道
|
||||
- ✅ 优先使用修剪的系列名称,对于未命名数据回退到 yLabel
|
||||
- ✅ 在分离模式下同步每个通道的 tooltip 并保留旧事件
|
||||
- ✅ 保持分离的轨道分开,同时共享一个 x 轴和一个交互层
|
||||
- ✅ 在紧凑模式下连接轨道而不留间隙,只保留底部 x 轴
|
||||
- ✅ 仅缩放活动的独立轨道并在模式改变时重置
|
||||
- ✅ 更新渲染属性并禁用缩放交互
|
||||
- ✅ 渲染数据绑定的标注和全系列或单系列图形
|
||||
- ✅ 通过受控 API 在最近的样本处创建点标注
|
||||
- ✅ 创建规范化的区间并忽略短于三像素的拖拽
|
||||
- ✅ 仅在缩放工具激活时绑定缩放
|
||||
- ✅ 创建/编辑/删除标注的完整流程
|
||||
- ✅ 创建/编辑/删除图形的完整流程
|
||||
- ✅ 键盘快捷键(Escape, Delete, Backspace)
|
||||
- ✅ 工具栏交互和模式切换
|
||||
- ✅ 编辑器显示和文本输入
|
||||
- ✅ 多通道数据的正确处理
|
||||
|
||||
---
|
||||
|
||||
## 📈 收益分析
|
||||
|
||||
### 1. 可维护性提升 ⭐⭐⭐⭐⭐
|
||||
|
||||
**组件定位更快**:
|
||||
|
||||
- 重构前: 在 1743 行文件中找轨道渲染代码
|
||||
- 重构后: 直接打开 WaveformTrack.vue (317 行)
|
||||
- **效率提升**: 5x ✅
|
||||
|
||||
**修改影响范围更小**:
|
||||
|
||||
- 重构前: 修改轨道可能影响主组件其他部分
|
||||
- 重构后: 修改轨道只影响 WaveformTrack.vue
|
||||
- **风险降低**: 80% ✅
|
||||
|
||||
---
|
||||
|
||||
### 2. 可测试性提升 ⭐⭐⭐⭐⭐
|
||||
|
||||
**独立单元测试**:
|
||||
|
||||
```typescript
|
||||
// 可以单独测试 Tooltip
|
||||
describe('WaveformTooltip', () => {
|
||||
it('calculates position to avoid overflow', () => {
|
||||
const wrapper = mount(WaveformTooltip, {
|
||||
props: {
|
||||
visible: true,
|
||||
position: { x: 750, y: 50 },
|
||||
containerWidth: 800,
|
||||
hoveredPoint: { x: 1, y: 2 },
|
||||
},
|
||||
})
|
||||
expect(wrapper.element.style.left).toBe('550px') // 避免溢出
|
||||
})
|
||||
})
|
||||
|
||||
// 可以单独测试轨道
|
||||
describe('WaveformTrack', () => {
|
||||
it('renders Y axis label with fallback', () => {
|
||||
const wrapper = mount(WaveformTrack, {
|
||||
props: {
|
||||
track: { series: { name: '' } },
|
||||
yLabel: '幅值',
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.waveform-chart__y-axis-label').text()).toBe('幅值')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 可复用性提升 ⭐⭐⭐⭐
|
||||
|
||||
**跨组件使用**:
|
||||
|
||||
```vue
|
||||
<!-- 在其他图表组件中使用 Tooltip -->
|
||||
<WaveformTooltip
|
||||
:visible="showTooltip"
|
||||
:position="mousePosition"
|
||||
:hovered-point="nearestDataPoint"
|
||||
:series-points="allSeriesData"
|
||||
/>
|
||||
|
||||
<!-- 在频谱图中使用 AnnotationLayer -->
|
||||
<WaveformAnnotationLayer
|
||||
:rendered-annotations="spectrumAnnotations"
|
||||
:rendered-shapes="frequencyMarkers"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 代码质量提升 ⭐⭐⭐⭐⭐
|
||||
|
||||
**职责单一**:
|
||||
|
||||
- 每个组件只负责一件事
|
||||
- WaveformTooltip = 显示信息
|
||||
- WaveformTrack = 渲染轨道
|
||||
- WaveformAnnotationLayer = 渲染标注
|
||||
- 主组件 = 业务逻辑协调
|
||||
|
||||
**接口清晰**:
|
||||
|
||||
```typescript
|
||||
// 每个组件都有明确的 Props 和 Emits 接口
|
||||
interface WaveformTooltipProps { ... }
|
||||
interface WaveformTrackProps { ... }
|
||||
interface WaveformAnnotationLayerProps { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 与整体重构的关系
|
||||
|
||||
### 已完成的模块化
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/ # ✅ 类型定义(阶段 1)
|
||||
├── core/ # ✅ 核心数据处理(阶段 1)
|
||||
├── utils/ # ✅ 工具函数(阶段 1)
|
||||
├── components/
|
||||
│ ├── WaveformChart.vue # 主组件(~1143 行)
|
||||
│ ├── WaveformToolbar.vue # ✅ 工具栏(174 行)
|
||||
│ ├── WaveformEditor.vue # ✅ 编辑器(149 行)
|
||||
│ ├── WaveformTooltip.vue # ✅ Tooltip(111 行)
|
||||
│ ├── WaveformTrack.vue # ✅ 轨道(317 行)
|
||||
│ ├── WaveformAnnotationLayer.vue # ✅ 标注层(281 行)
|
||||
│ ├── waveform-markup.ts
|
||||
│ └── waveform.ts
|
||||
```
|
||||
|
||||
### 模块化进度
|
||||
|
||||
- ✅ **阶段 1**: 类型和工具函数分离
|
||||
- ✅ **阶段 2**: 工具栏和编辑器组件拆分
|
||||
- ✅ **阶段 3**: Tooltip、Track、AnnotationLayer 组件拆分
|
||||
- 🎯 **完成度**: 100%
|
||||
|
||||
---
|
||||
|
||||
## 🚀 后续优化建议
|
||||
|
||||
### 1. 添加组件单元测试 ⭐⭐⭐⭐⭐
|
||||
|
||||
为新组件添加专门的测试文件:
|
||||
|
||||
```bash
|
||||
src/components/WaveformTooltip.test.ts
|
||||
src/components/WaveformTrack.test.ts
|
||||
src/components/WaveformAnnotationLayer.test.ts
|
||||
```
|
||||
|
||||
### 2. 性能优化 ⭐⭐⭐⭐
|
||||
|
||||
考虑使用 `v-memo` 指令优化大数据集:
|
||||
|
||||
```vue
|
||||
<WaveformTrack
|
||||
v-for="track in trackLayouts"
|
||||
v-memo="[track.path, track.xScale, track.yScale]"
|
||||
:key="track.index"
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. 类型提取 ⭐⭐⭐
|
||||
|
||||
将共享类型提取到单独文件:
|
||||
|
||||
```typescript
|
||||
// src/components/waveform-chart-types.ts
|
||||
export interface DisplaySeries { ... }
|
||||
export interface TrackLayout { ... }
|
||||
export interface RenderedAnnotation { ... }
|
||||
export interface RenderedShape { ... }
|
||||
```
|
||||
|
||||
### 4. Storybook 集成 ⭐⭐⭐
|
||||
|
||||
为每个子组件添加 Storybook stories:
|
||||
|
||||
```typescript
|
||||
// WaveformTooltip.stories.ts
|
||||
export const Default = {
|
||||
args: {
|
||||
visible: true,
|
||||
hoveredPoint: { x: 1, y: 2 },
|
||||
seriesPoints: [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收清单
|
||||
|
||||
- [x] 创建 WaveformTooltip.vue 组件
|
||||
- [x] 创建 WaveformAnnotationLayer.vue 组件
|
||||
- [x] 创建 WaveformTrack.vue 组件
|
||||
- [x] 更新 WaveformChart.vue 使用新组件
|
||||
- [x] 删除主组件中的冗余代码
|
||||
- [x] 保持向后兼容的 CSS 类名
|
||||
- [x] 所有测试通过 (24/24)
|
||||
- [x] TypeScript 类型检查通过
|
||||
- [x] ESLint 代码规范通过
|
||||
- [x] 功能验证通过
|
||||
- [x] 向后兼容性验证
|
||||
- [x] 编写完整文档
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
### 完成情况
|
||||
|
||||
✅ **主组件拆分成功完成**
|
||||
|
||||
- 新增 3 个独立组件
|
||||
- 主组件减少 ~600 行代码(34.4%)
|
||||
- 所有测试通过
|
||||
- 代码质量显著提升
|
||||
|
||||
### 核心价值
|
||||
|
||||
| 维度 | 改善 |
|
||||
| -------- | --------------------------- |
|
||||
| 可维护性 | ✅ 组件独立,易于定位和修改 |
|
||||
| 可测试性 | ✅ 支持独立单元测试 |
|
||||
| 可复用性 | ✅ 可在其他组件中使用 |
|
||||
| 代码质量 | ✅ 职责单一,接口清晰 |
|
||||
| 向后兼容 | ✅ 完全兼容现有代码 |
|
||||
|
||||
### 项目里程碑
|
||||
|
||||
```
|
||||
2026-07-17: 工具栏和编辑器组件拆分完成
|
||||
2026-07-18: Tooltip、Track、AnnotationLayer 拆分完成
|
||||
状态: ✅ 组件拆分工作全部完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**完成时间**: 2026-07-18
|
||||
**影响范围**: `WaveformChart.vue`, 新增 3 个组件
|
||||
**破坏性变更**: 无
|
||||
**向后兼容**: ✅ 完全兼容
|
||||
**测试结果**: ✅ 24/24 通过
|
||||
@@ -1,442 +0,0 @@
|
||||
# 今日工作完成总结 (2026-07-18)
|
||||
|
||||
## 📋 任务清单
|
||||
|
||||
✅ **任务 1**: 完整的目录结构拆分(按照 doc/03-开发计划.md 1.4 节)
|
||||
✅ **任务 2**: Y 轴标签重叠问题修复
|
||||
✅ **任务 3**: 工具栏和编辑器组件拆分
|
||||
|
||||
---
|
||||
|
||||
## 🎯 任务 1:完整目录结构拆分
|
||||
|
||||
### 完成内容
|
||||
|
||||
创建了完整的模块化目录结构:
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/ # ✨ 类型定义(143 行)
|
||||
│ ├── chart.ts # 图表类型
|
||||
│ ├── data.ts # 数据类型
|
||||
│ └── index.ts # 统一导出
|
||||
│
|
||||
├── core/ # ✨ 核心引擎(63 行,框架无关)
|
||||
│ ├── data.ts # 数据规范化
|
||||
│ └── index.ts # 统一导出
|
||||
│
|
||||
├── utils/ # ✨ 工具函数(162 行)
|
||||
│ ├── domain.ts # 域计算
|
||||
│ ├── formatters.ts # 格式化
|
||||
│ ├── geometry.ts # 几何计算
|
||||
│ └── index.ts # 统一导出
|
||||
│
|
||||
├── components/ # Vue 组件层
|
||||
│ ├── WaveformChart.vue
|
||||
│ ├── waveform.ts # ✨ 重构为兼容层
|
||||
│ ├── waveform-markup.ts
|
||||
│ ├── index.ts
|
||||
│ └── WaveformChart.test.ts
|
||||
│
|
||||
├── interactions/ # ✨ 预留(待阶段 3)
|
||||
├── hooks/ # ✨ 预留(待阶段 3)
|
||||
├── data/
|
||||
├── test/
|
||||
├── App.vue
|
||||
├── main.ts
|
||||
└── index.ts # ✨ 库主入口
|
||||
```
|
||||
|
||||
### 关键收益
|
||||
|
||||
| 指标 | 成果 |
|
||||
| ----------------- | --------------------------- |
|
||||
| ✅ 模块数量 | 从 3 个增加到 13 个 |
|
||||
| ✅ 单文件平均行数 | 从 762 行降到 219 行 (-71%) |
|
||||
| ✅ 类型定义 | 集中管理,易于维护 |
|
||||
| ✅ 核心引擎 | 框架无关,可跨框架复用 |
|
||||
| ✅ 向后兼容 | 完全兼容,旧代码无需修改 |
|
||||
|
||||
### 验证结果
|
||||
|
||||
```bash
|
||||
✅ 测试: 24/24 通过
|
||||
✅ 类型检查: 通过
|
||||
✅ 代码规范: 0 错误 0 警告
|
||||
```
|
||||
|
||||
### 相关文档
|
||||
|
||||
- [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md) - 完成总结
|
||||
- [DIRECTORY_STRUCTURE_REFACTORING.md](DIRECTORY_STRUCTURE_REFACTORING.md) - 详细报告
|
||||
|
||||
---
|
||||
|
||||
## 🎯 任务 2:Y 轴标签重叠问题修复
|
||||
|
||||
### 问题描述
|
||||
|
||||
在"多道紧凑"模式下,Y 轴标签(如 "BT2_2M"、"BT1_2M")会重叠,导致无法阅读。
|
||||
|
||||
### 解决方案
|
||||
|
||||
**智能间隔显示策略**:
|
||||
|
||||
- 当轨道高度 ≥ 80px:显示所有标签
|
||||
- 当轨道高度 40-79px:每隔 1 个显示
|
||||
- 当轨道高度 27-39px:每隔 2 个显示
|
||||
- 当轨道高度 < 27px:每隔 3+ 个显示
|
||||
|
||||
**视觉增强**:
|
||||
|
||||
- 添加半透明白色背景,提高标签可读性
|
||||
- 标签与背景对比度更高
|
||||
|
||||
### 核心代码
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
### 验证结果
|
||||
|
||||
```bash
|
||||
✅ 测试: 24/24 通过
|
||||
✅ 类型检查: 通过
|
||||
✅ 代码规范: 通过
|
||||
✅ 多种轨道数量场景测试通过
|
||||
```
|
||||
|
||||
### 相关文档
|
||||
|
||||
- [Y_AXIS_LABEL_FIX.md](Y_AXIS_LABEL_FIX.md) - 详细修复报告
|
||||
|
||||
---
|
||||
|
||||
## 🎯 任务 3:工具栏和编辑器组件拆分
|
||||
|
||||
### 完成内容
|
||||
|
||||
创建了 2 个独立的可复用组件:
|
||||
|
||||
#### 1. WaveformToolbar.vue (174 行)
|
||||
|
||||
- 交互模式切换(缩放、选择、标注)
|
||||
- 标注工具按钮(文字、垂直线、区间)
|
||||
- 编辑和删除操作
|
||||
|
||||
#### 2. WaveformEditor.vue (143 行)
|
||||
|
||||
- 多行文本输入
|
||||
- 自动聚焦和全选
|
||||
- 键盘快捷键(Ctrl+Enter 确认,Escape 取消)
|
||||
- 字符限制(500 字)
|
||||
|
||||
### 代码统计
|
||||
|
||||
| 指标 | 数值 |
|
||||
| -------------- | ----------------------- |
|
||||
| **新增组件** | 2 个 |
|
||||
| **新增代码** | 317 行 |
|
||||
| **主组件减少** | ~90 行 |
|
||||
| **主组件行数** | 1913 → ~1823 行 (-4.7%) |
|
||||
|
||||
### 架构改进
|
||||
|
||||
**重构前** (70 行内联代码):
|
||||
|
||||
```vue
|
||||
<div class="waveform-chart__toolbar">
|
||||
<button>...</button>
|
||||
<button>...</button>
|
||||
<!-- 8 个按钮 + 样式 -->
|
||||
</div>
|
||||
|
||||
<div class="waveform-chart__editor">
|
||||
<textarea ref="editorInput" @keydown="..." />
|
||||
<!-- 编辑器逻辑 + 样式 -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**重构后** (16 行组件调用):
|
||||
|
||||
```vue
|
||||
<WaveformToolbar
|
||||
:interaction-mode="activeInteractionMode"
|
||||
:can-edit-selection="canEditSelection"
|
||||
@update:interaction-mode="setInteractionMode"
|
||||
@edit="editSelection"
|
||||
@delete="deleteSelection"
|
||||
/>
|
||||
|
||||
<WaveformEditor
|
||||
:kind="editingDraft.kind"
|
||||
:initial-text="editingText"
|
||||
:style="editorStyle"
|
||||
@confirm="confirmEditing"
|
||||
@cancel="cancelEditing"
|
||||
/>
|
||||
```
|
||||
|
||||
### 验证结果
|
||||
|
||||
```bash
|
||||
✅ 测试: 24/24 通过
|
||||
✅ 类型检查: 通过
|
||||
✅ 代码规范: 通过
|
||||
✅ 所有交互功能正常
|
||||
```
|
||||
|
||||
### 相关文档
|
||||
|
||||
- [TOOLBAR_EDITOR_REFACTORING.md](TOOLBAR_EDITOR_REFACTORING.md) - 详细拆分报告
|
||||
|
||||
---
|
||||
|
||||
## 📊 今日成果汇总
|
||||
|
||||
### 代码变更统计
|
||||
|
||||
| 项目 | 新增 | 修改 | 删除 | 净增 |
|
||||
| ---------------- | ----------------- | ------------ | --------- | ----------- |
|
||||
| **目录结构拆分** | 8 个文件 (368 行) | 3 个文件 | - | +368 行 |
|
||||
| **Y 轴标签修复** | 31 行 | 15 行 | - | +46 行 |
|
||||
| **组件拆分** | 2 个组件 (317 行) | 主组件 | 90 行 | +227 行 |
|
||||
| **总计** | **10 个新文件** | **多个文件** | **90 行** | **+641 行** |
|
||||
|
||||
### 主组件瘦身进度
|
||||
|
||||
| 阶段 | 行数 | 变化 | 累计减少 |
|
||||
| -------------------- | ---- | ---- | ----------- |
|
||||
| 原始 | 1975 | - | - |
|
||||
| 阶段 1:工具函数拆分 | 1913 | -62 | -62 (3.1%) |
|
||||
| Y 轴标签修复 | 1944 | +31 | -31 (1.6%) |
|
||||
| 组件拆分 | 1823 | -121 | -152 (7.7%) |
|
||||
|
||||
**说明**: Y 轴标签修复新增了功能代码,但通过组件拆分又减少了更多代码。
|
||||
|
||||
---
|
||||
|
||||
## 🎨 架构质量提升
|
||||
|
||||
### 模块化程度
|
||||
|
||||
| 维度 | 重构前 | 重构后 | 改善 |
|
||||
| ------------ | ---------- | ----------- | ----------- |
|
||||
| 独立模块数 | 3 | 13 | ✅ +333% |
|
||||
| 类型定义文件 | 混在组件中 | 独立 types/ | ✅ 集中管理 |
|
||||
| 工具函数 | 混在组件中 | 独立 utils/ | ✅ 可复用 |
|
||||
| 核心引擎 | 耦合 Vue | 框架无关 | ✅ 可跨框架 |
|
||||
| UI 组件 | 单体 | 模块化 | ✅ 易维护 |
|
||||
|
||||
### 代码质量
|
||||
|
||||
| 指标 | 状态 |
|
||||
| ------------------- | ---------------- |
|
||||
| TypeScript 类型覆盖 | ✅ 100% |
|
||||
| 单元测试覆盖 | ✅ 24/24 通过 |
|
||||
| ESLint 规范 | ✅ 0 错误 0 警告 |
|
||||
| 依赖关系 | ✅ 单向,无循环 |
|
||||
| 向后兼容 | ✅ 完全兼容 |
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档产出
|
||||
|
||||
今日共创建 **5 份** 详细文档:
|
||||
|
||||
1. **[REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md)** (380 行)
|
||||
- 完整目录结构拆分总结
|
||||
- 架构设计说明
|
||||
- 开发指南
|
||||
|
||||
2. **[DIRECTORY_STRUCTURE_REFACTORING.md](DIRECTORY_STRUCTURE_REFACTORING.md)** (520 行)
|
||||
- 详细的目录拆分报告
|
||||
- 模块划分说明
|
||||
- 依赖关系图
|
||||
|
||||
3. **[Y_AXIS_LABEL_FIX.md](Y_AXIS_LABEL_FIX.md)** (280 行)
|
||||
- 问题分析
|
||||
- 解决方案设计
|
||||
- 测试验证
|
||||
|
||||
4. **[TOOLBAR_EDITOR_REFACTORING.md](TOOLBAR_EDITOR_REFACTORING.md)** (450 行)
|
||||
- 组件拆分详细说明
|
||||
- API 文档
|
||||
- 使用示例
|
||||
|
||||
5. **[REFACTORING_PHASE1_REPORT.md](REFACTORING_PHASE1_REPORT.md)** (已存在)
|
||||
- 阶段 1 工具函数拆分报告
|
||||
|
||||
**文档总计**: ~1630 行专业文档
|
||||
|
||||
---
|
||||
|
||||
## 🎯 项目当前状态
|
||||
|
||||
### 已完成的重构
|
||||
|
||||
```
|
||||
✅ 阶段 1: 工具函数拆分
|
||||
├── utils/domain.ts
|
||||
├── utils/formatters.ts
|
||||
└── utils/geometry.ts
|
||||
|
||||
✅ 目录结构完善
|
||||
├── types/ (类型定义)
|
||||
├── core/ (核心引擎)
|
||||
└── src/index.ts (库入口)
|
||||
|
||||
✅ UI 组件拆分 (部分)
|
||||
├── WaveformToolbar.vue
|
||||
└── WaveformEditor.vue
|
||||
|
||||
✅ Bug 修复
|
||||
└── Y 轴标签重叠问题
|
||||
```
|
||||
|
||||
### 待完成的任务
|
||||
|
||||
```
|
||||
📝 阶段 2: 核心引擎拆分 (预计 3-5 天)
|
||||
├── core/scales.ts (比例尺)
|
||||
├── core/axis.ts (坐标轴)
|
||||
├── core/path.ts (路径生成)
|
||||
└── core/layout.ts (布局计算)
|
||||
|
||||
📝 阶段 3: Composables 拆分 (预计 2-3 天)
|
||||
├── hooks/useZoom.ts
|
||||
├── hooks/useHover.ts
|
||||
├── hooks/useAnnotations.ts
|
||||
└── hooks/useSelection.ts
|
||||
|
||||
📝 阶段 4: 组件拆分 (预计 2-3 天)
|
||||
├── WaveformTooltip.vue
|
||||
├── WaveformTrack.vue
|
||||
└── WaveformAnnotationLayer.vue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 关键成就
|
||||
|
||||
### 1. 清晰的分层架构 ✅
|
||||
|
||||
```
|
||||
应用层 (App.vue)
|
||||
↓
|
||||
组件层 (WaveformChart, Toolbar, Editor)
|
||||
↓
|
||||
类型层 (types/)
|
||||
↓
|
||||
工具层 (utils/)
|
||||
↓
|
||||
核心层 (core/ - 框架无关)
|
||||
```
|
||||
|
||||
### 2. 完整的模块化体系 ✅
|
||||
|
||||
- **类型定义**: 集中管理,避免重复
|
||||
- **核心引擎**: 框架无关,可跨框架复用
|
||||
- **工具函数**: 纯函数,易于测试
|
||||
- **UI 组件**: 职责单一,可复用
|
||||
|
||||
### 3. 代码质量保障 ✅
|
||||
|
||||
- **100%** TypeScript 类型覆盖
|
||||
- **24** 个单元测试全部通过
|
||||
- **0** 代码规范错误和警告
|
||||
- **完全** 向后兼容
|
||||
|
||||
### 4. 文档完善 ✅
|
||||
|
||||
- **5** 份详细技术文档
|
||||
- **~1630** 行专业文档
|
||||
- **完整** 的架构说明和使用指南
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步建议
|
||||
|
||||
### 短期(1-2 周)
|
||||
|
||||
1. **继续组件拆分**
|
||||
- 拆分 WaveformTooltip.vue
|
||||
- 拆分 WaveformTrack.vue
|
||||
- 目标:主组件减少到 ~1500 行
|
||||
|
||||
2. **完善单元测试**
|
||||
- 为新组件编写单独的测试文件
|
||||
- 提高测试覆盖率
|
||||
|
||||
### 中期(3-4 周)
|
||||
|
||||
3. **核心引擎拆分**
|
||||
- 抽取 D3 渲染逻辑
|
||||
- 创建 core/scales.ts, core/axis.ts
|
||||
- 目标:主组件减少到 ~1000 行
|
||||
|
||||
4. **Composables 拆分**
|
||||
- 抽取交互逻辑为 hooks
|
||||
- 提高逻辑复用性
|
||||
|
||||
### 长期(1-2 月)
|
||||
|
||||
5. **完整的组件化架构**
|
||||
- 主组件作为容器(~400 行)
|
||||
- 子组件各司其职
|
||||
- 最终目标:单文件 < 200 行
|
||||
|
||||
---
|
||||
|
||||
## ✨ 总结
|
||||
|
||||
### 今日工作量
|
||||
|
||||
- **工作时间**: 约 4 小时
|
||||
- **代码变更**: +641 行(净增)
|
||||
- **新增文件**: 10 个
|
||||
- **修复问题**: 1 个(Y 轴标签重叠)
|
||||
- **文档产出**: 5 份(~1630 行)
|
||||
|
||||
### 核心价值
|
||||
|
||||
✅ **架构清晰**: 完整的模块化目录结构
|
||||
✅ **质量保证**: 所有测试和检查通过
|
||||
✅ **向后兼容**: 旧代码无需修改
|
||||
✅ **文档完善**: 详细的技术文档
|
||||
✅ **可扩展性**: 为后续重构打好基础
|
||||
|
||||
### 项目健康度
|
||||
|
||||
| 维度 | 评分 |
|
||||
| -------- | ---------- |
|
||||
| 代码组织 | ⭐⭐⭐⭐⭐ |
|
||||
| 测试覆盖 | ⭐⭐⭐⭐☆ |
|
||||
| 文档完善 | ⭐⭐⭐⭐⭐ |
|
||||
| 可维护性 | ⭐⭐⭐⭐⭐ |
|
||||
| 可扩展性 | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
**总体评分**: ⭐⭐⭐⭐⭐ (优秀)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 成就解锁
|
||||
|
||||
✅ **架构师**: 设计了完整的模块化架构
|
||||
✅ **工程师**: 实现了 3 个主要任务
|
||||
✅ **测试专家**: 保证了代码质量
|
||||
✅ **文档作者**: 编写了 1630 行文档
|
||||
✅ **问题解决者**: 修复了 Y 轴标签重叠问题
|
||||
|
||||
---
|
||||
|
||||
**完成日期**: 2026-07-18
|
||||
**工作质量**: 优秀 ⭐⭐⭐⭐⭐
|
||||
**团队建议**: 继续按照规划执行后续阶段
|
||||
@@ -1,578 +0,0 @@
|
||||
# 完整目录结构拆分报告
|
||||
|
||||
## 🎉 拆分完成
|
||||
|
||||
按照 **doc/03-开发计划.md** 中 1.4 节的规划,已成功完成完整的目录结构拆分。
|
||||
|
||||
---
|
||||
|
||||
## 📁 新的目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue 组件
|
||||
│ ├── WaveformChart.vue # 主图表组件 (1913 行)
|
||||
│ ├── waveform.ts # 向后兼容导出 (27 行) ✨ 重构
|
||||
│ ├── waveform-markup.ts # 标注工具 (154 行)
|
||||
│ ├── index.ts # 组件导出
|
||||
│ └── WaveformChart.test.ts # 单元测试
|
||||
│
|
||||
├── core/ # 核心引擎(框架无关)✨ 新增
|
||||
│ ├── data.ts # 数据规范化 (63 行)
|
||||
│ └── index.ts # 核心模块导出
|
||||
│
|
||||
├── types/ # 类型定义 ✨ 新增
|
||||
│ ├── chart.ts # 图表类型 (98 行)
|
||||
│ ├── data.ts # 数据类型 (45 行)
|
||||
│ └── index.ts # 类型统一导出
|
||||
│
|
||||
├── utils/ # 工具函数 ✨ 新增(阶段 1)
|
||||
│ ├── domain.ts # 域计算 (29 行)
|
||||
│ ├── formatters.ts # 格式化 (64 行)
|
||||
│ ├── geometry.ts # 几何计算 (50 行)
|
||||
│ └── index.ts # 工具统一导出
|
||||
│
|
||||
├── interactions/ # 交互层 ✨ 预留(待阶段 3)
|
||||
│ └── (待添加: zoom.ts, hover.ts, annotation.ts)
|
||||
│
|
||||
├── hooks/ # Vue Composables ✨ 预留(待阶段 3)
|
||||
│ └── (待添加: useChart.ts, useZoom.ts, useAnnotation.ts)
|
||||
│
|
||||
├── data/ # 示例数据
|
||||
│ └── wData.json
|
||||
│
|
||||
├── test/ # 测试配置
|
||||
│ └── setup.ts
|
||||
│
|
||||
├── App.vue # Demo 应用
|
||||
├── main.ts # 应用入口
|
||||
├── index.ts # 库主入口 ✨ 新增
|
||||
├── styles.css # 全局样式
|
||||
└── env.d.ts # TypeScript 声明
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 模块划分详解
|
||||
|
||||
### 1. **types/** - 类型定义层
|
||||
|
||||
**职责**:集中管理所有 TypeScript 类型定义
|
||||
|
||||
#### `types/chart.ts` (98 行)
|
||||
|
||||
- `WaveformPoint` - 波形数据点
|
||||
- `WaveformDisplayMode` - 显示模式(independent/separated/compact)
|
||||
- `WaveformInteractionMode` - 交互模式(zoom/select/annotation/vertical-line/range)
|
||||
- `WaveformAnnotation` - 标注数据结构
|
||||
- `WaveformShape` - 图形数据结构(垂直线/区间)
|
||||
- `WaveformMarkupSelection` - 标注选择状态
|
||||
|
||||
#### `types/data.ts` (45 行)
|
||||
|
||||
- `SingleWaveformData` - 单波形数据格式
|
||||
- `WaveformSeries` - 波形系列
|
||||
- `WaveformData` - 完整波形数据
|
||||
- `NormalizedWaveformSeries` - 规范化后的系列
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 类型定义集中管理
|
||||
- ✅ 易于维护和扩展
|
||||
- ✅ 避免循环依赖
|
||||
|
||||
---
|
||||
|
||||
### 2. **core/** - 核心引擎层(框架无关)
|
||||
|
||||
**职责**:提供与框架无关的核心数据处理逻辑
|
||||
|
||||
#### `core/data.ts` (63 行)
|
||||
|
||||
- `normalizeWaveformData()` - 规范化单波形数据
|
||||
- `normalizeWaveformSeries()` - 规范化波形系列
|
||||
- ID 唯一性验证
|
||||
- 数据过滤和排序
|
||||
- 格式统一化
|
||||
|
||||
**特点**:
|
||||
|
||||
- ✅ 纯 TypeScript,无 Vue 依赖
|
||||
- ✅ 可在任何框架中使用(React/Svelte/Angular)
|
||||
- ✅ 易于单独测试
|
||||
|
||||
**后续扩展方向**(阶段 2):
|
||||
|
||||
```
|
||||
core/
|
||||
├── data.ts # ✅ 已完成
|
||||
├── scales.ts # 📝 待添加:比例尺管理
|
||||
├── axis.ts # 📝 待添加:坐标轴渲染
|
||||
├── path.ts # 📝 待添加:路径生成
|
||||
├── layout.ts # 📝 待添加:布局计算
|
||||
└── downsample.ts # 📝 待添加:降采样算法
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **utils/** - 工具函数层
|
||||
|
||||
**职责**:提供纯函数工具集
|
||||
|
||||
#### `utils/domain.ts` (29 行)
|
||||
|
||||
- `paddedDomain()` - 计算带边距的数据域
|
||||
- `buildMinorTicks()` - 生成次要刻度
|
||||
|
||||
#### `utils/formatters.ts` (64 行)
|
||||
|
||||
- `displayTime()` - 时间单位转换
|
||||
- `formatEndpointTime()` - 端点时间格式化
|
||||
- `formatAxisTime()` - 坐标轴时间格式化
|
||||
- `formatTooltipTime()` - 悬浮提示时间格式化
|
||||
|
||||
#### `utils/geometry.ts` (50 行)
|
||||
|
||||
- `resolveTrackGeometry()` - 轨道几何布局
|
||||
- `clamp()` - 数值范围限制
|
||||
|
||||
**特点**:
|
||||
|
||||
- ✅ 所有函数都是纯函数
|
||||
- ✅ 完整的 JSDoc 注释
|
||||
- ✅ 易于单独测试和复用
|
||||
|
||||
---
|
||||
|
||||
### 4. **components/** - Vue 组件层
|
||||
|
||||
**职责**:Vue 组件和标注工具
|
||||
|
||||
#### `components/WaveformChart.vue` (1913 行)
|
||||
|
||||
- 主波形图组件
|
||||
- 保持不变(已在阶段 1 简化)
|
||||
|
||||
#### `components/waveform.ts` (27 行) - ✨ 重构为重新导出
|
||||
|
||||
```typescript
|
||||
// 向后兼容层
|
||||
export type { ... } from '../types'
|
||||
export { ... } from '../core'
|
||||
```
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 保持向后兼容
|
||||
- ✅ 旧代码无需修改导入路径
|
||||
- ✅ 内部使用新的模块结构
|
||||
|
||||
#### `components/waveform-markup.ts` (154 行)
|
||||
|
||||
- 标注和图形工具函数
|
||||
- 已更新为使用 `../types` 导入
|
||||
|
||||
---
|
||||
|
||||
### 5. **interactions/** - 交互层(预留)
|
||||
|
||||
**规划**:抽取交互逻辑(阶段 3)
|
||||
|
||||
```
|
||||
interactions/
|
||||
├── zoom.ts # 缩放平移逻辑
|
||||
├── hover.ts # 悬浮交互逻辑
|
||||
└── annotation.ts # 标注交互逻辑
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 交互逻辑模块化
|
||||
- 可独立测试
|
||||
- 可复用到其他图表组件
|
||||
|
||||
---
|
||||
|
||||
### 6. **hooks/** - Vue Composables(预留)
|
||||
|
||||
**规划**:抽取可复用的 Vue 组合式函数(阶段 3)
|
||||
|
||||
```
|
||||
hooks/
|
||||
├── useChart.ts # 图表状态管理
|
||||
├── useZoom.ts # 缩放行为 Hook
|
||||
├── useAnnotation.ts # 标注管理 Hook
|
||||
└── useHover.ts # 悬浮状态 Hook
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 逻辑复用性提升
|
||||
- 组件代码减少 ~500 行
|
||||
- 易于在其他组件中使用
|
||||
|
||||
---
|
||||
|
||||
### 7. **src/index.ts** - 库主入口 ✨ 新增
|
||||
|
||||
**职责**:提供统一的公共 API
|
||||
|
||||
```typescript
|
||||
// 导出组件
|
||||
export { default as WaveformChart } from './components/WaveformChart.vue'
|
||||
|
||||
// 导出类型
|
||||
export type { WaveformPoint, WaveformData, ... } from './types'
|
||||
|
||||
// 导出核心功能
|
||||
export { normalizeWaveformSeries, ... } from './core'
|
||||
|
||||
// 导出工具函数
|
||||
export { paddedDomain, formatAxisTime, ... } from './utils'
|
||||
|
||||
// 导出标注工具
|
||||
export { isFiniteAnnotation, ... } from './components/waveform-markup'
|
||||
```
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 统一的入口点
|
||||
- ✅ 清晰的 API 导出
|
||||
- ✅ 便于发布为 npm 包
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证结果
|
||||
|
||||
### 所有检查通过 ✅
|
||||
|
||||
| 检查项 | 状态 | 结果 |
|
||||
| ------------------- | ---- | ------------------ |
|
||||
| 单元测试 | ✅ | 24/24 通过 |
|
||||
| TypeScript 类型检查 | ✅ | 无错误 |
|
||||
| ESLint 代码规范 | ✅ | 0 错误 0 警告 |
|
||||
| 向后兼容性 | ✅ | 旧导入路径正常工作 |
|
||||
|
||||
---
|
||||
|
||||
## 📈 收益分析
|
||||
|
||||
### 1. 代码组织
|
||||
|
||||
**重构前**:
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── WaveformChart.vue (1975 行 - 巨型文件)
|
||||
├── waveform.ts (149 行 - 类型+逻辑混杂)
|
||||
└── waveform-markup.ts (162 行)
|
||||
```
|
||||
|
||||
**重构后**:
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/ (143 行 - 类型定义)
|
||||
├── core/ (63 行 - 数据处理)
|
||||
├── utils/ (162 行 - 工具函数)
|
||||
└── components/ (1913 行 - Vue 组件)
|
||||
```
|
||||
|
||||
**改进**:
|
||||
|
||||
- ✅ 模块职责清晰
|
||||
- ✅ 类型、逻辑、工具分离
|
||||
- ✅ 易于定位和修改
|
||||
|
||||
---
|
||||
|
||||
### 2. 可维护性
|
||||
|
||||
| 维度 | 重构前 | 重构后 | 提升 |
|
||||
| -------------- | ------ | ------ | ------------- |
|
||||
| 单文件平均行数 | 762 | 219 | ✅ 71% ↓ |
|
||||
| 模块内聚性 | 低 | 高 | ✅ 显著提升 |
|
||||
| 依赖关系 | 混乱 | 清晰 | ✅ 单向依赖 |
|
||||
| 新人理解成本 | 高 | 低 | ✅ 目录即文档 |
|
||||
|
||||
---
|
||||
|
||||
### 3. 可扩展性
|
||||
|
||||
**新增功能时的改动范围**:
|
||||
|
||||
| 场景 | 重构前 | 重构后 |
|
||||
| ---------------- | ---------------- | --------------------- |
|
||||
| 添加新的数据格式 | 修改 waveform.ts | 只修改 core/data.ts |
|
||||
| 添加新的图表类型 | 修改 waveform.ts | 只修改 types/chart.ts |
|
||||
| 添加新的工具函数 | 混在组件中 | 添加到对应 utils 模块 |
|
||||
| 添加新的交互模式 | 修改巨型组件 | 添加到 interactions/ |
|
||||
|
||||
**改进**:
|
||||
|
||||
- ✅ 修改范围最小化
|
||||
- ✅ 降低回归风险
|
||||
- ✅ 支持并行开发
|
||||
|
||||
---
|
||||
|
||||
### 4. 可测试性
|
||||
|
||||
**测试覆盖率提升路径**:
|
||||
|
||||
```
|
||||
当前测试:WaveformChart.test.ts (24 个测试)
|
||||
↓
|
||||
扩展测试:
|
||||
├── types/ (类型定义 - 无需测试)
|
||||
├── core/data.test.ts (10+ 测试)
|
||||
├── utils/domain.test.ts (5+ 测试)
|
||||
├── utils/formatters.test.ts (8+ 测试)
|
||||
├── utils/geometry.test.ts (5+ 测试)
|
||||
└── components/ (保留 24 个集成测试)
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- ✅ 测试粒度更细
|
||||
- ✅ 单元测试运行更快
|
||||
- ✅ 易于定位失败原因
|
||||
|
||||
---
|
||||
|
||||
### 5. 跨框架复用
|
||||
|
||||
**核心模块现在可以在任何框架中使用**:
|
||||
|
||||
```typescript
|
||||
// React 项目中使用
|
||||
import { normalizeWaveformSeries } from '@/waveform-analysis/core'
|
||||
import { paddedDomain } from '@/waveform-analysis/utils'
|
||||
|
||||
// Svelte 项目中使用
|
||||
import type { WaveformData } from '@/waveform-analysis/types'
|
||||
import { formatAxisTime } from '@/waveform-analysis/utils'
|
||||
|
||||
// Node.js 服务端使用
|
||||
const { normalizeWaveformData } = require('@/waveform-analysis/core')
|
||||
```
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 核心逻辑可复用
|
||||
- ✅ 降低迁移成本
|
||||
- ✅ 支持多端共享
|
||||
|
||||
---
|
||||
|
||||
## 🎯 依赖关系图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ App.vue (Demo 应用) │
|
||||
└────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌────────────────▼────────────────────────────────┐
|
||||
│ components/WaveformChart.vue (Vue 组件) │
|
||||
│ - 导入 types/* │
|
||||
│ - 导入 utils/* │
|
||||
│ - 导入 core/* │
|
||||
└────────┬────────┬────────┬───────────────────────┘
|
||||
│ │ │
|
||||
┌────▼───┐ ┌─▼────┐ ┌─▼────┐
|
||||
│ types/ │ │utils/│ │core/ │
|
||||
│ │ │ │ │ │
|
||||
└────────┘ └──────┘ └──┬───┘
|
||||
│
|
||||
┌───▼────┐
|
||||
│ types/ │
|
||||
└────────┘
|
||||
```
|
||||
|
||||
**依赖规则**:
|
||||
|
||||
- ✅ 单向依赖(从上到下)
|
||||
- ✅ 无循环依赖
|
||||
- ✅ 底层模块无 Vue 依赖
|
||||
|
||||
---
|
||||
|
||||
## 🚀 后续扩展路线
|
||||
|
||||
### 短期(1-2 周)
|
||||
|
||||
**阶段 2:抽取核心引擎**
|
||||
|
||||
```
|
||||
core/
|
||||
├── data.ts # ✅ 已完成
|
||||
├── scales.ts # 📝 比例尺管理
|
||||
├── axis.ts # 📝 坐标轴渲染
|
||||
├── path.ts # 📝 路径生成
|
||||
└── layout.ts # 📝 布局计算
|
||||
```
|
||||
|
||||
**预期收益**:主组件减少 ~400 行
|
||||
|
||||
---
|
||||
|
||||
### 中期(3-4 周)
|
||||
|
||||
**阶段 3:抽取 Composables**
|
||||
|
||||
```
|
||||
hooks/
|
||||
├── useZoom.ts # 📝 缩放逻辑
|
||||
├── useHover.ts # 📝 悬浮逻辑
|
||||
├── useAnnotations.ts # 📝 标注管理
|
||||
└── useSelection.ts # 📝 选择状态
|
||||
```
|
||||
|
||||
**预期收益**:主组件减少 ~500 行
|
||||
|
||||
---
|
||||
|
||||
### 长期(1-2 月)
|
||||
|
||||
**阶段 4:组件拆分**
|
||||
|
||||
```
|
||||
components/
|
||||
├── WaveformChart.vue # 主容器 (~400 行)
|
||||
├── WaveformTrack.vue # 单轨道
|
||||
├── WaveformAnnotation.vue # 标注渲染
|
||||
├── WaveformTooltip.vue # 悬浮提示
|
||||
└── WaveformToolbar.vue # 工具栏
|
||||
```
|
||||
|
||||
**最终目标**:
|
||||
|
||||
- 主组件 ~400 行(减少 80%)
|
||||
- 单文件平均 ~150 行
|
||||
- 完整的模块化架构
|
||||
|
||||
---
|
||||
|
||||
## 📚 向后兼容性
|
||||
|
||||
### 旧代码无需修改 ✅
|
||||
|
||||
```typescript
|
||||
// 旧的导入方式仍然可用
|
||||
import { WaveformChart, type WaveformData, type WaveformAnnotation } from './components'
|
||||
|
||||
// 或者
|
||||
import { WaveformData } from './components/waveform'
|
||||
|
||||
// 新的推荐方式
|
||||
import { WaveformChart, type WaveformData } from './index'
|
||||
```
|
||||
|
||||
**保证**:
|
||||
|
||||
- ✅ 所有旧导入路径正常工作
|
||||
- ✅ API 完全兼容
|
||||
- ✅ 无破坏性变更
|
||||
|
||||
---
|
||||
|
||||
## 📖 开发指南
|
||||
|
||||
### 添加新类型
|
||||
|
||||
```typescript
|
||||
// 1. 在 types/ 中定义
|
||||
// src/types/chart.ts
|
||||
export interface WaveformNewFeature {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 2. 在 types/index.ts 导出
|
||||
export type { WaveformNewFeature } from './chart'
|
||||
|
||||
// 3. 在 src/index.ts 导出
|
||||
export type { WaveformNewFeature } from './types'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 添加新工具函数
|
||||
|
||||
```typescript
|
||||
// 1. 在 utils/ 对应模块中实现
|
||||
// src/utils/formatters.ts
|
||||
export function formatNewValue(value: number): string {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 2. 在 utils/index.ts 导出
|
||||
export { formatNewValue } from './formatters'
|
||||
|
||||
// 3. 在 src/index.ts 导出
|
||||
export { formatNewValue } from './utils'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 添加核心功能
|
||||
|
||||
```typescript
|
||||
// 1. 在 core/ 中实现
|
||||
// src/core/downsample.ts
|
||||
export function downsampleData(points: WaveformPoint[]): WaveformPoint[] {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 2. 在 core/index.ts 导出
|
||||
export { downsampleData } from './downsample'
|
||||
|
||||
// 3. 在 src/index.ts 导出
|
||||
export { downsampleData } from './core'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ 总结
|
||||
|
||||
### 完成情况 ✅
|
||||
|
||||
- ✅ **目录结构完全按规划实施**
|
||||
- ✅ **所有测试通过(24/24)**
|
||||
- ✅ **类型检查通过**
|
||||
- ✅ **代码规范检查通过**
|
||||
- ✅ **向后兼容性保证**
|
||||
|
||||
### 核心收益
|
||||
|
||||
| 维度 | 改善 |
|
||||
| -------- | --------------------- |
|
||||
| 代码组织 | ✅ 清晰的分层架构 |
|
||||
| 可维护性 | ✅ 单文件平均减少 71% |
|
||||
| 可扩展性 | ✅ 模块化添加功能 |
|
||||
| 可测试性 | ✅ 支持细粒度测试 |
|
||||
| 可复用性 | ✅ 核心逻辑跨框架 |
|
||||
|
||||
### 模块统计
|
||||
|
||||
| 模块 | 文件数 | 代码行数 |
|
||||
| ----------- | ------ | -------- |
|
||||
| types/ | 3 | 143 |
|
||||
| core/ | 2 | 63 |
|
||||
| utils/ | 4 | 162 |
|
||||
| components/ | 4 | 2094 |
|
||||
| **总计** | **13** | **2462** |
|
||||
|
||||
### 下一步
|
||||
|
||||
项目现在具备了**清晰的模块化架构**,可以支持:
|
||||
|
||||
- ✅ 快速添加新功能
|
||||
- ✅ 多人并行开发
|
||||
- ✅ 独立测试和优化
|
||||
- ✅ 跨框架复用核心逻辑
|
||||
|
||||
---
|
||||
|
||||
**拆分完成时间**: 2026-07-18
|
||||
**验证状态**: ✅ 所有检查通过
|
||||
**向后兼容**: ✅ 完全兼容
|
||||
@@ -1,534 +0,0 @@
|
||||
# 架构重构分析报告
|
||||
|
||||
## 当前实现 vs 规划架构对比
|
||||
|
||||
### 📊 现状分析
|
||||
|
||||
**当前实现**(单体架构):
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── WaveformChart.vue (~1975 行) ⚠️ 过大
|
||||
│ ├── waveform.ts (~134 行)
|
||||
│ ├── waveform-markup.ts (~82 行)
|
||||
│ └── index.ts
|
||||
├── App.vue
|
||||
└── main.ts
|
||||
```
|
||||
|
||||
**规划架构**(模块化架构):
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue 组件层
|
||||
│ ├── D3WaveformChart/
|
||||
│ ├── Tooltip/
|
||||
│ ├── Annotation/
|
||||
│ └── ContextMenu/
|
||||
├── core/ # 核心引擎(框架无关)
|
||||
│ ├── renderer.ts
|
||||
│ ├── axis.ts
|
||||
│ ├── downsample.ts
|
||||
│ └── layout.ts
|
||||
├── interactions/ # 交互层
|
||||
│ ├── zoom.ts
|
||||
│ ├── hover.ts
|
||||
│ └── annotation.ts
|
||||
├── hooks/ # Vue Composables
|
||||
│ ├── useChart.ts
|
||||
│ ├── useZoom.ts
|
||||
│ └── useAnnotation.ts
|
||||
├── utils/ # 工具函数
|
||||
│ ├── formatters.ts
|
||||
│ ├── collision.ts
|
||||
│ └── scheduler.ts
|
||||
└── types/ # 类型定义
|
||||
├── chart.ts
|
||||
├── data.ts
|
||||
└── style.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 问题诊断
|
||||
|
||||
### 问题 1: 巨型组件 (God Component)
|
||||
|
||||
**现象**: `WaveformChart.vue` 1975 行,包含所有逻辑
|
||||
|
||||
**问题**:
|
||||
|
||||
- ❌ 难以维护:任何改动都要在这个文件里找
|
||||
- ❌ 难以测试:无法独立测试渲染、缩放、标注等模块
|
||||
- ❌ 代码耦合:Vue 组件逻辑与 D3 渲染逻辑混在一起
|
||||
- ❌ 复用困难:无法在其他框架(React/Svelte)中复用核心逻辑
|
||||
|
||||
**代码示例** (当前混在一起):
|
||||
|
||||
```typescript
|
||||
// WaveformChart.vue 内部同时包含:
|
||||
// 1. Vue 响应式逻辑
|
||||
const chartSeries = computed(() => ...)
|
||||
|
||||
// 2. D3 渲染逻辑
|
||||
function renderAxis() { select(...).call(axisLeft(...)) }
|
||||
|
||||
// 3. 缩放交互逻辑
|
||||
function handleZoom(event) { ... }
|
||||
|
||||
// 4. 标注管理逻辑
|
||||
function addAnnotation() { ... }
|
||||
|
||||
// 5. 格式化工具函数
|
||||
function formatTime(value) { ... }
|
||||
```
|
||||
|
||||
### 问题 2: 缺乏分层
|
||||
|
||||
**现象**: 数据层、渲染层、交互层混杂
|
||||
|
||||
**影响**:
|
||||
|
||||
- 无法单独优化某一层
|
||||
- 无法单独测试某一层
|
||||
- 修改数据格式可能影响渲染逻辑
|
||||
|
||||
### 问题 3: 缺乏可复用的 Composables
|
||||
|
||||
**现象**: 所有逻辑都在组件内部
|
||||
|
||||
**问题**:
|
||||
|
||||
- 无法在其他组件中复用缩放、悬浮等逻辑
|
||||
- 无法为不同场景定制组件
|
||||
|
||||
---
|
||||
|
||||
## ✅ 是否需要重构?
|
||||
|
||||
### 判断标准
|
||||
|
||||
| 指标 | 当前状态 | 建议阈值 | 是否需要重构 |
|
||||
| ---------- | ---------- | ---------- | ------------ |
|
||||
| 单文件行数 | 1975 | < 500 | ✅ **需要** |
|
||||
| 模块解耦度 | 低(单体) | 高(分层) | ✅ **需要** |
|
||||
| 可测试性 | 中等 | 高 | ⚠️ **建议** |
|
||||
| 跨框架复用 | 不支持 | 支持 | ⚠️ **建议** |
|
||||
| 团队协作 | 冲突风险高 | 低耦合 | ✅ **需要** |
|
||||
|
||||
### 结论
|
||||
|
||||
**建议进行模块化重构**,原因:
|
||||
|
||||
1. **维护性**: 1975 行的单文件已超过可维护阈值(通常 < 500 行)
|
||||
2. **扩展性**: 当前架构难以添加新功能(如 Web Worker、WASM 加速)
|
||||
3. **团队协作**: 多人同时修改同一文件容易冲突
|
||||
4. **测试效率**: 难以针对性地测试某个功能模块
|
||||
|
||||
---
|
||||
|
||||
## 🎯 重构方案
|
||||
|
||||
### 方案 A: 渐进式重构(推荐)⭐
|
||||
|
||||
**适用场景**: 项目正在使用中,不能中断
|
||||
|
||||
**策略**: 逐步抽取独立模块,保持向后兼容
|
||||
|
||||
#### 阶段 1: 抽取工具函数(1-2 天)
|
||||
|
||||
**目标**: 将纯函数抽离,降低主组件复杂度
|
||||
|
||||
```
|
||||
创建文件:
|
||||
src/utils/
|
||||
├── formatters.ts # 时间、数值格式化函数
|
||||
├── domain.ts # paddedDomain、buildMinorTicks
|
||||
└── geometry.ts # 碰撞检测、布局计算
|
||||
|
||||
修改:
|
||||
- WaveformChart.vue: 删除工具函数,改用 import
|
||||
```
|
||||
|
||||
**收益**:
|
||||
|
||||
- ✅ 主组件减少 ~200 行
|
||||
- ✅ 工具函数可单独测试
|
||||
- ✅ 可在其他组件复用
|
||||
|
||||
**示例**:
|
||||
|
||||
```typescript
|
||||
// src/utils/formatters.ts
|
||||
export function formatTime(value: number, unit: 'ms' | 's'): number {
|
||||
return unit === 'ms' ? value * 1000 : value
|
||||
}
|
||||
|
||||
export function formatEndpointTime(value: number, domain: [number, number]): string {
|
||||
// ...
|
||||
}
|
||||
|
||||
// src/utils/domain.ts
|
||||
export function paddedDomain(values: number[]): [number, number] {
|
||||
if (values.length === 0) return [0, 1]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 阶段 2: 抽取核心引擎(3-5 天)
|
||||
|
||||
**目标**: 将 D3 渲染逻辑抽离为框架无关的模块
|
||||
|
||||
```
|
||||
创建文件:
|
||||
src/core/
|
||||
├── scales.ts # 比例尺创建与管理
|
||||
├── axis.ts # 坐标轴渲染
|
||||
├── path.ts # 路径生成
|
||||
└── layout.ts # 轨道布局计算
|
||||
|
||||
修改:
|
||||
- WaveformChart.vue: 使用核心引擎 API
|
||||
```
|
||||
|
||||
**收益**:
|
||||
|
||||
- ✅ 主组件减少 ~400 行
|
||||
- ✅ 核心逻辑可跨框架复用
|
||||
- ✅ 可独立测试渲染逻辑
|
||||
|
||||
**示例**:
|
||||
|
||||
```typescript
|
||||
// src/core/scales.ts
|
||||
export function createXScale(
|
||||
domain: [number, number],
|
||||
range: [number, number],
|
||||
transform?: ZoomTransform,
|
||||
): ScaleLinear<number, number> {
|
||||
const base = scaleLinear(domain, range)
|
||||
return transform ? transform.rescaleX(base) : base
|
||||
}
|
||||
|
||||
// src/core/axis.ts
|
||||
export function renderXAxis(
|
||||
selection: Selection<SVGGElement>,
|
||||
scale: ScaleLinear<number, number>,
|
||||
tickValues: number[],
|
||||
formatter: (value: number) => string,
|
||||
): void {
|
||||
selection.call(
|
||||
axisBottom(scale)
|
||||
.tickValues(tickValues)
|
||||
.tickFormat(formatter)
|
||||
.tickSize(-4)
|
||||
.tickPadding(7)
|
||||
.tickSizeOuter(0),
|
||||
)
|
||||
}
|
||||
|
||||
// WaveformChart.vue 使用
|
||||
import { createXScale } from '@/core/scales'
|
||||
import { renderXAxis } from '@/core/axis'
|
||||
|
||||
const xScale = createXScale(domain, [0, width], transform)
|
||||
renderXAxis(select(xAxisElement), xScale, tickValues, formatAxisTime)
|
||||
```
|
||||
|
||||
#### 阶段 3: 抽取 Composables(2-3 天)
|
||||
|
||||
**目标**: 将交互逻辑抽离为可复用的 Vue hooks
|
||||
|
||||
```
|
||||
创建文件:
|
||||
src/hooks/
|
||||
├── useZoom.ts # 缩放交互逻辑
|
||||
├── useHover.ts # 悬浮提示逻辑
|
||||
├── useAnnotations.ts # 标注管理逻辑
|
||||
└── useSelection.ts # 选择状态管理
|
||||
|
||||
修改:
|
||||
- WaveformChart.vue: 使用 composables
|
||||
```
|
||||
|
||||
**收益**:
|
||||
|
||||
- ✅ 主组件减少 ~500 行
|
||||
- ✅ 交互逻辑可在其他组件复用
|
||||
- ✅ 单独测试交互逻辑
|
||||
|
||||
**示例**:
|
||||
|
||||
```typescript
|
||||
// src/hooks/useZoom.ts
|
||||
export function useZoom(options: {
|
||||
svgElement: Ref<SVGSVGElement | null>
|
||||
xScale: Ref<ScaleLinear<number, number>>
|
||||
displayMode: Ref<WaveformDisplayMode>
|
||||
onZoom: (transform: ZoomTransform) => void
|
||||
}) {
|
||||
const zoomBehavior = shallowRef<ZoomBehavior<SVGRectElement, unknown> | null>(null)
|
||||
|
||||
const configure = () => {
|
||||
if (!options.svgElement.value) return
|
||||
// 配置缩放行为
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
// 重置缩放
|
||||
}
|
||||
|
||||
watchEffect(configure)
|
||||
onBeforeUnmount(() => {
|
||||
// 清理
|
||||
})
|
||||
|
||||
return { reset }
|
||||
}
|
||||
|
||||
// WaveformChart.vue 使用
|
||||
const { reset: resetZoom } = useZoom({
|
||||
svgElement,
|
||||
xScale: computed(() => trackLayouts.value[0]?.xScale),
|
||||
displayMode,
|
||||
onZoom: handleZoom,
|
||||
})
|
||||
```
|
||||
|
||||
#### 阶段 4: 组件拆分(2-3 天)
|
||||
|
||||
**目标**: 将标注、悬浮提示等拆分为独立子组件
|
||||
|
||||
```
|
||||
创建文件:
|
||||
src/components/
|
||||
├── WaveformChart.vue # 主容器(缩减到 ~400 行)
|
||||
├── WaveformTrack.vue # 单个轨道组件
|
||||
├── WaveformAnnotation.vue # 标注渲染组件
|
||||
├── WaveformTooltip.vue # 悬浮提示组件
|
||||
└── WaveformToolbar.vue # 工具栏组件
|
||||
```
|
||||
|
||||
**收益**:
|
||||
|
||||
- ✅ 主组件缩减到 ~400 行
|
||||
- ✅ 组件职责清晰
|
||||
- ✅ 更好的代码组织
|
||||
|
||||
**示例**:
|
||||
|
||||
```vue
|
||||
<!-- WaveformChart.vue (简化后) -->
|
||||
<script setup lang="ts">
|
||||
import { useZoom, useHover, useAnnotations } from '@/hooks'
|
||||
import { createXScale, renderXAxis } from '@/core'
|
||||
|
||||
// 只保留组件编排逻辑
|
||||
const { reset: resetZoom } = useZoom(...)
|
||||
const { hoveredPoint } = useHover(...)
|
||||
const { annotations, addAnnotation } = useAnnotations(...)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg ref="svgElement">
|
||||
<WaveformTrack v-for="track in trackLayouts" :key="track.index" :track="track" />
|
||||
<WaveformAnnotation v-for="ann in annotations" :key="ann.id" :annotation="ann" />
|
||||
<WaveformTooltip v-if="hoveredPoint" :point="hoveredPoint" />
|
||||
</svg>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 B: 一次性重构(不推荐)
|
||||
|
||||
**风险**:
|
||||
|
||||
- 开发周期长(2-3 周)
|
||||
- 容易引入新 bug
|
||||
- 影响现有功能
|
||||
|
||||
**建议**: 除非项目处于早期阶段,否则不建议
|
||||
|
||||
---
|
||||
|
||||
## 📋 重构后的目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue 组件层 (~800 行)
|
||||
│ ├── WaveformChart.vue # 主容器 (~400 行)
|
||||
│ ├── WaveformTrack.vue # 轨道组件 (~150 行)
|
||||
│ ├── WaveformAnnotation.vue # 标注组件 (~120 行)
|
||||
│ ├── WaveformTooltip.vue # 提示组件 (~80 行)
|
||||
│ └── WaveformToolbar.vue # 工具栏 (~50 行)
|
||||
│
|
||||
├── core/ # 核心引擎 (~500 行)
|
||||
│ ├── scales.ts # 比例尺管理 (~100 行)
|
||||
│ ├── axis.ts # 坐标轴渲染 (~120 行)
|
||||
│ ├── path.ts # 路径生成 (~80 行)
|
||||
│ ├── layout.ts # 布局计算 (~150 行)
|
||||
│ └── index.ts # 导出
|
||||
│
|
||||
├── hooks/ # Composables (~600 行)
|
||||
│ ├── useZoom.ts # 缩放逻辑 (~180 行)
|
||||
│ ├── useHover.ts # 悬浮逻辑 (~120 行)
|
||||
│ ├── useAnnotations.ts # 标注管理 (~200 行)
|
||||
│ ├── useSelection.ts # 选择状态 (~100 行)
|
||||
│ └── index.ts
|
||||
│
|
||||
├── utils/ # 工具函数 (~300 行)
|
||||
│ ├── formatters.ts # 格式化 (~100 行)
|
||||
│ ├── domain.ts # 域计算 (~80 行)
|
||||
│ ├── geometry.ts # 几何计算 (~120 行)
|
||||
│ └── index.ts
|
||||
│
|
||||
├── types/ # 类型定义 (~200 行)
|
||||
│ ├── chart.ts # 图表类型
|
||||
│ ├── data.ts # 数据类型 (已有 waveform.ts)
|
||||
│ ├── interaction.ts # 交互类型
|
||||
│ └── index.ts
|
||||
│
|
||||
└── index.ts # 公共 API 导出
|
||||
```
|
||||
|
||||
**代码行数对比**:
|
||||
|
||||
- 重构前: `WaveformChart.vue` (1975 行)
|
||||
- 重构后: 分散到 15+ 个模块,单文件平均 ~120 行
|
||||
|
||||
---
|
||||
|
||||
## 📈 重构收益评估
|
||||
|
||||
### 代码质量
|
||||
|
||||
| 指标 | 重构前 | 重构后 | 提升 |
|
||||
| -------------- | ------ | ------ | ------------------- |
|
||||
| 单文件平均行数 | 1975 | ~120 | ✅ 94% ↓ |
|
||||
| 模块耦合度 | 高 | 低 | ✅ 显著改善 |
|
||||
| 可测试性 | 中 | 高 | ✅ 提升 40% |
|
||||
| 代码复用性 | 低 | 高 | ✅ 核心逻辑可跨框架 |
|
||||
|
||||
### 开发效率
|
||||
|
||||
| 场景 | 重构前 | 重构后 | 提升 |
|
||||
| ---------- | ------------------ | ---------------- | --------------- |
|
||||
| 定位 bug | 需要搜索 1975 行 | 直接找到模块 | ✅ 快 3-5 倍 |
|
||||
| 添加新功能 | 风险高,易引入回归 | 独立模块,风险低 | ✅ 安全性提升 |
|
||||
| 多人协作 | 频繁冲突 | 独立模块开发 | ✅ 冲突减少 70% |
|
||||
| 代码审查 | 难以审查巨型文件 | 小模块易审查 | ✅ 审查效率提升 |
|
||||
|
||||
### 维护成本
|
||||
|
||||
- **重构成本**: 8-12 天开发时间
|
||||
- **长期收益**: 维护成本降低 50%,新功能开发速度提升 30%
|
||||
- **ROI**: 重构后 2-3 个月即可收回成本
|
||||
|
||||
---
|
||||
|
||||
## 🚀 实施建议
|
||||
|
||||
### 立即行动(高优先级)✅
|
||||
|
||||
1. **阶段 1: 抽取工具函数**(本周完成)
|
||||
- 风险低,收益明显
|
||||
- 不影响现有功能
|
||||
- 为后续重构打基础
|
||||
|
||||
### 短期规划(1-2 周内)⚠️
|
||||
|
||||
2. **阶段 2: 抽取核心引擎**
|
||||
- 解耦 D3 渲染逻辑
|
||||
- 提升可测试性
|
||||
|
||||
3. **阶段 3: 抽取 Composables**
|
||||
- 提升代码复用性
|
||||
- 简化组件逻辑
|
||||
|
||||
### 中期规划(1 个月内)
|
||||
|
||||
4. **阶段 4: 组件拆分**
|
||||
- 完成架构重构
|
||||
- 达到生产级质量
|
||||
|
||||
### 暂缓执行
|
||||
|
||||
- **一次性完全重写**: 风险太高,不建议
|
||||
- **引入新框架/库**: 当前技术栈已足够好
|
||||
|
||||
---
|
||||
|
||||
## 🎯 最终建议
|
||||
|
||||
### 结论:**强烈建议进行渐进式重构** ⭐⭐⭐⭐⭐
|
||||
|
||||
**理由**:
|
||||
|
||||
1. ✅ 单文件 1975 行已严重超标(建议 < 500 行)
|
||||
2. ✅ 当前架构难以支撑后续功能扩展
|
||||
3. ✅ 渐进式重构风险可控,不影响现有功能
|
||||
4. ✅ 重构后维护成本显著降低
|
||||
|
||||
**时间投入**: 8-12 天
|
||||
**长期收益**: 维护效率提升 50%+,开发速度提升 30%+
|
||||
|
||||
### 下一步行动
|
||||
|
||||
**本周内完成**:
|
||||
|
||||
```bash
|
||||
# 1. 创建目录结构
|
||||
mkdir -p src/utils src/core src/hooks
|
||||
|
||||
# 2. 抽取工具函数
|
||||
# - formatters.ts (时间格式化)
|
||||
# - domain.ts (域计算)
|
||||
# - geometry.ts (几何计算)
|
||||
|
||||
# 3. 编写单元测试
|
||||
# - 确保抽取的函数行为一致
|
||||
|
||||
# 4. 更新 WaveformChart.vue
|
||||
# - 删除工具函数,改用 import
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
|
||||
- ✅ 主组件减少 ~200 行
|
||||
- ✅ 新增 3 个工具模块,每个 < 150 行
|
||||
- ✅ 单元测试覆盖率保持 > 80%
|
||||
|
||||
---
|
||||
|
||||
## 附录: 重构检查清单
|
||||
|
||||
### 重构前
|
||||
|
||||
- [ ] 确保所有测试通过
|
||||
- [ ] 创建功能分支 `refactor/modularize-architecture`
|
||||
- [ ] 备份当前代码
|
||||
- [ ] 记录当前性能基线
|
||||
|
||||
### 重构中
|
||||
|
||||
- [ ] 每个阶段独立提交
|
||||
- [ ] 每次提交后运行测试
|
||||
- [ ] 保持向后兼容
|
||||
- [ ] 更新类型定义
|
||||
|
||||
### 重构后
|
||||
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 类型检查通过
|
||||
- [ ] 代码规范检查通过
|
||||
- [ ] 性能无回退
|
||||
- [ ] 更新文档
|
||||
- [ ] Code Review
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建日期**: 2026-07-18
|
||||
**审查状态**: 待团队讨论
|
||||
@@ -1,596 +0,0 @@
|
||||
# 🎉 目录结构重构完成总结
|
||||
|
||||
## 执行概览
|
||||
|
||||
按照 **doc/03-开发计划.md** 中 1.4 节的规划,已成功完成完整的模块化目录结构拆分。
|
||||
|
||||
**执行时间**: 2026-07-18
|
||||
**总耗时**: 约 3 小时
|
||||
**测试状态**: ✅ 24/24 通过
|
||||
**类型检查**: ✅ 通过
|
||||
**代码规范**: ✅ 0 错误 0 警告
|
||||
|
||||
---
|
||||
|
||||
## 📊 重构前后对比
|
||||
|
||||
### 目录结构对比
|
||||
|
||||
#### 重构前(单体架构)
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── WaveformChart.vue (1975 行 - 巨型组件)
|
||||
│ ├── waveform.ts (149 行 - 类型+逻辑混杂)
|
||||
│ └── waveform-markup.ts (162 行)
|
||||
├── data/
|
||||
├── test/
|
||||
├── App.vue
|
||||
└── main.ts
|
||||
```
|
||||
|
||||
#### 重构后(模块化架构)✅
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue 组件层 (1913 行)
|
||||
├── core/ # 核心引擎 (63 行) ✨ 框架无关
|
||||
├── types/ # 类型定义 (143 行) ✨ 集中管理
|
||||
├── utils/ # 工具函数 (162 行) ✨ 纯函数
|
||||
├── interactions/ # 交互层(预留)
|
||||
├── hooks/ # Composables(预留)
|
||||
├── data/
|
||||
├── test/
|
||||
├── App.vue
|
||||
├── main.ts
|
||||
└── index.ts # ✨ 库主入口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 关键指标改善
|
||||
|
||||
| 指标 | 重构前 | 重构后 | 改善 |
|
||||
| -------------- | ------ | -------- | ---------------- |
|
||||
| 主组件行数 | 1975 | 1913 | ✅ -62 行 (3.1%) |
|
||||
| 单文件平均行数 | 762 | 219 | ✅ -71% |
|
||||
| 模块数量 | 3 | 13 | ✅ +333% |
|
||||
| 最大文件行数 | 1975 | 1913 | ✅ 减少 |
|
||||
| 类型定义文件 | 0 独立 | 2 专用 | ✅ 集中管理 |
|
||||
| 核心引擎独立性 | 无 | 框架无关 | ✅ 可跨框架 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 完成的工作清单
|
||||
|
||||
### ✅ 阶段 1:抽取工具函数(已完成)
|
||||
|
||||
- [x] 创建 `utils/domain.ts` - 域计算
|
||||
- [x] 创建 `utils/formatters.ts` - 格式化
|
||||
- [x] 创建 `utils/geometry.ts` - 几何计算
|
||||
- [x] 创建 `utils/index.ts` - 统一导出
|
||||
- [x] 更新主组件使用工具函数
|
||||
- [x] 所有测试通过
|
||||
|
||||
### ✅ 阶段 2:创建类型定义模块(已完成)
|
||||
|
||||
- [x] 创建 `types/chart.ts` - 图表类型
|
||||
- [x] 创建 `types/data.ts` - 数据类型
|
||||
- [x] 创建 `types/index.ts` - 统一导出
|
||||
- [x] 更新所有模块使用新类型路径
|
||||
|
||||
### ✅ 阶段 3:创建核心引擎模块(已完成)
|
||||
|
||||
- [x] 创建 `core/data.ts` - 数据规范化
|
||||
- [x] 创建 `core/index.ts` - 统一导出
|
||||
- [x] 重构 `components/waveform.ts` 为兼容层
|
||||
- [x] 更新依赖模块
|
||||
|
||||
### ✅ 阶段 4:创建库入口(已完成)
|
||||
|
||||
- [x] 创建 `src/index.ts` - 公共 API 导出
|
||||
- [x] 提供统一的导入路径
|
||||
- [x] 支持按需导入
|
||||
|
||||
### ✅ 阶段 5:预留扩展目录(已完成)
|
||||
|
||||
- [x] 创建 `interactions/` 目录
|
||||
- [x] 创建 `hooks/` 目录
|
||||
- [x] 为后续重构打好基础
|
||||
|
||||
---
|
||||
|
||||
## 📁 新架构详解
|
||||
|
||||
### 1️⃣ types/ - 类型定义层
|
||||
|
||||
**职责**: 集中管理所有 TypeScript 类型定义
|
||||
|
||||
```typescript
|
||||
// types/chart.ts
|
||||
export interface WaveformPoint { x: number; y: number }
|
||||
export type WaveformDisplayMode = 'independent' | 'separated' | 'compact'
|
||||
export interface WaveformAnnotation { ... }
|
||||
export type WaveformShape = ...
|
||||
|
||||
// types/data.ts
|
||||
export type SingleWaveformData = ...
|
||||
export interface WaveformSeries { ... }
|
||||
export type WaveformData = ...
|
||||
```
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 类型定义一目了然
|
||||
- ✅ 避免循环依赖
|
||||
- ✅ 易于维护和扩展
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ core/ - 核心引擎层(框架无关)
|
||||
|
||||
**职责**: 提供纯 TypeScript 数据处理逻辑
|
||||
|
||||
```typescript
|
||||
// core/data.ts
|
||||
export function normalizeWaveformData(data: SingleWaveformData): WaveformPoint[]
|
||||
export function normalizeWaveformSeries(data: WaveformData): NormalizedWaveformSeries[]
|
||||
```
|
||||
|
||||
**特点**:
|
||||
|
||||
- ✅ 无 Vue 依赖
|
||||
- ✅ 可在 React/Svelte/Angular 中使用
|
||||
- ✅ 易于单独测试
|
||||
|
||||
**未来扩展**:
|
||||
|
||||
```
|
||||
core/
|
||||
├── data.ts # ✅ 已完成
|
||||
├── scales.ts # 📝 比例尺管理(待添加)
|
||||
├── axis.ts # 📝 坐标轴渲染(待添加)
|
||||
├── path.ts # 📝 路径生成(待添加)
|
||||
└── layout.ts # 📝 布局计算(待添加)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ utils/ - 工具函数层
|
||||
|
||||
**职责**: 提供纯函数工具集
|
||||
|
||||
```typescript
|
||||
// utils/domain.ts
|
||||
export function paddedDomain(values: number[]): [number, number]
|
||||
export function buildMinorTicks(values: number[], subdivisions?: number): number[]
|
||||
|
||||
// utils/formatters.ts
|
||||
export function formatEndpointTime(
|
||||
value: number,
|
||||
domain: [number, number],
|
||||
timeUnit: TimeUnit,
|
||||
): string
|
||||
export function formatAxisTime(value: number, timeUnit: TimeUnit): string
|
||||
|
||||
// utils/geometry.ts
|
||||
export function resolveTrackGeometry(
|
||||
trackCount: number,
|
||||
displayMode: WaveformDisplayMode,
|
||||
innerHeight: number,
|
||||
): TrackGeometry
|
||||
export function clamp(value: number, min: number, max: number): number
|
||||
```
|
||||
|
||||
**特点**:
|
||||
|
||||
- ✅ 所有函数都是纯函数
|
||||
- ✅ 完整的 JSDoc 注释
|
||||
- ✅ 参数显式传递,无隐式依赖
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ components/ - Vue 组件层
|
||||
|
||||
**职责**: Vue 组件和标注工具
|
||||
|
||||
```typescript
|
||||
// components/WaveformChart.vue
|
||||
// 主波形图组件(1913 行)
|
||||
|
||||
// components/waveform.ts
|
||||
// 向后兼容层 - 重新导出新模块
|
||||
export type { ... } from '../types'
|
||||
export { ... } from '../core'
|
||||
|
||||
// components/waveform-markup.ts
|
||||
// 标注工具函数
|
||||
export function isFiniteAnnotation(annotation: WaveformAnnotation): boolean
|
||||
export function layoutAnnotationBox(...): AnnotationBoxLayout
|
||||
```
|
||||
|
||||
**向后兼容**:
|
||||
|
||||
- ✅ 旧导入路径仍可用
|
||||
- ✅ API 完全兼容
|
||||
- ✅ 无破坏性变更
|
||||
|
||||
---
|
||||
|
||||
### 5️⃣ src/index.ts - 库主入口
|
||||
|
||||
**职责**: 统一的公共 API
|
||||
|
||||
```typescript
|
||||
// 组件
|
||||
export { default as WaveformChart } from './components/WaveformChart.vue'
|
||||
|
||||
// 类型
|
||||
export type { WaveformPoint, WaveformData, ... } from './types'
|
||||
|
||||
// 核心功能
|
||||
export { normalizeWaveformSeries, ... } from './core'
|
||||
|
||||
// 工具函数
|
||||
export { paddedDomain, formatAxisTime, ... } from './utils'
|
||||
|
||||
// 标注工具
|
||||
export { isFiniteAnnotation, ... } from './components/waveform-markup'
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
|
||||
```typescript
|
||||
// 旧方式(仍可用)
|
||||
import { WaveformChart } from './components'
|
||||
|
||||
// 新方式(推荐)
|
||||
import { WaveformChart, type WaveformData } from './index'
|
||||
|
||||
// 按需导入
|
||||
import { paddedDomain, formatAxisTime } from './utils'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 依赖关系图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ App.vue (Demo 应用) │
|
||||
└──────────────┬──────────────────────┘
|
||||
│
|
||||
┌──────────────▼──────────────────────┐
|
||||
│ components/WaveformChart.vue │
|
||||
│ (Vue 组件层) │
|
||||
└──────┬────────┬────────┬────────────┘
|
||||
│ │ │
|
||||
┌───▼───┐┌──▼───┐┌──▼───┐
|
||||
│types/ ││utils/││core/ │
|
||||
│ ││ ││ │
|
||||
└───────┘└──────┘└──┬───┘
|
||||
│
|
||||
┌───▼───┐
|
||||
│types/ │
|
||||
└───────┘
|
||||
```
|
||||
|
||||
**依赖规则**:
|
||||
|
||||
- ✅ 单向依赖(自顶向下)
|
||||
- ✅ 无循环依赖
|
||||
- ✅ 底层模块无 Vue 依赖
|
||||
|
||||
---
|
||||
|
||||
## 💡 实际收益
|
||||
|
||||
### 1. 开发效率提升
|
||||
|
||||
**定位代码更快**:
|
||||
|
||||
- 重构前: 在 1975 行文件中搜索
|
||||
- 重构后: 直接找到对应模块(平均 ~150 行)
|
||||
- **效率提升**: 3-5 倍 ✅
|
||||
|
||||
**添加新功能更快**:
|
||||
|
||||
- 重构前: 需要理解整个巨型文件
|
||||
- 重构后: 只需理解相关模块
|
||||
- **开发速度**: 提升 30% ✅
|
||||
|
||||
**多人协作冲突更少**:
|
||||
|
||||
- 重构前: 多人修改同一巨型文件,频繁冲突
|
||||
- 重构后: 独立模块开发,冲突减少
|
||||
- **冲突率**: 降低 70% ✅
|
||||
|
||||
---
|
||||
|
||||
### 2. 代码质量提升
|
||||
|
||||
**可测试性**:
|
||||
|
||||
```typescript
|
||||
// 重构前:难以单独测试
|
||||
// 需要渲染整个 Vue 组件
|
||||
|
||||
// 重构后:纯函数易于测试
|
||||
import { paddedDomain } from '@/utils/domain'
|
||||
|
||||
describe('paddedDomain', () => {
|
||||
it('should handle empty array', () => {
|
||||
expect(paddedDomain([])).toEqual([0, 1])
|
||||
})
|
||||
|
||||
it('should add padding for single value', () => {
|
||||
expect(paddedDomain([5])).toEqual([4.75, 5.25])
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**类型安全**:
|
||||
|
||||
```typescript
|
||||
// 重构前:类型散落各处
|
||||
// 重构后:类型集中管理
|
||||
|
||||
import type { WaveformData } from '@/types'
|
||||
|
||||
function processData(data: WaveformData) {
|
||||
// TypeScript 自动推导和检查
|
||||
}
|
||||
```
|
||||
|
||||
**代码复用**:
|
||||
|
||||
```typescript
|
||||
// 重构前:逻辑锁定在 Vue 组件中
|
||||
// 重构后:核心逻辑可跨框架使用
|
||||
|
||||
// React 项目
|
||||
import { normalizeWaveformSeries } from '@/core'
|
||||
|
||||
// Node.js 服务端
|
||||
const { formatAxisTime } = require('@/utils')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 维护成本降低
|
||||
|
||||
| 维护场景 | 重构前 | 重构后 | 改善 |
|
||||
| -------------- | -------------- | ---------------------- | ----------- |
|
||||
| 修复格式化 bug | 在 1975 行中找 | 直接打开 formatters.ts | ✅ 5x 快 |
|
||||
| 添加新数据格式 | 修改巨型文件 | 只修改 core/data.ts | ✅ 风险降低 |
|
||||
| 升级 D3 版本 | 影响整个组件 | 只影响 core/ 模块 | ✅ 隔离影响 |
|
||||
| Code Review | 难以审查大文件 | 小模块易审查 | ✅ 审查效率 |
|
||||
|
||||
---
|
||||
|
||||
## 🎓 开发指南
|
||||
|
||||
### 添加新类型
|
||||
|
||||
```typescript
|
||||
// 1. 在 types/chart.ts 中定义
|
||||
export interface WaveformTooltipStyle {
|
||||
fontSize: number
|
||||
backgroundColor: string
|
||||
}
|
||||
|
||||
// 2. 在 types/index.ts 中导出
|
||||
export type { WaveformTooltipStyle } from './chart'
|
||||
|
||||
// 3. 在 src/index.ts 中导出(如需对外暴露)
|
||||
export type { WaveformTooltipStyle } from './types'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 添加工具函数
|
||||
|
||||
```typescript
|
||||
// 1. 在 utils/formatters.ts 中实现
|
||||
/**
|
||||
* 格式化数值为科学计数法
|
||||
* @param value 数值
|
||||
* @returns 格式化字符串
|
||||
*/
|
||||
export function formatScientific(value: number): string {
|
||||
return value.toExponential(2)
|
||||
}
|
||||
|
||||
// 2. 在 utils/index.ts 中导出
|
||||
export { formatScientific } from './formatters'
|
||||
|
||||
// 3. 在 src/index.ts 中导出(如需对外暴露)
|
||||
export { formatScientific } from './utils'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 添加核心功能
|
||||
|
||||
```typescript
|
||||
// 1. 在 core/ 创建新模块
|
||||
// core/downsample.ts
|
||||
import type { WaveformPoint } from '../types'
|
||||
|
||||
export function downsampleLTTB(points: WaveformPoint[], threshold: number): WaveformPoint[] {
|
||||
// Largest Triangle Three Buckets 算法
|
||||
// ...
|
||||
}
|
||||
|
||||
// 2. 在 core/index.ts 中导出
|
||||
export { downsampleLTTB } from './downsample'
|
||||
|
||||
// 3. 在 src/index.ts 中导出
|
||||
export { downsampleLTTB } from './core'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 最佳实践
|
||||
|
||||
### 1. 模块职责单一
|
||||
|
||||
```typescript
|
||||
// ✅ 好的实践
|
||||
// utils/formatters.ts - 只负责格式化
|
||||
export function formatTime() { ... }
|
||||
export function formatValue() { ... }
|
||||
|
||||
// ❌ 不好的实践
|
||||
// utils/helpers.ts - 职责混杂
|
||||
export function formatTime() { ... }
|
||||
export function calculateLayout() { ... }
|
||||
export function validateData() { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 保持纯函数
|
||||
|
||||
```typescript
|
||||
// ✅ 好的实践 - 纯函数
|
||||
export function formatEndpointTime(
|
||||
value: number,
|
||||
domain: [number, number],
|
||||
timeUnit: TimeUnit
|
||||
): string {
|
||||
// 所有依赖通过参数传递
|
||||
}
|
||||
|
||||
// ❌ 不好的实践 - 依赖外部状态
|
||||
let currentTimeUnit = 'ms'
|
||||
export function formatEndpointTime(value: number, domain: [number, number]): string {
|
||||
// 依赖外部变量
|
||||
return displayTime(value, currentTimeUnit).toLocaleString(...)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 完善的类型定义
|
||||
|
||||
```typescript
|
||||
// ✅ 好的实践
|
||||
export interface TrackGeometry {
|
||||
/** 轨道间距(像素) */
|
||||
gap: number
|
||||
/** 坐标轴区域高度(像素) */
|
||||
axisBand: number
|
||||
/** 单个轨道高度(像素) */
|
||||
height: number
|
||||
}
|
||||
|
||||
// ❌ 不好的实践
|
||||
export interface TrackGeometry {
|
||||
gap: number
|
||||
axisBand: number
|
||||
height: number
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔮 未来规划
|
||||
|
||||
### 短期(1-2 周)- 阶段 2
|
||||
|
||||
```
|
||||
core/
|
||||
├── data.ts # ✅ 已完成
|
||||
├── scales.ts # 📝 比例尺创建与管理
|
||||
├── axis.ts # 📝 坐标轴渲染逻辑
|
||||
├── path.ts # 📝 D3 路径生成
|
||||
└── layout.ts # 📝 轨道布局计算
|
||||
```
|
||||
|
||||
**预期**: 主组件减少 ~400 行
|
||||
|
||||
---
|
||||
|
||||
### 中期(3-4 周)- 阶段 3
|
||||
|
||||
```
|
||||
hooks/
|
||||
├── useZoom.ts # 📝 缩放交互 Hook
|
||||
├── useHover.ts # 📝 悬浮状态 Hook
|
||||
├── useAnnotations.ts # 📝 标注管理 Hook
|
||||
└── useSelection.ts # 📝 选择状态 Hook
|
||||
|
||||
interactions/
|
||||
├── zoom.ts # 📝 缩放交互逻辑
|
||||
├── hover.ts # 📝 悬浮交互逻辑
|
||||
└── annotation.ts # 📝 标注交互逻辑
|
||||
```
|
||||
|
||||
**预期**: 主组件减少 ~500 行
|
||||
|
||||
---
|
||||
|
||||
### 长期(1-2 月)- 阶段 4
|
||||
|
||||
```
|
||||
components/
|
||||
├── WaveformChart.vue # 主容器 (~400 行)
|
||||
├── WaveformTrack.vue # 单轨道组件
|
||||
├── WaveformAnnotation.vue # 标注渲染组件
|
||||
├── WaveformTooltip.vue # 悬浮提示组件
|
||||
└── WaveformToolbar.vue # 工具栏组件
|
||||
```
|
||||
|
||||
**最终目标**:
|
||||
|
||||
- 主组件 ~400 行(减少 80%)
|
||||
- 单文件平均 ~150 行
|
||||
- 完整的模块化架构
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
- [x] 所有测试通过(24/24)
|
||||
- [x] TypeScript 类型检查通过
|
||||
- [x] ESLint 代码规范通过(0 错误 0 警告)
|
||||
- [x] 向后兼容性验证
|
||||
- [x] 依赖关系无循环
|
||||
- [x] 模块职责清晰
|
||||
- [x] 文档更新完整
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - 完整架构文档
|
||||
- [REFACTORING_ANALYSIS.md](REFACTORING_ANALYSIS.md) - 重构分析报告
|
||||
- [DIRECTORY_STRUCTURE_REFACTORING.md](DIRECTORY_STRUCTURE_REFACTORING.md) - 目录拆分详细报告
|
||||
- [REFACTORING_PHASE1_REPORT.md](REFACTORING_PHASE1_REPORT.md) - 阶段 1 完成报告
|
||||
- [doc/03-开发计划.md](doc/03-开发计划.md) - 原始规划文档
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
✅ **目标达成**: 完全按照规划实施模块化目录结构
|
||||
✅ **质量保证**: 所有测试和检查通过
|
||||
✅ **向后兼容**: 旧代码无需修改
|
||||
✅ **基础打好**: 为后续扩展做好准备
|
||||
|
||||
**核心价值**:
|
||||
|
||||
- 代码组织清晰,易于理解
|
||||
- 模块职责单一,易于维护
|
||||
- 核心逻辑可复用,易于扩展
|
||||
- 依赖关系明确,易于测试
|
||||
|
||||
**下一步**: 随时可以继续执行阶段 2-4,进一步优化架构!
|
||||
|
||||
---
|
||||
|
||||
**完成时间**: 2026-07-18
|
||||
**执行人员**: Claude (AI)
|
||||
**审查状态**: ✅ 已验证
|
||||
@@ -1,326 +0,0 @@
|
||||
# 阶段 1 重构完成报告
|
||||
|
||||
## 📊 重构成果
|
||||
|
||||
### 代码行数变化
|
||||
|
||||
| 文件 | 重构前 | 重构后 | 变化 |
|
||||
| ------------------- | ------- | ------- | ----------------------- |
|
||||
| WaveformChart.vue | 1975 行 | 1913 行 | ✅ **减少 62 行** |
|
||||
| utils/domain.ts | - | 29 行 | ✅ 新增 |
|
||||
| utils/formatters.ts | - | 64 行 | ✅ 新增 |
|
||||
| utils/geometry.ts | - | 50 行 | ✅ 新增 |
|
||||
| utils/index.ts | - | 19 行 | ✅ 新增 |
|
||||
| **总计** | 1975 行 | 2075 行 | +100 行(含注释和导出) |
|
||||
|
||||
**实际效果**:主组件复杂度降低 **3.1%**,工具函数模块化后代码更清晰。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成的任务
|
||||
|
||||
### 1. 创建工具函数模块
|
||||
|
||||
#### 📁 `src/utils/domain.ts` - 域计算工具
|
||||
|
||||
**功能**:
|
||||
|
||||
- `paddedDomain()` - 计算带边距的数据域,处理空数组边界情况
|
||||
- `buildMinorTicks()` - 在主刻度之间生成次要刻度
|
||||
|
||||
**测试状态**:✅ 通过 (已有测试覆盖)
|
||||
|
||||
#### 📁 `src/utils/formatters.ts` - 格式化工具
|
||||
|
||||
**功能**:
|
||||
|
||||
- `displayTime()` - 根据时间单位转换显示值
|
||||
- `endpointFractionDigits()` - 计算端点标签的小数位数
|
||||
- `formatEndpointTime()` - 格式化端点时间(动态精度)
|
||||
- `formatAxisTime()` - 格式化坐标轴时间(整数)
|
||||
- `formatTooltipTime()` - 格式化悬浮提示时间(4 位小数)
|
||||
|
||||
**改进**:所有格式化函数现在接收 `timeUnit` 参数,更加纯函数化
|
||||
|
||||
**测试状态**:✅ 通过
|
||||
|
||||
#### 📁 `src/utils/geometry.ts` - 几何计算工具
|
||||
|
||||
**功能**:
|
||||
|
||||
- `resolveTrackGeometry()` - 计算轨道布局几何信息
|
||||
- `clamp()` - 限制数值在指定范围内
|
||||
|
||||
**改进**:`resolveTrackGeometry` 现在接收所有参数,不依赖组件 props
|
||||
|
||||
**测试状态**:✅ 通过
|
||||
|
||||
#### 📁 `src/utils/index.ts` - 统一导出
|
||||
|
||||
**功能**:提供统一的导出入口,简化导入语句
|
||||
|
||||
---
|
||||
|
||||
### 2. 重构 WaveformChart.vue
|
||||
|
||||
**变更内容**:
|
||||
|
||||
- ✅ 删除 8 个工具函数定义(~73 行代码)
|
||||
- ✅ 添加工具函数导入语句
|
||||
- ✅ 更新所有函数调用,传递正确的参数
|
||||
- ✅ 解决重复导入冲突
|
||||
|
||||
**导入优化**:
|
||||
|
||||
```typescript
|
||||
// 重构前:函数定义混杂在组件中
|
||||
function paddedDomain(values: number[]): [number, number] { ... }
|
||||
function formatEndpointTime(value: number, domain: [number, number]): string { ... }
|
||||
// ... 8 个函数定义
|
||||
|
||||
// 重构后:统一从 utils 导入
|
||||
import {
|
||||
buildMinorTicks,
|
||||
clamp,
|
||||
formatAxisTime,
|
||||
formatEndpointTime,
|
||||
formatTooltipTime,
|
||||
paddedDomain,
|
||||
resolveTrackGeometry,
|
||||
} from '../utils'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 重构 waveform-markup.ts
|
||||
|
||||
**变更内容**:
|
||||
|
||||
- ✅ 删除 `clamp` 函数定义
|
||||
- ✅ 从 `../utils/geometry` 导入 `clamp`
|
||||
- ✅ 保持所有功能正常运行
|
||||
|
||||
**改进**:避免重复定义,统一使用 utils 中的工具函数
|
||||
|
||||
---
|
||||
|
||||
## 🎯 验证结果
|
||||
|
||||
### 测试通过 ✅
|
||||
|
||||
```bash
|
||||
✅ 24/24 测试通过
|
||||
✅ 所有功能正常运行
|
||||
✅ 无回归错误
|
||||
```
|
||||
|
||||
### 类型检查通过 ✅
|
||||
|
||||
```bash
|
||||
✅ vue-tsc -b 无错误
|
||||
✅ TypeScript 类型安全
|
||||
```
|
||||
|
||||
### 代码规范通过 ✅
|
||||
|
||||
```bash
|
||||
✅ ESLint 0 错误 0 警告
|
||||
✅ 代码风格一致
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 重构收益
|
||||
|
||||
### 1. 可维护性提升
|
||||
|
||||
- ✅ **工具函数独立**:8 个函数抽离到专门模块
|
||||
- ✅ **职责清晰**:domain(域计算)、formatters(格式化)、geometry(几何计算)
|
||||
- ✅ **主组件简化**:WaveformChart.vue 减少 62 行
|
||||
|
||||
### 2. 可测试性提升
|
||||
|
||||
- ✅ **独立测试**:工具函数可单独测试,无需渲染组件
|
||||
- ✅ **纯函数**:所有工具函数都是纯函数,易于测试
|
||||
- ✅ **边界情况覆盖**:空数组、特殊值等边界情况已覆盖
|
||||
|
||||
### 3. 可复用性提升
|
||||
|
||||
- ✅ **跨组件复用**:工具函数可在其他组件中使用
|
||||
- ✅ **统一导出**:`utils/index.ts` 提供便捷的导入方式
|
||||
- ✅ **框架无关**:核心工具函数不依赖 Vue
|
||||
|
||||
### 4. 类型安全提升
|
||||
|
||||
- ✅ **明确类型导出**:`TimeUnit` 类型导出
|
||||
- ✅ **接口定义**:`TrackGeometry` 接口规范几何信息
|
||||
- ✅ **参数类型化**:所有函数都有完整的类型注解
|
||||
|
||||
---
|
||||
|
||||
## 🔄 对比分析
|
||||
|
||||
### 重构前
|
||||
|
||||
```typescript
|
||||
// WaveformChart.vue 内部 (1975 行)
|
||||
function paddedDomain(values: number[]): [number, number] { ... }
|
||||
function formatEndpointTime(value: number, domain: [number, number]): string {
|
||||
return displayTime(value).toLocaleString(...) // 依赖 props.timeUnit
|
||||
}
|
||||
function resolveTrackGeometry(trackCount: number): {...} {
|
||||
const desiredGap = props.displayMode === 'compact' ? ... // 依赖 props
|
||||
const maximumReserve = innerHeight.value * 0.45 // 依赖 reactive
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
|
||||
- ❌ 函数依赖组件 props 和状态
|
||||
- ❌ 难以单独测试
|
||||
- ❌ 无法在其他组件复用
|
||||
- ❌ 代码组织混乱
|
||||
|
||||
### 重构后
|
||||
|
||||
```typescript
|
||||
// utils/formatters.ts
|
||||
export function formatEndpointTime(
|
||||
value: number,
|
||||
domain: [number, number],
|
||||
timeUnit: TimeUnit // 显式参数
|
||||
): string { ... }
|
||||
|
||||
// utils/geometry.ts
|
||||
export function resolveTrackGeometry(
|
||||
trackCount: number,
|
||||
displayMode: WaveformDisplayMode, // 显式参数
|
||||
innerHeight: number // 显式参数
|
||||
): TrackGeometry { ... }
|
||||
|
||||
// WaveformChart.vue (1913 行)
|
||||
import { formatEndpointTime, resolveTrackGeometry } from '../utils'
|
||||
|
||||
const geometry = resolveTrackGeometry(trackCount, props.displayMode, innerHeight.value)
|
||||
const label = formatEndpointTime(domain[0], domain, props.timeUnit)
|
||||
```
|
||||
|
||||
**改进**:
|
||||
|
||||
- ✅ 纯函数,所有依赖通过参数传递
|
||||
- ✅ 易于单独测试
|
||||
- ✅ 可在任何地方复用
|
||||
- ✅ 代码组织清晰
|
||||
|
||||
---
|
||||
|
||||
## 📚 新增文档
|
||||
|
||||
所有工具函数都包含完整的 JSDoc 注释:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 计算带边距的数据域
|
||||
* @param values 数值数组
|
||||
* @returns 数据域 [最小值, 最大值],如果数组为空返回 [0, 1]
|
||||
*/
|
||||
export function paddedDomain(values: number[]): [number, number]
|
||||
|
||||
/**
|
||||
* 格式化端点时间(动态精度)
|
||||
* @param value 时间值(秒)
|
||||
* @param domain 数据域
|
||||
* @param timeUnit 时间单位
|
||||
* @returns 格式化的时间字符串
|
||||
*/
|
||||
export function formatEndpointTime(
|
||||
value: number,
|
||||
domain: [number, number],
|
||||
timeUnit: TimeUnit,
|
||||
): string
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步计划
|
||||
|
||||
### 阶段 2:抽取核心引擎(3-5 天)
|
||||
|
||||
```
|
||||
src/core/
|
||||
├── scales.ts # 比例尺创建与管理
|
||||
├── axis.ts # 坐标轴渲染
|
||||
├── path.ts # 路径生成
|
||||
└── layout.ts # 轨道布局计算
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 主组件减少 ~400 行
|
||||
- D3 逻辑框架无关
|
||||
- 可跨框架复用
|
||||
|
||||
### 阶段 3:抽取 Composables(2-3 天)
|
||||
|
||||
```
|
||||
src/hooks/
|
||||
├── useZoom.ts # 缩放交互
|
||||
├── useHover.ts # 悬浮提示
|
||||
├── useAnnotations.ts # 标注管理
|
||||
└── useSelection.ts # 选择状态
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 主组件减少 ~500 行
|
||||
- 交互逻辑可复用
|
||||
|
||||
### 阶段 4:组件拆分(2-3 天)
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── WaveformChart.vue # 主容器 (~400 行)
|
||||
├── WaveformTrack.vue # 单轨道
|
||||
├── WaveformAnnotation.vue # 标注渲染
|
||||
└── WaveformTooltip.vue # 悬浮提示
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
|
||||
- 组件职责清晰
|
||||
- 单文件平均 ~120 行
|
||||
|
||||
---
|
||||
|
||||
## ✨ 总结
|
||||
|
||||
✅ **阶段 1 成功完成**
|
||||
|
||||
**实际收益**:
|
||||
|
||||
- 主组件减少 62 行(3.1%)
|
||||
- 新增 3 个工具模块(162 行,含注释)
|
||||
- 所有测试通过,无功能回退
|
||||
- 代码组织更清晰,可维护性提升
|
||||
|
||||
**时间投入**:约 2 小时(按计划 1-2 天的任务提前完成)
|
||||
|
||||
**质量保证**:
|
||||
|
||||
- ✅ 24 个单元测试全部通过
|
||||
- ✅ TypeScript 类型检查通过
|
||||
- ✅ ESLint 代码规范检查通过
|
||||
- ✅ 无破坏性变更
|
||||
- ✅ 向后兼容
|
||||
|
||||
**团队建议**:
|
||||
|
||||
- 继续执行阶段 2-4,预计 7-11 天完成全部重构
|
||||
- 重构后主组件将从 1975 行缩减到 ~400 行(减少 80%)
|
||||
- 长期维护成本预计降低 50%+
|
||||
|
||||
---
|
||||
|
||||
**重构人员**: Claude (AI)
|
||||
**完成时间**: 2026-07-18
|
||||
**审查状态**: ✅ 已验证
|
||||
@@ -1,624 +0,0 @@
|
||||
# WaveformChart 按系统拆分重构完成报告
|
||||
|
||||
## 🎯 重构目标
|
||||
|
||||
将 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 # 导出
|
||||
```
|
||||
|
||||
### 重构后的结构
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── WaveformChart.vue # 主容器组件(协调各系统)
|
||||
├── index.ts # 公共导出(保持向后兼容)
|
||||
│
|
||||
├── core/ # 核心系统
|
||||
│ ├── constants.ts # 常量(颜色、边距等)
|
||||
│ ├── types.ts # 共享类型定义
|
||||
│ └── index.ts
|
||||
│
|
||||
├── data/ # 数据系统
|
||||
│ ├── types.ts # 数据相关类型(重导出)
|
||||
│ └── index.ts
|
||||
│
|
||||
├── rendering/ # 渲染系统
|
||||
│ ├── WaveformTrack.vue # 波形轨道组件
|
||||
│ └── index.ts
|
||||
│
|
||||
├── interaction/ # 交互系统
|
||||
│ ├── WaveformToolbar.vue # 工具栏组件
|
||||
│ ├── WaveformTooltip.vue # 悬浮提示组件
|
||||
│ └── index.ts
|
||||
│
|
||||
├── annotation/ # 标注系统
|
||||
│ ├── WaveformAnnotationLayer.vue # 标注渲染层
|
||||
│ ├── WaveformEditor.vue # 标注编辑器
|
||||
│ ├── markup.ts # 标注工具函数
|
||||
│ ├── types.ts # 标注相关类型(重导出)
|
||||
│ └── index.ts
|
||||
│
|
||||
├── waveform.ts # 向后兼容(重导出)
|
||||
└── waveform-markup.ts # 向后兼容(重导出)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 各系统详情
|
||||
|
||||
### 1. Core System (核心系统)
|
||||
|
||||
**目录**: `src/components/core/`
|
||||
|
||||
**职责**: 提供共享的类型、常量
|
||||
|
||||
**文件**:
|
||||
|
||||
- `constants.ts` - 定义全局常量
|
||||
- `channelColors` - 通道颜色数组
|
||||
- `margin` - 图表边距
|
||||
- `minimumHeight` - 最小高度
|
||||
|
||||
- `types.ts` - 核心类型定义
|
||||
- `DisplaySeries` - 显示系列接口
|
||||
- `HoveredSeriesPoint` - 悬浮点接口
|
||||
- `TrackLayout` - 轨道布局接口
|
||||
|
||||
**导出**: `core/index.ts`
|
||||
|
||||
```typescript
|
||||
export * from './constants'
|
||||
export * from './types'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Data System (数据系统)
|
||||
|
||||
**目录**: `src/components/data/`
|
||||
|
||||
**职责**: 重导出数据相关类型和函数
|
||||
|
||||
**文件**:
|
||||
|
||||
- `types.ts` - 重导出 `src/types` 中的所有数据类型
|
||||
- `WaveformData`, `WaveformSeries`, `WaveformPoint` 等
|
||||
- `normalizeWaveformData`, `normalizeWaveformSeries` 函数
|
||||
|
||||
**导出**: `data/index.ts`
|
||||
|
||||
```typescript
|
||||
export * from './types'
|
||||
```
|
||||
|
||||
**说明**:
|
||||
此系统作为桥接层,让组件内部可以通过相对路径 `../data/types` 导入类型,而不是 `../../types`,提高了代码的可读性。
|
||||
|
||||
---
|
||||
|
||||
### 3. Rendering System (渲染系统)
|
||||
|
||||
**目录**: `src/components/rendering/`
|
||||
|
||||
**职责**: 渲染波形轨道(网格、坐标轴、波形线)
|
||||
|
||||
**文件**:
|
||||
|
||||
- `WaveformTrack.vue` - 波形轨道组件 (317 行)
|
||||
- 渲染网格(主要/次要刻度)
|
||||
- 渲染 X/Y 坐标轴(使用 D3.js)
|
||||
- 渲染 Y 轴标签
|
||||
- 渲染波形线
|
||||
- 渲染十字线
|
||||
- 渲染帧编号水印
|
||||
- 独立模式下的交互覆盖层
|
||||
|
||||
**导出**: `rendering/index.ts`
|
||||
|
||||
```typescript
|
||||
export { default as WaveformTrack } from './WaveformTrack.vue'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Interaction System (交互系统)
|
||||
|
||||
**目录**: `src/components/interaction/`
|
||||
|
||||
**职责**: 处理用户交互(工具栏、悬浮提示)
|
||||
|
||||
**文件**:
|
||||
|
||||
- `WaveformToolbar.vue` - 工具栏组件 (174 行)
|
||||
- 交互模式切换(缩放、选择、标注等)
|
||||
- 编辑/删除按钮
|
||||
- 快捷键支持
|
||||
|
||||
- `WaveformTooltip.vue` - 悬浮提示组件 (111 行)
|
||||
- 显示数据点信息
|
||||
- 自动位置计算避免溢出
|
||||
- 多系列数据展示
|
||||
|
||||
**导出**: `interaction/index.ts`
|
||||
|
||||
```typescript
|
||||
export { default as WaveformToolbar } from './WaveformToolbar.vue'
|
||||
export { default as WaveformTooltip } from './WaveformTooltip.vue'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Annotation System (标注系统)
|
||||
|
||||
**目录**: `src/components/annotation/`
|
||||
|
||||
**职责**: 管理标注和图形(创建、编辑、删除、渲染)
|
||||
|
||||
**文件**:
|
||||
|
||||
- `WaveformAnnotationLayer.vue` - 标注渲染层 (281 行)
|
||||
- 渲染标注(箭头、文本框)
|
||||
- 渲染图形(垂直线、时间区间)
|
||||
- 渲染区间预览
|
||||
- 交互响应(选择、编辑)
|
||||
|
||||
- `WaveformEditor.vue` - 标注编辑器 (149 行)
|
||||
- 文本输入
|
||||
- 确认/取消操作
|
||||
- 键盘快捷键
|
||||
|
||||
- `markup.ts` - 标注工具函数
|
||||
- `layoutAnnotationBox()` - 标注框布局计算
|
||||
- `resolveAnnotationStyle()` - 标注样式解析
|
||||
- `resolveShapeStyle()` - 图形样式解析
|
||||
- `normalizeRangeShape()` - 区间标准化
|
||||
- 其他辅助函数
|
||||
|
||||
- `types.ts` - 重导出标注相关类型
|
||||
- `WaveformAnnotation`, `WaveformShape` 等
|
||||
|
||||
**导出**: `annotation/index.ts`
|
||||
|
||||
```typescript
|
||||
export { default as WaveformAnnotationLayer } from './WaveformAnnotationLayer.vue'
|
||||
export { default as WaveformEditor } from './WaveformEditor.vue'
|
||||
export * from './markup'
|
||||
export * from './types'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 主组件更新
|
||||
|
||||
### WaveformChart.vue 导入变化
|
||||
|
||||
**重构前**:
|
||||
|
||||
```typescript
|
||||
import WaveformToolbar from './WaveformToolbar.vue'
|
||||
import WaveformEditor from './WaveformEditor.vue'
|
||||
import WaveformTooltip from './WaveformTooltip.vue'
|
||||
import WaveformAnnotationLayer from './WaveformAnnotationLayer.vue'
|
||||
import WaveformTrack from './WaveformTrack.vue'
|
||||
import { normalizeWaveformSeries, type WaveformData, ... } from './waveform'
|
||||
import { layoutAnnotationBox, resolveAnnotationStyle, ... } from './waveform-markup'
|
||||
|
||||
const channelColors = [...]
|
||||
const margin = { top: 18, right: 24, bottom: 52, left: 64 }
|
||||
const minimumHeight = 180
|
||||
```
|
||||
|
||||
**重构后**:
|
||||
|
||||
```typescript
|
||||
// 从各系统导入
|
||||
import { WaveformToolbar, WaveformTooltip } from './interaction'
|
||||
import { WaveformEditor, WaveformAnnotationLayer } from './annotation'
|
||||
import { WaveformTrack } from './rendering'
|
||||
import { normalizeWaveformSeries, type WaveformData, ... } from './data/types'
|
||||
import { layoutAnnotationBox, resolveAnnotationStyle, ... } from './annotation/markup'
|
||||
import { channelColors, margin as chartMargin, minimumHeight as chartMinimumHeight } from './core/constants'
|
||||
|
||||
// 使用导入的常量
|
||||
const margin = chartMargin
|
||||
const minimumHeight = chartMinimumHeight
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌉 向后兼容性
|
||||
|
||||
### 保持旧的导入路径可用
|
||||
|
||||
为了确保不破坏现有代码,保留了原有的导入路径:
|
||||
|
||||
**1. `waveform.ts` - 向后兼容文件**
|
||||
|
||||
```typescript
|
||||
// 重新导出所有类型和函数
|
||||
export type { ... } from '../types'
|
||||
export { normalizeWaveformData, normalizeWaveformSeries } from '../core'
|
||||
```
|
||||
|
||||
**2. `waveform-markup.ts` - 向后兼容文件**
|
||||
|
||||
```typescript
|
||||
// 重新导出标注相关的所有内容
|
||||
export * from './annotation/markup'
|
||||
export type * from './annotation/types'
|
||||
```
|
||||
|
||||
**3. `index.ts` - 更新公共导出**
|
||||
|
||||
```typescript
|
||||
export { default as WaveformChart } from './WaveformChart.vue'
|
||||
|
||||
// 从新路径导出类型(透明给外部使用者)
|
||||
export type { ... } from './data/types'
|
||||
|
||||
// 可选:导出各系统的组件
|
||||
export { WaveformToolbar, WaveformTooltip } from './interaction'
|
||||
export { WaveformAnnotationLayer, WaveformEditor } from './annotation'
|
||||
export { WaveformTrack } from './rendering'
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
**外部使用者(完全兼容)**:
|
||||
|
||||
```typescript
|
||||
// 旧的导入方式仍然有效
|
||||
import { WaveformChart, type WaveformData } from '@/components'
|
||||
|
||||
// 新的导入方式(推荐)
|
||||
import { WaveformChart } from '@/components'
|
||||
import type { WaveformData } from '@/components'
|
||||
|
||||
// 高级用户可以直接使用子系统组件
|
||||
import { WaveformToolbar, WaveformTooltip } from '@/components/interaction'
|
||||
import { WaveformAnnotationLayer } from '@/components/annotation'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 重构收益
|
||||
|
||||
### 1. 可维护性 ⭐⭐⭐⭐⭐
|
||||
|
||||
**按系统定位代码**:
|
||||
|
||||
- 需要修改工具栏?→ 直接到 `interaction/` 目录
|
||||
- 需要修改标注渲染?→ 直接到 `annotation/` 目录
|
||||
- 需要修改轨道渲染?→ 直接到 `rendering/` 目录
|
||||
|
||||
**职责清晰**:
|
||||
|
||||
- 每个系统有独立的目录和明确的职责
|
||||
- 减少了跨系统的耦合
|
||||
- 降低了修改的影响范围
|
||||
|
||||
### 2. 可理解性 ⭐⭐⭐⭐⭐
|
||||
|
||||
**目录即文档**:
|
||||
|
||||
```
|
||||
interaction/ → 我是交互系统,负责用户交互
|
||||
annotation/ → 我是标注系统,负责标注管理
|
||||
rendering/ → 我是渲染系统,负责波形绘制
|
||||
core/ → 我是核心系统,提供共享资源
|
||||
data/ → 我是数据系统,处理数据
|
||||
```
|
||||
|
||||
**新人友好**:
|
||||
|
||||
- 从目录结构就能快速理解系统架构
|
||||
- 相关代码放在一起,容易理解上下文
|
||||
- 不需要在一个大文件中上下滚动
|
||||
|
||||
### 3. 可扩展性 ⭐⭐⭐⭐⭐
|
||||
|
||||
**添加新功能**:
|
||||
|
||||
- 添加新的交互模式?→ 在 `interaction/` 下添加
|
||||
- 添加新的标注类型?→ 在 `annotation/` 下添加
|
||||
- 添加新的渲染效果?→ 在 `rendering/` 下添加
|
||||
|
||||
**替换实现**:
|
||||
|
||||
- 可以替换整个系统而不影响其他部分
|
||||
- 例如:用 Canvas 替换 SVG 渲染,只需修改 `rendering/` 目录
|
||||
|
||||
**插件化潜力**:
|
||||
|
||||
- 各系统可以作为独立插件使用
|
||||
- 方便构建自定义版本(例如:只要渲染,不要标注)
|
||||
|
||||
### 4. 可测试性 ⭐⭐⭐⭐⭐
|
||||
|
||||
**独立测试**:
|
||||
|
||||
```typescript
|
||||
// 可以单独测试各系统的组件
|
||||
describe('WaveformToolbar', () => { ... })
|
||||
describe('WaveformAnnotationLayer', () => { ... })
|
||||
describe('WaveformTrack', () => { ... })
|
||||
```
|
||||
|
||||
**未来可以添加**:
|
||||
|
||||
- `interaction/useInteraction.test.ts` - 测试交互逻辑
|
||||
- `annotation/useAnnotation.test.ts` - 测试标注管理
|
||||
- `rendering/WaveformTrack.test.ts` - 测试轨道渲染
|
||||
|
||||
---
|
||||
|
||||
## 📈 代码统计
|
||||
|
||||
| 指标 | 数值 |
|
||||
| ---------------------- | -------------------------------------- |
|
||||
| **系统数量** | 5 个 |
|
||||
| **Core System** | 3 个文件 |
|
||||
| **Data System** | 2 个文件 |
|
||||
| **Rendering System** | 2 个文件 (1 组件) |
|
||||
| **Interaction System** | 3 个文件 (2 组件) |
|
||||
| **Annotation System** | 5 个文件 (2 组件) |
|
||||
| **向后兼容文件** | 2 个 (waveform.ts, waveform-markup.ts) |
|
||||
| **总文件数** | 17 个 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证结果
|
||||
|
||||
### 所有检查通过
|
||||
|
||||
```bash
|
||||
✅ TypeScript 类型检查通过
|
||||
✅ ESLint 代码规范通过
|
||||
✅ 所有单元测试通过 (24/24)
|
||||
✅ 向后兼容性验证通过
|
||||
```
|
||||
|
||||
### 测试详情
|
||||
|
||||
```
|
||||
Test Files 1 passed (1)
|
||||
Tests 24 passed (24)
|
||||
Duration 1.74s
|
||||
```
|
||||
|
||||
所有现有测试无需修改即可通过,证明重构保持了完全的向后兼容性。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 后续优化建议
|
||||
|
||||
### 1. 提取组合式函数 ⭐⭐⭐⭐⭐
|
||||
|
||||
将主组件中的逻辑提取为组合式函数:
|
||||
|
||||
```typescript
|
||||
// zoom/useZoom.ts
|
||||
export function useZoom(options: ZoomOptions) {
|
||||
const sharedTransform = shallowRef<ZoomTransform>(zoomIdentity)
|
||||
const independentTransforms = shallowRef<ZoomTransform[]>([])
|
||||
// ...
|
||||
return { sharedTransform, independentTransforms, ... }
|
||||
}
|
||||
|
||||
// interaction/useHover.ts
|
||||
export function useHover(options: HoverOptions) {
|
||||
const hoveredSeriesPoints = ref<HoveredSeriesPoint[]>([])
|
||||
const hoverPosition = ref({ x: 0, y: 0 })
|
||||
// ...
|
||||
return { hoveredSeriesPoints, hoverPosition, ... }
|
||||
}
|
||||
|
||||
// annotation/useAnnotation.ts
|
||||
export function useAnnotation(options: AnnotationOptions) {
|
||||
const selection = ref<WaveformMarkupSelection>(null)
|
||||
const editingDraft = ref<EditingDraft | null>(null)
|
||||
// ...
|
||||
return { selection, editingDraft, ... }
|
||||
}
|
||||
```
|
||||
|
||||
然后在主组件中使用:
|
||||
|
||||
```typescript
|
||||
// WaveformChart.vue
|
||||
const { sharedTransform, independentTransforms, ... } = useZoom(...)
|
||||
const { hoveredSeriesPoints, hoverPosition, ... } = useHover(...)
|
||||
const { selection, editingDraft, ... } = useAnnotation(...)
|
||||
```
|
||||
|
||||
**收益**:
|
||||
|
||||
- 逻辑更清晰,职责更单一
|
||||
- 易于测试(不需要挂载组件)
|
||||
- 可复用在其他组件中
|
||||
|
||||
### 2. 添加系统级文档 ⭐⭐⭐⭐
|
||||
|
||||
为每个系统添加 README.md:
|
||||
|
||||
```
|
||||
interaction/
|
||||
├── README.md # 交互系统说明
|
||||
├── WaveformToolbar.vue
|
||||
├── WaveformTooltip.vue
|
||||
└── index.ts
|
||||
|
||||
annotation/
|
||||
├── README.md # 标注系统说明
|
||||
├── WaveformAnnotationLayer.vue
|
||||
├── WaveformEditor.vue
|
||||
├── markup.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### 3. 创建系统级测试 ⭐⭐⭐⭐
|
||||
|
||||
```
|
||||
interaction/
|
||||
├── WaveformToolbar.test.ts
|
||||
├── WaveformTooltip.test.ts
|
||||
└── useInteraction.test.ts
|
||||
|
||||
annotation/
|
||||
├── WaveformAnnotationLayer.test.ts
|
||||
├── WaveformEditor.test.ts
|
||||
├── markup.test.ts
|
||||
└── useAnnotation.test.ts
|
||||
```
|
||||
|
||||
### 4. 进一步拆分大组件 ⭐⭐⭐
|
||||
|
||||
**WaveformTrack.vue** (317 行) 可以拆分为:
|
||||
|
||||
```
|
||||
rendering/
|
||||
├── WaveformTrack.vue # 轨道容器
|
||||
├── Grid.vue # 网格组件
|
||||
├── Axis.vue # 坐标轴组件
|
||||
├── WaveformLine.vue # 波形线组件
|
||||
└── Crosshair.vue # 十字线组件
|
||||
```
|
||||
|
||||
**WaveformAnnotationLayer.vue** (281 行) 可以拆分为:
|
||||
|
||||
```
|
||||
annotation/
|
||||
├── WaveformAnnotationLayer.vue # 标注层容器
|
||||
├── AnnotationItem.vue # 单个标注
|
||||
├── ShapeItem.vue # 单个图形
|
||||
└── RangePreview.vue # 区间预览
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 重构对比
|
||||
|
||||
### 重构前
|
||||
|
||||
```
|
||||
components/
|
||||
├── [7 个组件文件平铺]
|
||||
├── [2 个 TS 文件平铺]
|
||||
└── index.ts
|
||||
|
||||
❌ 文件平铺,职责不清晰
|
||||
❌ 难以定位相关代码
|
||||
❌ 新人需要猜测文件关系
|
||||
```
|
||||
|
||||
### 重构后
|
||||
|
||||
```
|
||||
components/
|
||||
├── core/ # 核心系统
|
||||
├── data/ # 数据系统
|
||||
├── rendering/ # 渲染系统
|
||||
├── interaction/ # 交互系统
|
||||
├── annotation/ # 标注系统
|
||||
└── index.ts # 统一导出
|
||||
|
||||
✅ 按系统组织,职责清晰
|
||||
✅ 易于定位相关代码
|
||||
✅ 从目录结构就能理解架构
|
||||
✅ 保持向后兼容
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 迁移指南
|
||||
|
||||
### 对外部使用者
|
||||
|
||||
**无需任何修改!**
|
||||
|
||||
所有原有的导入方式都保持兼容:
|
||||
|
||||
```typescript
|
||||
// ✅ 原有代码继续工作
|
||||
import { WaveformChart, type WaveformData } from '@/components'
|
||||
```
|
||||
|
||||
### 对内部开发者
|
||||
|
||||
**推荐使用新的导入路径**:
|
||||
|
||||
```typescript
|
||||
// ✅ 新的推荐方式(更清晰)
|
||||
import { WaveformToolbar } from './interaction'
|
||||
import { WaveformAnnotationLayer } from './annotation'
|
||||
import { WaveformTrack } from './rendering'
|
||||
import type { WaveformData } from './data/types'
|
||||
import { channelColors } from './core/constants'
|
||||
```
|
||||
|
||||
### 添加新功能时
|
||||
|
||||
**遵循系统划分原则**:
|
||||
|
||||
1. 确定功能属于哪个系统
|
||||
2. 在对应系统目录下创建文件
|
||||
3. 在系统的 `index.ts` 中导出
|
||||
4. 在主组件中导入使用
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
### 完成情况
|
||||
|
||||
✅ **系统拆分成功完成**
|
||||
|
||||
- 创建 5 个功能系统目录
|
||||
- 移动 5 个组件到对应系统
|
||||
- 提取共享常量和类型
|
||||
- 保持完全向后兼容
|
||||
- 所有测试通过
|
||||
|
||||
### 核心价值
|
||||
|
||||
| 维度 | 改善 |
|
||||
| -------- | ----------------------------- |
|
||||
| 可维护性 | ✅ 按系统组织,易于定位和修改 |
|
||||
| 可理解性 | ✅ 目录即文档,架构清晰 |
|
||||
| 可扩展性 | ✅ 易于添加新功能和替换实现 |
|
||||
| 可测试性 | ✅ 系统独立,支持单独测试 |
|
||||
| 向后兼容 | ✅ 完全兼容现有代码 |
|
||||
|
||||
### 项目里程碑
|
||||
|
||||
```
|
||||
2026-07-17: 工具栏和编辑器组件拆分完成
|
||||
2026-07-18: Tooltip、Track、AnnotationLayer 拆分完成
|
||||
2026-07-18: 按系统重新组织完成 ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**完成时间**: 2026-07-18
|
||||
**影响范围**: 重组 `src/components/` 目录结构
|
||||
**破坏性变更**: 无
|
||||
**向后兼容**: ✅ 完全兼容
|
||||
**测试结果**: ✅ 24/24 通过
|
||||
**重构方式**: 渐进式、非破坏性
|
||||
@@ -1,580 +0,0 @@
|
||||
# 工具栏和编辑器组件拆分报告
|
||||
|
||||
## 🎯 任务概述
|
||||
|
||||
将 `WaveformChart.vue` 中的工具栏和编辑器代码抽取为独立的可复用组件,提高代码的可维护性和可测试性。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成的工作
|
||||
|
||||
### 1. 新建 `WaveformToolbar.vue` 组件
|
||||
|
||||
**文件**: `src/components/WaveformToolbar.vue` (174 行)
|
||||
|
||||
**功能**:
|
||||
|
||||
- 交互模式切换(缩放、选择、标注)
|
||||
- 标注工具按钮(文字标注、垂直线、时间区间)
|
||||
- 编辑和删除操作按钮
|
||||
|
||||
**Props**:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
/** 当前激活的交互模式 */
|
||||
interactionMode: WaveformInteractionMode
|
||||
/** 是否可以编辑选中项 */
|
||||
canEditSelection: boolean
|
||||
}
|
||||
```
|
||||
|
||||
**Emits**:
|
||||
|
||||
```typescript
|
||||
interface Emits {
|
||||
/** 交互模式变更 */
|
||||
(e: 'update:interaction-mode', mode: WaveformInteractionMode): void
|
||||
/** 编辑选中项 */
|
||||
(e: 'edit'): void
|
||||
/** 删除选中项 */
|
||||
(e: 'delete'): void
|
||||
}
|
||||
```
|
||||
|
||||
**样式特点**:
|
||||
|
||||
- 绝对定位在图表右上角
|
||||
- 半透明白色背景,带圆角和阴影
|
||||
- 按钮 hover 和 active 状态
|
||||
- 禁用状态处理
|
||||
- 工具栏分隔线
|
||||
|
||||
---
|
||||
|
||||
### 2. 新建 `WaveformEditor.vue` 组件
|
||||
|
||||
**文件**: `src/components/WaveformEditor.vue` (143 行)
|
||||
|
||||
**功能**:
|
||||
|
||||
- 多行文本输入
|
||||
- 自动 focus 和 select
|
||||
- 键盘快捷键支持(Ctrl+Enter 确认,Escape 取消)
|
||||
- 确认和取消操作
|
||||
|
||||
**Props**:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
/** 编辑模式类型 */
|
||||
kind: 'annotation' | 'shape'
|
||||
/** 初始文本 */
|
||||
initialText: string
|
||||
/** 编辑器位置样式 */
|
||||
style: CSSProperties
|
||||
}
|
||||
```
|
||||
|
||||
**Emits**:
|
||||
|
||||
```typescript
|
||||
interface Emits {
|
||||
/** 确认编辑 */
|
||||
(e: 'confirm', text: string): void
|
||||
/** 取消编辑 */
|
||||
(e: 'cancel'): void
|
||||
}
|
||||
```
|
||||
|
||||
**交互特性**:
|
||||
|
||||
- 自动聚焦和全选文本
|
||||
- `Ctrl + Enter` 快速确认
|
||||
- `Escape` 快速取消
|
||||
- 500 字符限制
|
||||
- 3 行文本框,可调整高度
|
||||
|
||||
---
|
||||
|
||||
### 3. 重构 `WaveformChart.vue` 主组件
|
||||
|
||||
**删除内容** (~110 行):
|
||||
|
||||
- ✅ 工具栏模板代码 (~70 行)
|
||||
- ✅ 编辑器模板代码 (~20 行)
|
||||
- ✅ 工具栏样式 (~50 行)
|
||||
- ✅ 编辑器样式 (~40 行)
|
||||
- ✅ `editorInput` ref
|
||||
- ✅ `handleEditorKeydown` 函数
|
||||
- ✅ `openEditor` 中的 focus/select 代码
|
||||
|
||||
**添加内容** (~20 行):
|
||||
|
||||
- ✅ 导入 `WaveformToolbar` 和 `WaveformEditor`
|
||||
- ✅ 简洁的组件使用语法
|
||||
- ✅ 更新 `confirmEditing` 接收文本参数
|
||||
|
||||
**模板对比**:
|
||||
|
||||
**重构前** (70 行):
|
||||
|
||||
```vue
|
||||
<div v-if="showAnnotationToolbar" class="waveform-chart__toolbar" ...>
|
||||
<button type="button" :class="{ 'is-active': ... }" @click="...">
|
||||
<ZoomIn :size="16" />
|
||||
</button>
|
||||
<button type="button" :class="{ 'is-active': ... }" @click="...">
|
||||
<MousePointer2 :size="16" />
|
||||
</button>
|
||||
<!-- ... 8 个按钮 + 分隔线 -->
|
||||
</div>
|
||||
|
||||
<div v-if="editingDraft" class="waveform-chart__editor" :style="...">
|
||||
<textarea ref="editorInput" v-model="editingText" ... @keydown="..." />
|
||||
<div class="waveform-chart__editor-actions">
|
||||
<button type="button" ... @click="cancelEditing">
|
||||
<X :size="15" />
|
||||
</button>
|
||||
<button type="button" class="is-primary" ... @click="confirmEditing">
|
||||
<Check :size="15" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**重构后** (16 行):
|
||||
|
||||
```vue
|
||||
<!-- 工具栏 -->
|
||||
<WaveformToolbar
|
||||
v-if="showAnnotationToolbar"
|
||||
:interaction-mode="activeInteractionMode"
|
||||
:can-edit-selection="canEditSelection"
|
||||
@update:interaction-mode="setInteractionMode"
|
||||
@edit="editSelection"
|
||||
@delete="deleteSelection"
|
||||
/>
|
||||
|
||||
<!-- 编辑器 -->
|
||||
<WaveformEditor
|
||||
v-if="editingDraft"
|
||||
:kind="editingDraft.kind"
|
||||
:initial-text="editingText"
|
||||
:style="editorStyle"
|
||||
@confirm="confirmEditing"
|
||||
@cancel="cancelEditing"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 更新测试文件
|
||||
|
||||
**文件**: `src/components/WaveformChart.test.ts`
|
||||
|
||||
**修改内容**:
|
||||
|
||||
- ✅ 更新类名 `.waveform-chart__editor` → `.waveform-editor`
|
||||
- ✅ 更新 aria-label `确认标注` → `确认`
|
||||
- ✅ 所有 4 处测试用例更新完成
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
| 指标 | 数值 |
|
||||
| ------------------- | ------------------- |
|
||||
| **新增组件** | 2 个 |
|
||||
| **WaveformToolbar** | 174 行 |
|
||||
| **WaveformEditor** | 143 行 |
|
||||
| **主组件减少** | ~90 行 |
|
||||
| **总代码量** | +227 行(含新组件) |
|
||||
| **主组件行数** | 1913 → ~1823 行 |
|
||||
|
||||
**实际效果**:
|
||||
|
||||
- 主组件复杂度降低 **4.7%**
|
||||
- 工具栏和编辑器逻辑完全独立
|
||||
- 可复用性大幅提升
|
||||
|
||||
---
|
||||
|
||||
## 🎨 架构改进
|
||||
|
||||
### 组件依赖关系
|
||||
|
||||
```
|
||||
WaveformChart.vue (主组件)
|
||||
├── WaveformToolbar.vue (工具栏)
|
||||
│ ├── Props: interactionMode, canEditSelection
|
||||
│ └── Emits: update:interaction-mode, edit, delete
|
||||
│
|
||||
└── WaveformEditor.vue (编辑器)
|
||||
├── Props: kind, initialText, style
|
||||
└── Emits: confirm, cancel
|
||||
```
|
||||
|
||||
### 职责划分
|
||||
|
||||
#### WaveformChart (主组件)
|
||||
|
||||
- 数据管理和状态协调
|
||||
- 渲染波形图、坐标轴、网格
|
||||
- 处理交互事件(缩放、悬浮、选择)
|
||||
- 标注和图形的增删改逻辑
|
||||
|
||||
#### WaveformToolbar (工具栏)
|
||||
|
||||
- 交互模式切换 UI
|
||||
- 按钮状态管理
|
||||
- 视觉样式(hover、active、disabled)
|
||||
|
||||
#### WaveformEditor (编辑器)
|
||||
|
||||
- 文本输入 UI
|
||||
- 键盘快捷键
|
||||
- 自动聚焦
|
||||
- 输入验证(字符限制)
|
||||
|
||||
---
|
||||
|
||||
## 💡 设计亮点
|
||||
|
||||
### 1. Props 最小化
|
||||
|
||||
工具栏和编辑器只接收必要的 props,避免过度耦合:
|
||||
|
||||
```typescript
|
||||
// ✅ 好的设计
|
||||
<WaveformToolbar :interaction-mode="..." :can-edit-selection="..." />
|
||||
|
||||
// ❌ 避免的设计
|
||||
<WaveformToolbar :annotations="..." :shapes="..." :selection="..." />
|
||||
```
|
||||
|
||||
### 2. 事件向上传递
|
||||
|
||||
子组件不直接修改状态,通过事件通知父组件:
|
||||
|
||||
```typescript
|
||||
// 工具栏只负责通知模式变更
|
||||
emit('update:interaction-mode', mode)
|
||||
|
||||
// 编辑器只负责传递文本
|
||||
emit('confirm', text.trim())
|
||||
```
|
||||
|
||||
### 3. 样式隔离
|
||||
|
||||
使用 `<style scoped>` 确保样式不污染全局:
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
.waveform-toolbar { ... }
|
||||
.waveform-editor { ... }
|
||||
</style>
|
||||
```
|
||||
|
||||
### 4. 可复用性
|
||||
|
||||
组件可以在其他场景中使用:
|
||||
|
||||
```vue
|
||||
<!-- 在其他图表组件中复用工具栏 -->
|
||||
<WaveformToolbar
|
||||
:interaction-mode="currentMode"
|
||||
:can-edit-selection="hasSelection"
|
||||
@update:interaction-mode="handleModeChange"
|
||||
/>
|
||||
|
||||
<!-- 在其他地方复用编辑器 -->
|
||||
<WaveformEditor
|
||||
kind="annotation"
|
||||
initial-text="默认文本"
|
||||
:style="{ top: '100px', left: '200px' }"
|
||||
@confirm="handleSave"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### 测试结果
|
||||
|
||||
```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
|
||||
<!-- 在时间序列图组件中使用 -->
|
||||
<TimeSeriesChart>
|
||||
<WaveformToolbar ... />
|
||||
</TimeSeriesChart>
|
||||
|
||||
<!-- 在频谱图组件中使用 -->
|
||||
<SpectrumChart>
|
||||
<WaveformToolbar ... />
|
||||
</SpectrumChart>
|
||||
|
||||
<!-- 在任何需要文本输入的地方使用编辑器 -->
|
||||
<WaveformEditor kind="annotation" initial-text="初始内容" @confirm="handleConfirm" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
<WaveformToolbar
|
||||
:interaction-mode="currentMode"
|
||||
:can-edit-selection="hasSelection"
|
||||
@update:interaction-mode="handleModeChange"
|
||||
@edit="handleEdit"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### WaveformEditor API
|
||||
|
||||
#### Props
|
||||
|
||||
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| ------------- | ------------------------- | ---- | ------ | -------------- |
|
||||
| `kind` | `'annotation' \| 'shape'` | ✅ | - | 编辑器类型 |
|
||||
| `initialText` | `string` | ✅ | - | 初始文本内容 |
|
||||
| `style` | `CSSProperties` | ✅ | - | 编辑器位置样式 |
|
||||
|
||||
#### Events
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
| --------- | -------------- | ---------------------------------- |
|
||||
| `confirm` | `text: string` | 确认编辑时触发,返回 trim 后的文本 |
|
||||
| `cancel` | - | 取消编辑时触发 |
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```vue
|
||||
<WaveformEditor
|
||||
kind="annotation"
|
||||
initial-text="默认标注文字"
|
||||
:style="{ top: '100px', left: '200px' }"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收清单
|
||||
|
||||
- [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 个组件
|
||||
**破坏性变更**: 无
|
||||
**向后兼容**: ✅ 完全兼容
|
||||
@@ -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
|
||||
<g v-if="resolveYAxisLabel(track.series) && shouldShowYAxisLabel(track.height, track.index)">
|
||||
<!-- 标签背景(提高可读性) -->
|
||||
<rect
|
||||
class="waveform-chart__y-axis-label-bg"
|
||||
:x="-58"
|
||||
:y="track.height / 2 - 40"
|
||||
width="24"
|
||||
height="80"
|
||||
rx="2"
|
||||
/>
|
||||
<!-- Y 轴标签文字 -->
|
||||
<text
|
||||
class="waveform-chart__y-axis-label"
|
||||
:fill="track.series.color"
|
||||
:transform="`translate(-46, ${track.height / 2}) rotate(-90)`"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="central"
|
||||
>
|
||||
{{ resolveYAxisLabel(track.series) }}
|
||||
</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
### 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)`
|
||||
- 使用 `<g>` 包裹标签和背景
|
||||
- 添加标签背景 `<rect class="waveform-chart__y-axis-label-bg">`
|
||||
|
||||
#### 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` 组件
|
||||
**破坏性变更**: 无
|
||||
**向后兼容**: ✅ 完全兼容
|
||||
Reference in New Issue
Block a user