chendeben 10 månader sedan
förälder
incheckning
5942a1347f

+ 2 - 8
.cursor/rules/project-structure.mdc

@@ -58,13 +58,6 @@ TTMusic是一个基于鸿蒙ArkTS开发的音乐播放器应用,支持本地
 
 ## 关键文件说明
 
-### 主题系统
-项目支持多主题切换,主题配置在 `AppTheme.ets` 中定义:
-- `DefaultColors` - 默认浅色主题
-- `TwilightColors` - 暮色深色主题
-- `ForestColors` - 森林浅色主题
-- `CoralColors` - 珊瑚浅色主题
-- `MidnightColors` - 极夜深色主题
 
 ### 常量配置
 `CommonConstants.ets` 包含应用中使用的所有常量:
@@ -82,4 +75,5 @@ TTMusic是一个基于鸿蒙ArkTS开发的音乐播放器应用,支持本地
 2. 状态管理使用 `@State` 和 `@StorageProp` 装饰器
 3. 遵循ArkTS语法限制,特别是避免使用解构赋值和计算属性名
 4. 使用项目定义的常量而非硬编码值
-5. 遵循项目的命名规范和代码风格
+5. 遵循项目的命名规范和代码风格
+6. 新增的日志都需要带上"heanup"前缀

+ 172 - 0
CLAUDE.md

@@ -0,0 +1,172 @@
+# CLAUDE.md
+always response 中文
+
+本文件为 Claude Code (claude.ai/code) 在此代码库中工作提供指导。
+
+## 项目概述
+
+TTMusic 是基于 OpenHarmony ArkTS 开发的功能丰富的音乐播放器应用,支持本地和网络音频播放,集成了 ijkplayer 进行媒体处理。
+
+## 构建命令
+
+### 依赖管理
+```bash
+# 安装依赖
+ohpm install
+
+# 更新特定依赖
+ohpm update @ohos/ijkplayer
+```
+
+### 运行测试
+当前项目没有正式的单元测试。测试通过手动设备测试和 DevEco Studio 的内置调试工具进行。
+
+## 架构概览
+
+### 模块结构
+```
+TTMusic/
+├── entry/                  # 主应用模块
+│   ├── src/main/ets/
+│   │   ├── pages/         # 应用页面 (SplashIndex, MainIndex 等)
+│   │   ├── view/          # 可复用UI组件 (LocalMusic, TitleBar 等)
+│   │   ├── viewmodel/     # 数据模型和业务逻辑
+│   │   ├── common/        # 工具类、常量和共享代码
+│   │   ├── controller/    # 控制层 (AvSessionController, KnockController)
+│   │   └── dialog/        # 对话框组件
+├── ijkplayer/             # 基于 FFmpeg 的媒体播放器原生模块
+├── lib/                   # 共享库 (歌词解析等)
+└── hvigor/               # 构建配置
+```
+
+### 核心组件
+
+**核心播放器架构:**
+- `LocalMusic.ets` - 主音乐播放器界面和播放控制
+- `IjkMediaPlayer` - 使用 FFmpeg 的原生媒体播放器后端
+- `AvSessionController.ets` - 用于系统集成的音频会话管理
+- `PlayerModel.ets` - 可观察的播放器状态模型
+
+**导航和UI:**
+- `MainIndex.ets` - 主标签导航 (当前已禁用标签,简化版)
+- `SplashIndex.ets` - 应用初始化和加载页面
+- `PlaylistDetailPage.ets` - 播放列表管理和详情视图
+
+**数据管理:**
+- `MediaTable.ets` & `PlaylistTable.ets` - 媒体和播放列表的数据库操作
+- `ConfigManager.ets` - 基于API的远程配置系统
+- `GlobalContext.ets` - 应用级状态管理
+
+### 数据库架构
+使用关系数据库 (RDB) 用于:
+- 媒体文件元数据和索引
+- 播放列表管理
+- 用户偏好设置
+
+### 配置系统
+通过 `ConfigManager.ets` 进行远程配置管理:
+- 从 `https://pay.ss5.xyz/switches/lists` 获取设置
+- 支持 boolean、number、string 和 JSON 类型
+- 与 AppStorage 集成以实现响应式UI更新
+
+## 关键技术模式
+
+### ArkTS 特定注意事项
+- **禁止解构赋值**: 使用传统循环而不是 `for (const [key, value] of Object.entries(obj))`
+- **需要空值安全**: 在对象方法调用前总是检查 null/undefined
+- **禁止计算属性名**: 使用 `obj[key] = value` 而不是 `{[key]: value}`
+- **显式错误类型**: 使用 `catch (e: Error)` 而不是 `catch (e)`
+- **基于Promise的异步**: 数据库操作使用 `.then()/.catch()` 而不是 async/await
+
+### 状态管理
+- `@State` 用于组件本地状态
+- `@StorageProp`/`@StorageLink` 用于 AppStorage 集成
+- `@Observed` 类用于复杂数据模型
+- 通过 `GlobalContext` 单例进行全局状态管理
+
+### 音频播放集成
+```typescript
+// 标准播放器初始化模式
+const player = IjkMediaPlayer.getInstance();
+player.setDataSource(audioUrl);
+player.prepareAsync();
+player.setOnCompletionListener(this.handleCompletion.bind(this));
+```
+
+### 主题系统
+多个内置主题 (默认、暮色、森林、珊瑚、极夜) 支持:
+- 通过 AppStorage 进行动态颜色切换
+- 基于资源的颜色定义 (`$r('app.color.brand')`)
+- 明暗模式支持
+
+## 开发指南
+
+### 文件组织
+- `pages/` 目录中的页面使用 `@Entry` 装饰器
+- `view/` 目录中的可复用组件
+- `viewmodel/` 中的业务逻辑,使用适当的模型类
+- `common/util/` 中按功能组织的工具类
+
+### 代码风格
+- 类名使用 PascalCase (例如 `MediaTable`)
+- 方法名使用 camelCase (例如 `queryByParentPath`)
+- 常量使用 UPPER_SNAKE_CASE (例如 `DB_COLUMNS.FILE_PATH`)
+- 私有属性使用 `_camelCase` 前缀
+
+### 错误处理
+- 在 catch 块中总是使用显式的 Error 类型
+- 使用项目的 Logger 工具记录错误
+- 通过 ToastUtil 显示用户友好的消息
+- 正确处理数据库 Promise 拒绝
+
+### API 集成
+- 使用 NetAxiosUtil 进行 HTTP 请求
+- 通过 ConfigManager 进行远程配置
+- 正确的 JSON 解析和错误处理
+- 在 CommonConstants 中定义的 API 端点
+
+## 常见开发任务
+
+### 添加新音乐格式
+1. 更新 `CommonConstants.REAL_MUSIC_FORMAT` 数组
+2. 确认 ijkplayer 支持该格式
+3. 使用实际媒体文件测试
+
+### 实现新主题
+1. 在 `AppTheme.ets` 中添加颜色定义
+2. 在设置中更新主题选择UI
+3. 在所有使用主题颜色的组件中测试
+
+### 数据库模式更新
+1. 在相应的 Table 类中修改表创建
+2. 在 `onCreate` 回调中添加版本升级逻辑
+3. 处理现有数据的迁移
+
+### 添加新对话框组件
+1. 在 `dialog/` 目录中创建,遵循现有模式
+2. 使用 `@pura/harmony-dialog` 保持样式一致性
+3. 与父页面状态管理集成
+
+## 重要依赖
+
+- `@ohos/ijkplayer` - 媒体播放引擎 (基于FFmpeg)
+- `@pura/harmony-utils` - 工具函数和助手
+- `@pura/harmony-dialog` - 对话框管理系统
+- `@seagazer/cclyric` - 歌词解析和显示
+- `@chinalike/popup` - 弹窗和模态框组件
+
+## 测试和调试
+
+- 使用 DevEco Studio 的内置调试工具
+- 使用 common/util/Logger.ets 中的 `Logger.info()`、`Logger.error()` 记录日志
+- 在实际设备上测试音频功能
+- 通过日志输出检查数据库操作
+
+## 平台特定注意事项
+
+- 需要 OpenHarmony API 12 (5.0.0(12)) 或更高版本
+- 支持手机、平板和 2in1 设备
+- 通过 `audioPlayback` 后台模式启用后台音频播放
+- 在 module.json5 中配置音频/视频文件类型的文件关联
+- 日志都需要加上一个前缀:“heanup”
+- 禁止使用unknown和any类型

+ 0 - 3
entry/src/main/ets/MyAbilityStage.ets

@@ -23,9 +23,6 @@ export default class MyAbilityStage extends AbilityStage {
       const themeMode = AppStorage.get<number>('themeMode') ?? 0;
       if (themeMode === 0) {
         AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
-        hilog.info(0x0000, 'Heanup', '新colorMode = %{public}s', JSON.stringify(newConfig.colorMode) ?? '');
-      } else {
-        hilog.info(0x0000, 'Heanup', 'themeMode != 0, skip updating colorMode');
       }
     } catch (err) {
       hilog.error(0x0000, 'Heanup', 'Failed to update color mode: %{public}s', err.message);

+ 100 - 0
entry/src/main/ets/common/util/MediaTable.ets

@@ -771,6 +771,106 @@ export default class MediaTable {
     });
   }
 
