Răsfoiți Sursa

实现播放webdav文件

chendeben 9 luni în urmă
părinte
comite
40bd7de636

+ 49 - 14
entry/src/main/ets/common/util/RcpSocketUtil.ets

@@ -327,8 +327,11 @@ export class RcpSocket {
           .finally(() => {
             console.info(UtilName, 'testTag', 'PROPFIND执行完毕')
             if (response != '') {
+              console.info(UtilName, 'testTag', 'WebDAV响应内容长度:', response.length.toString());
+              console.info(UtilName, 'testTag', 'WebDAV响应前500字符:', response.substring(0, 500));
               // 提取文件信息
               const filesInfo = this.extractHrefContents(response, path, url);
+              console.info(UtilName, 'testTag', '解析出文件数量:', filesInfo.length.toString());
               if (filesInfo.length !== 0) {
                 console.info(UtilName, 'testTag', '请求成功')
                 rcpSession.close()
@@ -576,19 +579,34 @@ export class RcpSocket {
 
       // 获取完整的href路径
       const fullHref = this.decodeXMLEntities(decodeURI(hrefMatch[1]));
+      console.info(UtilName, 'testTag', '提取到href:', fullHref);
 
-      // 从href中提取文件名(最后一个/后的部分)
-      let name = fullHref;
-      const lastSlashIndex = fullHref.lastIndexOf('/');
-      if (lastSlashIndex >= 0 && lastSlashIndex < fullHref.length - 1) {
-        name = fullHref.substring(lastSlashIndex + 1);
-      } else if (fullHref.endsWith('/')) {
-        // 如果是目录(以/结尾),取倒数第二段
-        const withoutTrailingSlash = fullHref.substring(0, fullHref.length - 1);
-        const secondLastSlash = withoutTrailingSlash.lastIndexOf('/');
-        if (secondLastSlash >= 0) {
-          name = withoutTrailingSlash.substring(secondLastSlash + 1) + '/';
+      // 尝试提取 <D:displayname> 作为文件名
+      let displayNameMatch = responseBlock.match(/<D:displayname>(.*?)<\/D:displayname>/i);
+      if (!displayNameMatch) {
+        displayNameMatch = responseBlock.match(/<lp1:displayname>(.*?)<\/lp1:displayname>/i);
+      }
+
+      let name = '';
+      if (displayNameMatch && displayNameMatch[1]) {
+        // 如果有 displayname,使用它
+        name = this.decodeXMLEntities(displayNameMatch[1]);
+        console.info(UtilName, 'testTag', '使用displayname作为文件名:', name);
+      } else {
+        // 否则从href中提取文件名(最后一个/后的部分)
+        name = fullHref;
+        const lastSlashIndex = fullHref.lastIndexOf('/');
+        if (lastSlashIndex >= 0 && lastSlashIndex < fullHref.length - 1) {
+          name = fullHref.substring(lastSlashIndex + 1);
+        } else if (fullHref.endsWith('/')) {
+          // 如果是目录(以/结尾),取倒数第二段
+          const withoutTrailingSlash = fullHref.substring(0, fullHref.length - 1);
+          const secondLastSlash = withoutTrailingSlash.lastIndexOf('/');
+          if (secondLastSlash >= 0) {
+            name = withoutTrailingSlash.substring(secondLastSlash + 1) + '/';
+          }
         }
+        console.info(UtilName, 'testTag', '从href提取文件名:', name);
       }
 
       // 跳过根目录本身
@@ -616,8 +634,17 @@ export class RcpSocket {
       fileInfo.contentLength = size;
       // 判断是否为文件夹(以/结尾或没有contentLength)
       fileInfo.isDirectory = fullHref.endsWith('/') || size === 0;
+
+      console.info(UtilName, 'testTag', '创建FileInfo对象:', 'name=', name, 'fileName=', fileInfo.fileName, 'href=', fileInfo.href, 'isDirectory=', fileInfo.isDirectory.toString());
       filesInfo.push(fileInfo);
     }
+
+    // 验证返回前的文件信息
+    for (let i = 0; i < filesInfo.length; i++) {
+      const file = filesInfo[i];
+      console.info(UtilName, 'testTag', '返回前检查', i.toString(), ': fileName=', file.fileName, ', name=', file.name);
+    }
+
     return filesInfo;
   }
 
@@ -683,11 +710,19 @@ export class RcpSocket {
     entityMap.set('&quot;', '"')
     entityMap.set('&apos;', "'")
 
-
-    // 正则匹配替换
-    return str.replace(/&(amp|lt|gt|quot|apos);/g, (match: string, entity: string) => {
+    // 先替换 XML 实体
+    let result = str.replace(/&(amp|lt|gt|quot|apos);/g, (match: string, entity: string) => {
       const decoded = entityMap.get(`&${entity};`);
       return decoded ? decoded : match;
     });
+
+    // 再进行 URL 解码
+    try {
+      result = decodeURIComponent(result);
+    } catch (error) {
+      // 如果解码失败,保持原样
+    }
+
+    return result;
   }
 }

+ 40 - 38
entry/src/main/ets/common/util/WebdavManager.ets

@@ -96,9 +96,11 @@ export class WebdavManager {
   }
 
   public notifyObservers(event: string): void {
-    Logger.info(TAG, `通知观察者: ${event}`);
+    Logger.info(TAG, '通知观察者:', event);
+    Logger.info(TAG, '观察者数量:', this.observers.length.toString());
     for (let i = 0; i < this.observers.length; i++) {
       const observer = this.observers[i];
+      Logger.info(TAG, '调用观察者', i.toString(), ',事件:', event);
       observer(event);
     }
   }
@@ -106,7 +108,7 @@ export class WebdavManager {
   // ==================== 数据库操作 ====================
 
   // 创建WebDAV账户表
-  public async createWebDavTableInDB(): Promise<void> {
+  public createWebDavTableInDB(): Promise<void> {
     const sql = `CREATE TABLE IF NOT EXISTS ${this.webDavTable} (
       id INTEGER PRIMARY KEY AUTOINCREMENT,
       name TEXT,
@@ -124,14 +126,14 @@ export class WebdavManager {
       enableHttps INTEGER DEFAULT 0
     )`;
 
-    try {
-      await this.dataBaseUtil.executeSql(sql);
-      Logger.info(TAG, 'WebDAV账户表创建成功');
-    } catch (err) {
-      const error = err as Error;
-      Logger.error(TAG, `创建WebDAV账户表失败: ${error.message}`);
-      throw error;
-    }
+    return this.dataBaseUtil.executeSql(sql)
+      .then(() => {
+        Logger.info(TAG, 'WebDAV账户表创建成功');
+      })
+      .catch((err: Error) => {
+        Logger.error(TAG, '创建WebDAV账户表失败:', err.message);
+        throw err;
+      });
   }
 
   // 从数据库查询所有账户
@@ -166,11 +168,11 @@ export class WebdavManager {
       }
       resultSet.close();
 
-      Logger.info(TAG, `从数据库加载了 ${this.webDavAccounts.length} 个WebDAV账户`);
+      Logger.info(TAG, '从数据库加载了', this.webDavAccounts.length.toString(), '个WebDAV账户');
       this.notifyObservers(WebdavManagerStates.QueryAccountsSucceed);
     } catch (err) {
       const error = err as Error;
-      Logger.error(TAG, `查询WebDAV账户失败: ${error.message}`);
+      Logger.error(TAG, '查询WebDAV账户失败:', error.message);
       this.notifyObservers(WebdavManagerStates.QueryAccountsFailed);
       throw error;
     }
@@ -209,13 +211,13 @@ export class WebdavManager {
       };
 
       await this.dataBaseUtil.insertData(this.webDavTable, values);
-      Logger.info(TAG, `插入WebDAV账户成功: ${name}`);
+      Logger.info(TAG, '插入WebDAV账户成功:', name);
 
       await this.queryWebDavAccountsFromDB();
       this.notifyObservers(WebdavManagerStates.InsertAccountSucceed);
     } catch (err) {
       const error = err as Error;
-      Logger.error(TAG, `插入WebDAV账户失败: ${error.message}`);
+      Logger.error(TAG, '插入WebDAV账户失败:', error.message);
       this.notifyObservers(WebdavManagerStates.InsertAccountFailed);
       throw error;
     }
@@ -244,13 +246,13 @@ export class WebdavManager {
       predicates.equalTo('id', account.id);
 
       await this.dataBaseUtil.updateData(this.webDavTable, values, predicates);
-      Logger.info(TAG, `更新WebDAV账户成功: ${account.name}`);
+      Logger.info(TAG, '更新WebDAV账户成功:', account.name);
 
       await this.queryWebDavAccountsFromDB();
       this.notifyObservers(WebdavManagerStates.EditAccountSucceed);
     } catch (err) {
       const error = err as Error;
-      Logger.error(TAG, `更新WebDAV账户失败: ${error.message}`);
+      Logger.error(TAG, '更新WebDAV账户失败:', error.message);
       this.notifyObservers(WebdavManagerStates.EditAccountFailed);
       throw error;
     }
@@ -263,13 +265,13 @@ export class WebdavManager {
       predicates.equalTo('id', account.id);
 
       await this.dataBaseUtil.deleteData(predicates);
-      Logger.info(TAG, `删除WebDAV账户成功: ${account.name}`);
+      Logger.info(TAG, '删除WebDAV账户成功:', account.name);
 
       await this.queryWebDavAccountsFromDB();
       this.notifyObservers(WebdavManagerStates.RemoveAccountSucceed);
     } catch (err) {
       const error = err as Error;
-      Logger.error(TAG, `删除WebDAV账户失败: ${error.message}`);
+      Logger.error(TAG, '删除WebDAV账户失败:', error.message);
       this.notifyObservers(WebdavManagerStates.RemoveAccountFailed);
       throw error;
     }
@@ -321,10 +323,16 @@ export class WebdavManager {
       );
 
       // 保存所有文件(包括文件夹)
-      this.webDavFiles = files;
+      // 直接使用从RcpSocketUtil返回的FileInfo对象
+      this.webDavFiles = [];
+      for (let i = 0; i < files.length; i++) {
+        const file = files[i];
+        // 直接添加原始文件对象
+        this.webDavFiles.push(file);
+      }
 
       // 调试:输出获取到的文件总数
-      Logger.info(TAG, `从WebDAV获取到 ${files.length} 个文件/文件夹`);
+      Logger.info(TAG, '从WebDAV获取到 ' + files.length + ' 个文件/文件夹');
 
       // 分别统计文件夹和音频文件
       let folderCount = 0;
@@ -334,25 +342,19 @@ export class WebdavManager {
       this.webDavSongs = [];
       for (let i = 0; i < files.length; i++) {
         const file = files[i];
-        Logger.info(TAG, `文件 ${i}: ${file.fileName}, href: ${file.href}, isDirectory: ${file.isDirectory}`);
+        const fileName = file.fileName;
 
         if (file.isDirectory) {
           folderCount++;
-          Logger.info(TAG, `📁 文件夹: ${file.fileName}`);
-        } else if (this.isAudioFile(file.fileName)) {
+        } else if (this.isAudioFile(fileName)) {
           audioCount++;
-          Logger.info(TAG, `🎵 音频文件: ${file.fileName}`);
           const song = this.fileInfoToSong(file, account);
           this.webDavSongs.push(song);
-        } else {
-          Logger.info(TAG, `📄 其他文件: ${file.fileName}`);
         }
       }
 
-      Logger.info(TAG, `从WebDAV加载了 ${folderCount} 个文件夹, ${audioCount} 首歌曲`);
       this.notifyObservers(WebdavManagerStates.LoadFilesInfoSucceed);
     } catch (error) {
-      Logger.error(TAG, `从WebDAV加载文件列表失败: ${error}`);
       this.ErrorMessage = error as BusinessError;
       this.notifyObservers(WebdavManagerStates.LoadFilesInfoFailed);
     }
@@ -428,13 +430,13 @@ export class WebdavManager {
       return;
     }
 
-    Logger.info(TAG, `准备进入文件夹: ${folder.fileName}`);
-    Logger.info(TAG, `当前路径: ${this.currentPath}`);
-    Logger.info(TAG, `目标路径: ${folder.href}`);
+    Logger.info(TAG, '准备进入文件夹:', folder.fileName);
+    Logger.info(TAG, '当前路径:', this.currentPath);
+    Logger.info(TAG, '目标路径:', folder.href);
 
     // 保存当前路径到历史记录
     this.pathHistory.push(this.currentPath);
-    Logger.info(TAG, `路径历史: ${JSON.stringify(this.pathHistory)}`);
+    Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
 
     // 加载文件夹内容
     await this.loadFilesInfoFromWebdav(folder.href);
@@ -483,7 +485,7 @@ export class WebdavManager {
       // 这里可以添加从Preferences加载历史配置的逻辑
       Logger.info(TAG, '从Preferences加载配置');
     } catch (error) {
-      Logger.error(TAG, `从Preferences加载配置失败: ${error}`);
+      Logger.error(TAG, '从Preferences加载配置失败:', error.toString());
     }
   }
 
@@ -493,7 +495,7 @@ export class WebdavManager {
       // 这里可以添加保存配置到Preferences的逻辑
       Logger.info(TAG, '保存配置到Preferences');
     } catch (error) {
-      Logger.error(TAG, `保存配置到Preferences失败: ${error}`);
+      Logger.error(TAG, '保存配置到Preferences失败:', error.toString());
     }
   }
 
@@ -503,7 +505,7 @@ export class WebdavManager {
   public addToDownloadQueue(song: Song, account: WebDavAccount): void {
     const task: TransferTask = { song, account };
     this.downloadQueue.push(task);
-    Logger.info(TAG, `添加到下载队列: ${song.title}`);
+    Logger.info(TAG, '添加到下载队列:', song.title);
     this.notifyObservers(WebdavManagerStates.DownloadQueueChanged);
   }
 
@@ -512,7 +514,7 @@ export class WebdavManager {
     if (index >= 0 && index < this.downloadQueue.length) {
       const task = this.downloadQueue[index];
       this.downloadQueue.splice(index, 1);
-      Logger.info(TAG, `从下载队列移除: ${task.song.title}`);
+      Logger.info(TAG, '从下载队列移除:', task.song.title);
       this.notifyObservers(WebdavManagerStates.DownloadQueueChanged);
     }
   }
@@ -530,7 +532,7 @@ export class WebdavManager {
   public addToUploadQueue(song: Song, account: WebDavAccount): void {
     const task: TransferTask = { song, account };
     this.uploadQueue.push(task);
-    Logger.info(TAG, `添加到上传队列: ${song.title}`);
+    Logger.info(TAG, '添加到上传队列:', song.title);
     this.notifyObservers(WebdavManagerStates.UploadQueueChanged);
   }
 
@@ -539,7 +541,7 @@ export class WebdavManager {
     if (index >= 0 && index < this.uploadQueue.length) {
       const task = this.uploadQueue[index];
       this.uploadQueue.splice(index, 1);
-      Logger.info(TAG, `从上传队列移除: ${task.song.title}`);
+      Logger.info(TAG, '从上传队列移除:', task.song.title);
       this.notifyObservers(WebdavManagerStates.UploadQueueChanged);
     }
   }

+ 143 - 86
entry/src/main/ets/pages/WebDavMainPage.ets

@@ -25,6 +25,16 @@ interface PlaylistEventData {
 
 const TAG = 'heanup WebDavMainPage';
 
+// URL解码函数
+function decodeUrlEncodedString(encodedStr: string): string {
+  try {
+    return decodeURIComponent(encodedStr);
+  } catch (error) {
+    // 如果解码失败,返回原始字符串
+    return encodedStr;
+  }
+}
+
 @Preview
 @Entry
 @Component
@@ -34,10 +44,47 @@ export struct WebDavMainPage {
   @State selectedAccount: WebDavAccount | null = null;
   @State songs: Song[] = [];
   @State isLoading: boolean = false;
+  @State webDavFiles: FileInfo[] = []; // 直接在页面中保存文件列表副本
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
   @StorageProp('isDarkMode') isDarkMode: boolean = false;
   @State topRectHeight: number = 0; // 顶部安全区高度
 
+  @State visibleFoldersState: FileInfo[] = []; // 改为普通状态变量
+
+  // 更新可见文件夹列表
+  private updateVisibleFolders(): void {
+    try {
+      // 安全检查webDavFiles
+      if (!this.webDavFiles || !Array.isArray(this.webDavFiles)) {
+        this.visibleFoldersState = [];
+        return;
+      }
+
+      const allFolders = this.webDavFiles.filter(f => f.isDirectory);
+      const visible: FileInfo[] = [];
+
+      for (let i = 0; i < allFolders.length; i++) {
+        const folder = allFolders[i];
+
+        // 安全检查folder对象
+        if (!folder || typeof folder.fileName !== 'string') {
+          continue;
+        }
+
+        const shouldShow = this.isDirectChildOfCurrentPath(folder);
+
+        if (shouldShow) {
+          visible.push(folder);
+        }
+      }
+
+      this.visibleFoldersState = visible;
+    } catch (error) {
+      Logger.error(TAG, '更新文件夹列表失败:', error.toString());
+      this.visibleFoldersState = [];
+    }
+  }
+
   // 对话框控制器
   private accountDialogController: CustomDialogController | null = null;
   // 保存事件处理器引用,用于取消订阅
@@ -76,14 +123,18 @@ export struct WebDavMainPage {
 
   // 处理WebDAV事件
   private handleWebdavEvent(event: string): void {
-    Logger.info(TAG, `收到WebDAV事件: ${event}`);
-
     switch (event) {
       case WebdavManagerStates.LoadFilesInfoSucceed:
         this.songs = this.webdavManager.webDavSongs;
+        // 直接引用webdavManager的数组,避免@Observed序列化问题
+        this.webDavFiles = this.webdavManager.webDavFiles;
         this.isLoading = false;
+
+        // 更新可见文件夹列表
+        this.updateVisibleFolders();
+
         promptAction.showToast({
-          message: `加载成功: ${this.webdavManager.webDavFiles.filter(f => f.isDirectory).length}个文件夹, ${this.songs.length}首歌曲`
+          message: '加载成功: ' + this.webDavFiles.filter(f => f.isDirectory).length + '个文件夹, ' + this.songs.length + '首歌曲'
         });
         break;
       case WebdavManagerStates.LoadFilesInfoFailed:
@@ -95,8 +146,6 @@ export struct WebDavMainPage {
       case WebdavManagerStates.RemoveAccountSucceed:
         this.loadAccounts();
         break;
-      default:
-        break;
     }
   }
 
@@ -109,41 +158,61 @@ export struct WebDavMainPage {
   }
 
   // 加载文件列表
-  private async loadFiles(): Promise<void> {
+  private loadFiles(): void {
     if (!this.selectedAccount) {
       promptAction.showToast({ message: '请先选择账户' });
       return;
     }
 
     this.isLoading = true;
-    try {
-      await this.webdavManager.loadFilesInfoFromWebdav();
-    } catch (error) {
-      Logger.error(TAG, `加载文件失败: ${error}`);
-      this.isLoading = false;
-    }
+    this.webdavManager.loadFilesInfoFromWebdav()
+      .catch((error: Error) => {
+        Logger.error(TAG, '加载文件失败: ' + error.message);
+        this.isLoading = false;
+      });
   }
 
   // 进入文件夹
-  private async enterFolder(folder: FileInfo): Promise<void> {
+  private enterFolder(folder: FileInfo): void {
     this.isLoading = true;
-    try {
-      await this.webdavManager.enterFolder(folder);
-    } catch (error) {
-      Logger.error(TAG, `进入文件夹失败: ${error}`);
-      this.isLoading = false;
+    this.webdavManager.enterFolder(folder)
+      .catch((error: Error) => {
+        Logger.error(TAG, '进入文件夹失败: ' + error.message);
+        this.isLoading = false;
+      });
+  }
+
+  // 检查是否为当前目录的直接子项
+  private isDirectChildOfCurrentPath(folder: FileInfo): boolean {
+    const currentPath = this.webdavManager.currentPath || '';
+
+    // 如果在根目录(currentPath为空或'/'),显示所有第一级文件夹
+    if (currentPath === '' || currentPath === '/') {
+      // 根目录情况下,显示所有第一级文件夹(href格式为/foldername)
+      return folder.href.startsWith('/') &&
+             folder.href !== '/' &&
+             !folder.href.substring(1).includes('/');
     }
+
+    // 非根目录情况,计算相对路径
+    let relativePath = folder.href;
+    if (currentPath !== '/') {
+      relativePath = folder.href.replace(currentPath, '');
+    }
+    relativePath = relativePath.replace(/^\//, '').replace(/\/$/, '');
+
+    // 只有相对路径不为空且不包含/时才认为是直接子项
+    return relativePath !== '' && !relativePath.includes('/');
   }
 
   // 返回上级目录
-  private async goBack(): Promise<void> {
+  private goBack(): void {
     this.isLoading = true;
-    try {
-      await this.webdavManager.goBack();
-    } catch (error) {
-      Logger.error(TAG, `返回失败: ${error}`);
-      this.isLoading = false;
-    }
+    this.webdavManager.goBack()
+      .catch((error: Error) => {
+        Logger.error(TAG, '返回失败: ' + error.message);
+        this.isLoading = false;
+      });
   }
 
   // 切换账户
@@ -158,27 +227,26 @@ export struct WebDavMainPage {
       builder: WebDavAccountDialog({
         isEditMode: false,
         account: new WebDavAccount(),
-        onConfirm: async (account: WebDavAccount) => {
-          try {
-            await this.webdavManager.insertAccount(
-              account.name,
-              account.host,
-              account.localHost,
-              account.isUseLocalHost,
-              account.port,
-              account.filepath,
-              account.lyricFilePath,
-              account.uploadFilePath,
-              account.imageFilePath,
-              account.account,
-              account.password,
-              account.enableHttps
-            );
+        onConfirm: (account: WebDavAccount) => {
+          this.webdavManager.insertAccount(
+            account.name,
+            account.host,
+            account.localHost,
+            account.isUseLocalHost,
+            account.port,
+            account.filepath,
+            account.lyricFilePath,
+            account.uploadFilePath,
+            account.imageFilePath,
+            account.account,
+            account.password,
+            account.enableHttps
+          ).then(() => {
             promptAction.showToast({ message: '添加成功' });
-          } catch (error) {
-            Logger.error(TAG, `添加账户失败: ${error}`);
+          }).catch((error: Error) => {
+            Logger.error(TAG, '添加账户失败: ' + error.message);
             promptAction.showToast({ message: '添加失败' });
-          }
+          });
         }
       }),
       autoCancel: true,
@@ -193,14 +261,14 @@ export struct WebDavMainPage {
       builder: WebDavAccountDialog({
         isEditMode: true,
         account: account,
-        onConfirm: async (updatedAccount: WebDavAccount) => {
-          try {
-            await this.webdavManager.editAccount(updatedAccount);
-            promptAction.showToast({ message: '更新成功' });
-          } catch (error) {
-            Logger.error(TAG, `更新账户失败: ${error}`);
-            promptAction.showToast({ message: '更新失败' });
-          }
+        onConfirm: (updatedAccount: WebDavAccount) => {
+          this.webdavManager.editAccount(updatedAccount)
+            .then(() => {
+              promptAction.showToast({ message: '更新成功' });
+            }).catch((error: Error) => {
+              Logger.error(TAG, '更新账户失败: ' + error.message);
+              promptAction.showToast({ message: '更新失败' });
+            });
         }
       }),
       autoCancel: true,
@@ -230,7 +298,7 @@ export struct WebDavMainPage {
     }).then((result) => {
       if (result.index >= 0 && result.index < this.accounts.length) {
         this.switchAccount(this.accounts[result.index]);
-        promptAction.showToast({ message: `已切换到: ${this.accounts[result.index].name}` });
+        promptAction.showToast({ message: '已切换到: ' + this.accounts[result.index].name });
       }
     });
   }
@@ -258,8 +326,8 @@ export struct WebDavMainPage {
   private playSong(song: Song, index: number): void {
     try {
       Logger.info(TAG, `heanup === WebDAV playSong 方法被调用 ===`);
-      Logger.info(TAG, `heanup 播放指定歌曲: ${song.title}, 索引: ${index}`);
-      Logger.info(TAG, `heanup 歌曲列表长度: ${this.songs.length}`);
+      Logger.info(TAG, 'heanup 播放指定歌曲: ' + song.title + ', 索引: ' + index);
+      Logger.info(TAG, 'heanup 歌曲列表长度: ' + this.songs.length);
 
       // 将当前歌曲列表转换为VideoItem数组
       const videoItems: VideoItem[] = [];
@@ -271,26 +339,26 @@ export struct WebDavMainPage {
         songFilePaths.push(item.filePath); // 使用filePath作为文件路径
       }
 
-      Logger.info(TAG, `heanup 所有WebDAV歌曲文件路径: ${JSON.stringify(songFilePaths)}`);
+      Logger.info(TAG, 'heanup 所有WebDAV歌曲文件路径: ' + JSON.stringify(songFilePaths));
 
       // 保存WebDAV歌曲数据到全局上下文
       const globalContext = GlobalContext.getContext();
       globalContext.setObject('videoItems', videoItems);
       globalContext.setObject('currentPlayIndex', index);
-      Logger.info(TAG, `heanup 已将WebDAV歌曲列表保存到全局上下文,长度:${videoItems.length}`);
+      Logger.info(TAG, 'heanup 已将WebDAV歌曲列表保存到全局上下文,长度:' + videoItems.length);
 
       // 发送播放事件,类似歌单播放的方式
       const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY };
       const playlistData: PlaylistEventData = {
         playlistId: 'webdav-playlist', // 使用特殊的ID标识WebDAV播放列表
-        playlistName: `WebDAV - ${this.selectedAccount?.name || '未知账户'}`,
+        playlistName: 'WebDAV - ' + (this.selectedAccount?.name || '未知账户'),
         songCount: this.songs.length,
         startIndex: index,
         songFilePaths: songFilePaths
       };
 
-      Logger.info(TAG, `heanup 准备发送WebDAV播放事件,eventId: ${eventPlaylistPlay.eventId}`);
-      Logger.info(TAG, `heanup 发送WebDAV播放事件数据: ${JSON.stringify(playlistData)}`);
+      Logger.info(TAG, 'heanup 准备发送WebDAV播放事件,eventId: ' + eventPlaylistPlay.eventId);
+      Logger.info(TAG, 'heanup 发送WebDAV播放事件数据: ' + JSON.stringify(playlistData));
 
       const eventData: emitter.EventData = {
         data: playlistData
@@ -305,12 +373,12 @@ export struct WebDavMainPage {
           fromWebDAV: true
         }
       }).catch((error: Error) => {
-        Logger.error(TAG, `跳转首页失败: ${error.message}`);
+        Logger.error(TAG, '跳转首页失败: ' + error.message);
         promptAction.showToast({ message: '跳转失败' });
       });
     } catch (error) {
       const err = error as Error;
-      Logger.error(TAG, `播放歌曲失败: ${err.message}`);
+      Logger.error(TAG, '播放歌曲失败: ' + err.message);
       promptAction.showToast({ message: '播放失败' });
     }
   }
@@ -480,8 +548,8 @@ export struct WebDavMainPage {
         }
 
         // 统计信息
-        if (this.webdavManager.webDavFiles.length > 0) {
-          Text(`${this.webdavManager.webDavFiles.filter(f => f.isDirectory).length} 个文件夹, ${this.songs.length} 首歌曲`)
+        if (this.webDavFiles.length > 0) {
+          Text(this.visibleFoldersState.length + ' 个文件夹, ' + this.songs.length + ' 首歌曲')
             .fontSize(13)
             .fontColor($r('app.color.index_tab_font_color'))
             .opacity(0.6)
@@ -507,10 +575,10 @@ export struct WebDavMainPage {
       }
 
       // 文件列表(文件夹 + 歌曲)
-      if (this.webdavManager.webDavFiles.length > 0) {
+      if (this.webDavFiles.length > 0) {
         List({ space: 0 }) {
-          // 显示文件夹
-          ForEach(this.webdavManager.webDavFiles.filter(f => f.isDirectory), (folder: FileInfo) => {
+          // 显示文件夹 - 只显示当前目录下的直接子文件夹
+          ForEach(this.visibleFoldersState, (folder: FileInfo) => {
             ListItem() {
               this.buildFolderItem(folder)
             }
@@ -548,19 +616,16 @@ export struct WebDavMainPage {
   // 文件夹列表项
   @Builder
   buildFolderItem(folder: FileInfo) {
-    Row({ space: 12 }) {
-      // 文件夹图标
-      Image($r('app.media.cloudDisk'))
-        .width(48)
-        .height(48)
+    Row({ space: 2 }) {
+      Image($r('app.media.folder'))
+        .width(32)
+        .height(32)
         .fillColor(this.themeColor)
-        .borderRadius(4)
-        .padding(8)
-        .backgroundColor($r('app.color.input_background'))
+        .margin({ left: 8, right: 6 })
 
       // 文件夹信息
       Column({ space: 4 }) {
-        Text(folder.fileName.replace('/', ''))
+        Text(decodeUrlEncodedString(folder.fileName.replace('/', '')))
           .fontSize(15)
           .fontColor($r('app.color.index_tab_font_color'))
           .maxLines(1)
@@ -573,14 +638,6 @@ export struct WebDavMainPage {
       }
       .alignItems(HorizontalAlign.Start)
       .layoutWeight(1)
-
-      // 右箭头图标
-      Image($r('app.media.ic_play'))
-        .width(20)
-        .height(20)
-        .fillColor($r('app.color.index_tab_font_color'))
-        .opacity(0.4)
-        .rotate({ angle: 180 })
     }
     .width('100%')
     .padding(12)
@@ -611,13 +668,13 @@ export struct WebDavMainPage {
 
       // 歌曲信息
       Column({ space: 4 }) {
-        Text(song.title)
+        Text(decodeUrlEncodedString(song.title))
           .fontSize(15)
           .fontColor($r('app.color.index_tab_font_color'))
           .maxLines(1)
           .textOverflow({ overflow: TextOverflow.Ellipsis })
 
-        Text(song.artist)
+        Text(decodeUrlEncodedString(song.artist))
           .fontSize(13)
           .fontColor($r('app.color.index_tab_font_color'))
           .opacity(0.6)