Parcourir la source

feat(dialog): 歌单添加歌曲实现目录浏览与歌曲选择功能

chendeben il y a 9 mois
Parent
commit
6f6a48cb34

+ 539 - 128
entry/src/main/ets/dialog/AddSongsToPlaylistDialog.ets

@@ -10,6 +10,7 @@ import { CommonConstants } from '../common/constants/CommonConstants';
 import { SegmentButton } from '@kit.ArkUI';
 import { SegmentButtonOptions, SegmentButtonItemTuple } from '@kit.ArkUI';
 import { Utility } from '../common/util/Utility';
+import fs from '@ohos.file.fs';
 
 // 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
 function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
@@ -59,17 +60,22 @@ struct AddSongsToPlaylistDialogContent {
   private readonly PAGE_SIZE: number = 50
   // 用于存储完整数据的引用
   private allSongs: VideoItem[] = []
+  private baseDownloadPath: string = ''
   // 回调函数
   onConfirm?: (songs: VideoItem[]) => void
   onCancel?: () => void
   
   // 添加目录浏览相关状态
-  @State viewMode: number[] = [0]; // 0: 全部歌曲, 1: 目录模式
+  @State @Watch('onViewModeChange') viewMode: number[] = [0]; // 0: 全部歌曲, 1: 目录模式
   @State currentPath: string = '';
   @State folderItems: VideoItem[] = [];
   @State selectedFolders: Set<string> = new Set();
   @State isFolderLoading: boolean = false;
   private table: MediaTable = new MediaTable(getContext(this))
+  // 用于高效判断歌曲选中状态(避免在大列表中频繁遍历)
+  @State totalSelectedCount: number = 0;
+  private selectedSongIds: Set<string> = new Set();
+  private updateTimer: number = -1;
   // SegmentButton选项配置
   @State viewModeOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
     buttons: viewModeButtons,
@@ -80,7 +86,7 @@ struct AddSongsToPlaylistDialogContent {
     multiply: false
   });
 
