Просмотр исходного кода

feat(playlist): 增强歌单管理功能和深色模式支持

chendeben 9 месяцев назад
Родитель
Сommit
228adbdcb0

+ 117 - 10
entry/src/main/ets/common/util/PlaylistTable.ets

@@ -184,12 +184,18 @@ export default class PlaylistTable {
       const playlists: Playlist[] = [];
       if (resultSet.goToFirstRow()) {
         do {
+          const playlistId = resultSet.getString(resultSet.getColumnIndex('id'));
+          const playlistName = resultSet.getString(resultSet.getColumnIndex('name'));
+          const songCount = resultSet.getLong(resultSet.getColumnIndex('songCount'));
+
+          Logger.info('heanup PlaylistTable', `queryAllPlaylists: 从数据库读取 - ID: ${playlistId}, 名称: ${playlistName}, 歌曲数: ${songCount}`);
+
           const playlist = new Playlist(
-            resultSet.getString(resultSet.getColumnIndex('id')),
-            resultSet.getString(resultSet.getColumnIndex('name')),
+            playlistId,
+            playlistName,
             resultSet.getString(resultSet.getColumnIndex('createTime')),
             resultSet.getString(resultSet.getColumnIndex('updateTime')),
-            resultSet.getLong(resultSet.getColumnIndex('songCount')),
+            songCount,
             resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
             resultSet.getString(resultSet.getColumnIndex('coverPath')),
             resultSet.getString(resultSet.getColumnIndex('description'))
@@ -343,24 +349,81 @@ export default class PlaylistTable {
    */
   async removeSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
     if (!this.rdbStore) {
+      Logger.error('heanup PlaylistTable', 'removeSongFromPlaylist: 数据库未初始化');
       return false;
     }
 
     try {
+      Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 开始删除歌曲 ${songFilePath} 从歌单 ${playlistId}`);
+
       const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
       await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
-      
+      Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 数据库删除操作完成`);
+
       // 更新歌单歌曲数量
+      Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 开始更新歌单数量`);
       await this.updatePlaylistSongCount(playlistId);
-      
-      Logger.info('heanup PlaylistTable', `歌曲从歌单移除成功: ${songFilePath}`);
+
+      Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 歌曲从歌单移除成功: ${songFilePath}`);
       return true;
     } catch (error) {
-      Logger.error('heanup PlaylistTable', `从歌单移除歌曲失败: ${error.message}`);
+      Logger.error('heanup PlaylistTable', `removeSongFromPlaylist: 从歌单移除歌曲失败: ${error.message}`);
       return false;
     }
   }
 
+  /**
+   * 从所有歌单中移除指定歌曲(当歌曲文件被删除时调用)
+   */
+  async removeSongFromAllPlaylists(songFilePath: string): Promise<string[]> {
+    if (!this.rdbStore) {
+      Logger.error('heanup PlaylistTable', 'removeSongFromAllPlaylists: 数据库未初始化');
+      return [];
+    }
+
+    try {
+      Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 开始处理歌曲 ${songFilePath}`);
+
+      // 1. 先查询这首歌在哪些歌单中
+      const sql = 'SELECT DISTINCT playlistId FROM playlistSongTable WHERE songFilePath = ?';
+      const resultSet = await this.rdbStore.querySql(sql, [songFilePath]);
+
+      const affectedPlaylistIds: string[] = [];
+      if (resultSet.goToFirstRow()) {
+        do {
+          const playlistId = resultSet.getString(0);
+          affectedPlaylistIds.push(playlistId);
+          Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 找到歌单 ${playlistId}`);
+        } while (resultSet.goToNextRow());
+      }
+      resultSet.close();
+
+      if (affectedPlaylistIds.length === 0) {
+        Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲不在任何歌单中: ${songFilePath}`);
+        return [];
+      }
+
+      Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲在 ${affectedPlaylistIds.length} 个歌单中`);
+
+      // 2. 从所有歌单中删除这首歌
+      const deleteSql = 'DELETE FROM playlistSongTable WHERE songFilePath = ?';
+      await this.rdbStore.executeSql(deleteSql, [songFilePath]);
+      Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 已从数据库删除歌曲记录`);
+
+      // 3. 更新所有受影响歌单的歌曲数量
+      for (const playlistId of affectedPlaylistIds) {
+        Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 开始更新歌单 ${playlistId} 的数量`);
+        await this.updatePlaylistSongCount(playlistId);
+      }
+
+      Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲已从 ${affectedPlaylistIds.length} 个歌单中移除: ${songFilePath}`);
+      return affectedPlaylistIds;
+    } catch (error) {
+      Logger.error('heanup PlaylistTable', `removeSongFromAllPlaylists: 从所有歌单移除歌曲失败: ${error.message}`);
+      return [];
+    }
+  }
+
   /**
    * 查询歌单中的歌曲
    */
@@ -439,24 +502,68 @@ export default class PlaylistTable {
    */
   private async updatePlaylistSongCount(playlistId: string): Promise<void> {
     if (!this.rdbStore) {
+      Logger.error('heanup PlaylistTable', 'updatePlaylistSongCount: 数据库未初始化');
       return;
     }
 
     try {
+      Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 开始更新歌单 ${playlistId} 的歌曲数量`);
+
       const countSql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ?';
       const resultSet = await this.rdbStore.querySql(countSql, [playlistId]);
