import { RemoteDriveManager } from '../common/util/RemoteDriveManager'; import { WebDavAccount } from '../viewmodel/WebDavAccount'; import { Song } from '../viewmodel/Song'; import { RemoteDriveManagerStates } from '../common/enums/RemoteDriveManagerStates'; import Logger from '../common/util/Logger'; import { promptAction, SymbolGlyphModifier,router, window } from '@kit.ArkUI'; import { CommonConstants } from '../common/constants/CommonConstants'; import { VideoItem } from '../viewmodel/VideoItem'; import { GlobalContext } from '../common/util/GlobalContext'; import { display } from '@kit.ArkUI'; import { FileInfo } from '../viewmodel/FileInfo'; import { emitter } from '@kit.BasicServicesKit'; import { EventConstants } from '../common/constants/EventConstants'; import { LazyDataSource } from '../common/util/LazyDataSource'; import { PreferencesUtil, StrUtil } from '@pura/harmony-utils'; import { ButtonFancyModifier, MenuModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'; import { getRemoteDriveAccountLabel, getRemoteDriveDisplayLabel, getRemoteDrivePlaylistPrefix } from '../common/util/RemoteDriveLabel'; import { RemoteDriveType } from '../common/enums/RemoteDriveType'; import { Utility } from '../common/util/Utility'; /** * 歌单播放事件数据 */ interface PlaylistEventData { playlistId: string; playlistName: string; songCount: number; startIndex: number; songFilePaths: string[]; // 移除webDavAuthInfo,现在直接使用videoItems中的webdav_account_id } const TAG = 'heanup WebDavMainPage'; // WebDAV歌曲数据全局内存存储 let globalWebdavVideoItems: VideoItem[] = []; let globalWebdavCurrentPlayIndex: number = 0; // 导出函数供LocalMusic访问 export function getWebdavVideoItems(): VideoItem[] { return globalWebdavVideoItems; } export function getWebdavCurrentPlayIndex(): number { return globalWebdavCurrentPlayIndex; } export function clearWebdavVideoItems(): void { globalWebdavVideoItems = []; globalWebdavCurrentPlayIndex = 0; } // URL解码函数 function decodeUrlEncodedString(encodedStr: string): string { try { return decodeURIComponent(encodedStr); } catch (error) { // 如果解码失败,返回原始字符串 return encodedStr; } } @Preview @Entry @Component export struct WebDavMainPage { @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0; searchController: SearchController = new SearchController() @StorageProp('animationState') animationState: AnimationStatus= AnimationStatus.Running @State isNoJumpToHome: boolean = false //网盘播放不跳转首页 @State isSearchMode: boolean = false @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined; @StorageProp('topSafeHeight') topSafeHeight: number = 0; @State webdavManager: RemoteDriveManager = RemoteDriveManager.getInstance(); @State accounts: WebDavAccount[] = []; @Prop @Watch('onSwitchAccount') selectedAccount: WebDavAccount; @State songs: VideoItem[] = []; @State dataSource:LazyDataSource = new LazyDataSource(this.songs) @Link mType: number; @Link offsetX: number; @Link isShowDrawer: boolean; @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 breadcrumbs:string[] = []//面包屑导航 @State visibleFoldersState: FileInfo[] = []; // 改为普通状态变量 @State isShowFileName: boolean = false//是否显示文件名 @State isLongNameRoLL: boolean = true//长歌名滚动 @State sortType: number = 0 //默认排序方式 async onSwitchAccount(){ console.log('onecold 切换账户:', this.selectedAccount.name); this.songs = []; this.visibleFoldersState = []; this.updateListData(this.songs) // 注意:不再需要清空全局上下文,因为LocalMusic已改用实例变量缓存 // 当新账户加载时,新的认证信息会自动覆盖旧的 Logger.info(TAG, 'heanup 切换账户,准备加载新账户文件'); this.isLoading = true; await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount) .catch((error: Error) => { Logger.error(TAG, '加载文件失败: ' + error.message); this.isLoading = false; }); this.breadcrumbs = this.webdavManager.getBreadcrumbs(); } updateListData(mList:Array, noSort?: boolean){ if (!noSort) { this.sortType = PreferencesUtil.getNumberSync('webDavSortType', 0) this.doSortType(this.sortType) } this.dataSource.pushArrayData(mList) } // 更新可见文件夹列表 private updateVisibleFolders(): void { try { // 安全检查webDavFiles if (!this.webDavFiles || !Array.isArray(this.webDavFiles)) { this.visibleFoldersState = []; return; } const allFolders = this.webDavFiles.filter(f => f.isDirectory); const isNavAccount = this.selectedAccount?.webType === RemoteDriveType.Navidrome; if (isNavAccount) { const sortedNavFolders = allFolders.sort((a, b) => a.fileName.localeCompare(b.fileName)); this.visibleFoldersState = sortedNavFolders; return; } const visible: FileInfo[] = []; for (let i = 0; i < allFolders.length; i++) { const folder = allFolders[i]; // 安全检查folder对象 if (!folder || typeof folder.fileName !== 'string') { continue; } let shouldShow = this.isDirectChildOfCurrentPath(folder); if (shouldShow) { visible.push(folder); } } console.log('更新文件夹列表:', visible); this.visibleFoldersState = visible; } catch (error) { Logger.error(TAG, '更新文件夹列表失败:', error.toString()); this.visibleFoldersState = []; } } // 对话框控制器 private accountDialogController: CustomDialogController | null = null; // 保存事件处理器引用,用于取消订阅 private eventHandler: (event: string) => void = (event: string) => { this.handleWebdavEvent(event); }; aboutToAppear(): void { this.isShowFileName = PreferencesUtil.getBooleanSync('isShowFileName', false) this.isLongNameRoLL = PreferencesUtil.getBooleanSync('isLongNameRoLL', true) this.sortType = PreferencesUtil.getNumberSync('webDavSortType', 0) this.isNoJumpToHome = PreferencesUtil.getBooleanSync('isNoJumpToHome', true) // 获取顶部安全区高度 this.getTopRectHeight(); // 加载账户列表 this.loadAccounts(); this.loadFiles() this.breadcrumbs = this.webdavManager.getBreadcrumbs(); // 订阅WebDAV状态变化 this.webdavManager.subscribe(this.eventHandler); } // 获取顶部安全区高度 private getTopRectHeight(): void { window.getLastWindow(getContext(this), (err, data) => { if (err.code) { Logger.error(TAG, '获取窗口失败: ' + JSON.stringify(err)); return; } const area = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); this.topRectHeight = px2vp(area.topRect.height); Logger.info(TAG, '顶部安全区高度: ' + this.topRectHeight); }); } aboutToDisappear(): void { // 取消订阅 this.webdavManager.unsubscribe(this.eventHandler); } // 处理WebDAV事件 private handleWebdavEvent(event: string): void { switch (event) { case RemoteDriveManagerStates.LoadFilesInfoSucceed: this.songs = this.webdavManager.webDavSongs; this.updateListData(this.songs) // 直接引用webdavManager的数组,避免@Observed序列化问题 this.webDavFiles = this.webdavManager.webDavFiles; this.isLoading = false; // 更新可见文件夹列表 this.updateVisibleFolders(); // promptAction.showToast({ // message: '加载成功: ' + this.webDavFiles.filter(f => f.isDirectory).length + '个文件夹, ' + this.songs.length + '首歌曲' // }); break; case RemoteDriveManagerStates.LoadFilesInfoFailed: this.isLoading = false; this.getUIContext().getPromptAction().showToast({ message: '加载失败' }); break; case RemoteDriveManagerStates.InsertAccountSucceed: case RemoteDriveManagerStates.EditAccountSucceed: case RemoteDriveManagerStates.RemoveAccountSucceed: this.loadAccounts(); break; } } // 加载账户列表 private loadAccounts(): void { this.accounts = this.webdavManager.getAllWebDavAccounts(); } // 加载文件列表 private loadFiles(): void { if (!this.selectedAccount) { this.getUIContext().getPromptAction().showToast({ message: '请先选择账户' }); return; } this.isLoading = true; this.webdavManager.loadFilesInfoFromWebdav() .catch((error: Error) => { Logger.error(TAG, '加载文件失败: ' + error.message); this.isLoading = false; }); } // 进入文件夹 private enterFolder(folder: FileInfo): void { this.isLoading = true; 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 === '/') { const folderPath = folder.href.replace(/\/$/, ''); // 去掉尾部斜杠 return folder.href.startsWith('/') && folder.href !== '/' && !folderPath.substring(1).includes('/'); } // 非根目录情况,计算相对路径 let relativePath = folder.href; if (currentPath !== '/') { relativePath = folder.href.replace(currentPath, ''); } relativePath = relativePath.replace(/^\//, '').replace(/\/$/, ''); // 只有相对路径不为空且不包含/时才认为是直接子项 return relativePath !== '' && !relativePath.includes('/'); } // 返回上级目录 private goBack(): void { if(this.webdavManager.canGoBack()){ this.isLoading = true; this.webdavManager.goBack() .catch((error: Error) => { Logger.error(TAG, '返回失败: ' + error.message); this.isLoading = false; }); this.breadcrumbs = this.webdavManager.getBreadcrumbs(); } } // 切换账户 private switchAccount(account: WebDavAccount): void { this.selectedAccount = account; this.songs = []; this.updateListData(this.songs) } // 播放WebDAV歌曲 private playSong(song: VideoItem, index: number): void { try { Logger.info(TAG, `heanup === WebDAV playSong 方法被调用 ===`); Logger.info(TAG, 'heanup 播放指定歌曲: ' + song.name + ', 索引: ' + index); Logger.info(TAG, 'heanup 歌曲列表长度: ' + this.songs.length); // 检查歌曲是否有webdav_account_id Logger.info(TAG, `heanup 播放歌曲的webdav_account_id: ${song.webdav_account_id || '未设置'}`); // 确保所有歌曲都设置了正确的webdav_account_id if (this.selectedAccount && this.selectedAccount.id) { const accountIds = this.songs.map(s => s.webdav_account_id).filter(id => id); Logger.info(TAG, `heanup 当前歌曲列表中有 ${accountIds.length}/${this.songs.length} 首歌曲设置了webdav_account_id`); // 如果发现歌曲缺少webdav_account_id,立即设置 this.songs.forEach((item, idx) => { if (!item.webdav_account_id) { item.webdav_account_id = this.selectedAccount!.id.toString(); Logger.info(TAG, `heanup 为歌曲 "${item.name}" 设置webdav_account_id: ${item.webdav_account_id}`); } }); } // 直接使用当前的VideoItem数组 const videoItems: VideoItem[] = this.songs; const songFilePaths: string[] = []; for (let i = 0; i < this.songs.length; i++) { const item = this.songs[i]; songFilePaths.push(item.filePath); // 使用filePath作为文件路径 } // 直接通过事件传递videoItems数据,不使用GlobalContext const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }; const playlistData: PlaylistEventData = { playlistId: 'webdav-playlist', // 使用特殊的ID标识网盘播放列表 playlistName: `${getRemoteDriveDisplayLabel(this.selectedAccount?.webType)} - ${this.selectedAccount?.name || '未知账户'}`, songCount: this.songs.length, startIndex: index, songFilePaths: songFilePaths }; // 保存videoItems到全局内存 globalWebdavVideoItems = videoItems; globalWebdavCurrentPlayIndex = index; Logger.info(TAG, `heanup 保存WebDAV歌曲到全局内存,长度: ${videoItems.length}, 索引: ${index}`); // 发送播放请求事件,只传递索引信息 emitter.emit(eventPlaylistPlay, { data: playlistData }); Logger.info(TAG, `heanup 发送WebDAV播放事件,只传递索引: ${index}`); if(!this.isNoJumpToHome){ // 跳转到首页播放器 this.getUIContext()?.animateTo({ duration: 555 }, () => { this.mType = 0 }) } } catch (error) { const err = error as Error; Logger.error(TAG, '播放歌曲失败: ' + err.message); this.getUIContext().getPromptAction().showToast({ message: '播放失败' }); } } /** * 一键创建歌单:将当前WebDAV歌曲全部加入新歌单 */ private async createPlaylistFromCurrentWebDav(): Promise { try { Logger.info(TAG, 'heanup 一键创建歌单开始'); if (!this.selectedAccount || !this.selectedAccount.id) { this.getUIContext().getPromptAction().showToast({ message: `请先选择${getRemoteDriveAccountLabel()}` }); return; } if (!this.songs || this.songs.length === 0) { // 回退到manager内的歌曲(可能还未复制到页面state) this.songs = this.webdavManager.webDavSongs; Logger.info(TAG, `heanup 页面songs为空,回退webdavManager.webDavSongs,长度=${this.songs.length}`); if (this.songs.length === 0) { this.getUIContext().getPromptAction().showToast({ message: '当前目录没有可添加的歌曲' }); return; } } Logger.info(TAG, `heanup 歌单创建前歌曲数量: ${this.songs.length}`); // 确保每首歌都有webdav_account_id for (let i = 0; i < this.songs.length; i++) { if (!this.songs[i].webdav_account_id) { this.songs[i].webdav_account_id = this.selectedAccount.id.toString(); } } // 构建歌单名称:账户名 + 当前路径(简化) const rawPath = this.webdavManager.currentPath || '/'; const shortPath = rawPath === '/' ? '根目录' : rawPath.replace(/\/$/, '').split('/').slice(-1)[0]; const playlistName = `${getRemoteDrivePlaylistPrefix(this.selectedAccount.webType)}-${this.selectedAccount.name}-${shortPath}`; // 创建歌单 // 安全获取HostContext const uiContext = this.getUIContext(); const hostCtx = uiContext ? uiContext.getHostContext() : undefined; if (!hostCtx) { this.getUIContext().getPromptAction().showToast({ message: '无法获取上下文' }); return; } const playlistModule = await import('../common/util/PlaylistTable'); const mediaModule = await import('../common/util/MediaTable'); const playlistTable = new playlistModule.default(hostCtx); const mediaTable = new mediaModule.default(hostCtx); // 等待MediaTable底层RDB初始化完成 await new Promise((resolve) => { mediaTable.getRdbStore(hostCtx, () => { Logger.info(TAG, 'heanup mediaTable RDB 初始化完成'); resolve(); }); }); // 先将WebDAV歌曲入库(若不存在) let upsertSuccess = 0; for (let i = 0; i < this.songs.length; i++) { const v = this.songs[i]; if (!v.id || v.id === '') { // 使用filePath作为唯一ID v.id = v.filePath; } if (!v.parentPath) { const idxp = v.filePath.lastIndexOf('/'); if (idxp > 0) { v.parentPath = v.filePath.substring(0, idxp); } } const ok = await mediaTable.upsertWebDavVideoItem(v); Logger.info(TAG, `heanup upsert 第${i+1}/${this.songs.length}首: ${v.filePath} => ${ok}`); if (ok) { upsertSuccess++; } } Logger.info(TAG, `heanup WebDAV歌曲入库完成: 成功 ${upsertSuccess}/${this.songs.length}`); // 先查询是否已有同名歌单,避免重复创建导致混淆 const existing = (await playlistTable.queryAllPlaylists()).find(p => p.name === playlistName); if (existing) { this.getUIContext().getPromptAction().showToast({ message: '歌单已存在,直接追加歌曲' }); const filePathsExist: string[] = this.songs.map(s => s.filePath); await playlistTable.addSongsToPlaylist(existing.id, filePathsExist); router.pushUrl({ url: 'pages/PlaylistDetailPage', params: { playlist: existing } }); return; } Logger.info(TAG, `heanup 准备创建歌单: ${playlistName}`); const created = await playlistTable.createPlaylist(playlistName, `来自${getRemoteDriveDisplayLabel(this.selectedAccount.webType)}: ${this.selectedAccount.name} 路径: ${rawPath}`); if (!created) { this.getUIContext().getPromptAction().showToast({ message: '歌单创建失败' }); return; } // 查询刚创建的歌单ID const playlists = await playlistTable.queryAllPlaylists(); const target = playlists.reverse().find(p => p.name === playlistName); // 取最近创建的同名歌单 if (!target) { this.getUIContext().getPromptAction().showToast({ message: '无法找到新建歌单' }); return; } // 批量添加歌曲 const filePaths: string[] = this.songs.map(s => s.filePath); Logger.info(TAG, `heanup 开始批量添加歌曲到歌单: ${target.id}`); const addResult = await playlistTable.addSongsToPlaylist(target.id, filePaths); Logger.info(TAG, `heanup 批量添加结果: ${addResult}`); if (!addResult) { Logger.warn(TAG, '批量添加歌曲返回false,可能全部已存在或写入失败'); } this.getUIContext().getPromptAction().showToast({ message: `歌单创建成功: ${playlistName}` }); Logger.info(TAG, `heanup 一键创建歌单成功: ${playlistName}, 添加 ${filePaths.length} 首歌曲`); // 跳转到歌单详情页面 router.pushUrl({ url: 'pages/PlaylistDetailPage', params: { playlist: target } }); } catch (error) { Logger.error(TAG, '一键创建歌单失败: ' + (error as Error).message); this.getUIContext().getPromptAction().showToast({ message: '一键创建歌单失败' }); } } // 导航到指定层级的面包屑路径 private navigateToBreadcrumb(breadcrumbIndex: number): void { try { this.isLoading = true; this.webdavManager.navigateToBreadcrumb(breadcrumbIndex).then((path: string) => { this.webdavManager.enterFolderFromPath(path) this.breadcrumbs = this.webdavManager.getBreadcrumbs(); console.info('onecold this.breadcrumbs = ' + JSON.stringify(this.breadcrumbs)) console.info('onecold this.webdavManager.currentPath = ' + this.webdavManager.currentPath) }) .catch((error: Error) => { Logger.error(TAG, '导航到面包屑路径失败: ' + error.message); this.isLoading = false; }); } catch (error) { Logger.error(TAG, '导航到面包屑路径异常: ' + (error as Error).message); this.isLoading = false; } } @Builder SortMenuBuilder() { Menu() { MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')), content: $r('app.string.sort_by_name') }) .onClick(async () => { this.doSortType(0) PreferencesUtil.put("webDavSortType", 0) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')), content: '按名称降序' }) .onClick(async () => { this.doSortType(1) PreferencesUtil.put("webDavSortType", 1) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')), content:'按时间升序' }) .onClick(async () => { this.doSortType(2) PreferencesUtil.put("webDavSortType", 2) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')), content: '按时间降序' }) .onClick(async () => { this.doSortType(3) PreferencesUtil.put("webDavSortType", 3) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')), content:'按大小升序' }) .onClick(async () => { this.doSortType(4) PreferencesUtil.put("webDavSortType", 4) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')), content: '按大小降序' }) .onClick(async () => { this.doSortType(5) PreferencesUtil.put("webDavSortType", 5) }) }.attributeModifier(new MenuModifier()) } doSortType(index: number) { // 对歌曲列表进行排序 switch (index) { case 0: Utility.doSortListAscending(this.songs,this.isShowFileName) this.visibleFoldersState.sort((a, b) => { return a.fileName.localeCompare(b.fileName); }); break; case 1: Utility.doSortListDescending(this.songs,this.isShowFileName) this.visibleFoldersState.sort((a, b) => { return b.fileName.localeCompare(a.fileName); }); break; case 2: this.songs.sort((a, b) => { return a.cTime.localeCompare(b.cTime); }); this.visibleFoldersState.sort((a, b) => { return a.time-b.time; }); break; case 3: this.songs.sort((a, b) => { return b.cTime.localeCompare(a.cTime); }); this.visibleFoldersState.sort((a, b) => { return b.time-a.time; }); break; case 4: this.songs.sort((a, b) => { return a.videoSize - b.videoSize; }); break; case 5: this.songs.sort((a, b) => { return b.videoSize - a.videoSize; }); break; } this.updateListData(this.songs,true) } @Builder topTitleBar(){ Column() { Row({ space: 15 }) { if (!this.isSearchMode ) { //左侧滑动按钮 Button({ type: ButtonType.Circle, stateEffect: true }) { SymbolGlyph($r('sys.symbol.sort')) .attributeModifier(new SymbolGlyphFancyModifier(25, '', '')) } .attributeModifier(new ButtonFancyModifier(40, 40)) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .animation({ duration: 300, curve: Curve.Ease }) .onClick(() => { this.getUIContext().animateTo({ duration: 555 }, () => { // 动画闭包内控制Image组件的出现和消失 this.isShowDrawer = !this.isShowDrawer this.offsetX = 0 }) }) .attributeModifier(new ShadowModifier()) .zIndex(0) Text(this.selectedAccount.name) .margin({left:3,right:10}) .fontColor($r('app.color.text_color')) .fontSize(19) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .layoutWeight(1) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) }else{ //左侧搜索返回按钮 Button({ type: ButtonType.Circle, stateEffect: true }) { SymbolGlyph($r('sys.symbol.chevron_left')) .attributeModifier(new SymbolGlyphFancyModifier(24, '', '')) } .attributeModifier(new ButtonFancyModifier(40, 40)) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .animation({ duration: 300, curve: Curve.Ease }) .onClick(() => { this.isSearchMode = false this.onSearchInput('') }) .attributeModifier(new ShadowModifier()) .zIndex(0) } //搜索框 Search({ controller: this.searchController,value: this.searchText, placeholder: '搜索标题、艺术家...' }) .searchButton('搜索',{fontColor:this.themeColor}) .searchIcon({ src: $r('sys.media.ohos_ic_public_search_filled') }) .cancelButton({ style: CancelButtonStyle.CONSTANT, icon: { src: $r('sys.media.ohos_ic_public_cancel_filled') } }) .layoutWeight(1) .height(35) .maxLength(20) .backgroundColor(this.isDarkMode?Color.Black:'#F5F5F5') .placeholderColor(Color.Grey) .placeholderFont({ size: 14, weight: 400 }) .textFont({ size: 14, weight: 400 }) .onSubmit((value: string) => { console.log('onecold onSubmit ='+value) this.searchController.stopEditing() this.onSearchInput(this.searchText); }) .onChange((value: string) => { console.log('onecold onChange ='+value) this.onSearchInput(value); }) .visibility(this.isSearchMode?Visibility.Visible:Visibility.None) .animation({ duration: 300, curve: Curve.Ease }) //搜索按钮 if (!this.isSearchMode) { Button({ type: ButtonType.Circle, stateEffect: true }) { SymbolGlyph($r('sys.symbol.magnifyingglass')) .attributeModifier(new SymbolGlyphFancyModifier(25, '', '')) } .attributeModifier(new ButtonFancyModifier(40, 40)) .animation({ duration: 300, curve: Curve.Ease }) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .attributeModifier(new ShadowModifier()) .zIndex(0) .onClick(()=>{ this.filteredFolderList = [...this.visibleFoldersState]; // 显示所有文件夹 this.isSearchMode = true }) //排序按钮 Button({ type: ButtonType.Circle, stateEffect: true }) { SymbolGlyph($r('sys.symbol.list_number')) .attributeModifier(new SymbolGlyphFancyModifier(25, '', '')) } .attributeModifier(new ButtonFancyModifier(40, 40)) .animation({ duration: 300, curve: Curve.Ease }) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .bindMenu(this.SortMenuBuilder) .attributeModifier(new ShadowModifier()) .zIndex(0) //添加按钮 Button({ type: ButtonType.Circle, stateEffect: true }) { SymbolGlyph($r('sys.symbol.plus')) .attributeModifier(new SymbolGlyphFancyModifier(25, '', '')) } .attributeModifier(new ButtonFancyModifier(40, 40)) .animation({ duration: 300, curve: Curve.Ease }) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(() => { this.createPlaylistFromCurrentWebDav(); }) .attributeModifier(new ShadowModifier()) .zIndex(0) } } } .padding({ top: this.topSafeHeight+10, left: 10, right: 10,bottom:2 }) .width('100%') } //搜索功能的实现 @State searchText: string = ''; // 用户输入内容 @State filteredList: Array = []; // 过滤后的歌曲结果 @State filteredFolderList: Array = []; // 过滤后的文件夹结果 // 实时搜索逻辑(带防抖) // 实时搜索逻辑(带防抖) private onSearchInput(value: string) { this.searchText = value.trim(); let mSearchList: Array = [] mSearchList = this.songs // 新增条件判断:空输入时显示所有数据 if (this.searchText === '') { this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新 this.filteredFolderList = [...this.visibleFoldersState]; // 显示所有文件夹 } else { this.filteredList = mSearchList.filter((item: VideoItem) => { //支持模糊匹配和艺术家 专辑匹配 const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i'); return regex.test(item.name.toLowerCase())|| regex.test(item.fileName?.toLowerCase() ?? "") || regex.test(item.artist?.toLowerCase() ?? "") || regex.test(item.album?.toLowerCase() ?? "") }); // 对文件夹进行过滤 this.filteredFolderList = this.visibleFoldersState.filter((folder: FileInfo) => { const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i'); return regex.test(folder.fileName?.toLowerCase() ?? "") || regex.test(decodeUrlEncodedString(folder.fileName?.replace('/', '') ?? "").toLowerCase()); }); } this.updateListData(this.filteredList); } build() { Stack() { if (this.accounts.length === 0) { this.buildEmptyView(); } else { this.buildContentView(); } Column() { this.topTitleBar() this.breaker() } } .alignContent(Alignment.Top) .width('100%') .height('100%') } @Builder breaker() { // 加载按钮和面包屑导航 Column({ space: 8 }) { // 面包屑导航 if (this.webdavManager.currentPath !== '') { Row({ space: 8 }) { Button({ type: ButtonType.Circle }) { Image(this.webdavManager.canGoBack()?$r('app.media.back'):this.selectedAccount.coverPath) .width(15) .height(15) .borderRadius(10) .alt($r('app.media.cloudDisk')) .fillColor(Color.White) } .width(20) .height(20) .margin({left:5}) .backgroundColor(this.themeColor) .onClick(() => this.goBack()) Row({ space: 4 }) { ForEach(this.breadcrumbs, (crumb: string, index: number) => { Row() { Text(crumb) .fontSize(15) .fontColor(this.themeColor) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .onClick(() => { this.navigateToBreadcrumb(index); }) // 添加分隔符(除了最后一个元素) if (index < this.breadcrumbs.length - 1) { Text('/') .fontSize(15) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.6) } }) } .layoutWeight(1) } .width('100%') .padding({ left: 4, right: 4 }) } } .width('100%') .padding({ left: 12, right: 12,top: 10,bottom: 5 }) } // 空状态视图 @Builder buildEmptyView() { Column({ space: 20 }) { Image($r('app.media.cloudDisk')) .width(120) .height(120) .opacity(0.3) Text(`暂无${getRemoteDriveAccountLabel()}`) .fontSize(16) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.6) } .justifyContent(FlexAlign.Center) .width('100%') .padding({top: this.topSafeHeight+50}) .layoutWeight(1) } // 内容视图 @Builder buildContentView() { Column() { // 加载状态 Row() { LoadingProgress() .width(30) .height(30) .color(this.themeColor) Text('加载中...') .fontSize(14) .fontColor($r('app.color.index_tab_font_color')) .margin({ left: 12 }) } .padding({top: this.topSafeHeight + 90, bottom: 20, left: 20, right: 20}) .visibility(this.isLoading?Visibility.Visible:Visibility.None) .opacity(this.isLoading ? 1 : 0) .animation({ duration: 500, curve: 'ease-in-out' // 可选动画曲线 }) // 文件列表(文件夹 + 歌曲) if (this.webDavFiles.length > 0) { List({ space: 0 }) { // 显示文件夹 - 只显示当前目录下的直接子文件夹 ForEach(this.isSearchMode ? this.filteredFolderList : this.visibleFoldersState, (folder: FileInfo) => { ListItem() { this.buildFolderItem(folder) } .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.MIDDLE }) }, (folder: FileInfo) => folder.name+folder.fileName) // 显示歌曲 LazyForEach(this.dataSource, (song: VideoItem, index: number) => { ListItem() { this.buildSongItem(song, index) } .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.MIDDLE }) }, (item: VideoItem) => item.filePath) } .contentStartOffset(this.topSafeHeight + 80) .contentEndOffset(this.bottomSafeHeight) .layoutWeight(1) .margin({ top: 4 }) } else if (!this.isLoading) { Column() { Text('暂无内容') .fontSize(14) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.6) Text('点击"左侧菜单"网盘加载') .fontSize(12) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.4) .margin({ top: 8 }) } .justifyContent(FlexAlign.Center) .layoutWeight(1) } } .layoutWeight(1) } // 文件夹列表项 @Builder buildFolderItem(folder: FileInfo) { Button({ type: ButtonType.Normal, stateEffect: false }) { Row({ space: 2 }) { SymbolGlyph($r('sys.symbol.folder')) .fontSize(48) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 8, right: 6 }) // 文件夹信息 Column({ space: 4 }) { Text(decodeUrlEncodedString(folder.fileName.replace('/', ''))) .fontSize(15) .fontColor($r('app.color.index_tab_font_color')) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text('文件夹') .fontSize(13) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.6) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) } } .reuseId('dir_item') .width('100%') .padding(12) .backgroundColor(Color.Transparent) // .backgroundColor($r('app.color.start_window_background')) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .onClick(() => { this.enterFolder(folder); this.breadcrumbs = this.webdavManager.getBreadcrumbs(); }) } // 歌曲列表项 @Builder buildSongItem(song: VideoItem, index: number) { Button({ type: ButtonType.Normal, stateEffect: false }) { Row({ space: 12 }) { // 序号 // 歌曲封面 Image(song.pixelMap) .width(48) .height(48) .borderRadius(4) .alt($r('app.media.music_red')) .fillColor(this.themeColor) .objectFit(ImageFit.Cover) .margin({ left: 8 }) // 歌曲信息 Column({ space: 4 }) { Text(this.isShowFileName?song.fileName :song.name) .fontSize(15) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color')) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row(){ Text(song.artist+" ") .fontSize(13) .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color')) .opacity(0.6) .maxLines(1) .visibility(song.artist?Visibility.Visible:Visibility.None) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(decodeUrlEncodedString(song.size||"")) .fontSize(13) .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor: $r('app.color.index_tab_font_color')) .opacity(0.6) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(song.cTime) .fontSize(13) .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor: $r('app.color.index_tab_font_color')) .opacity(0.6) .maxLines(1) .padding({left: 10}) .visibility(StrUtil.isNotEmpty(song.artist)?Visibility.None:Visibility.Visible) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .width('90%') } .alignItems(HorizontalAlign.Start) .layoutWeight(1) .padding({ right: 20 }) Column() { //多选按钮的Checkbox 先注释掉 // Checkbox({ name: 'checkbox' + index }) // .select(this.selectedFiles.some(x => x.filePath === item.filePath)) // .selectedColor(this.themeColor) // .shape(CheckBoxShape.CIRCLE) // .opacity(this.isMultiSelect ? 1 : 0) // .animation({ // duration: 666, // curve: 'Smooth' // 可选动画曲线 // }) // .visibility(this.isMultiSelect ? Visibility.Visible : Visibility.None) // .onChange((checked: boolean) => this.handleFileSelection(item, checked)) // .margin({ left: 20, top: 8, bottom: 8,right:18 }) // .width(22) // .height(22) ImageAnimator() .images(CommonConstants.IMAGE_FRAME_INFO)// 动画数组 .duration(1000)// 持续 .state(this.animationState)// 动画状态 .fillMode(FillMode.Forwards) .width(18) .margin({ right: 12, top: 8, bottom: 8 }) .visibility(this.currentSong?.filePath==song.filePath ? Visibility.Visible : Visibility.None) .height(18) .iterations(-1) // 播放次数 } } } .width('100%') .padding(12) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .backgroundColor(Color.Transparent) .onClick(() => { this.playSong(song, index); }) } }