-  aboutToAppear() {
+  async aboutToAppear() {
     // 初始化深色模式状态
     this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
     LogUtil.info('heanup  AddSongsToPlaylistDialogContent aboutToAppear 开始')
@@ -91,8 +97,19 @@ struct AddSongsToPlaylistDialogContent {
     this.allSongs = [...this.mediaKuList]
     this.filteredSongs = [...this.mediaKuList]
     this.loadInitialData()
-    this.currentPath =  PreferencesUtil.getStringSync('download_path','/storage/Users/currentUser');
-    this.loadFile(this.currentPath)
+    this.clearSelection()
+    this.baseDownloadPath = this.normalizeFsPath(PreferencesUtil.getStringSync('download_path', '/storage/Users/currentUser'))
+    this.currentPath = this.baseDownloadPath
+    // 异步加载目录数据
+    await this.loadFile(this.currentPath)
+  }
+
+  aboutToDisappear() {
+    // 清理定时器,防止内存泄漏
+    if (this.updateTimer !== -1) {
+      clearTimeout(this.updateTimer)
+      this.updateTimer = -1
+    }
   }
 
   /**
@@ -105,30 +122,370 @@ struct AddSongsToPlaylistDialogContent {
   }
   @State fileList: Array<string> = []
 
-  loadFile(curPath: string,){
-    this.fileList = FileUtil.listFileSync(curPath);
+  async loadFile(curPath: string) {
+    const fsPath = this.normalizeFsPath(curPath)
+    this.isFolderLoading = true
+    this.currentPath = fsPath
+    let directories: Array<VideoItem> = []
+    let files: Array<VideoItem> = []
+    const normalizedCurrent = this.normalizeLocalPath(fsPath)
+
+    try {
+      const entries: Array<string> = fs.listFileSync(fsPath) as Array<string>
+      this.fileList = entries
+
+      for (let i = 0; i < entries.length; i++) {
+        const name = entries[i]
+        if (name.startsWith('.')) {
+          continue
+        }
+        const fullPath = `${fsPath}/${name}`
+        try {
+          const stat: fs.Stat = fs.statSync(fullPath) as fs.Stat
+          if (stat.isDirectory()) {
+            directories.push(new VideoItem(name, name, fullPath, CommonConstants.TYPE_IS_DIR, 0, ''))
+          } else {
+            // 仅显示音频文件
+            if (!Utility.isMeidaByExtension(fullPath)) {
+              continue
+            }
+            const normalized = this.normalizeLocalPath(fullPath)
+            const matched = this.allSongs.find((item: VideoItem) =>
+              this.normalizeLocalPath(item.filePath) === normalized)
+            if (matched) {
+              files.push(matched)
+            } else {
+              // 构造最小信息的 VideoItem 兜底显示
+              const fallback = new VideoItem(name, normalized, fullPath, CommonConstants.TYPE_LOCAL, stat.size, '')
+              fallback.fileName = name
+              fallback.parentPath = fsPath
+              files.push(fallback)
+            }
+          }
+        } catch (error) {
+          LogUtil.warn('heanup AddSongsToPlaylistDialog', `无法访问: ${fullPath}`)
+        }
+      }
+
+      // 目录优先,其次按名称排序
+      this.folderItems = directories.concat(files).sort((a: VideoItem, b: VideoItem) => {
+        if (a.type === CommonConstants.TYPE_IS_DIR && b.type !== CommonConstants.TYPE_IS_DIR) {
+          return -1
+        }
+        if (a.type !== CommonConstants.TYPE_IS_DIR && b.type === CommonConstants.TYPE_IS_DIR) {
+          return 1
+        }
+        return a.name.localeCompare(b.name)
+      })
+    } catch (error) {
+      LogUtil.error('heanup AddSongsToPlaylistDialog', `加载文件列表失败: ${(error as Error).message}`)
+      this.fileList = []
+      this.folderItems = directories
+    } finally {
+      this.isFolderLoading = false
+    }
+  }
+
+  /**
+   * 用于文件系统访问的路径清洗:去掉多余 //,保留单个前导 /
+   */
+  private normalizeFsPath(path: string): string {
+    if (!path) {
+      return ''
+    }
+    let cleaned = path.replace('file://', '').replace('file://docs', '').replace('docs://', '')
+    cleaned = cleaned.replace(/\/{2,}/g, '/')
+    if (!cleaned.startsWith('/')) {
+      cleaned = '/' + cleaned
+    }
+    if (cleaned.endsWith('/') && cleaned.length > 1) {
+      cleaned = cleaned.slice(0, -1)
+    }
+    return cleaned
+  }
+
+  /**
+   * 安全获取 parentPath,避免因异常抛出导致目录加载中断
+   */
+  private safeGetParentPath(item: VideoItem): string {
+    if (item.parentPath) {
+      return item.parentPath
+    }
+    try {
+      return FileUtil.getParentPath(item.filePath)
+    } catch (error) {
+      const idx = item.filePath?.lastIndexOf('/') ?? -1
+      if (idx > 0) {
+        return item.filePath.substring(0, idx)
+      }
+      LogUtil.warn('heanup AddSongsToPlaylistDialog', `fallback parentPath for ${item.filePath}: ${(error as Error).message}`)
+      return ''
+    }
+  }
+
+  private querySongsByParentPath(path: string): Promise<VideoItem[]> {
+    return new Promise((resolve) => {
+      try {
+        this.table.queryByParentPath(path, (result: VideoItem[]) => resolve(result))
+      } catch (error) {
+        LogUtil.error('heanup AddSongsToPlaylistDialog', `queryByParentPath 异常: ${(error as Error).message}`)
+        resolve([])
+      }
+    })
+  }
+
+  onViewModeChange() {
+    if (this.viewMode && this.viewMode[0] === 1) {
+      this.loadFile(this.currentPath)
+    } else {
+      this.updateSelectedCount()
+    }
+  }
+
+  private enterFolder(path: string) {
+    this.loadFile(path)
+  }
+
+  private normalizeLocalPath(path: string): string {
+    if (!path) {
+      return ''
+    }
+    let normalized = path.replace('file://docs', '').replace('file://', '')
+    if (normalized.startsWith('/docs/')) {
+      normalized = normalized.replace('/docs', '')
+    } else if (normalized.startsWith('docs/')) {
+      normalized = normalized.substring(4)
+      if (!normalized.startsWith('/')) {
+        normalized = '/' + normalized
+      }
+    }
+    if (!normalized.startsWith('/')) {
+      normalized = '/' + normalized
+    }
+    if (normalized.endsWith('/') && normalized.length > 1) {
+      normalized = normalized.slice(0, -1)
+    }
+    return normalized
+  }
+
+  private navigateToParent() {
+    if (!this.currentPath) {
+      return
+    }
+    // 限制在下载目录之下
+    if (this.baseDownloadPath && this.normalizeFsPath(this.currentPath) === this.baseDownloadPath) {
+      LogUtil.info('heanup AddSongsToPlaylistDialog', 'navigateToParent blocked at baseDownloadPath')
+      return
+    }
+    const idx = this.currentPath.lastIndexOf('/')
+    if (idx <= 0) {
+      return
+    }
+    const parent = this.currentPath.substring(0, idx)
+    if (!parent || parent === this.currentPath) {
+      return
+    }
+    this.loadFile(parent)
+  }
+
+  private canNavigateUp(): boolean {
+    if (!this.currentPath) {
+      return false
+    }
+    const current = this.normalizeFsPath(this.currentPath)
+    if (!this.baseDownloadPath) {
+      return current.lastIndexOf('/') > 0
+    }
+    return current.startsWith(this.baseDownloadPath) && current !== this.baseDownloadPath
+  }
+
+  private toggleSongSelection(song: VideoItem) {
+    if (song.type === CommonConstants.TYPE_IS_DIR) {
+      this.toggleFolderSelection(song.filePath)
+      return
+    }
+    
+    const exists = this.selectedSongIds.has(song.id)
+    if (exists) {
+      this.selectedSongs = this.selectedSongs.filter((s: VideoItem) => s.id !== song.id)
+      this.selectedSongIds.delete(song.id)
+    } else {
+      this.selectedSongs = [...this.selectedSongs, song]
+      this.selectedSongIds.add(song.id)
+    }
+    this.updateSelectedCount()
+  }
 
-    let directories: Array<VideoItem> = [];
-    let files: Array<VideoItem> = [];
+  private toggleFolderSelection(folderPath: string) {
+    const normalized = this.normalizeLocalPath(folderPath)
+    const next = new Set(this.selectedFolders)
+    if (next.has(normalized)) {
+      next.delete(normalized)
+    } else {
+      next.add(normalized)
+    }
+    LogUtil.info('heanup AddSongsToPlaylistDialog', `toggleFolderSelection folder=${normalized}, now=${Array.from(next).join(',')}`)
+    this.selectedFolders = next
+    this.updateSelectedCount()
+  }
 
+  private clearSelection() {
+    this.selectedSongs = []
+    this.selectedFolders = new Set()
+    this.selectedSongIds.clear()
+    this.totalSelectedCount = 0
+  }
 
-    for (let i = 0; i < this.fileList.length; i++) {
-      console.info(`The name of file: ${this.fileList[i]}`);
-      let path = curPath + '/' + this.fileList[i]
-      if (FileUtil.isDirectory(path)) {
-        let item: VideoItem =
-          new VideoItem(this.fileList[i].toString(), this.fileList[i].toString(), path, CommonConstants.TYPE_IS_DIR, 0,
-            '')
-        directories.push(item);
+  private mergeSongs(primary: VideoItem[], extra: VideoItem[]): VideoItem[] {
+    const map = new Map<string, VideoItem>()
+    primary.forEach((item: VideoItem) => {
+      map.set(this.normalizeLocalPath(item.filePath), item)
+    })
+    extra.forEach((item: VideoItem) => {
+      const key = this.normalizeLocalPath(item.filePath)
+      if (!map.has(key)) {
+        map.set(key, item)
       }
+    })
+    return Array.from(map.values())
+  }
+
+  private safeParentFromPath(path: string): string {
+    if (!path) {
+      return ''
+    }
+    try {
+      return FileUtil.getParentPath(path)
+    } catch (error) {
+      const idx = path.lastIndexOf('/')
+      return idx > 0 ? path.substring(0, idx) : ''
     }
-    this.table.queryByParentPath(curPath, async (result: VideoItem[]) => {
+  }
+
+  private getSongsFromFolders(folderPaths: string[]): VideoItem[] {
+    if (!folderPaths || folderPaths.length === 0) {
+      LogUtil.info('heanup AddSongsToPlaylistDialog', 'getSongsFromFolders empty folderPaths')
+      return []
+    }
+
+    try {
+      // 使用 Set 优化查找性能,并统一路径格式
+      const normalizedSet = new Set(folderPaths.map((path: string) => this.normalizeLocalPath(path)))
+      const result: VideoItem[] = []
+      
+      // 单次遍历,避免嵌套循环
+      for (let i = 0; i < this.allSongs.length; i++) {
+        const item = this.allSongs[i]
+        if (!item || item.type === CommonConstants.TYPE_IS_DIR || !item.filePath) {
+          continue
+        }
+
+        const parentPath = this.normalizeLocalPath(item.parentPath || this.safeParentFromPath(item.filePath))
+        const filePath = this.normalizeLocalPath(item.filePath)
+        
+        // 直接使用 Set 查找,O(1) 时间复杂度
+        if (normalizedSet.has(parentPath)) {
+          result.push(item)
+          continue
+        }
+        
+        // 检查是否在子目录中(这部分仍需遍历,但通常文件夹数量不多)
+        for (const folder of normalizedSet) {
+          if (filePath.startsWith(folder + '/')) {
+            result.push(item)
+            break
+          }
+        }
+      }
 
-      files = result;
-      this.folderItems = directories.concat(files);
+      // 额外兜底:将当前目录列表中展示的文件也纳入(防止媒体表尚未入库时无法计数)
+      this.folderItems.forEach((item: VideoItem) => {
+        if (!item || item.type === CommonConstants.TYPE_IS_DIR) {
+          return
+        }
+        const parentPath = this.normalizeLocalPath(item.parentPath || this.safeParentFromPath(item.filePath))
+        const filePath = this.normalizeLocalPath(item.filePath)
+        if (normalizedSet.has(parentPath) || Array.from(normalizedSet).some(folder => filePath.startsWith(folder + '/'))) {
+          result.push(item)
+        }
+      })
+      LogUtil.info('heanup AddSongsToPlaylistDialog', `getSongsFromFolders folders=${Array.from(normalizedSet).join(',')}, result=${result.length}`)
+      
+      return result
+    } catch (error) {
+      LogUtil.error('heanup AddSongsToPlaylistDialog', `getSongsFromFolders error: ${(error as Error).message}`)
+      return []
+    }
+  }
 
-    });
+  private updateSelectedCount() {
+    // 清除之前的定时器,实现防抖
+    if (this.updateTimer !== -1) {
+      clearTimeout(this.updateTimer)
+    }
+    
+    // 延迟执行计算,避免阻塞主线程
+    this.updateTimer = setTimeout(() => {
+      try {
+        const folderSongs = this.getSongsFromFolders(Array.from(this.selectedFolders))
+        const merged = this.mergeSongs(
+          this.selectedSongs.filter((item: VideoItem) => item.type !== CommonConstants.TYPE_IS_DIR), 
+          folderSongs
+        )
+        this.totalSelectedCount = merged.length
+        LogUtil.info('heanup AddSongsToPlaylistDialog', `updateSelectedCount folders=${Array.from(this.selectedFolders).join(',')}, folderSongs=${folderSongs.length}, directSongs=${this.selectedSongs.length}, total=${this.totalSelectedCount}`)
+      } catch (error) {
+        LogUtil.error('heanup AddSongsToPlaylistDialog', `更新选中数量失败: ${(error as Error).message}`)
+        // 发生错误时至少显示已选歌曲数量
+        this.totalSelectedCount = this.selectedSongs.filter(
+          (item: VideoItem) => item.type !== CommonConstants.TYPE_IS_DIR
+        ).length
+      }
+      this.updateTimer = -1
+    }, 50)
+  }
 
+  /**
+   * 异步全选歌曲,分批处理避免阻塞主线程
+   */
+  private selectAllSongsAsync() {
+    const allSongs = this.filteredSongs && this.filteredSongs.length > 0 ? this.filteredSongs : this.allSongs
+    LogUtil.info('heanup AddSongsToPlaylistDialog', `selectAllSongsAsync start, total=${allSongs.length}`)
+    const BATCH_SIZE = 500 // 单批进一步增大,减少全选等待时间
+    let currentIndex = 0
+    const tempSelected: VideoItem[] = [...this.selectedSongs] // 保留已选歌曲
+    const tempIds = new Set(this.selectedSongIds)
+    
+    const processBatch = () => {
+      const endIndex = Math.min(currentIndex + BATCH_SIZE, allSongs.length)
+      const batch = allSongs.slice(currentIndex, endIndex)
+      
+      // 批量添加到临时列表和 Set
+      batch.forEach((song: VideoItem) => {
+        if (!tempIds.has(song.id)) {
+          tempSelected.push(song)
+          tempIds.add(song.id)
+        }
+      })
+      
+      // 重新赋值触发响应式更新
+      this.selectedSongs = [...tempSelected]
+      this.selectedSongIds = new Set(tempIds)
+      
+      currentIndex = endIndex
+      
+      if (currentIndex < allSongs.length) {
+        // 还有更多数据,继续处理下一批
+        setTimeout(processBatch, 0) // 使用 setTimeout 让出主线程
+      } else {
+        // 全部处理完成,更新计数
+        this.updateSelectedCount()
+        LogUtil.info('AddSongsToPlaylistDialog', `全选完成,共选中 ${this.selectedSongs.length} 首歌曲`)
+      }
+    }
+    
+    // 开始第一批处理
+    processBatch()
   }
 
   /**
@@ -254,33 +611,37 @@ struct AddSongsToPlaylistDialogContent {
         .width('100%')
         .margin({ top: 8 })
 
-      // 已选择歌曲数量
-      if (this.selectedSongs.length > 0) {
+      // 已选择歌曲数量(包含目录展开后的歌曲)
+      if (this.totalSelectedCount > 0) {
         Row() {
-          Text(`已选择 ${this.selectedSongs.length}  首歌曲`)
+          Text(`已选择 ${this.totalSelectedCount} 首歌曲`)
             .fontSize(14)
             .fontColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
             .fontWeight(FontWeight.Medium)
 
           Blank()
 
-          Button(this.selectedSongs.length === this.dataSource.dataArray.length
-            && this.dataSource.dataArray.length > 0 ? '全不选' : '全选')
-            .fontSize(12)
-            .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
-            .backgroundColor(Color.Transparent)
-            .height(30)
-            .padding({ left: 8, right: 8 })
-            .onClick(() => {
-              // 判断是否已经全选
-              if (this.selectedSongs.length === this.dataSource.dataArray.length && this.dataSource.dataArray.length > 0) {
-                // 如果已经全选,则清空选择
-                this.selectedSongs = []
-              } else {
-                // 如果没有全选,则选择当前显示的所有歌曲
-                this.selectedSongs = [...this.dataSource.dataArray]
-              }
-            })
+          if (this.viewMode[0] === 0) {
+            Button((this.selectedSongs.length === this.dataSource.dataArray.length
+              && this.dataSource.dataArray.length > 0) ? '全不选' : '全选')
+              .fontSize(12)
+              .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+              .backgroundColor(Color.Transparent)
+              .height(30)
+              .padding({ left: 8, right: 8 })
+              .onClick(() => {
+                if (this.selectedSongs.length === this.dataSource.dataArray.length
+                  && this.dataSource.dataArray.length > 0) {
+                  // 全不选:清空所有选择
+                  this.selectedSongs = []
+                  this.selectedSongIds.clear()
+                  this.updateSelectedCount()
+                } else {
+                  // 全选:异步分批处理,避免阻塞主线程
+                  this.selectAllSongsAsync()
+                }
+              })
+          }
 
           Button('清空')
             .fontSize(12)
@@ -289,7 +650,7 @@ struct AddSongsToPlaylistDialogContent {
             .height(30)
             .padding({ left: 8, right: 8 })
             .onClick(() => {
-              this.selectedSongs = []
+              this.clearSelection()
             })
         }
         .width('100%')
@@ -336,15 +697,15 @@ struct AddSongsToPlaylistDialogContent {
             DialogHelper.closeDialog('addSongsToPlaylistDialog')
           })
 
-        Button(`添加${this.selectedSongs.length > 0 ? `(${this.selectedSongs.length})` : ''}`)
+        Button(`添加${this.totalSelectedCount > 0 ? `(${this.totalSelectedCount})` : ''}`)
           .width('45%')
           .height(40)
           .backgroundColor(this.isDarkMode ? $r('app.color.silvery'):this.themeColor)
           .borderRadius(8)
           .fontSize(14)
           .fontColor(Color.White)
-          .enabled(this.selectedSongs.length > 0)
-          .opacity(this.selectedSongs.length > 0 ? 1 : 0.5)
+          .enabled(this.totalSelectedCount > 0)
+          .opacity(this.totalSelectedCount > 0 ? 1 : 0.5)
           .onClick(() => {
             this.handleConfirm()
           })
@@ -364,12 +725,18 @@ struct AddSongsToPlaylistDialogContent {
    * 处理确认操作
    */
   private handleConfirm() {
-    if (this.selectedSongs.length === 0) {
+    const folderSongs = this.getSongsFromFolders(Array.from(this.selectedFolders))
+    const submitSongs = this.mergeSongs(
+      this.selectedSongs.filter((item: VideoItem) => item.type !== CommonConstants.TYPE_IS_DIR),
+      folderSongs
+    )
+    LogUtil.info('heanup AddSongsToPlaylistDialog', `handleConfirm selectedFolders=${Array.from(this.selectedFolders).join(',')}, folderSongs=${folderSongs.length}, directSongs=${this.selectedSongs.length}, submit=${submitSongs.length}`)
+    if (submitSongs.length === 0) {
       ToastUtil.showToast(' 请选择至少一首歌曲')
       return
     }
 
-    this.onConfirm?.(this.selectedSongs)
+    this.onConfirm?.(submitSongs)
     DialogHelper.closeDialog('addSongsToPlaylistDialog')
   }
 
@@ -420,35 +787,18 @@ struct AddSongsToPlaylistDialogContent {
 
                 // 选择框
                 Checkbox({ name: 'song_' + song.id })
-                  .select(this.selectedSongs.some(s => s.id === song.id))
+                  .select(this.selectedSongIds.has(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)
-                      }
-                    }
+                  .onChange(() => {
+                    this.toggleSongSelection(song)
                   })
               }
               .width('100%')
               .padding(12)
               .borderRadius(10)
               .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)
-                }
+                this.toggleSongSelection(song)
               })
             }
             .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 })