-      
+
       let count = 0;
       if (resultSet.goToFirstRow()) {
         count = resultSet.getLong(resultSet.getColumnIndex('count'));
       }
       resultSet.close();
-      
+
+      Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 统计到 ${count} 首歌曲`);
+
       const updateSql = 'UPDATE playlistTable SET songCount = ?, updateTime = ? WHERE id = ?';
       const updateTime = new Date().toISOString();
       await this.rdbStore.executeSql(updateSql, [count, updateTime, playlistId]);
+
+      Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 数据库更新成功,songCount = ${count}`);
     } catch (error) {
-      Logger.error('heanup PlaylistTable', `更新歌单歌曲数量失败: ${error.message}`);
+      Logger.error('heanup PlaylistTable', `updatePlaylistSongCount: 更新歌单歌曲数量失败: ${error.message}`);
+    }
+  }
+
+  /**
+   * 强制同步歌单歌曲数量(公开方法,用于修复数据不一致)
+   */
+  async syncPlaylistSongCount(playlistId: string): Promise<void> {
+    await this.ensureInitialized();
+    await this.updatePlaylistSongCount(playlistId);
+    Logger.info('heanup PlaylistTable', `已强制同步歌单 ${playlistId} 的歌曲数量`);
+  }
+
+  /**
+   * 清理歌单中无效的歌曲记录(歌曲文件已不存在)
+   */
+  async removeInvalidSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      Logger.error('heanup PlaylistTable', 'removeInvalidSongFromPlaylist: 数据库未初始化');
+      return false;
+    }
+
+    try {
+      Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 开始清理无效歌曲 ${songFilePath} 从歌单 ${playlistId}`);
+
+      const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
+      await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
+      Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 数据库删除操作完成`);
+
+      // 更新歌单歌曲数量
+      Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 开始更新歌单数量`);
+      await this.updatePlaylistSongCount(playlistId);
+
+      Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 无效歌曲从歌单清理成功: ${songFilePath}`);
+      return true;
+    } catch (error) {
+      Logger.error('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 清理无效歌曲失败: ${error.message}`);
+      return false;
     }
   }
 

+ 40 - 16
entry/src/main/ets/dialog/AddSongsToPlaylistDialog.ets

@@ -5,9 +5,22 @@ import { VideoItem } from '../viewmodel/VideoItem';
 import MediaTable from '../common/util/MediaTable';
 import PlaylistTable from '../common/util/PlaylistTable';
 import { LazyDataSource } from '../common/util/LazyDataSource';
-import { common } from '@kit.AbilityKit';
+import { ConfigurationConstant, common } from '@kit.AbilityKit';
 import { CommonConstants } from '../common/constants/CommonConstants';
 
