# 组件文档

## 组件架构概览

OFPlayer 的组件系统遵循以下设计原则：

1. **单向数据流** - Props 向下，Events 向上
2. **逻辑与视图分离** - 业务逻辑在 Composables/Stores，组件仅负责展示
3. **组件组合** - 通过插槽和组合实现复用
4. **可访问性** - 使用 ARIA 属性增强无障碍访问

## 页面组件

### PlayerPage.vue

**位置**: `src/pages/PlayerPage.vue`

**路由**: `/`

**职责**: 主播放器页面，是应用的核心页面

**功能**:
- 连接 `ofplayerApp` 与 UI 组件
- 管理沉浸式播放视图的显示
- 协调各面板的数据流

**使用的应用 API**:
```javascript
const {
  libraries, playlists, tracks, currentTrack,
  isPlaying, currentTime, duration, volume,
  selectTrack, togglePlayback, playNext, playPrevious,
  // ...更多
} = useOFPlayerApp()
```

---

### ProductPage.vue

**位置**: `src/pages/ProductPage.vue`

**路由**: `/product`

**职责**: 产品介绍/营销页面

---

### DownloadPage.vue

**位置**: `src/pages/DownloadPage.vue`

**路由**: `/download`

**职责**: 下载页面

---

### PrivacyPage.vue

**位置**: `src/pages/PrivacyPage.vue`

**路由**: `/privacy`

**职责**: 隐私政策页面

## 核心 UI 组件

### PlayerPanel.vue

**位置**: `src/components/PlayerPanel.vue`

**职责**: 中心面板，包含曲目列表、搜索/排序/过滤、播放控制

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `tracks` | `Array` | `[]` | 当前显示的曲目列表 |
| `currentLibrary` | `Object` | `null` | 当前库对象 |
| `libraries` | `Array` | `[]` | 所有库列表 |
| `playlists` | `Array` | `[]` | 所有播放列表 |
| `currentCollection` | `Object` | `null` | 当前集合（播放列表/智能视图） |
| `currentTrackId` | `String` | `null` | 当前播放曲目 ID |
| `currentTrack` | `Object` | `null` | 当前播放曲目对象 |
| `hasAnyTracks` | `Boolean` | `false` | 是否有任何曲目 |
| `isPlaying` | `Boolean` | `false` | 是否正在播放 |
| `currentTime` | `Number` | `0` | 当前播放位置（秒） |
| `duration` | `Number` | `0` | 曲目时长（秒） |
| `volume` | `Number` | `0.8` | 音量（0-1） |
| `searchQuery` | `String` | `''` | 搜索查询 |
| `sortOption` | `String` | `'recent'` | 排序选项 |
| `typeFilter` | `String` | `'all'` | 类型过滤器 |
| `showTechnicalMetadata` | `Boolean` | `true` | 是否显示技术元数据 |

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `select-track` | `trackId: string` | 选择曲目 |
| `toggle-playback` | - | 切换播放状态 |
| `play-previous` | - | 播放上一首 |
| `play-next` | - | 播放下一首 |
| `seek` | `time: number` | 定位到时间点 |
| `set-volume` | `volume: number` | 设置音量 |
| `set-search-query` | `query: string` | 设置搜索查询 |
| `set-sort-option` | `option: string` | 设置排序选项 |
| `set-type-filter` | `filter: string` | 设置类型过滤 |
| `add-track-to-playlist` | `{ trackId, playlistId }` | 添加曲目到播放列表 |
| `remove-track-from-playlist` | `{ trackId, playlistId }` | 从播放列表移除曲目 |
| `delete-track` | `trackId: string` | 删除曲目 |
| `toggle-favorite` | `trackId: string` | 切换收藏状态 |
| `open-immersive-player` | - | 打开沉浸式播放视图 |
| `play-group` | `tracks: Array` | 播放一组曲目 |

**内部功能**:
- 虚拟列表（超过 90 首曲目时自动启用）
- 搜索过滤
- 排序（最近、标题、艺术家、专辑、时长等）
- 类型过滤（按文件格式）
- 曲目操作菜单（添加到播放列表、删除等）
- Inspector 面板（显示曲目详细元数据）

---

### LibraryPanel.vue

**位置**: `src/components/LibraryPanel.vue`

**职责**: 左侧边栏，包含库选择器、播放列表、智能视图、导入、设置

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `libraries` | `Array` | `[]` | 库列表 |
| `playlists` | `Array` | `[]` | 播放列表 |
| `smartCollections` | `Array` | `[]` | 智能集合列表 |
| `activeLibrary` | `String` | `'local'` | 当前活跃库 ID |
| `activeCollection` | `String` | `'all-tracks'` | 当前活跃集合 key |
| `multiSourceGateState` | `String` | `'enabled'` | 多源功能门控状态 |

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `import-files` | `files: File[]` | 导入文件 |
| `set-active-library` | `libraryId: string` | 切换活跃库 |
| `set-active-collection` | `collectionKey: string` | 切换活跃集合 |
| `open-settings` | - | 打开设置 |
| `create-library` | `name: string` | 创建库 |
| `rename-library` | `(id, name)` | 重命名库 |
| `delete-library` | `id: string` | 删除库 |
| `create-playlist` | `name: string` | 创建播放列表 |
| `rename-playlist` | `(id, name)` | 重命名播放列表 |
| `delete-playlist` | `id: string` | 删除播放列表 |
| `open-upgrade-dialog` | - | 打开升级对话框 |

**内部功能**:
- 库列表展示与切换
- 播放列表管理（创建、重命名、删除）
- 智能视图展示
- 文件导入触发
- 外部链接（官网、下载）

---

### SettingsModal.vue