@@ -484,75 +834,136 @@ struct AddSongsToPlaylistDialogContent {
   @Builder
   getFolderView() {
     Column({ space: 12 }) {
-      Text('目录视图')
-        .fontSize(16)
-        .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
-      
-      // 这里将实现目录视图的布局
-      Scroll() {
-        Column() {
-          ForEach(this.folderItems, (item: VideoItem, index: number) => {
-            Row() {
-              // 根据是否有parentPath判断是目录还是文件
-              Text(item.parentPath ? '📁' : '🎵')
-                .fontSize(20)
-              
-              Column({ space: 2 }) {
-                Text(item.name || '未知歌曲')
-                  .fontSize(14)
-                  .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
-                  .maxLines(1)
-                  .textOverflow({ overflow: TextOverflow.Ellipsis })
-                
-                // 显示艺术家和时长信息
-                if (item.artist) {
-                  Text(item.artist + '  ' + (item.duration || ''))
-                    .fontSize(12)
-                    .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
+      Row({ space: 8 }) {
+        Text('目录视图')
+          .fontSize(16)
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+        Blank()
+        Button('上一级')
+          .fontSize(12)
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+          .height(28)
+          .padding({ left: 10, right: 10 })
+          .backgroundColor(Color.Transparent)
+          .border({ width: 1, color: this.isDarkMode ? '#444444' : '#dddddd' })
+          .borderRadius(14)
+          .enabled(this.canNavigateUp())
+          .opacity(this.canNavigateUp() ? 1 : 0.4)
+          .onClick(() => {
+            this.navigateToParent()
+          })
+        Button('刷新')
+          .fontSize(12)
+          .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
+          .height(28)
+          .padding({ left: 10, right: 10 })
+          .backgroundColor(Color.Transparent)
+          .border({ width: 1, color: this.isDarkMode ? '#444444' : '#dddddd' })
+          .borderRadius(14)
+          .onClick(() => {
+            this.loadFile(this.currentPath)
+          })
+      }
+      .width('100%')
+
+      Text(this.normalizeLocalPath(this.currentPath) || '未选择路径')
+        .fontSize(12)
+        .fontColor(this.isDarkMode ? '#8E8E93' : '#666666')
+        .maxLines(1)
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+        .padding({ left: 4, right: 4 })
+
+      if (this.isFolderLoading) {
+        Row() {
+          LoadingProgress()
+            .width(20)
+            .height(20)
+          Text('正在加载目录...')
+            .fontSize(12)
+            .fontColor(this.isDarkMode ? '#8E8E93' : '#666666')
+            .margin({ left: 8 })
+        }
+        .width('100%')
+        .height(80)
+        .justifyContent(FlexAlign.Center)
+      } else {
+        Scroll() {
+          Column() {
+            ForEach(this.folderItems, (item: VideoItem, index: number) => {
+              Row() {
+                Text(item.type === CommonConstants.TYPE_IS_DIR ? '📁' : '🎵')
+                  .fontSize(20)
+                  .margin({ right: 6 })
+
+                Column({ space: 2 }) {
+                  Text(item.name || '未知歌曲')
+                    .fontSize(14)
+                    .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
                     .maxLines(1)
                     .textOverflow({ overflow: TextOverflow.Ellipsis })
+
+                  if (item.artist && item.type !== CommonConstants.TYPE_IS_DIR) {
+                    Text(item.artist + '  ' + (item.duration || ''))
+                      .fontSize(12)
+                      .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
+                      .maxLines(1)
+                      .textOverflow({ overflow: TextOverflow.Ellipsis })
+                  } else if (item.type === CommonConstants.TYPE_IS_DIR) {
+                    Text('包含子目录内的所有音乐将一起添加')
+                      .fontSize(12)
+                      .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
+                      .maxLines(1)
+                  }
                 }
-              }
-              .layoutWeight(1)
-              .margin({ left: 8 })
-              
-              // 显示选择框
-              Checkbox({ name: 'folder_item_' + item.id })
-                .select(this.selectedSongs.some((s: VideoItem) => s.id === item.id))
-                .selectedColor($r('app.color.theme_color'))
-                .onChange((checked: boolean) => {
-                  if (checked) {
-                    if (!this.selectedSongs.some((s: VideoItem) => s.id === item.id)) {
-                      this.selectedSongs.push(item);
-                    }
-                  } else {
-                    const index: number = this.selectedSongs.findIndex((s: VideoItem) => s.id === item.id);
-                    if (index > -1) {
-                      this.selectedSongs.splice(index, 1);
-                    }
+                .layoutWeight(1)
+                .margin({ left: 8 })
+
+                if (item.type === CommonConstants.TYPE_IS_DIR) {
+                  Button() {
+                    Text('进入')
+                      .fontSize(12)
+                      .fontColor(this.isDarkMode ? '#FFFFFF' : this.themeColor)
                   }
-                })
-            }
-            .width('100%')
-            .padding(12)
-            .backgroundColor(this.isDarkMode ? '#2C2C2E' : '#F8F8F8')
-            .borderRadius(8)
-            .margin({ bottom: 4 })
-            .onClick(() => {
-              const isSelected: boolean = this.selectedSongs.some((s: VideoItem) => s.id === item.id);
-              if (isSelected) {
-                const index: number = this.selectedSongs.findIndex((s: VideoItem) => s.id === item.id);
-                if (index > -1) {
-                  this.selectedSongs.splice(index, 1);
+                    .height(30)
+                    .padding({ left: 10, right: 10 })
+                    .backgroundColor(this.isDarkMode ? '#3a3a3c' : '#f1f1f1')
+                    .borderRadius(8)
+                    .margin({ right: 8 })
+                    .onClick(() => {
+                      this.enterFolder(item.filePath)
+                    })
                 }
-              } else {
-                this.selectedSongs.push(item);
+
+                Checkbox({ name: 'folder_item_' + item.id })
+                  .select((item.type === CommonConstants.TYPE_IS_DIR)
+                    ? this.selectedFolders.has(this.normalizeLocalPath(item.filePath))
+                    : this.selectedSongIds.has(item.id))
+                  .selectedColor($r('app.color.theme_color'))
+                  .onChange(() => {
+                    if (item.type === CommonConstants.TYPE_IS_DIR) {
+                      this.toggleFolderSelection(item.filePath)
+                    } else {
+                      this.toggleSongSelection(item)
+                    }
+                  })
               }
-            })
-          }, (item: VideoItem) => item.id)
+              .width('100%')
+              .padding(12)
+              .backgroundColor(this.isDarkMode ? '#2C2C2E' : '#F8F8F8')
+              .borderRadius(8)
+              .margin({ bottom: 4 })
+              .onClick(() => {
+                if (item.type === CommonConstants.TYPE_IS_DIR) {
+                  this.toggleFolderSelection(item.filePath)
+                } else {
+                  this.toggleSongSelection(item)
+                }
+              })
+            }, (item: VideoItem) => item.id)
+          }
         }
+        .height(300)
       }
-      .height(300)
     }
     .width('100%')
   }

+ 48 - 6
entry/src/main/ets/view/NavidromePage.ets

@@ -90,6 +90,17 @@ export struct NavidromePage {
   onTabSelectedIndexesChanged() {
     this.selectedTab = this.tabSelectedIndexes[0];
     console.info('heanup', `Selected tab: ${this.tabs[this.selectedTab]}`);
+    
+    // 从艺术家或专辑切换回全部时,清除筛选状态
+    if (this.selectedTab === 0 && this.filterType !== NavFilterType.None) {
+      // 不清除筛选,保持筛选状态
+    } else if (this.selectedTab !== 0) {
+      // 切换到艺术家或专辑标签页时,清除筛选和搜索状态
+      this.clearFilter();
+      this.isSearchMode = false;
+      this.searchText = '';
+      this.filteredList = [];
+    }
   }
 
   //切换不同的NavidromePage
@@ -322,6 +333,11 @@ export struct NavidromePage {
     videoItem.navArtistId = song.artistId;
     videoItem.navAlbumId = song.albumId;
     videoItem.pixelMapPath = coverUrl;
+    
+    // 调试日志:检查歌曲的 albumId 和 artistId
+    if (this.allVideos.length < 3) {
+      Logger.info('heanup', `歌曲 ${title}: artistId=${song.artistId}, albumId=${song.albumId}, artist=${song.artist}, album=${song.album}`);
+    }
     if (song.track !== undefined && song.track !== null) {
       videoItem.track = song.track.toString();
     }
@@ -840,10 +856,21 @@ export struct NavidromePage {
       return this.filteredList;
 
     if (this.filterType === NavFilterType.Artist && this.filterId.length > 0) {
-      return this.allVideos.filter(item => item.navArtistId === this.filterId || item.artist === this.filterLabel);
+      const filtered = this.allVideos.filter(item => item.navArtistId === this.filterId || item.artist === this.filterLabel);
+      Logger.info('heanup', `艺术家筛选: filterId=${this.filterId}, filterLabel=${this.filterLabel}, 结果数=${filtered.length}`);
+      return filtered;
     }
     if (this.filterType === NavFilterType.Album && this.filterId.length > 0) {
-      return this.allVideos.filter(item => item.navAlbumId === this.filterId || item.album === this.filterLabel);
+      const filtered = this.allVideos.filter(item => {
+        const match = item.navAlbumId === this.filterId || item.album === this.filterLabel;
+        if (!match && this.allVideos.indexOf(item) < 3) {
+          // 只打印前3首歌的调试信息
+          Logger.info('heanup', `歌曲 ${item.name}: navAlbumId=${item.navAlbumId}, album=${item.album}, 期望albumId=${this.filterId}, 期望album=${this.filterLabel}`);
+        }
+        return match;
+      });
+      Logger.info('heanup', `专辑筛选: filterId=${this.filterId}, filterLabel=${this.filterLabel}, 结果数=${filtered.length}`);
+      return filtered;
     }
 
     return this.allVideos;
@@ -864,14 +891,29 @@ export struct NavidromePage {
   }
 
   private applyFilter(type: NavFilterType, id: string, label: string): void {
+    Logger.info('heanup', `应用筛选: type=${type}, id=${id}, label=${label}`);
+    
+    // 先更新状态
+    this.filterType = type;
+    this.filterId = id;
+    this.filterLabel = label;
+    // 退出搜索模式,确保显示筛选结果
+    this.isSearchMode = false;
+    this.searchText = '';
+    this.filteredList = [];
+    
+    // 调试日志:检查筛选结果
+    const visibleSongs = this.getVisibleSongs();
+    Logger.info('heanup', `筛选后歌曲数量: ${visibleSongs.length}, 总歌曲数: ${this.allVideos.length}`);
+    if (visibleSongs.length > 0) {
+      Logger.info('heanup', `第一首歌: ${visibleSongs[0].name}, albumId=${visibleSongs[0].navAlbumId}, album=${visibleSongs[0].album}`);
+    }
+    
+    // 然后执行动画切换标签页
     this.getUIContext().animateTo({ duration: 555 }, () => {
-      this.filterType = type;
-      this.filterId = id;
-      this.filterLabel = label;
       this.selectedTab = 0;
       this.tabSelectedIndexes = [0];
     })
-
   }
 
   private clearFilter(): void {