+// 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
+function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
+  if (isDarkMode) {
+    // 深色模式下返回更深的灰色或半透明黑色
+    return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`;
+  }
+  const color = themeColor.replace('#', '');
+  const r = parseInt(color.substring(0, 2), 16);
+  const g = parseInt(color.substring(2, 4), 16);
+  const b = parseInt(color.substring(4, 6), 16);
+  return `rgba(${r},${g},${b},${alpha})`;
+}
+
 /**
  * 添加歌曲到歌单对话框内容组件
  */
@@ -16,6 +29,13 @@ struct AddSongsToPlaylistDialogContent {
   context = this.getUIContext().getHostContext() as common.UIAbilityContext
   @State opacityItem: number = 1; // 控制透明度的状态变量
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
+  @State isDarkMode: boolean = false
+  @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
+    ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
+
+  onColorModeChange() {
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
+  }
   @State selectedSongs: VideoItem[] = []
   @State isLoading: boolean = false
   @State searchText: string = ''
@@ -38,7 +58,10 @@ struct AddSongsToPlaylistDialogContent {
   onCancel?: () => void
 
   aboutToAppear() {
+    // 初始化深色模式状态
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
     LogUtil.info('heanup  AddSongsToPlaylistDialogContent aboutToAppear 开始')
+    LogUtil.info('heanup  初始化深色模式状态: ' + this.isDarkMode + ', currentMode: ' + this.currentMode)
     LogUtil.info('heanup  playlist: ' +
       (this.playlist ? JSON.stringify({ id: this.playlist.id, name: this.playlist.name }) : 'null'))
     console.info('onecold  mediaKuList 对话框 aboutToAppear=' + this.mediaKuList.length)
@@ -150,17 +173,18 @@ struct AddSongsToPlaylistDialogContent {
         Image($r('app.media.ic_action_search'))
           .width(20)
           .height(20)
-          .fillColor($r('app.color.text_color'))
+          .fillColor(this.isDarkMode ? '#8E8E93' : $r('app.color.text_color'))
           .opacity(0.6)
 
         TextInput({ placeholder: '搜索歌曲、歌手或专辑', text: this.searchText })
           .layoutWeight(1)
           .height(35)
-          .backgroundColor($r('app.color.input_background'))
+          .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
           .borderRadius(8)
           .padding({ left: 8, right: 8 })
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+          .placeholderColor(this.isDarkMode ? '#8E8E93' : '#999999')
           .onChange((value: string) => {
             this.searchText = value
             this.filterSongs()
@@ -168,7 +192,7 @@ struct AddSongsToPlaylistDialogContent {
       }
       .width('100%')
       .padding(12)
-      .backgroundColor($r('app.color.input_background'))
+      .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
       .borderRadius(20)
 
       // 已选择歌曲数量
@@ -176,7 +200,7 @@ struct AddSongsToPlaylistDialogContent {
         Row() {
           Text(`已选择 ${this.selectedSongs.length}  首歌曲`)
             .fontSize(14)
-            .fontColor(this.themeColor)
+            .fontColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
             .fontWeight(FontWeight.Medium)
 
           Blank()
@@ -184,7 +208,7 @@ struct AddSongsToPlaylistDialogContent {
           Button(this.selectedSongs.length === this.dataSource.dataArray.length
             && this.dataSource.dataArray.length > 0 ? '全不选' : '全选')
             .fontSize(12)
-            .fontColor($r('app.color.text_color'))
+            .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
             .backgroundColor(Color.Transparent)
             .height(30)
             .padding({ left: 8, right: 8 })
@@ -201,7 +225,7 @@ struct AddSongsToPlaylistDialogContent {
 
           Button('清空')
             .fontSize(12)
-            .fontColor($r('app.color.text_color'))
+            .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
             .backgroundColor(Color.Transparent)
             .height(30)
             .padding({ left: 8, right: 8 })
@@ -223,7 +247,7 @@ struct AddSongsToPlaylistDialogContent {
 
           Text(this.searchText ? '没有找到匹配的歌曲' : '没有可添加的歌曲')
             .fontSize(14)
-            .fontColor('#999999')
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
         }
         .width('100%')
         .height(300)
@@ -249,7 +273,7 @@ struct AddSongsToPlaylistDialogContent {
         Button(`添加${this.selectedSongs.length > 0 ? `(${this.selectedSongs.length})` : ''}`)
           .width('45%')
           .height(40)
-          .backgroundColor(this.themeColor)
+          .backgroundColor(this.isDarkMode ? themeColorWithAlpha(this.themeColor, 0.8, this.isDarkMode) : this.themeColor)
           .borderRadius(8)
           .fontSize(14)
           .fontColor(Color.White)
@@ -265,7 +289,7 @@ struct AddSongsToPlaylistDialogContent {
     }
     .width('100%')
     .constraintSize({ maxWidth: 400 })
-    .backgroundColor($r('app.color.dialog_background'))
+    .backgroundColor(this.isDarkMode ? '#1C1C1E' : $r('app.color.dialog_background'))
     .borderRadius(12)
     .padding({ left: 20, right: 20 })
   }
@@ -293,7 +317,7 @@ struct AddSongsToPlaylistDialogContent {
               Row({ space: 12 }) {
 
                 Image(StrUtil.isEmpty(song.pixelMapPath) ? $r('app.media.music_red') : song.pixelMapPath)
-                  .fillColor(this.themeColor)
+                  .fillColor(StrUtil.isEmpty(song.pixelMapPath) ? (this.isDarkMode ? Color.White : this.themeColor) : undefined)
                   .height(40)
                   .width(40)
                   .alt($r('app.media.music_red'))
@@ -307,7 +331,7 @@ struct AddSongsToPlaylistDialogContent {
                 Column({ space: 4 }) {
                   Text(song.name || '未知歌曲')
                     .fontSize(14)
-                    .fontColor($r('app.color.text_color'))
+                    .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
                     .maxLines(1)
                     .textOverflow({ overflow: TextOverflow.Ellipsis })
                     .width('100%')
@@ -316,7 +340,7 @@ struct AddSongsToPlaylistDialogContent {
                     if (song.artist) {
                       Text(song.artist+'  '+song.duration)
                         .fontSize(12)
-                        .fontColor('#999999')
+                        .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
                         .maxLines(1)
                         .textOverflow({ overflow: TextOverflow.Ellipsis })
                         .layoutWeight(1)
@@ -402,7 +426,7 @@ struct AddSongsToPlaylistDialogContent {
             .width(20)
           Text('加载中...')
             .fontSize(12)
-            .fontColor('#999999')
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
             .margin({ left: 8 })
         }
         .width('100%')
@@ -412,7 +436,7 @@ struct AddSongsToPlaylistDialogContent {
         Row() {
           Text('上拉加载更多')
             .fontSize(12)
-            .fontColor('#999999')
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
         }
         .width('100%')
         .height(40)

+ 44 - 15
entry/src/main/ets/dialog/AddToPlaylistDialog.ets

@@ -2,6 +2,21 @@ import { DialogHelper } from '@pura/harmony-dialog';
 import { Playlist } from '../viewmodel/Playlist';
 import { ToastUtil } from '@pura/harmony-utils';
 import { VideoItem } from '../viewmodel/VideoItem';
+import { CommonConstants } from '../common/constants/CommonConstants';
+import { ConfigurationConstant } from '@kit.AbilityKit';
+
+// 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
+function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
+  if (isDarkMode) {
+    // 深色模式下返回更深的灰色或半透明黑色
+    return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`;
+  }
+  const color = themeColor.replace('#', '');
+  const r = parseInt(color.substring(0, 2), 16);
+  const g = parseInt(color.substring(2, 4), 16);
+  const b = parseInt(color.substring(4, 6), 16);
+  return `rgba(${r},${g},${b},${alpha})`;
+}
 
 /**
  * 添加到歌单对话框内容组件
@@ -11,12 +26,25 @@ struct AddToPlaylistDialogContent {
   @State playlists: Playlist[] = []
   @State selectedPlaylistId: string = ''
   private currentSong?: VideoItem
+  @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
+  @State isDarkMode: boolean = false
+  @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
+    ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
+
+  onColorModeChange() {
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
+  }
 
   // 回调函数
   onConfirm?: (playlistId: string) => void
   onCancel?: () => void
   onCreateNew?: () => void
 
+  aboutToAppear() {
+    // 初始化深色模式状态
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
+  }
+
   build() {
     Column({ space: 16 }) {
       // 标题
@@ -40,13 +68,13 @@ struct AddToPlaylistDialogContent {
           Column({ space: 4 }) {
             Text(this.currentSong.name || '未知歌曲')
               .fontSize(14)
-              .fontColor($r('app.color.text_color'))
+              .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
               .maxLines(1)
               .textOverflow({ overflow: TextOverflow.Ellipsis })
 
             Text(this.currentSong.artist || '未知艺术家')
               .fontSize(12)
-              .fontColor('#999999')
+              .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
               .maxLines(1)
               .textOverflow({ overflow: TextOverflow.Ellipsis })
           }
@@ -55,7 +83,7 @@ struct AddToPlaylistDialogContent {
         }
         .width('100%')
         .padding(12)
-        .backgroundColor($r('app.color.input_background'))
+        .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
         .borderRadius(8)
       }
 
@@ -65,16 +93,16 @@ struct AddToPlaylistDialogContent {
           Image($r('sys.symbol.plus'))
             .width(20)
             .height(20)
-            .fillColor($r('app.color.theme_color'))
+            .fillColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
 
           Text('创建新歌单')
             .fontSize(14)
-            .fontColor($r('app.color.theme_color'))
+            .fontColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
         }
       }
       .width('100%')
       .height(44)
-      .backgroundColor($r('app.color.input_background'))
+      .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
       .borderRadius(8)
       .onClick(() => {
         this.onCreateNew?.()
@@ -91,7 +119,7 @@ struct AddToPlaylistDialogContent {
       if (this.playlists.length > 0) {
         Text('选择歌单')
           .fontSize(14)
-          .fontColor('#999999')
+          .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
           .alignSelf(ItemAlign.Start)
 
         Scroll() {
@@ -109,13 +137,13 @@ struct AddToPlaylistDialogContent {
                 Column({ space: 4 }) {
                   Text(playlist.name)
                     .fontSize(14)
-                    .fontColor($r('app.color.text_color'))
+                    .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
                     .maxLines(1)
                     .textOverflow({ overflow: TextOverflow.Ellipsis })
 
                   Text(`${playlist.songCount} 首歌曲`)
                     .fontSize(12)
-                    .fontColor('#999999')
+                    .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
                 }
                 .alignItems(HorizontalAlign.Start)
                 .layoutWeight(1)
@@ -125,13 +153,14 @@ struct AddToPlaylistDialogContent {
                   Image($r('sys.symbol.checkmark'))
                     .width(20)
                     .height(20)
-                    .fillColor($r('app.color.theme_color'))
+                    .fillColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
                 }
               }
               .width('100%')
               .padding(12)
               .backgroundColor(this.selectedPlaylistId === playlist.id ?
-                '#E6F0FF' : $r('app.color.input_background'))
+                themeColorWithAlpha(this.themeColor, 0.1, this.isDarkMode) :
+                (this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')))
               .borderRadius(8)
               .onClick(() => {
                 this.selectedPlaylistId = playlist.id
@@ -151,11 +180,11 @@ struct AddToPlaylistDialogContent {
 
           Text('暂无歌单')
             .fontSize(14)
-            .fontColor('#999999')
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
 
           Text('点击上方按钮创建第一个歌单吧')
             .fontSize(12)
-            .fontColor('#999999')
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
         }
         .width('100%')
         .padding(20)
@@ -179,7 +208,7 @@ struct AddToPlaylistDialogContent {
         Button('添加')
           .width('45%')
           .height(40)
-          .backgroundColor($r('app.color.theme_color'))
+          .backgroundColor(this.isDarkMode ? themeColorWithAlpha(this.themeColor, 0.8, this.isDarkMode) : this.themeColor)
           .borderRadius(8)
           .fontSize(14)
           .fontColor(Color.White)
@@ -195,7 +224,7 @@ struct AddToPlaylistDialogContent {
     }
     .width('90%')
     .constraintSize({ maxWidth: 400 })
-    .backgroundColor($r('app.color.dialog_background'))
+    .backgroundColor(this.isDarkMode ? '#1C1C1E' : $r('app.color.dialog_background'))
     .borderRadius(12)
     .padding({ left: 20, right: 20 })
   }

+ 25 - 16
entry/src/main/ets/dialog/PlaylistDialog.ets

@@ -43,6 +43,10 @@ struct PlaylistDialogContent {
   onCancel?: () => void
 
   aboutToAppear() {
+    // 初始化深色模式状态
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
+    Logger.info('heanup PlaylistDialog', `初始化深色模式状态: ${this.isDarkMode}, currentMode: ${this.currentMode}`)
+
     if (this.originalPlaylist) {
       this.isEditMode = true
       this.playlistName = this.originalPlaylist.name
@@ -69,7 +73,8 @@ struct PlaylistDialogContent {
       Column({ space: 8 }) {
         Text('歌单封面(可选)')
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
+          .fontWeight(FontWeight.Medium)
           .alignSelf(ItemAlign.Start)
 
         // 封面选择区域
@@ -88,11 +93,11 @@ struct PlaylistDialogContent {
                 Image($r('app.media.hm_music'))
                   .width(40)
                   .height(40)
-                  .fillColor($r('app.color.text_color'))
+                  .fillColor(this.isDarkMode ? Color.White : this.themeColor)
               }
               .width(80)
               .height(80)
-              .backgroundColor($r('app.color.input_background'))
+              .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
               .borderRadius(8)
               .justifyContent(FlexAlign.Center)
             }
@@ -107,8 +112,8 @@ struct PlaylistDialogContent {
               .width(120)
               .height(36)
               .fontSize(12)
-              .fontColor($r('app.color.text_color'))
-              .backgroundColor($r('app.color.input_background'))
+              .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+              .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
               .onClick(() => {
                 this.handleSelectCover()
               })
@@ -118,8 +123,8 @@ struct PlaylistDialogContent {
                 .width(120)
                 .height(36)
                 .fontSize(12)
-                .fontColor(Color.Red)
-                .backgroundColor('#FFE5E5')
+                .fontColor(this.isDarkMode ? '#FF453A' : Color.Red)
+                .backgroundColor(this.isDarkMode ? 'rgba(255,69,58,0.2)' : '#FFE5E5')
                 .onClick(() => {
                   this.handleRemoveCover()
                 })
@@ -137,17 +142,19 @@ struct PlaylistDialogContent {
       Column({ space: 8 }) {
         Text('歌单名称')
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
+          .fontWeight(FontWeight.Medium)
           .alignSelf(ItemAlign.Start)
-        
+
         TextInput({ placeholder: '请输入歌单名称', text: this.playlistName })
           .width('100%')
           .height(40)
-          .backgroundColor($r('app.color.input_background'))
+          .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
           .borderRadius(8)
           .padding({ left: 12, right: 12 })
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+          .placeholderColor(this.isDarkMode ? '#8E8E93' : '#999999')
           .onChange((value: string) => {
             this.playlistName = value
           })
@@ -159,17 +166,19 @@ struct PlaylistDialogContent {
       Column({ space: 8 }) {
         Text('歌单描述(可选)')
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
+          .fontWeight(FontWeight.Medium)
           .alignSelf(ItemAlign.Start)
-        
+
         TextArea({ placeholder: '请输入歌单描述', text: this.playlistDescription })
           .width('100%')
           .height(80)
-          .backgroundColor($r('app.color.input_background'))
+          .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
           .borderRadius(8)
           .padding({ left: 12, right: 12, top: 8, bottom: 8 })
           .fontSize(14)
-          .fontColor($r('app.color.text_color'))
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+          .placeholderColor(this.isDarkMode ? '#8E8E93' : '#999999')
           .onChange((value: string) => {
             this.playlistDescription = value
           })
@@ -208,7 +217,7 @@ struct PlaylistDialogContent {
       .margin({ top: 20, bottom: 20 })
     }
     .width('100%')
-    .backgroundColor($r('app.color.dialog_background'))
+    .backgroundColor(this.isDarkMode ? '#1C1C1E' : $r('app.color.dialog_background'))
     .borderRadius(12)
     .padding({ left: 20, right: 20 })
   }

+ 11 - 5
entry/src/main/ets/pages/NewIndex.ets

@@ -215,6 +215,7 @@ struct NewIndex {
 
 
   onPageShow() {
+    LogUtil.info('heanup NewIndex', 'onPageShow 被调用,刷新歌单列表')
     // 加载歌单列表
     this.loadPlaylistList()
   }
@@ -290,6 +291,7 @@ struct NewIndex {
 
     // 监听歌单刷新事件
     emitter.on({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, (eventData: emitter.EventData) => {
+      LogUtil.info('heanup NewIndex', '收到歌单刷新事件,开始刷新歌单列表')
       this.loadPlaylistList()
     });
   }
@@ -1245,16 +1247,20 @@ struct NewIndex {
    */
   async loadPlaylistList() {
     try {
+      LogUtil.info('heanup NewIndex', '开始加载歌单列表')
       if (this.playlistTable) {
         const playlists = await this.playlistTable.queryAllPlaylists()
-        this.playlistList = playlists
-        console.info(`onecold 成功加载 ${playlists.length} 个歌单`)
-
+        // 强制触发UI更新
+        this.playlistList = [...playlists]
+        LogUtil.info('heanup NewIndex', `成功加载 ${playlists.length} 个歌单`)
+        playlists.forEach((playlist, index) => {
+          LogUtil.info('heanup NewIndex', `歌单${index + 1}: ${playlist.name}, 歌曲数: ${playlist.songCount}`)
+        })
       } else {
-        console.warn('onecold 歌单表未初始化')
+        LogUtil.warn('heanup NewIndex', '歌单表未初始化')
       }
     } catch (error) {
-      console.error('加载歌单列表失败:', error)
+      LogUtil.error('heanup NewIndex', `加载歌单列表失败: ${(error as Error).message}`)
     }
   }
 }