**位置**: `src/components/SettingsModal.vue`

**职责**: 设置对话框

**功能**:
- 主题切换
- 语言切换
- 音量偏好
- 元数据显示选项
- 数据导出/清除
- 遥测同意

---

### DialogModal.vue

**位置**: `src/components/DialogModal.vue`

**职责**: 通用对话框组件

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isOpen` | `Boolean` | `false` | 是否打开 |
| `title` | `String` | `''` | 对话框标题 |
| `message` | `String` | `''` | 对话框消息 |
| `inputLabel` | `String` | `''` | 输入框标签 |
| `inputValue` | `String` | `''` | 输入框值 |
| `showInput` | `Boolean` | `false` | 是否显示输入框 |
| `isDanger` | `Boolean` | `false` | 是否为危险操作 |
| `confirmLabel` | `String` | `'Confirm'` | 确认按钮文本 |
| `cancelLabel` | `String` | `'Cancel'` | 取消按钮文本 |
| `hint` | `String` | `''` | 提示文本 |
| `hintLinkLabel` | `String` | `''` | 提示链接文本 |
| `hintRoute` | `String` | `''` | 提示链接路由 |

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `close` | - | 关闭对话框 |
| `confirm` | `value?: string` | 确认（可能携带输入值） |

---

### MenuDropdown.vue

**位置**: `src/components/MenuDropdown.vue`

**职责**: 下拉菜单组件

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isOpen` | `Boolean` | `false` | 是否打开 |
| `anchorEl` | `HTMLElement` | `null` | 锚点元素（用于定位） |
| `items` | `Array` | `[]` | 菜单项列表 |

**菜单项结构**:
```javascript
{
  key: string,      // 唯一标识
  label: string,    // 显示文本
  disabled?: boolean // 是否禁用
}
```

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `close` | - | 关闭菜单 |
| `select` | `item: Object` | 选择菜单项 |

---

### TrackPlaylistDialog.vue

**位置**: `src/components/TrackPlaylistDialog.vue`

**职责**: 添加曲目到播放列表的对话框

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isOpen` | `Boolean` | `false` | 是否打开 |
| `track` | `Object` | `null` | 要添加的曲目 |
| `libraries` | `Array` | `[]` | 库列表 |
| `playlists` | `Array` | `[]` | 播放列表 |
| `preferredLibraryId` | `String` | `null` | 默认选中的库 |
| `preferredPlaylistId` | `String` | `null` | 默认选中的播放列表 |

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `close` | - | 关闭对话框 |
| `confirm` | `{ trackId, playlistId }` | 确认添加 |

---

### AlbumBrowserPanel.vue

**位置**: `src/components/AlbumBrowserPanel.vue`

**职责**: 专辑/艺术家浏览面板（便当盒网格视图）

**Props 接口**:

| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `browserKind` | `String` | - | 浏览类型（'album' / 'artist'） |
| `groups` | `Array` | `[]` | 分组数据 |
| `currentTrackId` | `String` | `null` | 当前播放曲目 ID |
| `isPlaying` | `Boolean` | `false` | 是否正在播放 |
| `searchQuery` | `String` | `''` | 搜索查询 |

**Events 接口**:

| Event | 参数 | 说明 |
|-------|------|------|
| `select-track` | `track: Object` | 选择曲目 |
| `play-group` | `tracks: Array` | 播放一组曲目 |
| `cover-changed` | - | 封面变化 |

---

### LyricsPanel.vue

**位置**: `src/components/LyricsPanel.vue`

**职责**: 歌词显示面板

---

### ImmersivePlayerView.vue

**位置**: `src/components/ImmersivePlayerView.vue`

**职责**: 沉浸式播放视图（全屏）

---

### LyricsPlayerView.vue

**位置**: `src/components/LyricsPlayerView.vue`

**职责**: 歌词专注播放视图

---

### OnboardingGuide.vue

**位置**: `src/components/OnboardingGuide.vue`

**职责**: 首次运行引导

---

### TelemetryConsentDialog.vue

**位置**: `src/components/TelemetryConsentDialog.vue`

**职责**: 遥测同意对话框

---

### StartupBadge.vue

**位置**: `src/components/StartupBadge.vue`

**职责**: 开发模式启动徽章

## Monetization 组件

### UpgradeDialog.vue

**位置**: `src/components/monetize/UpgradeDialog.vue`

**职责**: 升级对话框（Pro 功能）

---

### ProBadge.vue

**位置**: `src/components/monetize/ProBadge.vue`

**职责**: Pro 徽章标识

---

### PlanCard.vue

**位置**: `src/components/monetize/PlanCard.vue`

**职责**: 套餐卡片

---

### FeatureGateNotice.vue

**位置**: `src/components/monetize/FeatureGateNotice.vue`

**职责**: 功能门控提示

## 组件使用示例

### 基本用法

```vue
<script setup>
import { useOFPlayerApp } from '../app/ofplayerApp'

const {
  tracks, currentTrack, isPlaying,
  selectTrack, togglePlayback
} = useOFPlayerApp()
</script>

<template>
  <PlayerPanel
    :tracks="tracks"
    :current-track="currentTrack"
    :is-playing="isPlaying"
    @select-track="selectTrack"
    @toggle-playback="togglePlayback"
  />
</template>
```

### 创建新组件

```vue
<script setup>
import { computed } from 'vue'
import { useOFPlayerApp } from '../app/ofplayerApp'

const props = defineProps({
  // 定义 Props
})

const emit = defineEmits([
  // 定义 Events
])

const ofplayer = useOFPlayerApp()

// 使用应用状态
const currentTrack = computed(() => ofplayer.currentTrack.value)
</script>

<template>
  <!-- 组件模板 -->
</template>
```