+  /**
+   * Query a VideoItem by file path
+   * @param filePath The file path to query
+   * @returns Promise that resolves with the VideoItem or null if not found
+   */
+  public queryVideoByFilePath(filePath: string): Promise<VideoItem | null> {
+    return new Promise((resolve, reject) => {
+      try {
+        // Create query predicates
+        const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
+        predicates.equalTo(DB_COLUMNS.FILE_PATH, filePath);
+
+        // Execute the query
+        this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
+          try {
+            if (resultSet.rowCount === 0) {
+              Logger.info(RdbUtils.RDB_TAG, `No record found for filePath: ${filePath}`);
+              resolve(null);
+              return;
+            }
+
+            // Get the first row
+            if (resultSet.goToFirstRow()) {
+              const videoItem = this.buildVideoItem(resultSet);
+              resolve(videoItem);
+            } else {
+              resolve(null);
+            }
+          } catch (err) {
+            Logger.error(RdbUtils.RDB_TAG, `Error querying video by filePath: ${err.message}`);
+            reject(err);
+          } finally {
+            // Ensure the result set is closed
+            resultSet.close();
+          }
+        });
+      } catch (err) {
+        Logger.error(RdbUtils.RDB_TAG, `Error creating query: ${err.message}`);
+        reject(err);
+      }
+    });
+  }
+
+  /**
+   * Query all video items
+   * @returns Promise that resolves with an array of VideoItem objects
+   */
+  public queryAllVideos(): Promise<VideoItem[]> {
+    return new Promise((resolve, reject) => {
+      try {
+        Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 开始查询所有音乐文件');
+        
+        // Create query predicates for all records
+        const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
+        // Only query music files (type = 0)
+        predicates.equalTo(DB_COLUMNS.TYPE, 0);
+        // Order by name
+        predicates.orderByAsc(DB_COLUMNS.NAME);
+        
+        Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 创建查询条件完成');
+
+        // Execute the query
+        this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
+          try {
+            Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 查询回调执行');
+            
+            if (resultSet.rowCount === 0) {
+              Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 没有找到音乐文件');
+              resolve([]);
+              return;
+            }
+
+            Logger.info(RdbUtils.RDB_TAG, `queryAllVideos: 找到 ${resultSet.rowCount} 条记录`);
+            
+            const items: VideoItem[] = [];
+            
+            // Go to first row
+            if (resultSet.goToFirstRow()) {
+              do {
+                const item = this.buildVideoItem(resultSet);
+                items.push(item);
+              } while (resultSet.goToNextRow());
+            }
+            
+            Logger.info(RdbUtils.RDB_TAG, `queryAllVideos: 解析完成,返回 ${items.length} 个项目`);
+            resolve(items);
+          } catch (err) {
+            Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 解析结果集出错: ${err.message}`);
+            Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 错误堆栈: ${err.stack || '无堆栈信息'}`);
+            reject(err);
+          }
+          // 注意:不在这里关闭 resultSet,因为 RdbUtils.query 会在回调函数执行后关闭它
+        });
+      } catch (err) {
+        Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 创建查询出错: ${err.message}`);
+        Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 错误堆栈: ${err.stack || '无堆栈信息'}`);
+        reject(err);
+      }
+    });
+  }
 
 }
 

+ 109 - 6
entry/src/main/ets/common/util/PlaylistTable.ets