+ 78 - 7
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -103,6 +103,9 @@ export struct PlaylistDetailPage {
   }
 
   aboutToAppear() {
+    // 初始化深色模式状态
+    this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
+    LogUtil.info('heanup PlaylistDetailPage', `初始化深色模式状态: ${this.isDarkMode}, currentMode: ${this.currentMode}`)
 
     // 获取传入的歌单对象
     const params = router.getParams() as Record<string, Object>
@@ -150,6 +153,27 @@ export struct PlaylistDetailPage {
       // 将 PlaylistSong 转换为 VideoItem
       this.songList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
 
+      // 同步更新歌单的歌曲数量(确保与实际歌曲列表一致)
+      if (this.playlist) {
+        const oldCount = this.playlist.songCount
+        const actualCount = this.songList.length
+
+        if (oldCount !== actualCount) {
+          LogUtil.warn('heanup PlaylistDetailPage', `检测到歌单数量不一致!数据库记录: ${oldCount}, 实际歌曲: ${actualCount}`)
+
+          // 强制同步数据库中的歌曲数量
+          await this.playlistTable.syncPlaylistSongCount(this.playlistId)
+
+          // 发送刷新事件,通知主页面更新
+          const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }
+          emitter.emit(eventRefresh, {})
+          LogUtil.info('heanup PlaylistDetailPage', '已强制同步数据库并发送刷新事件')
+        }
+
+        this.playlist.songCount = actualCount
+        LogUtil.info('heanup PlaylistDetailPage', `加载歌单详情完成: ${this.playlist.name}, 实际歌曲数: ${actualCount}`)
+      }
+
       this.sonDataSource.pushArrayData(this.songList)
       // 歌单加载完成后,加载当前播放状态
       this.loadCurrentPlaybackStatus()
@@ -450,12 +474,41 @@ export struct PlaylistDetailPage {
         if (index !== -1) {
           this.songList.splice(index, 1)
           this.songList = [...this.songList] // 触发UI更新
+          this.sonDataSource.pushArrayData(this.songList) // 更新数据源
         }
-        
-        // 更新歌单信息
+
+        // 更新本地歌单歌曲数量(数据库的songCount已在removeSongFromPlaylist中通过updatePlaylistSongCount更新)
         this.playlist.songCount = this.songList.length
-        await this.playlistTable.updatePlaylist(this.playlist.id, this.playlist.name, this.playlist.description)
-        
+
+        // 发送歌单刷新事件,通知主页面更新歌单数量
+        const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }
+        emitter.emit(eventRefresh, {})
+        LogUtil.info('heanup PlaylistDetailPage', `已发送歌单刷新事件,歌单: ${this.playlist.name}, 当前歌曲数: ${this.songList.length}`)
+
+        // 如果当前正在播放这个歌单,需要通知播放器更新播放列表
+        // 通过重新发送播放歌单事件,但不改变当前播放位置
+        const currentSong = AppStorage.get<VideoItem>('currentSong')
+        if (currentSong && this.curIndex >= 0) {
+          // 如果删除的是当前播放的歌曲
+          if (song.filePath === currentSong.filePath) {
+            LogUtil.info('heanup 删除的是当前播放的歌曲,播放器会自动切换到下一首')
+          }
+
+          // 更新播放列表(重新发送歌单播放事件,保持当前播放位置)
+          const playlistData: PlaylistEventData = {
+            playlistId: this.playlist.id,
+            playlistName: this.playlist.name,
+            songCount: this.songList.length,
+            startIndex: this.curIndex >= this.songList.length ? Math.max(0, this.songList.length - 1) : this.curIndex,
+            songFilePaths: this.songList.map(s => s.filePath)
+          }
+
+          const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }
+          const eventData: emitter.EventData = { data: playlistData }
+          emitter.emit(eventPlaylistPlay, eventData)
+          LogUtil.info('heanup 已更新播放列表,移除了被删除的歌曲')
+        }
+
         ToastUtil.showToast('已从歌单移除')
       } else {
         ToastUtil.showToast('移除失败')
@@ -1366,6 +1419,8 @@ export async function convertPlaylistSongsToVideoItems(context: Context,playlist
   LogUtil.info(`heanup 开始转换歌曲,共 ${playlistSongs.length} 首`)
 
   const mediaTable: MediaTable = new MediaTable(context)
+  const playlistTable: PlaylistTable = new PlaylistTable(context)
+
   try {
     await new Promise<void>((resolve, reject) => {
       mediaTable.getRdbStore(context,  (err:Error) => {
@@ -1376,6 +1431,10 @@ export async function convertPlaylistSongsToVideoItems(context: Context,playlist
     LogUtil.error(`heanup 数据库连接失败: ${error}`)
     return [] // 返回空数组而不是崩溃
   }
+
+  // 用于记录被清理的无效歌曲数量
+  let cleanedCount = 0
+
   for (const playlistSong of playlistSongs) {
     try {
       LogUtil.info(`heanup 查询歌曲: ${playlistSong.songFilePath}`)
@@ -1387,15 +1446,27 @@ export async function convertPlaylistSongsToVideoItems(context: Context,playlist
         LogUtil.info(`heanup 找到歌曲: ${videoItem.name}`)
         videoItems.push(videoItem)
       } else {
-        LogUtil.warn(`heanup 数据库中未找到歌曲,已被删除,不添加到歌单: ${playlistSong.songFilePath}`)
-        // 不再添加不存在的歌曲到列表中,只记录日志
+        LogUtil.warn(`heanup 数据库中未找到歌曲,已被删除,开始清理: ${playlistSong.songFilePath}`)
+
+        // 自动清理歌单中的无效歌曲记录
+        const cleaned = await playlistTable.removeInvalidSongFromPlaylist(
+          playlistSong.playlistId,
+          playlistSong.songFilePath
+        )
+
+        if (cleaned) {
+          cleanedCount++
+          LogUtil.info(`heanup 成功清理无效歌曲记录: ${playlistSong.songFilePath}`)
+        } else {
+          LogUtil.error(`heanup 清理无效歌曲记录失败: ${playlistSong.songFilePath}`)
+        }
       }
     } catch (error) {
       LogUtil.error('heanup 转换歌曲失败: ' + error)
     }
   }
 
-  LogUtil.info(`heanup 转换完成,共 ${videoItems.length} 首歌曲(原始 ${playlistSongs.length} 首)`)
+  LogUtil.info(`heanup 转换完成,共 ${videoItems.length} 首歌曲(原始 ${playlistSongs.length} 首,清理无效记录 ${cleanedCount} 首)`)
   return videoItems
 }
 

+ 43 - 7
entry/src/main/ets/view/DeleteComptent.ets

@@ -3,6 +3,9 @@ import { CommonConstants } from '../common/constants/CommonConstants';
 import { VideoItem } from '../viewmodel/VideoItem';
 import { ArrayUtil, FileUtil, LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
 import MediaTable from '../common/util/MediaTable';
+import PlaylistTable from '../common/util/PlaylistTable';
+import { EventConstants } from '../common/constants/EventConstants';
+import { emitter } from '@kit.BasicServicesKit';
 import { taskpool } from '@kit.ArkTS';
 
 // 批量删除
@@ -192,12 +195,18 @@ async function deleteMultipleFiles(
   const selectedFiles: VideoItem[] = JSON.parse(selectedFilesStr)
   if (ArrayUtil.isNotEmpty(selectedFiles)) {
     const table: MediaTable = new MediaTable(context)
+    const playlistTable: PlaylistTable = new PlaylistTable(context)
+
+    // 初始化数据库
     await new Promise<void>((resolve, reject) => {
       table.getRdbStore(context,  (err:Error) => {
         err ? reject(err) : resolve();
       });
     });
-    selectedFiles.forEach((item) => {
+
+    let hasPlaylistChanges = false; // 标记是否有歌单变化
+
+    for (const item of selectedFiles) {
       console.info('onecold delete filePath = ' + item.filePath);
       if (item.type === CommonConstants.TYPE_IS_DIR) {
         table.deleteDataForParentPath(item.filePath, () => {
@@ -211,17 +220,37 @@ async function deleteMultipleFiles(
 
 
       } else {
+        // 先从所有歌单中移除这首歌(如果存在的话)
+        LogUtil.info('heanup DeleteComptent', `准备从歌单中移除歌曲: ${item.name}, filePath: ${item.filePath}`);
+        if (item.filePath) {
+          try {
+            const affectedPlaylists = await playlistTable.removeSongFromAllPlaylists(item.filePath);
+            LogUtil.info('heanup DeleteComptent', `removeSongFromAllPlaylists 返回了 ${affectedPlaylists.length} 个受影响的歌单`);
+            if (affectedPlaylists.length > 0) {
+              hasPlaylistChanges = true;
+              LogUtil.info('heanup DeleteComptent', `歌曲已从 ${affectedPlaylists.length} 个歌单中移除: ${item.name}`);
+            } else {
+              LogUtil.info('heanup DeleteComptent', `歌曲不在任何歌单中: ${item.name}`);
+            }
+          } catch (error) {
+            LogUtil.error('heanup DeleteComptent', `从歌单移除歌曲失败: ${(error as Error).message}`);
+          }
+        } else {
+          LogUtil.warn('heanup DeleteComptent', `歌曲 ${item.name} 的 filePath 为空`);
+        }
+
         table.deleteData(item, async () => {
           if(isDeleteYuan){
             await FileUtil.unlink(item.filePath)
 
           }
           console.info(`onecold 封面文件=: ${item.pixelMapPath}`);
-          const picPath = FileUtil.getFilePath(item.pixelMapPath)//获取图
-          if (isDeletePicture && item.pixelMapPath &&
-          FileUtil.accessSync(picPath)) {
-            await FileUtil.unlink(picPath);
-            console.info(`onecold 封面文件已删除: ${picPath}`);
+          if (isDeletePicture && item.pixelMapPath) {
+            const picPath = FileUtil.getFilePath(item.pixelMapPath)//获取图
+            if (FileUtil.accessSync(picPath)) {
+              await FileUtil.unlink(picPath);
+              console.info(`onecold 封面文件已删除: ${picPath}`);
+            }
           }
           const lyricPath = item.filePath?.replace(/\.[^/.]+$/, ".lrc");
           console.info(`onecold 歌词文件=: ${lyricPath}`);
@@ -241,7 +270,14 @@ async function deleteMultipleFiles(
 
 
       }
-    });
+    }
+
+    // 如果有歌单变化,发送刷新事件
+    if (hasPlaylistChanges) {
+      const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH };
+      emitter.emit(eventRefresh, {});
+      LogUtil.info('heanup DeleteComptent', '已发送歌单刷新事件,更新歌单数量');
+    }
 
 
   }