| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- import { WebdavManager } from '../common/util/WebdavManager';
- import { WebDavAccount } from '../viewmodel/WebDavAccount';
- import { Song } from '../viewmodel/Song';
- import { WebdavManagerStates } from '../common/enums/WebdavManagerStates';
- import Logger from '../common/util/Logger';
- import { promptAction, 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';
- /**
- * 歌单播放事件数据
- */
- interface PlaylistEventData {
- playlistId: string;
- playlistName: string;
- songCount: number;
- startIndex: number;
- songFilePaths: string[];
- webDavAuthInfo?: WebDavAuthInfo; // 新增WebDAV认证信息
- }
- /**
- * WebDAV认证信息
- */
- interface WebDavAuthInfo {
- accountId: number;
- host: string;
- port: number;
- account: string;
- password: string;
- enableHttps: boolean;
- }
- const TAG = 'heanup WebDavMainPage';
- // URL解码函数
- function decodeUrlEncodedString(encodedStr: string): string {
- try {
- return decodeURIComponent(encodedStr);
- } catch (error) {
- // 如果解码失败,返回原始字符串
- return encodedStr;
- }
- }
- @Preview
- @Entry
- @Component
- export struct WebDavMainPage {
- @State webdavManager: WebdavManager = WebdavManager.getInstance();
- @State accounts: WebDavAccount[] = [];
- @Prop @Watch('onSwitchAccount') selectedAccount: WebDavAccount;
- @State songs: Song[] = [];
- @State dataSource:LazyDataSource<Song> = 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[] = []; // 改为普通状态变量
- onSwitchAccount(){
- console.log('onecold 切换账户:', this.selectedAccount.name);
- this.songs = [];
- this.updateListData(this.songs)
- this.isLoading = true;
- this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount)
- .catch((error: Error) => {
- Logger.error(TAG, '加载文件失败: ' + error.message);
- this.isLoading = false;
- });
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- }
- updateListData(mList:Array<Song>){
- 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 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;
- // 保存事件处理器引用,用于取消订阅
- private eventHandler: (event: string) => void = (event: string) => {
- this.handleWebdavEvent(event);
- };
- aboutToAppear(): void {
- // 获取顶部安全区高度
- 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 WebdavManagerStates.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 WebdavManagerStates.LoadFilesInfoFailed:
- this.isLoading = false;
- this.getUIContext().getPromptAction().showToast({ message: '加载失败' });
- break;
- case WebdavManagerStates.InsertAccountSucceed:
- case WebdavManagerStates.EditAccountSucceed:
- case WebdavManagerStates.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 === '/') {
- // 根目录情况下,显示所有第一级文件夹(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 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)
- }
- // 将Song转换为VideoItem
- private convertSongToVideoItem(song: Song, index: number): VideoItem {
- const videoItem = new VideoItem(
- song.title,
- index.toString(),
- song.src, // WebDAV URL作为文件路径
- CommonConstants.TYPE_INTERNET, // 使用网络类型
- song.fileSize,
- song.time.toString(),
- undefined, // pixelMap
- undefined, // size
- typeof song.img === 'string' ? song.img : undefined, // pixelMapPath
- song.artist,
- undefined, // album
- song.name // fileName
- );
- return videoItem;
- }
- // 播放WebDAV歌曲
- 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);
- // 保存WebDAV认证信息到全局上下文(用于播放器认证)
- const globalContext = GlobalContext.getContext();
- if (this.selectedAccount && this.selectedAccount.account && this.selectedAccount.password) {
- const webDavAuthInfo: WebDavAuthInfo = {
- accountId: this.selectedAccount.id,
- host: this.selectedAccount.host,
- port: this.selectedAccount.port,
- account: this.selectedAccount.account,
- password: this.selectedAccount.password,
- enableHttps: this.selectedAccount.enableHttps
- };
- globalContext.setObject('webDavAuthInfo', webDavAuthInfo);
- Logger.info(TAG, 'heanup 已保存WebDAV认证信息到全局上下文'+JSON.stringify(webDavAuthInfo));
- }
- // 将当前歌曲列表转换为VideoItem数组
- const videoItems: VideoItem[] = [];
- const songFilePaths: string[] = [];
- for (let i = 0; i < this.songs.length; i++) {
- const item = this.convertSongToVideoItem(this.songs[i], i);
- videoItems.push(item);
- songFilePaths.push(item.filePath); // 使用filePath作为文件路径
- }
- Logger.info(TAG, 'heanup 所有WebDAV歌曲文件路径: ' + JSON.stringify(songFilePaths));
- // 保存WebDAV歌曲数据到全局上下文
- globalContext.setObject('videoItems', videoItems);
- globalContext.setObject('currentPlayIndex', index);
- Logger.info(TAG, 'heanup 已将WebDAV歌曲列表保存到全局上下文,长度:' + videoItems.length);
- // 验证保存是否成功
- const savedVideoItems = globalContext.getObject('videoItems') as VideoItem[];
- const savedIndex = globalContext.getObject('currentPlayIndex') as number;
- Logger.info(TAG, 'heanup 验证保存结果 - videoItems长度: ' + (savedVideoItems?.length || 0) + ', currentPlayIndex: ' + savedIndex);
- // 检查认证信息是否还在
- const savedAuthInfo = globalContext.getObject('webDavAuthInfo') as WebDavAuthInfo;
- if (savedAuthInfo) {
- Logger.info(TAG, 'heanup 认证信息验证成功,账户ID: ' + savedAuthInfo.accountId);
- } else {
- Logger.error(TAG, 'heanup 认证信息验证失败,webDavAuthInfo为空');
- }
- // 发送播放事件,类似歌单播放的方式
- const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY };
- // 准备WebDAV认证信息用于传递
- let authInfoForEvent: WebDavAuthInfo | undefined = undefined;
- if (this.selectedAccount && this.selectedAccount.account && this.selectedAccount.password) {
- authInfoForEvent = {
- accountId: this.selectedAccount.id,
- host: this.selectedAccount.host,
- port: this.selectedAccount.port,
- account: this.selectedAccount.account,
- password: this.selectedAccount.password,
- enableHttps: this.selectedAccount.enableHttps
- };
- Logger.info(TAG, 'heanup 将WebDAV认证信息包含在播放事件中');
- }
- const playlistData: PlaylistEventData = {
- playlistId: 'webdav-playlist', // 使用特殊的ID标识WebDAV播放列表
- playlistName: 'WebDAV - ' + (this.selectedAccount?.name || '未知账户'),
- songCount: this.songs.length,
- startIndex: index,
- songFilePaths: songFilePaths,
- webDavAuthInfo: authInfoForEvent // 直接传递认证信息
- };
- Logger.info(TAG, 'heanup 准备发送WebDAV播放事件,eventId: ' + eventPlaylistPlay.eventId);
- Logger.info(TAG, 'heanup 发送WebDAV播放事件数据: ' + JSON.stringify(playlistData));
- const eventData: emitter.EventData = {
- data: playlistData
- };
- emitter.emit(eventPlaylistPlay, eventData);
- // 跳转到首页播放器
- router.pushUrl({
- url: 'pages/NewIndex',
- params: {
- fromWebDAV: true
- }
- }).catch((error: Error) => {
- Logger.error(TAG, '跳转首页失败: ' + error.message);
- this.getUIContext().getPromptAction().showToast({ message: '跳转失败' });
- });
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, '播放歌曲失败: ' + err.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();
- })
- .catch((error: Error) => {
- Logger.error(TAG, '导航到面包屑路径失败: ' + error.message);
- this.isLoading = false;
- });
- } catch (error) {
- Logger.error(TAG, '导航到面包屑路径异常: ' + (error as Error).message);
- this.isLoading = false;
- }
- }
- build() {
- Column() {
- // 顶部安全区和标题栏
- Column() {
- Blank()
- .height(this.topRectHeight + 5)
- .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
- .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
- // 标题栏
- Row() {
- Image($r('app.media.menu'))
- .width(24)
- .height(24)
- .margin({ left: 12, right: 8 })
- .onClick(() => {
- this.getUIContext()?.animateTo({ duration: 555 }, () => {
- // this.mType =0
- this.isShowDrawer = !this.isShowDrawer
- this.offsetX = 0
- })
- });
- Text('WebDAV网盘')
- .fontSize(18)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Medium)
- .layoutWeight(1)
- .textAlign(TextAlign.Center)
- }
- .height(48)
- .width('100%')
- .alignItems(VerticalAlign.Center)
- .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
- }
- // 内容区域
- if (this.accounts.length === 0) {
- this.buildEmptyView();
- } else {
- this.buildContentView();
- }
- }
- .width('100%')
- .height('100%')
- .backgroundColor($r('app.color.start_window_background'))
- }
- // 空状态视图
- @Builder
- buildEmptyView() {
- Column({ space: 20 }) {
- Image($r('app.media.cloudDisk'))
- .width(120)
- .height(120)
- .opacity(0.3)
- Text('暂无WebDAV账户')
- .fontSize(16)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .layoutWeight(1)
- }
- // 内容视图
- @Builder
- buildContentView() {
- Column() {
- // 加载按钮和面包屑导航
- Column({ space: 8 }) {
- // 面包屑导航
- if (this.webdavManager.currentPath !== '') {
- Row({ space: 8 }) {
- Button({ type: ButtonType.Circle }) {
- Image(this.webdavManager.canGoBack()?$r('app.media.back'):$r('app.media.cloudDisk'))
- .width(15)
- .height(15)
- .fillColor(Color.White)
- }
- .width(20)
- .height(20)
- .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 })
- }
- // 统计信息
- if (this.webDavFiles.length > 0) {
- Row() {
- Text(this.visibleFoldersState.length + ' 个文件夹, ' + this.songs.length + ' 首歌曲')
- .fontSize(13)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- .layoutWeight(1)
- .textAlign(TextAlign.Start)
- Blank()
- }
- .padding({ left: 4, right: 4 })
- }
- }
- .width('100%')
- .padding(12)
- .margin({ top: 8 })
- // 加载状态
- Row() {
- LoadingProgress()
- .width(30)
- .height(30)
- .color(this.themeColor)
- Text('加载中...')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'))
- .margin({ left: 12 })
- }
- .padding(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.visibleFoldersState, (folder: FileInfo) => {
- ListItem() {
- this.buildFolderItem(folder)
- }
- })
- // 显示歌曲
- LazyForEach(this.dataSource, (song: Song, index: number) => {
- ListItem() {
- this.buildSongItem(song, index)
- }
- })
- }
- .layoutWeight(1)
- .divider({ strokeWidth: 1, color: '#EEEEEE' })
- .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) {
- 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)
- }
- .width('100%')
- .padding(12)
- .backgroundColor($r('app.color.start_window_background'))
- .onClick(() => {
- this.enterFolder(folder);
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- })
- }
- // 歌曲列表项
- @Builder
- buildSongItem(song: Song, index: number) {
- Row({ space: 12 }) {
- // 序号
- // 歌曲封面
- Image(song.img)
- .width(48)
- .height(48)
- .borderRadius(4)
- .objectFit(ImageFit.Cover)
- // 歌曲信息
- Column({ space: 4 }) {
- Text(decodeUrlEncodedString(song.title))
- .fontSize(15)
- .fontColor($r('app.color.index_tab_font_color'))
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- Text(decodeUrlEncodedString(song.artist))
- .fontSize(13)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .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)
- }
- .width('100%')
- .padding(12)
- .backgroundColor($r('app.color.start_window_background'))
- .onClick(() => {
- this.playSong(song, index);
- })
- }
- }
|