@@ -10,10 +10,18 @@ import { Playlist, PlaylistSong } from '../../viewmodel/Playlist';
 export default class PlaylistTable {
   private context: Context;
   private rdbStore: relationalStore.RdbStore | null = null;
+  private initPromise: Promise<void>;
 
   constructor(context: Context) {
     this.context = context;
-    this.initRdbStore();
+    this.initPromise = this.initRdbStore();
+  }
+
+  /**
+   * 确保数据库已初始化
+   */
+  private async ensureInitialized(): Promise<void> {
+    await this.initPromise;
   }
 
   /**
@@ -69,7 +77,10 @@ export default class PlaylistTable {
    * 创建歌单
    */
   async createPlaylist(name: string, description?: string, coverPath?: string): Promise<boolean> {
+    await this.ensureInitialized();
+
     if (!this.rdbStore) {
+      Logger.error('PlaylistTable', '数据库未初始化');
       return false;
     }
 
@@ -78,7 +89,7 @@ export default class PlaylistTable {
       const now = new Date().toISOString();
       const sql = 'INSERT INTO playlistTable (id, name, coverPath, description, createTime, updateTime, songCount, sortOrder) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
       const params = [id, name, coverPath || null, description || null, now, now, 0, 0];
-      
+
       await this.rdbStore.executeSql(sql, params);
       Logger.info('PlaylistTable', `歌单创建成功: ${name}`);
       return true;
@@ -158,12 +169,16 @@ export default class PlaylistTable {
    * 查询所有歌单
    */
   async queryAllPlaylists(): Promise<Playlist[]> {
+    await this.ensureInitialized();
+
     if (!this.rdbStore) {
+      Logger.error('PlaylistTable', '数据库未初始化');
       return [];
     }
 
     try {
       const sql = 'SELECT * FROM playlistTable ORDER BY sortOrder ASC, createTime DESC';
+      Logger.info('PlaylistTable', '开始查询所有歌单');
       const resultSet = await this.rdbStore.querySql(sql);
       
       const playlists: Playlist[] = [];
@@ -184,6 +199,7 @@ export default class PlaylistTable {
       }
       
       resultSet.close();
+      Logger.info('PlaylistTable', `查询到 ${playlists.length} 个歌单`);
       return playlists;
     } catch (error) {
       Logger.error('PlaylistTable', `查询歌单失败: ${error.message}`);
@@ -230,11 +246,16 @@ export default class PlaylistTable {
    * 添加歌曲到歌单
    */
   async addSongToPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
+    await this.ensureInitialized();
+
     if (!this.rdbStore) {
+      Logger.error('PlaylistTable', '数据库未初始化');
       return false;
     }
 
     try {
+      Logger.info('PlaylistTable', `开始添加歌曲到歌单: playlistId=${playlistId}, songFilePath=${songFilePath}`);
+
       // 检查歌曲是否已在歌单中
       const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
       if (isInPlaylist) {
@@ -246,12 +267,15 @@ export default class PlaylistTable {
       const addTime = new Date().toISOString();
       const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
       const params = [id, playlistId, songFilePath, addTime, 0];
-      
+
+      Logger.info('PlaylistTable', `执行SQL插入: ${sql}`);
       await this.rdbStore.executeSql(sql, params);
-      
+      Logger.info('PlaylistTable', '歌曲插入成功');
+
       // 更新歌单歌曲数量
+      Logger.info('PlaylistTable', '开始更新歌单歌曲数量');
       await this.updatePlaylistSongCount(playlistId);
-      
+
       Logger.info('PlaylistTable', `歌曲添加到歌单成功: ${songFilePath}`);
       return true;
     } catch (error) {
@@ -260,6 +284,72 @@ export default class PlaylistTable {
     }
   }
 
+  /**
+   * 批量添加歌曲到歌单
+   */
+  async addSongsToPlaylist(playlistId: string, songFilePaths: string[]): Promise<boolean> {
+    await this.ensureInitialized();
+
+    if (!this.rdbStore) {
+      Logger.error('PlaylistTable', '数据库未初始化');
+      return false;
+    }
+
+    if (!songFilePaths || songFilePaths.length === 0) {
+      Logger.error('PlaylistTable', '歌曲路径列表为空');
+      return false;
+    }
+
+    try {
+      Logger.info('PlaylistTable', `开始批量添加歌曲到歌单: playlistId=${playlistId}, 歌曲数量=${songFilePaths.length}`);
+
+      // 开始事务
+      await this.rdbStore.executeSql('BEGIN TRANSACTION');
+
+      let successCount = 0;
+      for (const songFilePath of songFilePaths) {
+        try {
+          // 检查歌曲是否已在歌单中
+          const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
+          if (isInPlaylist) {
+            Logger.info('PlaylistTable', `歌曲已在歌单中,跳过: ${songFilePath}`);
+            continue;
+          }
+
+          const id = this.generateId();
+          const addTime = new Date().toISOString();
+          const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
+          const params = [id, playlistId, songFilePath, addTime, 0];
+
+          await this.rdbStore.executeSql(sql, params);
+          successCount++;
+        } catch (error) {
+          Logger.error('PlaylistTable', `添加歌曲失败: ${songFilePath}, 错误: ${error.message}`);
+        }
+      }
+
+      // 提交事务
+      await this.rdbStore.executeSql('COMMIT');
+
+      // 更新歌单歌曲数量
+      Logger.info('PlaylistTable', '开始更新歌单歌曲数量');
+      await this.updatePlaylistSongCount(playlistId);
+
+      Logger.info('PlaylistTable', `批量添加歌曲完成,成功添加 ${successCount} 首,共 ${songFilePaths.length} 首`);
+      return successCount > 0;
+    } catch (error) {
+      // 回滚事务
+      try {
+        await this.rdbStore.executeSql('ROLLBACK');
+      } catch (rollbackError) {
+        Logger.error('PlaylistTable', `事务回滚失败: ${rollbackError.message}`);
+      }
+      
+      Logger.error('PlaylistTable', `批量添加歌曲到歌单失败: ${error.message}`);
+      return false;
+    }
+  }
+
   /**
    * 从歌单中移除歌曲
    */
@@ -287,16 +377,24 @@ export default class PlaylistTable {
    * 查询歌单中的歌曲
    */
   async queryPlaylistSongs(playlistId: string): Promise<PlaylistSong[]> {
+    await this.ensureInitialized();
+
     if (!this.rdbStore) {
+      Logger.error('PlaylistTable', '数据库未初始化');
       return [];
     }
 
     try {
       const sql = 'SELECT * FROM playlistSongTable WHERE playlistId = ? ORDER BY sortOrder ASC, addTime ASC';
+      Logger.info('PlaylistTable', `queryPlaylistSongs: 开始查询歌单歌曲, playlistId=${playlistId}`);
+      Logger.info('PlaylistTable', `queryPlaylistSongs: SQL=${sql}`);
+      
       const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
+      Logger.info('PlaylistTable', `queryPlaylistSongs: 查询执行完成, rowCount=${resultSet.rowCount}`);
       
       const songs: PlaylistSong[] = [];
       if (resultSet.goToFirstRow()) {
+        Logger.info('PlaylistTable', 'queryPlaylistSongs: 移动到第一行成功');
         do {
           const song = new PlaylistSong(
             resultSet.getString(resultSet.getColumnIndex('id')),
@@ -306,13 +404,18 @@ export default class PlaylistTable {
             resultSet.getLong(resultSet.getColumnIndex('sortOrder'))
           );
           songs.push(song);
+          Logger.info('PlaylistTable', `queryPlaylistSongs: 添加歌曲, songFilePath=${song.songFilePath}`);
         } while (resultSet.goToNextRow());
+      } else {
+        Logger.info('PlaylistTable', 'queryPlaylistSongs: 没有数据,无法移动到第一行');
       }
       
       resultSet.close();
+      Logger.info('PlaylistTable', `queryPlaylistSongs: 查询到 ${songs.length} 首歌曲`);
       return songs;
     } catch (error) {
-      Logger.error('PlaylistTable', `查询歌单歌曲失败: ${error.message}`);
+      Logger.error('PlaylistTable', `queryPlaylistSongs: 查询歌单歌曲失败: ${error.message}`);
+      Logger.error('PlaylistTable', `queryPlaylistSongs: 错误堆栈: ${error.stack || '无堆栈信息'}`);
       return [];
     }
   }

+ 1 - 1
entry/src/main/ets/common/util/RdbUtils.ets

@@ -16,7 +16,7 @@ export default class RdbUtils {
   private tableName: string;
   private sqlCreateTable: string;
   private columns: Array<string>;
-  static readonly RDB_TAG: string = 'onecold RdbUtils';
+  static readonly RDB_TAG: string = 'heanup onecold RdbUtils';
 
   /**
    * Rdb数据库配置。

+ 376 - 0
entry/src/main/ets/dialog/AddSongsToPlaylistDialog.ets

@@ -0,0 +1,376 @@
+import { DialogHelper } from '@pura/harmony-dialog';
+import { Playlist } from '../viewmodel/Playlist';
+import { ToastUtil, LogUtil } from '@pura/harmony-utils';
+import { VideoItem } from '../viewmodel/VideoItem';
+import MediaTable from '../common/util/MediaTable';
+import PlaylistTable from '../common/util/PlaylistTable';
+
+/**
+ * 添加歌曲到歌单对话框内容组件
+ */
+@Component
+struct AddSongsToPlaylistDialogContent {
+  @State allSongs: VideoItem[] = []
+  @State selectedSongs: VideoItem[] = []
+  @State isLoading: boolean = true
+  @State searchText: string = ''
+  @State filteredSongs: VideoItem[] = []
+  @State isDbInitialized: boolean = false
+  @Prop playlist: Playlist | null = null
+  private mediaTable: MediaTable = new MediaTable(getContext(this))
+  private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
+
+  // 回调函数
+  onConfirm?: (songs: VideoItem[]) => void
+  onCancel?: () => void
+
+  aboutToAppear() {
+    LogUtil.info('heanup AddSongsToPlaylistDialogContent aboutToAppear 开始')
+    LogUtil.info('heanup playlist: ' + (this.playlist ? JSON.stringify({ id: this.playlist.id, name: this.playlist.name }) : 'null'))
+    
+    // 初始化数据库连接
+    this.mediaTable.getRdbStore(getContext(this), () => {
+      LogUtil.info('heanup MediaTable 数据库初始化完成')
+      this.isDbInitialized = true
+      this.loadAllSongs()
+    })
+    
+    LogUtil.info('heanup AddSongsToPlaylistDialogContent aboutToAppear 结束')
+  }
+
+  /**
+   * 加载所有歌曲
+   */
+  async loadAllSongs() {
+    if (!this.isDbInitialized) {
+      LogUtil.warn('heanup 数据库未初始化,等待初始化完成')
+      return
+    }
+    
+    try {
+      LogUtil.info('heanup loadAllSongs 开始')
+      this.isLoading = true
+      LogUtil.info('heanup 开始查询所有歌曲')
+      this.allSongs = await this.mediaTable.queryAllVideos()
+      LogUtil.info('heanup 查询到 ' + this.allSongs.length + ' 首歌曲')
+      this.filteredSongs = [...this.allSongs]
+      
+      // 过滤掉已经在歌单中的歌曲
+      if (this.playlist) {
+        LogUtil.info('heanup 开始查询歌单中的歌曲,歌单ID: ' + this.playlist.id)
+        const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlist.id)
+        LogUtil.info('heanup 歌单中有 ' + playlistSongs.length + ' 首歌曲')
+        const playlistSongPaths = playlistSongs.map(song => song.songFilePath)
+        this.filteredSongs = this.filteredSongs.filter(song => !playlistSongPaths.includes(song.filePath))
+        this.allSongs = [...this.filteredSongs]
+        LogUtil.info('heanup 过滤后有 ' + this.filteredSongs.length + ' 首歌曲可添加')
+      } else {
+        LogUtil.warn('heanup playlist 为 null')
+      }
+    } catch (error) {
+      LogUtil.error('heanup 加载歌曲列表失败: ' + error)
+      ToastUtil.showToast('加载歌曲列表失败')
+    } finally {
+      LogUtil.info('heanup 设置 isLoading 为 false')
+      this.isLoading = false
+    }
+  }
+
+  /**
+   * 搜索过滤歌曲
+   */
+  filterSongs() {
+    if (!this.searchText.trim()) {
+      this.filteredSongs = [...this.allSongs]
+    } else {
+      const searchLower = this.searchText.toLowerCase()
+      this.filteredSongs = this.allSongs.filter(song => 
+        (song.name && song.name.toLowerCase().includes(searchLower)) ||
+        (song.artist && song.artist.toLowerCase().includes(searchLower)) ||
+        (song.album && song.album.toLowerCase().includes(searchLower))
+      )
+    }
+  }
+
+  build() {
+    Column({ space: 16 }) {
+      // 标题
+      Text('添加歌曲到歌单')
+        .fontSize(18)
+        .fontWeight(FontWeight.Bold)
+        .fontColor($r('app.color.text_color'))
+        .margin({ top: 20 })
+
+      // 搜索框
+      Row({ space: 8 }) {
+        Image($r('app.media.ic_action_search'))
+          .width(20)
+          .height(20)
+          .fillColor($r('app.color.text_color'))
+          .opacity(0.6)
+
+        TextInput({ placeholder: '搜索歌曲、歌手或专辑', text: this.searchText })
+          .layoutWeight(1)
+          .height(40)
+          .backgroundColor($r('app.color.input_background'))
+          .borderRadius(8)
+          .padding({ left: 8, right: 8 })
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .onChange((value: string) => {
+            this.searchText = value
+            this.filterSongs()
+          })
+      }
+      .width('100%')
+      .padding(12)
+      .backgroundColor($r('app.color.input_background'))
+      .borderRadius(8)
+
+      // 已选择歌曲数量
+      if (this.selectedSongs.length > 0) {
+        Row() {
+          Text(`已选择 ${this.selectedSongs.length} 首歌曲`)
+            .fontSize(14)
+            .fontColor($r('app.color.theme_color'))
+            .fontWeight(FontWeight.Medium)
+
+          Blank()
+
+          Button('清空')
+            .fontSize(12)
+            .fontColor($r('app.color.text_color'))
+            .backgroundColor(Color.Transparent)
+            .height(30)
+            .padding({ left: 8, right: 8 })
+            .onClick(() => {
+              this.selectedSongs = []
+            })
+        }
+        .width('100%')
+        .padding({ left: 4, right: 4 })
+      }
+
+      // 歌曲列表
+      if (this.isLoading) {
+        Column() {
+          LoadingProgress()
+            .width(40)
+            .height(40)
+            .color($r('app.color.theme_color'))
+          
+          Text('加载中...')
+            .fontSize(14)
+            .fontColor($r('app.color.text_color'))
+            .margin({ top: 12 })
+        }
+        .width('100%')
+        .height(300)
+        .justifyContent(FlexAlign.Center)
+      } else if (this.filteredSongs.length === 0) {
+        Column({ space: 12 }) {
+          Image($r('app.media.music_red'))
+            .width(64)
+            .height(64)
+            .opacity(0.3)
+
+          Text(this.searchText ? '没有找到匹配的歌曲' : '没有可添加的歌曲')
+            .fontSize(14)
+            .fontColor('#999999')
+        }
+        .width('100%')
+        .height(300)
+        .justifyContent(FlexAlign.Center)
+      } else {
+        Scroll() {
+          Column({ space: 0 }) {
+            ForEach(this.filteredSongs, (song: VideoItem) => {
+              Row({ space: 12 }) {
+                // 选择框
+                Checkbox({ name: 'song_' + song.id })
+                  .select(this.selectedSongs.some(s => s.id === song.id))
+                  .selectedColor($r('app.color.theme_color'))
+                  .shape(CheckBoxShape.ROUNDED_SQUARE)
+                  .onChange((checked: boolean) => {
+                    if (checked) {
+                      if (!this.selectedSongs.some(s => s.id === song.id)) {
+                        this.selectedSongs.push(song)
+                      }
+                    } else {
+                      const index = this.selectedSongs.findIndex(s => s.id === song.id)
+                      if (index > -1) {
+                        this.selectedSongs.splice(index, 1)
+                      }
+                    }
+                  })
+
+                // 歌曲信息
+                Column({ space: 4 }) {
+                  Text(song.name || '未知歌曲')
+                    .fontSize(14)
+                    .fontColor($r('app.color.text_color'))
+                    .maxLines(1)
+                    .textOverflow({ overflow: TextOverflow.Ellipsis })
+                    .width('100%')
+
+                  Row() {
+                    if (song.artist) {
+                      Text(song.artist)
+                        .fontSize(12)
+                        .fontColor('#999999')
+                        .maxLines(1)
+                        .textOverflow({ overflow: TextOverflow.Ellipsis })
+                        .layoutWeight(1)
+                    }
+
+                    if (song.artist && song.album) {
+                      Text('·')
+                        .fontSize(12)
+                        .fontColor('#999999')
+                    }
+
+                    if (song.album) {
+                      Text(song.album)
+                        .fontSize(12)
+                        .fontColor('#999999')
+                        .maxLines(1)
+                        .textOverflow({ overflow: TextOverflow.Ellipsis })
+                        .layoutWeight(1)
+                    }
+                  }
+                  .width('100%')
+                }
+                .alignItems(HorizontalAlign.Start)
+                .layoutWeight(1)
+              }
+              .width('100%')
+              .padding(12)
+              .borderRadius(8)
+              .onClick(() => {
+                const isSelected = this.selectedSongs.some(s => s.id === song.id)
+                if (isSelected) {
+                  const index = this.selectedSongs.findIndex(s => s.id === song.id)
+                  if (index > -1) {
+                    this.selectedSongs.splice(index, 1)
+                  }
+                } else {
+                  this.selectedSongs.push(song)
+                }
+              })
+            }, (song: VideoItem) => song.id)
+          }
+        }
+        .scrollBar(BarState.Auto)
+        .scrollable(ScrollDirection.Vertical)
+        .height(300)
+      }
+
+      // 按钮区域
+      Row({ space: 12 }) {
+        Button('取消')
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.cancel_button_background'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor($r('app.color.cancel_button_text'))
+          .onClick(() => {
+            this.onCancel?.()
+            DialogHelper.closeDialog('addSongsToPlaylistDialog')
+          })
+
+        Button(`添加${this.selectedSongs.length > 0 ? `(${this.selectedSongs.length})` : ''}`)
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.theme_color'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor(Color.White)
+          .enabled(this.selectedSongs.length > 0)
+          .opacity(this.selectedSongs.length > 0 ? 1 : 0.5)
+          .onClick(() => {
+            this.handleConfirm()
+          })
+      }
+      .width('100%')
+      .justifyContent(FlexAlign.SpaceBetween)
+      .margin({ top: 20, bottom: 20 })
+    }
+    .width('90%')
+    .constraintSize({ maxWidth: 400 })
+    .backgroundColor($r('app.color.dialog_background'))
+    .borderRadius(12)
+    .padding({ left: 20, right: 20 })
+  }
+
+  /**
+   * 处理确认操作
+   */
+  private handleConfirm() {
+    if (this.selectedSongs.length === 0) {
+      ToastUtil.showToast('请选择至少一首歌曲')
+      return
+    }
+
+    this.onConfirm?.(this.selectedSongs)
+    DialogHelper.closeDialog('addSongsToPlaylistDialog')
+  }
+}
+
+/**
+ * 添加歌曲到歌单对话框管理器
+ */
+@Component
+export struct AddSongsToPlaylistDialogManager {
+  /**
+   * 添加歌曲到歌单对话框构建器
+   */
+  @Builder
+  buildAddSongsToPlaylistDialog(
+    playlist: Playlist,
+    onConfirm: (songs: VideoItem[]) => void,
+    onCancel?: () => void
+  ) {
+    AddSongsToPlaylistDialogContent({
+      playlist: playlist,
+      onConfirm: onConfirm,
+      onCancel: onCancel
+    })
+  }
+
+  /**
+   * 显示添加歌曲到歌单对话框
+   */
+  showAddSongsToPlaylistDialog(
+    playlist: Playlist,
+    onConfirm: (songs: VideoItem[]) => void,
+    onCancel?: () => void
+  ) {
+    DialogHelper.showCustomContentDialog({
+      dialogId: 'addSongsToPlaylistDialog',
+      title: '添加歌曲到歌单',
+      autoCancel: true,
+      contentBuilder: () => {
+        this.buildAddSongsToPlaylistDialog(playlist, onConfirm, onCancel)
+      },
+      buttons: []
+    })
+  }
+
+  build() {
+  }
+}
+
+// 创建全局实例
+const dialogManager = new AddSongsToPlaylistDialogManager()
+
+/**
+ * 显示添加歌曲到歌单对话框
+ */
+export function showAddSongsToPlaylistDialog(
+  playlist: Playlist,
+  onConfirm: (songs: VideoItem[]) => void,
+  onCancel?: () => void
+) {
+  dialogManager.showAddSongsToPlaylistDialog(playlist, onConfirm, onCancel)
+}

+ 281 - 0
entry/src/main/ets/dialog/AddToPlaylistDialog.ets

@@ -0,0 +1,281 @@
+import { DialogHelper } from '@pura/harmony-dialog';
+import { Playlist } from '../viewmodel/Playlist';
+import { ToastUtil } from '@pura/harmony-utils';
+import { VideoItem } from '../viewmodel/VideoItem';
+
+/**
+ * 添加到歌单对话框内容组件
+ */
+@Component
+struct AddToPlaylistDialogContent {
+  @State playlists: Playlist[] = []
+  @State selectedPlaylistId: string = ''
+  private currentSong?: VideoItem
+
+  // 回调函数
+  onConfirm?: (playlistId: string) => void
+  onCancel?: () => void
+  onCreateNew?: () => void
+
+  build() {
+    Column({ space: 16 }) {
+      // 标题
+      Text('添加到歌单')
+        .fontSize(18)
+        .fontWeight(FontWeight.Bold)
+        .fontColor($r('app.color.text_color'))
+        .margin({ top: 20 })
+
+      // 歌曲信息
+      if (this.currentSong) {
+        Row({ space: 12 }) {
+          // 封面
+          Image(this.currentSong.pixelMap || $r('app.media.icon'))
+            .width(48)
+            .height(48)
+            .borderRadius(8)
+            .objectFit(ImageFit.Cover)
+
+          // 歌曲名和艺术家
+          Column({ space: 4 }) {
+            Text(this.currentSong.name || '未知歌曲')
+              .fontSize(14)
+              .fontColor($r('app.color.text_color'))
+              .maxLines(1)
+              .textOverflow({ overflow: TextOverflow.Ellipsis })
+
+            Text(this.currentSong.artist || '未知艺术家')
+              .fontSize(12)
+              .fontColor('#999999')
+              .maxLines(1)
+              .textOverflow({ overflow: TextOverflow.Ellipsis })
+          }
+          .alignItems(HorizontalAlign.Start)
+          .layoutWeight(1)
+        }
+        .width('100%')
+        .padding(12)
+        .backgroundColor($r('app.color.input_background'))
+        .borderRadius(8)
+      }
+
+      // 创建新歌单按钮
+      Button() {
+        Row({ space: 8 }) {
+          Image($r('sys.symbol.plus'))
+            .width(20)
+            .height(20)
+            .fillColor($r('app.color.theme_color'))
+
+          Text('创建新歌单')
+            .fontSize(14)
+            .fontColor($r('app.color.theme_color'))
+        }
+      }
+      .width('100%')
+      .height(44)
+      .backgroundColor($r('app.color.input_background'))
+      .borderRadius(8)
+      .onClick(() => {
+        this.onCreateNew?.()
+        DialogHelper.closeDialog('addToPlaylistDialog')
+      })
+
+      // 分隔线
+      if (this.playlists.length > 0) {
+        Divider()
+          .color($r('app.color.divider_color'))
+      }
+
+      // 歌单列表
+      if (this.playlists.length > 0) {
+        Text('选择歌单')
+          .fontSize(14)
+          .fontColor('#999999')
+          .alignSelf(ItemAlign.Start)
+
+        Scroll() {
+          Column({ space: 8 }) {
+            ForEach(this.playlists, (playlist: Playlist) => {
+              Row({ space: 12 }) {
+                // 封面
+                Image(playlist.coverPath || $r('app.media.icon'))
+                  .width(48)
+                  .height(48)
+                  .borderRadius(8)
+                  .objectFit(ImageFit.Cover)
+
+                // 歌单信息
+                Column({ space: 4 }) {
+                  Text(playlist.name)
+                    .fontSize(14)
+                    .fontColor($r('app.color.text_color'))
+                    .maxLines(1)
+                    .textOverflow({ overflow: TextOverflow.Ellipsis })
+
+                  Text(`${playlist.songCount} 首歌曲`)
+                    .fontSize(12)
+                    .fontColor('#999999')
+                }
+                .alignItems(HorizontalAlign.Start)
+                .layoutWeight(1)
+
+                // 选中标记
+                if (this.selectedPlaylistId === playlist.id) {
+                  Image($r('sys.symbol.checkmark'))
+                    .width(20)
+                    .height(20)
+                    .fillColor($r('app.color.theme_color'))
+                }
+              }
+              .width('100%')
+              .padding(12)
+              .backgroundColor(this.selectedPlaylistId === playlist.id ?
+                '#E6F0FF' : $r('app.color.input_background'))
+              .borderRadius(8)
+              .onClick(() => {
+                this.selectedPlaylistId = playlist.id
+              })
+            }, (playlist: Playlist) => playlist.id)
+          }
+        }
+        .scrollBar(BarState.Auto)
+        .scrollable(ScrollDirection.Vertical)
+        .height(300)
+      } else {
+        Column({ space: 12 }) {
+          Image($r('app.media.icon'))
+            .width(64)
+            .height(64)
+            .opacity(0.3)
+
+          Text('暂无歌单')
+            .fontSize(14)
+            .fontColor('#999999')
+
+          Text('点击上方按钮创建第一个歌单吧')
+            .fontSize(12)
+            .fontColor('#999999')
+        }
+        .width('100%')
+        .padding(20)
+        .justifyContent(FlexAlign.Center)
+      }
+
+      // 按钮区域
+      Row({ space: 12 }) {
+        Button('取消')
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.cancel_button_background'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor($r('app.color.cancel_button_text'))
+          .onClick(() => {
+            this.onCancel?.()
+            DialogHelper.closeDialog('addToPlaylistDialog')
+          })
+
+        Button('添加')
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.theme_color'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor(Color.White)
+          .enabled(this.selectedPlaylistId !== '')
+          .opacity(this.selectedPlaylistId !== '' ? 1 : 0.5)
+          .onClick(() => {
+            this.handleConfirm()
+          })
+      }
+      .width('100%')
+      .justifyContent(FlexAlign.SpaceBetween)
+      .margin({ top: 20, bottom: 20 })
+    }
+    .width('90%')
+    .constraintSize({ maxWidth: 400 })
+    .backgroundColor($r('app.color.dialog_background'))
+    .borderRadius(12)
+    .padding({ left: 20, right: 20 })
+  }
+
+  /**
+   * 处理确认操作
+   */
+  private handleConfirm() {
+    if (!this.selectedPlaylistId) {
+      ToastUtil.showToast('请选择一个歌单')
+      return
+    }
+
+    this.onConfirm?.(this.selectedPlaylistId)
+    DialogHelper.closeDialog('addToPlaylistDialog')
+  }
+}
+
+/**
+ * 添加到歌单对话框管理器
+ */
+@Component
+export struct AddToPlaylistDialogManager {
+  /**
+   * 添加到歌单对话框构建器
+   */
+  @Builder
+  buildAddToPlaylistDialog(
+    song: VideoItem,
+    playlists: Playlist[],
+    onConfirm: (playlistId: string) => void,
+    onCancel?: () => void,
+    onCreateNew?: () => void
+  ) {
+    AddToPlaylistDialogContent({
+      currentSong: song,
+      playlists: playlists,
+      onConfirm: onConfirm,
+      onCancel: onCancel,
+      onCreateNew: onCreateNew
+    })
+  }
+
+  /**
+   * 显示添加到歌单对话框
+   */
+  showAddToPlaylistDialog(
+    song: VideoItem,
+    playlists: Playlist[],
+    onConfirm: (playlistId: string) => void,
+    onCancel?: () => void,
+    onCreateNew?: () => void
+  ) {
+    DialogHelper.showCustomContentDialog({
+      dialogId: 'addToPlaylistDialog',
+      title: '添加到歌单',
+      autoCancel: true,
+      contentBuilder: () => {
+        this.buildAddToPlaylistDialog(song, playlists, onConfirm, onCancel, onCreateNew)
+      },
+      buttons: []
+    })
+  }
+
+  build() {
+  }
+}
+
+// 创建全局实例
+const dialogManager = new AddToPlaylistDialogManager()
+
+/**
+ * 显示添加到歌单对话框
+ */
+export function showAddToPlaylistDialog(
+  song: VideoItem,
+  playlists: Playlist[],
+  onConfirm: (playlistId: string) => void,
+  onCancel?: () => void,
+  onCreateNew?: () => void
+) {
+  dialogManager.showAddToPlaylistDialog(song, playlists, onConfirm, onCancel, onCreateNew)
+}

+ 0 - 2
entry/src/main/ets/pages/NewIndex.ets

@@ -252,8 +252,6 @@ struct NewIndex {
     // if (this.isLogin) {
     //   void this.fetchUserInfo();
     // }
-    console.log('Heanup isLogin:' + this.isLogin)
-    console.log('Heanup Utility.isNobleForOld():' + Utility.isNobleForOld())
 
     await UserUtil.fetchUserInfo();
     this.refreshUserInfoState();

+ 686 - 124
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -1,10 +1,24 @@
 import { Playlist, PlaylistSong } from '../viewmodel/Playlist';
 import { VideoItem } from '../viewmodel/VideoItem';
 import PlaylistTable from '../common/util/PlaylistTable';
+import MediaTable from '../common/util/MediaTable';
 import { emitter } from '@kit.BasicServicesKit';
-import { ToastUtil, AppUtil } from '@pura/harmony-utils';
+import { ToastUtil, AppUtil, LogUtil } from '@pura/harmony-utils';
 import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
+import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
 import { router } from '@kit.ArkUI';
+import { GlobalContext } from '../common/util/GlobalContext';
+
+/**
+ * 简化的歌单播放事件数据
+ */
+interface SimplifiedPlaylistEventData {
+  playlistId: string;
+  playlistName: string;
+  songCount: number;
+  startIndex: number;
+  songFilePaths: string[];
+}
 
 /**
  * 歌单详情页面
@@ -17,8 +31,14 @@ export struct PlaylistDetailPage {
   @State songList: VideoItem[] = []
   @State isLoading: boolean = true
   @State isShowEditDialog: boolean = false
-  
+  @State isPlaying: boolean = false
+  @State curIndex: number = -1
+  @State pageOpacity: number = 0
+  @State contentScale: number = 0.95
+  @State showContent: boolean = false
+
   private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
+  private mediaTable: MediaTable = new MediaTable(getContext(this))
   private playlistId: string = ''
 
   aboutToAppear() {
@@ -29,6 +49,14 @@ export struct PlaylistDetailPage {
       this.playlistId = this.playlist.id
       this.loadPlaylistDetail()
     }
+
+    // 监听播放状态变化
+    this.setupPlaybackStatusListener()
+  }
+
+  aboutToDisappear() {
+    // 移除事件监听
+    this.removePlaybackStatusListener()
   }
 
   /**
@@ -50,10 +78,106 @@ export struct PlaylistDetailPage {
       // 将 PlaylistSong 转换为 VideoItem
       this.songList = await this.convertPlaylistSongsToVideoItems(playlistSongs)
     } catch (error) {
-      console.error('加载歌单详情失败:', error)
+      LogUtil.error('heanup 加载歌单详情失败: ' + error)
       ToastUtil.showToast('加载歌单详情失败')
     } finally {
       this.isLoading = false
+      // 启动页面进入动画
+      this.animatePageEntry()
+    }
+  }
+
+  /**
+   * 格式化时长显示
+   */
+  formatDuration(duration?: number): string {
+    if (!duration || duration <= 0) {
+      return ''
+    }
+
+    const minutes = Math.floor(duration / 60)
+    const seconds = Math.floor(duration % 60)
+    return `${minutes}:${seconds.toString().padStart(2, '0')}`
+  }
+
+  /**
+   * 计算歌单总时长
+   */
+  calculateTotalDuration(): number {
+    let total = 0
+    for (const song of this.songList) {
+      const duration = song.duration || 0
+      total = total + (typeof duration === 'number' ? duration : 0)
+    }
+    return total
+  }
+
+  /**
+   * 格式化总时长显示
+   */
+  formatTotalDuration(totalSeconds: number): string {
+    if (totalSeconds <= 0) {
+      return ''
+    }
+
+    const hours = Math.floor(totalSeconds / 3600)
+    const minutes = Math.floor((totalSeconds % 3600) / 60)
+
+    if (hours > 0) {
+      return `${hours}小时${minutes}分钟`
+    } else {
+      return `${minutes}分钟`
+    }
+  }
+
+  /**
+   * 页面入场动画
+   */
+  animatePageEntry() {
+    animateTo({
+      duration: 600,
+      curve: Curve.EaseOut,
+      delay: 100,
+      onFinish: () => {
+        this.showContent = true
+      }
+    }, () => {
+      this.pageOpacity = 1
+      this.contentScale = 1
+    })
+  }
+
+  /**
+   * 设置播放状态监听
+   */
+  setupPlaybackStatusListener() {
+    try {
+      // 监听播放状态变化事件
+      const eventPlaybackStatus: emitter.InnerEvent = { eventId: 2003 }
+      emitter.on(eventPlaybackStatus, (eventData: emitter.EventData) => {
+        LogUtil.info('heanup PlaylistDetailPage 收到播放状态变化事件')
+        if (eventData.data) {
+          const data = eventData.data as Record<string, Object>
+          this.isPlaying = data['isPlaying'] as boolean || false
+          this.curIndex = data['curIndex'] as number || -1
+
+          // 如果当前播放的是这个歌单的歌曲,高亮显示
+          LogUtil.info(`heanup 播放状态更新: isPlaying=${this.isPlaying}, curIndex=${this.curIndex}`)
+        }
+      })
+    } catch (error) {
+      LogUtil.error('heanup 设置播放状态监听失败: ' + error)
+    }
+  }
+
+  /**
+   * 移除播放状态监听
+   */
+  removePlaybackStatusListener() {
+    try {
+      emitter.off(2003)
+    } catch (error) {
+      LogUtil.error('heanup 移除播放状态监听失败: ' + error)
     }
   }
 
@@ -62,36 +186,50 @@ export struct PlaylistDetailPage {
    */
   async convertPlaylistSongsToVideoItems(playlistSongs: PlaylistSong[]): Promise<VideoItem[]> {
     const videoItems: VideoItem[] = []
-    
+
+    LogUtil.info(`heanup 开始转换歌曲,共 ${playlistSongs.length} 首`)
+
     for (const playlistSong of playlistSongs) {
       try {
-        // 获取文件名
-        const fileName = playlistSong.songFilePath.split('/').pop() || ''
-        const name = fileName.replace(/\.[^/.]+$/, "") // 移除文件扩展名
-        
-        // 创建基本的 VideoItem 对象
-        const videoItem = new VideoItem(
-          name, // name
-          Date.now().toString(), // id
-          playlistSong.songFilePath, // filePath
-          0, // type (音乐类型)
-          0, // videoSize
-          new Date().toISOString(), // cTime
-          undefined, // pixelMap
-          undefined, // size
-          undefined, // pixelMapPath
-          undefined, // artist
-          undefined, // album
-          fileName, // fileName
-          undefined // lastPlayed
-        )
-        
-        videoItems.push(videoItem)
+        LogUtil.info(`heanup 查询歌曲: ${playlistSong.songFilePath}`)
+
+        // 从数据库查询完整的歌曲信息
+        const videoItem = await this.mediaTable.queryVideoByFilePath(playlistSong.songFilePath)
+
+        if (videoItem) {
+          LogUtil.info(`heanup 找到歌曲: ${videoItem.name}`)
+          videoItems.push(videoItem)
+        } else {
+          LogUtil.warn(`heanup 数据库中未找到歌曲: ${playlistSong.songFilePath}`)
+
+          // 如果数据库中没有,创建一个基本的 VideoItem
+          const fileName = playlistSong.songFilePath.split('/').pop() || ''
+          const name = fileName.replace(/\.[^/.]+$/, "") // 移除文件扩展名
+
+          const basicVideoItem = new VideoItem(
+            name, // name
+            Date.now().toString() + Math.random(), // id
+            playlistSong.songFilePath, // filePath
+            0, // type (音乐类型)
+            0, // videoSize
+            playlistSong.addTime, // cTime
+            undefined, // pixelMap
+            undefined, // size
+            undefined, // pixelMapPath
+            undefined, // artist
+            undefined, // album
+            fileName, // fileName
+            undefined // lastPlayed
+          )
+
+          videoItems.push(basicVideoItem)
+        }
       } catch (error) {
-        console.error('转换歌曲失败:', error)
+        LogUtil.error('heanup 转换歌曲失败: ' + error)
       }
     }
-    
+
+    LogUtil.info(`heanup 转换完成,共 ${videoItems.length} 首歌曲`)
     return videoItems
   }
 
@@ -106,32 +244,64 @@ export struct PlaylistDetailPage {
 
     // 发送播放歌单事件
     const eventPlaylistPlay: emitter.InnerEvent = { eventId: 2002 }
+
+    LogUtil.info(`heanup 准备发送播放事件,playlist: ${this.playlist ? '存在' : '不存在'}, songs: ${this.songList.length}首`)
+
+    // 尝试简化数据结构,只发送必要的信息
+    const simplifiedData: SimplifiedPlaylistEventData = {
+      playlistId: this.playlist?.id || '',
+      playlistName: this.playlist?.name || '',
+      songCount: this.songList.length,
+      startIndex: 0,
+      // 只发送歌曲的必要信息
+      songFilePaths: this.songList.map(song => song.filePath)
+    };
+
+    LogUtil.info(`heanup 发送简化事件数据: ${JSON.stringify(simplifiedData)}`)
+
     const eventData: emitter.EventData = {
-      data: {
-        playlist: this.playlist,
-        songs: this.songList,
-        startIndex: 0
-      }
+      data: simplifiedData
     };
+
     emitter.emit(eventPlaylistPlay, eventData)
     
     ToastUtil.showToast('开始播放歌单')
   }
 
   /**
-   * 播放指定歌曲
+   * 放指定歌曲
    */
   playSong(song: VideoItem, index: number) {
-    // 发送播放歌单事件,指定开始播放的歌曲
+    LogUtil.info(`heanup === playSong 方法被调用 ===`)
+    LogUtil.info(`heanup 播放指定歌曲: ${song.name}, 索引: ${index}`)
+    LogUtil.info(`heanup 歌曲列表长度: ${this.songList.length}`)
+    LogUtil.info(`heanup 歌单ID: ${this.playlist?.id}, 歌单名称: ${this.playlist?.name}`)
+
+    // 检查歌曲文件路径
+    LogUtil.info(`heanup 所有歌曲文件路径: ${JSON.stringify(this.songList.map(s => s.filePath))}`)
+
+    // 发送播放歌单事件,使用简化数据结构
     const eventPlaylistPlay: emitter.InnerEvent = { eventId: 2002 }
+    const simplifiedData: SimplifiedPlaylistEventData = {
+      playlistId: this.playlist?.id || '',
+      playlistName: this.playlist?.name || '',
+      songCount: this.songList.length,
+      startIndex: index,
+      // 只发送所有歌曲的文件路径
+      songFilePaths: this.songList.map(s => s.filePath)
+    };
+
+    LogUtil.info(`heanup 准备发送事件,eventId: ${eventPlaylistPlay.eventId}`)
+    LogUtil.info(`heanup 发送单歌曲播放事件数据: ${JSON.stringify(simplifiedData)}`)
+
     const eventData: emitter.EventData = {
-      data: {
-        playlist: this.playlist,
-        songs: this.songList,
-        startIndex: index
-      }
+      data: simplifiedData
     };
+
+    LogUtil.info(`heanup eventData对象: ${JSON.stringify(eventData)}`)
+    LogUtil.info(`heanup 即将调用 emitter.emit`)
     emitter.emit(eventPlaylistPlay, eventData)
+    LogUtil.info(`heanup emitter.emit 调用完成`)
   }
 
   /**
@@ -274,18 +444,31 @@ export struct PlaylistDetailPage {
         .layoutWeight(1)
         .justifyContent(FlexAlign.Center)
       } else if (this.playlist) {
-        // 歌单信息区域
+        // 歌单信息区域 - 参考LocalMusic的封面标题区域设计
         Column() {
-          // 歌单封面和基本信息
+          // 背景区域
           Row() {
-            Image(this.playlist.coverPath || $r('app.media.hm_playlist'))
-              .width(120)
-              .height(120)
-              .borderRadius(8)
-              .clip(true)
-              .margin({ right: 16 })
-            
             Column() {
+              // 歌单封面
+              Image(this.playlist.coverPath || $r('app.media.hm_playlist'))
+                .width(100)
+                .height(100)
+                .borderRadius(12)
+                .clip(true)
+                .interpolation(ImageInterpolation.High)
+                .autoResize(true)
+                .shadow({
+                  radius: 15,
+                  color: '#0000001a',
+                  offsetX: 0,
+                  offsetY: 6
+                })
+            }
+            .alignItems(HorizontalAlign.Start)
+            .margin({ left: 24, top: 16, bottom: 16 })
+
+            Column() {
+              // 歌单名称
               Text(this.playlist.name)
                 .fontSize(20)
                 .fontWeight(FontWeight.Bold)
@@ -293,119 +476,498 @@ export struct PlaylistDetailPage {
                 .maxLines(2)
                 .textOverflow({ overflow: TextOverflow.Ellipsis })
                 .margin({ bottom: 8 })
-              
+
+              // 歌单描述
               if (this.playlist.description) {
                 Text(this.playlist.description)
                   .fontSize(14)
                   .fontColor($r('app.color.text_color'))
-                  .opacity(0.7)
-                  .maxLines(3)
+                  .opacity(0.8)
+                  .maxLines(2)
                   .textOverflow({ overflow: TextOverflow.Ellipsis })
-                  .margin({ bottom: 8 })
+                  .margin({ bottom: 12 })
+                  .lineHeight(18)
               }
-              
-              Text(`${this.playlist.songCount}首歌曲`)
-                .fontSize(12)
-                .fontColor($r('app.color.text_color'))
-                .opacity(0.6)
+
+              // 歌单统计信息
+              Row() {
+                Text(`共${this.playlist.songCount}首歌`)
+                  .fontSize(13)
+                  .fontWeight(FontWeight.Bold)
+                  .fontColor($r('app.color.text_color'))
+                  .opacity(0.8)
+
+                if (this.calculateTotalDuration() > 0) {
+                  Text(` · ${this.formatTotalDuration(this.calculateTotalDuration())}`)
+                    .fontSize(13)
+                    .fontColor($r('app.color.text_color'))
+                    .opacity(0.7)
+                    .margin({ left: 4 })
+                }
+              }
+              .margin({ top: 8 })
             }
+            .height('100%')
             .layoutWeight(1)
+            .margin({ left: 16 })
+            .justifyContent(FlexAlign.Center)
             .alignItems(HorizontalAlign.Start)
           }
           .width('100%')
-          .padding({ left: 16, right: 16, top: 16, bottom: 16 })
-          
-          // 操作按钮
-          Row({ space: 12 }) {
-            Button('播放全部')
-              .width('45%')
-              .height(40)
-              .backgroundColor($r('app.color.theme_color'))
-              .borderRadius(20)
-              .fontSize(14)
-              .fontColor(Color.White)
-              .onClick(() => {
-                this.playPlaylist()
-              })
-            
-            Button('编辑歌单')
-              .width('45%')
-              .height(40)
-              .backgroundColor($r('app.color.secondary_button_background'))
-              .borderRadius(20)
-              .fontSize(14)
-              .fontColor($r('app.color.text_color'))
-              .onClick(() => {
-                this.editPlaylist()
-              })
+          .height(140)
+          .justifyContent(FlexAlign.SpaceBetween)
+          .padding({ right: 24 })
+
+          // 操作按钮区域
+          Row({ space: 16 }) {
+            // 播放全部按钮 - 参考LocalMusic的按钮样式
+            Button() {
+              Row({ space: 8 }) {
+                Image($r('app.media.ic_play'))
+                  .width(16)
+                  .height(16)
+                  .fillColor(Color.White)
+
+                Text('播放全部')
+                  .fontSize(14)
+                  .fontColor(Color.White)
+                  .fontWeight(FontWeight.Medium)
+              }
+              .justifyContent(FlexAlign.Center)
+            }
+            .width(140)
+            .height(44)
+            .backgroundColor($r('app.color.theme_color'))
+            .borderRadius(22)
+            .shadow({
+              radius: 8,
+              color: '#0a59f740',
+              offsetX: 0,
+              offsetY: 4
+            })
+            .onClick(() => {
+              this.playPlaylist()
+            })
+            .stateStyles({
+              normal: {
+                .backgroundColor($r('app.color.theme_color'))
+                .scale({ x: 1, y: 1 })
+              },
+              pressed: {
+                .backgroundColor('#0a59f7cc')
+                .scale({ x: 0.96, y: 0.96 })
+              }
+            })
+            .animation({
+              duration: 150,
+              curve: Curve.EaseInOut
+            })
+
+            // 编辑歌单按钮
+            Button() {
+              Row({ space: 6 }) {
+                Text('✏️')
+                  .fontSize(16)
+                  .fontColor($r('app.color.theme_color'))
+
+                Text('编辑')
+                  .fontSize(14)
+                  .fontColor($r('app.color.theme_color'))
+                  .fontWeight(FontWeight.Medium)
+              }
+              .justifyContent(FlexAlign.Center)
+            }
+            .width(100)
+            .height(44)
+            .backgroundColor(Color.Transparent)
+            .borderRadius(22)
+            .border({
+              width: 1.5,
+              color: $r('app.color.theme_color')
+            })
+            .onClick(() => {
+              this.editPlaylist()
+            })
+            .stateStyles({
+              normal: {
+                .backgroundColor(Color.Transparent)
+                .scale({ x: 1, y: 1 })
+              },
+              pressed: {
+                .backgroundColor('#0a59f715')
+                .scale({ x: 0.96, y: 0.96 })
+              }
+            })
+            .animation({
+              duration: 150,
+              curve: Curve.EaseInOut
+            })
+
+            // 删除歌单按钮
+            Button() {
+              Text('🗑️')
+                .fontSize(16)
+                .fontColor('#ff4757')
+            }
+            .width(44)
+            .height(44)
+            .backgroundColor(Color.Transparent)
+            .borderRadius(22)
+            .border({
+              width: 1.5,
+              color: '#ff4757'
+            })
+            .onClick(() => {
+              this.deletePlaylist()
+            })
+            .stateStyles({
+              normal: {
+                .backgroundColor(Color.Transparent)
+                .scale({ x: 1, y: 1 })
+              },
+              pressed: {
+                .backgroundColor('#ff475715')
+                .scale({ x: 0.96, y: 0.96 })
+              }
+            })
+            .animation({
+              duration: 150,
+              curve: Curve.EaseInOut
+            })
           }
           .width('100%')
-          .padding({ left: 16, right: 16, bottom: 16 })
+          .padding({ left: 24, right: 24, bottom: 20 })
+          .justifyContent(FlexAlign.Start)
         }
         .backgroundColor($r('app.color.bg_card'))
         .margin({ left: 16, right: 16, top: 16 })
-        .borderRadius(12)
+        .borderRadius(16)
+        .shadow({
+          radius: 12,
+          color: '#00000014',
+          offsetX: 0,
+          offsetY: 4
+        })
+        .scale({ x: this.contentScale, y: this.contentScale })
+        .opacity(this.pageOpacity)
+        .transition(TransitionEffect.OPACITY.animation({ duration: 600, curve: Curve.EaseOut }))
+        .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 600, curve: Curve.EaseOut }))
+
+        // 歌曲列表标题 - 简化设计,与LocalMusic保持一致
+        if (this.songList.length > 0) {
+          Row() {
+            Text('歌曲列表')
+              .fontSize(16)
+              .fontWeight(FontWeight.Medium)
+              .fontColor($r('app.color.text_color'))
+              .opacity(0.9)
+              .layoutWeight(1)
+
+            Text(`${this.songList.length}首`)
+              .fontSize(14)
+              .fontColor($r('app.color.text_color'))
+              .opacity(0.6)
+          }
+          .width('100%')
+          .padding({ left: 24, right: 24, top: 16, bottom: 12 })
+          .opacity(this.pageOpacity)
+          .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
+        }
 
-        // 歌曲列表
+        // 歌曲列表 - 参考LocalMusic的MusicItem样式
         if (this.songList.length > 0) {
-          List() {
+          List({ space: 0 }) {
             ForEach(this.songList, (song: VideoItem, index: number) => {
               ListItem() {
-                Row() {
-                  Text(`${index + 1}`)
-                    .fontSize(14)
-                    .fontColor($r('app.color.text_color'))
-                    .width(40)
-                    .textAlign(TextAlign.Center)
-                  
-                  Column() {
-                    Text(song.name || song.fileName || '')
-                      .fontSize(16)
-                      .fontColor($r('app.color.text_color'))
-                      .maxLines(1)
-                      .textOverflow({ overflow: TextOverflow.Ellipsis })
+                Button({ type: ButtonType.Normal, stateEffect: true }) {
+                  Row() {
+                    // 音乐图标 - 圆形设计,参考LocalMusic
+                    Stack() {
+                      Image($r('app.media.music_red'))
+                        .width(40)
+                        .height(40)
+                        .fillColor(this.isPlaying && this.curIndex === index ? $r('app.color.theme_color') : $r('app.color.text_color'))
+                        .borderRadius('100%')
+                        .clip(true)
+                        .interpolation(ImageInterpolation.High)
+                        .autoResize(true)
+                        .margin({ left: 20 })
+                        .onClick(() => {
+                          this.playSong(song, index)
+                        })
+
+                      // 播放中动画覆盖层
+                      if (this.isPlaying && this.curIndex === index) {
+                        Text('♪')
+                          .fontSize(16)
+                          .fontColor(Color.White)
+                          .position({ x: 0, y: 0 })
+                          .width(40)
+                          .height(40)
+                          .textAlign(TextAlign.Center)
+                          .animation({
+                            duration: 800,
+                            curve: Curve.EaseInOut,
+                            iterations: -1,
+                            playMode: PlayMode.Alternate
+                          })
+                      }
+                    }
+                    .width(60)
+
+                    // 歌曲信息 - 参考LocalMusic的布局
+                    Column() {
+                      // 歌曲名称
+                      Text(song.name || song.fileName || '')
+                        .fontSize(16)
+                        .fontColor(this.isPlaying && this.curIndex === index ? $r('app.color.theme_color') : $r('app.color.text_color'))
+                        .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
+                        .maxLines(1)
+                        .textOverflow({ overflow: TextOverflow.Ellipsis })
+                        .width('100%')
+                        .margin({ top: 8, left: 12 })
+
+                      // 歌手和专辑信息 - 参考LocalMusic的第二行布局
+                      Row() {
+                        if (song.artist) {
+                          Text(song.artist)
+                            .fontSize(13)
+                            .fontColor(this.isPlaying && this.curIndex === index ? $r('app.color.theme_color') : $r('app.color.text_color'))
+                            .maxLines(1)
+                            .textOverflow({ overflow: TextOverflow.Ellipsis })
+                            .layoutWeight(1)
+                            .opacity(0.8)
+                        }
+
+                        if (song.artist && song.album) {
+                          Text('·')
+                            .fontSize(13)
+                            .fontColor($r('app.color.text_color'))
+                            .opacity(0.6)
+                        }
+
+                        if (song.album) {
+                          Text(song.album)
+                            .fontSize(13)
+                            .fontColor(this.isPlaying && this.curIndex === index ? $r('app.color.theme_color') : $r('app.color.text_color'))
+                            .maxLines(1)
+                            .textOverflow({ overflow: TextOverflow.Ellipsis })
+                            .layoutWeight(1)
+                            .opacity(0.8)
+                        }
+
+                        Blank()
+
+                        // 时长显示
+                        if (song.duration && typeof song.duration === 'number' && song.duration > 0) {
+                          Text(this.formatDuration(song.duration))
+                            .fontSize(13)
+                            .fontColor(this.isPlaying && this.curIndex === index ? $r('app.color.theme_color') : $r('app.color.text_color'))
+                            .opacity(0.7)
+                            .margin({ right: 20 })
+                        }
+                      }
                       .width('100%')
+                      .margin({ left: 12, top: 4 })
+                      .alignItems(VerticalAlign.Center)
+                    }
+                    .layoutWeight(1)
+                    .alignItems(HorizontalAlign.Start)
+                    .justifyContent(FlexAlign.Center)
+
+                    // 更多操作按钮
+                    Text('⋯')
+                      .fontSize(20)
+                      .fontColor($r('app.color.text_color'))
+                      .opacity(0.6)
+                      .margin({ right: 15 })
+                      .onClick(() => {
+                        this.showSongMenu(song)
+                      })
                   }
-                  .layoutWeight(1)
-                  .alignItems(HorizontalAlign.Start)
-                  .margin({ left: 16 })
+                  .width('100%')
+                  .height(70)
+                  .justifyContent(FlexAlign.Start)
+                  .alignItems(VerticalAlign.Center)
                 }
+                .backgroundColor(Color.Transparent)
+                .height(70)
                 .width('100%')
-                .height(56)
-                .padding({ left: 16, right: 16 })
                 .onClick(() => {
                   this.playSong(song, index)
                 })
-                .gesture(LongPressGesture().onAction(() => {
-                  this.showSongMenu(song)
+                .gesture(
+                  LongPressGesture()
+                    .onAction(() => {
+                      this.showSongMenu(song)
+                    })
+                )
+                .stateStyles({
+                  normal: {
+                    .backgroundColor(Color.Transparent)
+                  },
+                  pressed: {
+                    .backgroundColor('#f1f3f5')
+                  }
+                })
+                .opacity(this.pageOpacity)
+                .translate({ x: 0, y: this.showContent ? 0 : 20 })
+                .transition(TransitionEffect.OPACITY.animation({
+                  duration: 600,
+                  curve: Curve.EaseOut,
+                  delay: 300 + index * 30
+                }))
+                .transition(TransitionEffect.translate({ y: 20 }).animation({
+                  duration: 600,
+                  curve: Curve.EaseOut,
+                  delay: 300 + index * 30
                 }))
               }
             })
           }
+          .width('100%')
           .layoutWeight(1)
-          .padding({ left: 16, right: 16 })
+          .backgroundColor($r('app.color.bg_card'))
+          .margin({ left: 16, right: 16 })
+          .borderRadius(12)
+          .divider({
+            strokeWidth: 0.5,
+            color: '#0000001a',
+            startMargin: 80,
+            endMargin: 20
+          })
+          .opacity(this.pageOpacity)
+          .scale({ x: this.contentScale, y: this.contentScale })
+          .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
+          .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
         } else {
           // 空状态
           Column() {
-            Image($r('app.media.music_red'))
-              .width(80)
-              .height(80)
-              .opacity(0.3)
-              .margin({ bottom: 16 })
-            
-            Text('歌单为空')
-              .fontSize(16)
-              .fontColor($r('app.color.text_color'))
-              .opacity(0.6)
-              .margin({ bottom: 8 })
-            
-            Text('添加一些歌曲到歌单中')
-              .fontSize(14)
-              .fontColor($r('app.color.text_color'))
-              .opacity(0.4)
+            // 空状态容器
+            Column() {
+              // 空状态图标容器
+              Stack() {
+                // 背景圆圈
+                Circle({ width: 160, height: 160 })
+                  .fill('#0a59f715')
+                  .border({
+                    width: 2,
+                    color: '#0a59f71a'
+                  })
+
+                // 中间圆圈
+                Circle({ width: 120, height: 120 })
+                  .fill('#0a59f722')
+
+                // 音符图标
+                Image($r('app.media.music_red'))
+                  .width(64)
+                  .height(64)
+                  .opacity(0.6)
+                  .fillColor($r('app.color.theme_color'))
+              }
+              .margin({ bottom: 32 })
+
+              // 空状态标题
+              Text('歌单还是空的')
+                .fontSize(22)
+                .fontColor($r('app.color.text_color'))
+                .fontWeight(FontWeight.Bold)
+                .margin({ bottom: 12 })
+                .letterSpacing(0.5)
+
+              // 空状态描述
+              Text('快来添加你喜欢的音乐吧\n让这个歌单充满美妙的旋律')
+                .fontSize(15)
+                .fontColor($r('app.color.text_color'))
+                .opacity(0.8)
+                .margin({ bottom: 40 })
+                .textAlign(TextAlign.Center)
+                .lineHeight(24)
+                .maxLines(2)
+
+              // 添加歌曲按钮
+              Button() {
+                Row({ space: 8 }) {
+                  Text('+')
+                    .fontSize(20)
+                    .fontColor(Color.White)
+
+                  Text('添加歌曲')
+                    .fontSize(16)
+                    .fontColor(Color.White)
+                    .fontWeight(FontWeight.Medium)
+                }
+                .justifyContent(FlexAlign.Center)
+              }
+              .width(160)
+              .height(48)
+              .backgroundColor($r('app.color.theme_color'))
+              .borderRadius(24)
+              .shadow({
+                radius: 12,
+                color: '#0a59f74d',
+                offsetX: 0,
+                offsetY: 6
+              })
+              .onClick(() => {
+                showAddSongsToPlaylistDialog(
+                  this.playlist!,
+                  async (songs: VideoItem[]) => {
+                    // 添加选中的歌曲到歌单
+                    const success = await this.playlistTable.addSongsToPlaylist(this.playlistId, songs.map(song => song.filePath))
+                    
+                    if (success) {
+                      ToastUtil.showToast(`成功添加 ${songs.length} 首歌曲`)
+                      // 重新加载歌单详情
+                      this.loadPlaylistDetail()
+                    } else {
+                      ToastUtil.showToast('添加歌曲失败')
+                    }
+                  }
+                )
+              })
+              .stateStyles({
+                normal: {
+                  .backgroundColor($r('app.color.theme_color'))
+                  .scale({ x: 1, y: 1 })
+                },
+                pressed: {
+                  .backgroundColor('#0a59f7cc')
+                  .scale({ x: 0.96, y: 0.96 })
+                }
+              })
+              .animation({
+                duration: 200,
+                curve: Curve.EaseInOut
+              })
+
+              // 快速操作提示
+              Text('或长按歌单选择更多操作')
+                .fontSize(13)
+                .fontColor($r('app.color.text_color'))
+                .margin({ top: 16 })
+                .opacity(0.7)
+            }
+            .width('100%')
+            .padding(32)
+            .backgroundColor($r('app.color.bg_card'))
+            .borderRadius(20)
+            .margin({ left: 16, right: 16 })
+            .shadow({
+              radius: 16,
+              color: '#0000000f',
+              offsetX: 0,
+              offsetY: 8
+            })
+            .opacity(this.pageOpacity)
+            .scale({ x: this.contentScale, y: this.contentScale })
+            .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
+            .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
           }
           .layoutWeight(1)
           .justifyContent(FlexAlign.Center)
+          .padding({ top: 20, bottom: 20 })
         }
       }
     }

+ 236 - 2
entry/src/main/ets/view/LocalMusic.ets

@@ -87,8 +87,22 @@ import { ABLoopComptent } from '../view/ABLoopComptent';
 import { FixMessyView } from '../view/FixMessyView';
 import { parseCueFile,CueTrack,CueInfo } from '../common/util/CueUtils';
 import { CueComptent } from '../view/CueComptent';
+import PlaylistTable from '../common/util/PlaylistTable';
+import { showAddToPlaylistDialog } from '../dialog/AddToPlaylistDialog';
+import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog';
 const TAG = 'LocalMusic';
 
+/**
+ * 简化的歌单播放事件数据
+ */
+interface SimplifiedPlaylistEventData {
+  playlistId: string;
+  playlistName: string;
+  songCount: number;
+  startIndex: number;
+  songFilePaths: string[];
+}
+
 const DEFAULT_INDEX =
   ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
     'X', 'Y', 'Z']
@@ -175,6 +189,8 @@ export struct LocalMusic {
   @Consume isHistory: boolean
   static readonly HISTORY_MUSIC: string = 'music_historyList';
   private table: MediaTable = new MediaTable(getContext(this))
+  private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
+  @State allPlaylists: Playlist[] = []
   @State isZero: boolean = false
   @State fileList: Array<string> = []
   @State dirList: Array<VideoItem> = []
@@ -553,10 +569,45 @@ export struct LocalMusic {
     // 监听歌单播放请求事件
     let eventPlaylistPlay: emitter.InnerEvent = { eventId: 2002 }
     emitter.on(eventPlaylistPlay, (eventData: emitter.EventData) => {
+      Logger.info('heanup eventPlaylistPlay received - full eventData: ' + JSON.stringify(eventData))
+      Logger.info('heanup eventPlaylistPlay received - eventData.data: ' + JSON.stringify(eventData.data))
+
       const data = eventData.data as Record<string, Object>
+
+      if (!data) {
+        Logger.error('heanup eventPlaylistPlay: eventData.data is undefined or null')
+        return
+      }
+
+      // 检查简化数据结构
+      if (data.playlistId && data.songFilePaths && data.startIndex !== undefined) {
+        Logger.info('heanup eventPlaylistPlay: received simplified playlist data')
+        // 手动构建简化数据对象以避免类型转换问题
+        const simplifiedData: SimplifiedPlaylistEventData = {
+          playlistId: data.playlistId as string,
+          playlistName: data.playlistName as string,
+          songCount: data.songCount as number,
+          startIndex: data.startIndex as number,
+          songFilePaths: data.songFilePaths as string[]
+        }
+        // 根据文件路径重新构建歌曲列表
+        this.handleSimplifiedPlaylistPlayRequest(
+          simplifiedData.playlistId,
+          simplifiedData.playlistName,
+          simplifiedData.songFilePaths,
+          simplifiedData.startIndex
+        )
+        return
+      }
+
+      // 检查原始数据结构(向后兼容)
       if (data.playlist && data.songs && data.startIndex !== undefined) {
+        Logger.info('heanup eventPlaylistPlay: received original playlist data')
         this.handlePlaylistPlayRequest(data.playlist as Playlist, data.songs as VideoItem[], data.startIndex as number)
+        return
       }
+
+      Logger.error('heanup eventPlaylistPlay: invalid data structure received')
     });
 
     let eventSetting: emitter.InnerEvent = { eventId: 333 }
@@ -2570,6 +2621,61 @@ export struct LocalMusic {
     }
   }
 
+  /**
+   * 加载所有歌单
+   */
+  async loadAllPlaylists() {
+    try {
+      this.allPlaylists = await this.playlistTable.queryAllPlaylists()
+      LogUtil.info('Loaded playlists: ' + this.allPlaylists.length)
+    } catch (error) {
+      LogUtil.error('Failed to load playlists: ' + error.message)
+    }
+  }
+
+  /**
+   * 显示添加到歌单对话框
+   */
+  async showAddToPlaylistDialog(item: VideoItem) {
+    // 加载最新的歌单列表
+    await this.loadAllPlaylists()
+
+    showAddToPlaylistDialog(
+      item,
+      this.allPlaylists,
+      async (playlistId: string) => {
+        // 添加歌曲到歌单
+        const success = await this.playlistTable.addSongToPlaylist(playlistId, item.filePath)
+        if (success) {
+          ToastUtil.showToast('已添加到歌单')
+          // 重新加载歌单列表
+          await this.loadAllPlaylists()
+        } else {
+          ToastUtil.showToast('歌曲已在该歌单中')
+        }
+      },
+      () => {
+        LogUtil.info('取消添加到歌单')
+      },
+      () => {
+        // 创建新歌单
+        showCreatePlaylistDialog(
+          async (name: string, description: string) => {
+            const success = await this.playlistTable.createPlaylist(name, description)
+            if (success) {
+              ToastUtil.showToast('歌单创建成功')
+              // 重新加载歌单列表并打开添加对话框
+              await this.loadAllPlaylists()
+              await this.showAddToPlaylistDialog(item)
+            } else {
+              ToastUtil.showToast('歌单创建失败')
+            }
+          }
+        )
+      }
+    )
+  }
+
   doFav(item: VideoItem) {
     LogUtil.info('onecold doFav isFav=' + item.isFav)
 
@@ -4524,6 +4630,16 @@ export struct LocalMusic {
                 this.longItemFilePath = ''
               })
 
+            MenuItem({
+              symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.music_note_list')),
+              content: '添加到歌单'
+            })
+              .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible)
+              .onClick(async () => {
+                this.longItemFilePath = ''
+                await this.showAddToPlaylistDialog(item)
+              })
+
             MenuItem({
               symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')),
               content: '编辑标签'
@@ -5001,6 +5117,7 @@ export struct LocalMusic {
   }
 
   private async doPlay(item: VideoItem, index?: number, isFromSonPlayList?: boolean,isOpen?:boolean) {
+    Logger.info(`heanup doPlay被调用 - 歌曲: ${item.name}, 文件路径: ${item.filePath}, 索引: ${index}, 类型: ${item.type}, isFromSonPlayList: ${isFromSonPlayList}`)
 
     switch (item.type) {
       case CommonConstants.TYPE_IS_DIR:
@@ -5226,6 +5343,8 @@ export struct LocalMusic {
           if (index !== undefined) {
             this.curIndex = index
           }
+          // 确保播放列表是完整的歌单歌曲列表(已在finishLoadingPlaylist中设置)
+          Logger.info(`heanup isFromSonPlayList分支 - 当前播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`)
         }else {
           let globalVideoList = Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL) as VideoItem[];
           this.curIndex = Utility.getCurIndexFromGlobalList(globalVideoList, item.filePath)
@@ -11910,6 +12029,21 @@ export struct LocalMusic {
 
   public setIsPlaying(isPlayer: boolean) {
     this.isPlaying = isPlayer;
+
+    // 发送播放状态变化事件给歌单详情页面
+    try {
+      const eventPlaybackStatus: emitter.InnerEvent = { eventId: 2003 }
+      const eventData: emitter.EventData = {
+        data: {
+          isPlaying: this.isPlaying,
+          curIndex: this.curIndex
+        }
+      };
+      emitter.emit(eventPlaybackStatus, eventData)
+      Logger.info(`heanup 发送播放状态变化事件: isPlaying=${this.isPlaying}, curIndex=${this.curIndex}`)
+    } catch (error) {
+      Logger.error('heanup 发送播放状态变化事件失败: ' + error)
+    }
   }
 
   private setPlaybackStateChangeListener(): void {
@@ -12907,12 +13041,112 @@ export struct LocalMusic {
     // 4. 保存播放列表
     PreferencesUtil.putSync('LastMusicList', this.songList)
 
-    console.info(`开始播放歌单: ${playlist.name}, 歌曲数量: ${songs.length}, 开始索引: ${startIndex}`)
+    console.info(`heanup 开始播放歌单: ${playlist.name}, 歌曲数量: ${songs.length}, 开始索引: ${startIndex}`)
   } catch (error) {
-    console.error('处理歌单播放请求失败:', error)
+    console.error('heanup 处理歌单播放请求失败:', error)
     ToastUtil.showToast('播放失败')
   }
 }
+
+  /**
+   * 处理简化的歌单播放请求
+   */
+  private handleSimplifiedPlaylistPlayRequest(playlistId: string, playlistName: string, songFilePaths: string[], startIndex: number) {
+    try {
+      Logger.info(`heanup 处理简化歌单播放请求: ${playlistName}, 歌曲数量: ${songFilePaths.length}, 开始索引: ${startIndex}`)
+
+      // 从数据库重新加载这些歌曲
+      const songs: VideoItem[] = []
+      let completedQueries = 0
+
+      Logger.info(`heanup 开始从数据库查询 ${songFilePaths.length} 首歌曲`)
+
+      for (const filePath of songFilePaths) {
+        Logger.info(`heanup 正在查询歌曲: ${filePath}`)
+        // 使用已初始化的MediaTable实例从数据库查询歌曲信息
+        const queryPromise = this.table.queryVideoByFilePath(filePath)
+        Logger.info(`heanup 创建了查询Promise,开始等待结果: ${filePath}`)
+
+        queryPromise.then((videoItem) => {
+          completedQueries++
+          Logger.info(`heanup 查询Promise返回结果: ${filePath}, 找到歌曲: ${videoItem ? videoItem.name : 'null'}`)
+
+          if (videoItem) {
+            songs.push(videoItem)
+            Logger.info(`heanup 从数据库找到歌曲: ${videoItem.name} (${completedQueries}/${songFilePaths.length})`)
+          } else {
+            Logger.warn(`heanup 数据库中未找到歌曲: ${filePath} (${completedQueries}/${songFilePaths.length})`)
+          }
+
+          // 检查是否所有歌曲都已加载完成
+          if (completedQueries === songFilePaths.length) {
+            Logger.info(`heanup 所有数据库查询完成,共找到 ${songs.length} 首歌曲`)
+            this.finishLoadingPlaylist(songs, startIndex, playlistName)
+          }
+        }).catch((error: Error) => {
+          completedQueries++
+          Logger.error(`heanup 查询歌曲失败: ${filePath}, 错误: ${error.message} (${completedQueries}/${songFilePaths.length})`)
+
+          // 即使出错也要检查是否完成所有查询
+          if (completedQueries === songFilePaths.length) {
+            Logger.info(`heanup 所有数据库查询完成(包含错误),共找到 ${songs.length} 首歌曲`)
+            this.finishLoadingPlaylist(songs, startIndex, playlistName)
+          }
+        })
+      }
+    } catch (error) {
+      Logger.error('heanup 处理简化歌单播放请求失败: ' + error)
+      ToastUtil.showToast('播放失败')
+    }
+  }
+
+  /**
+   * 完成歌单加载并开始播放
+   */
+  private finishLoadingPlaylist(songs: VideoItem[], startIndex: number, playlistName: string) {
+    if (songs.length === 0) {
+      Logger.error('heanup 没有找到任何可播放的歌曲')
+      ToastUtil.showToast('没有找到可播放的歌曲')
+      return
+    }
+
+    Logger.info(`heanup 找到 ${songs.length} 首可播放的歌曲`)
+
+    // 1. 替换当前播放列表并更新存储
+    Logger.info(`heanup 更新前 - 当前播放列表长度: ${this.songList.length}`)
+    this.songList = songs
+    // 确保存储也更新(@StorageLink会自动同步)
+    Logger.info(`heanup 更新后 - 新播放列表长度: ${this.songList.length}`)
+
+    // 2. 更新数据源
+    Logger.info(`heanup 更新前 - 数据源长度: ${this.sonDataSource.totalCount()}`)
+    this.sonDataSource.pushArrayData(songs)
+    Logger.info(`heanup 更新后 - 数据源长度: ${this.sonDataSource.totalCount()}`)
+
+    // 3. 强制触发UI重新渲染
+    Logger.info('heanup 触发UI重新渲染')
+    this.sonDataSource.notifyDataReload()
+
+    // 4. 设置当前播放索引
+    this.curIndex = startIndex
+
+    // 5. 播放指定歌曲
+    if (songs[startIndex]) {
+      Logger.info(`heanup 开始播放歌曲: ${songs[startIndex].name}, 索引: ${startIndex}`)
+      Logger.info(`heanup 播放前 - 播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`)
+      // 确保当前播放的歌曲也更新到存储
+      AppStorage.setOrCreate('currentSong', songs[startIndex])
+      // 设置isFromSonPlayList=true,避免doPlay方法重新从全局列表构建播放列表
+      Logger.info(`heanup 调用doPlay,参数: 歌曲=${songs[startIndex].name}, 索引=${startIndex}, isFromSonPlayList=true`)
+      this.doPlay(songs[startIndex], startIndex, true)
+      Logger.info(`heanup doPlay调用完成`)
+    }
+
+    // 5. 保存播放列表
+    PreferencesUtil.putSync('LastMusicList', this.songList)
+
+    ToastUtil.showToast(`开始播放歌单: ${playlistName}`)
+  }
 }
 //视频气泡窗口的布局
 @Builder

+ 0 - 1
entry/src/main/ets/view/TitleBar.ets

@@ -509,7 +509,6 @@ export namespace TitleBar {
 
     setTitleBarBackground(value: ResourceColor): Model {
       this.titleBarBackground = value;
-      console.log('Heanup: TitleBar背景色:'+JSON.stringify(value))
       return this;
     }